Files
sanguo_vnpy_v2/sanguo_trader/cta_adapter.py
T

99 lines
4.1 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""PaperCtaEnginevnpy_ctastrategy CtaTemplate 的纸面适配器(spec §5)。
实现 CtaTemplate 所需的 cta_engine 接口(send_order/cancel_order),
拦截 send_order → 构造 PaperOrder → 收集到 pending_orders
供 PaperEngine 撮合。参考 vnpy_ctastrategy BacktestingEngine 的策略桥接
(它也是假 cta_engine)。
vnpy_ctastrategy 是 pip 依赖(容器有,本机可能无)——本模块不顶部 import 它,
只在 load_strategy 时 lazy import;本机用 mock 策略测 send_order 收集逻辑。
"""
import logging
from .models import MatchSession, OrderSide, PaperOrder
logger = logging.getLogger(__name__)
def _direction_to_side(direction) -> OrderSide:
"""vnpy Direction.LONG/SHORT(或 ''/'')→ OrderSide。"""
s = str(direction)
if "LONG" in s or "" in s:
return OrderSide.BUY
if "SHORT" in s or "" in s:
return OrderSide.SELL
return OrderSide.BUY
class PaperCtaEngine:
"""纸面 cta_engine:拦截 CtaTemplate.send_order 转 PaperOrder。"""
def __init__(self, strategy_id: str,
match_session: MatchSession | str = MatchSession.NEXT_OPEN,
listing_days: int = 0, size: int = 1) -> None:
self.strategy_id = strategy_id
self.match_session = MatchSession(match_session) if isinstance(match_session, str) else match_session
self.listing_days = listing_days
self.size = size # 合约乘数:A 股 1 手=100 股 → size=100mock 默认 1
self.strategy = None
self.pending_orders: list[PaperOrder] = []
def set_strategy(self, strategy) -> None:
self.strategy = strategy
def send_order(self, strategy, direction, offset, price, volume,
stop: bool = False, lock: bool = False, net: bool = False) -> list[str]:
"""拦截 CtaTemplate.send_order → 收集 PaperOrder。返回假 vt_orderids。"""
del stop, lock, net # 未用(首版不支持 stop/lock/net
vt_symbol = getattr(strategy, "vt_symbol", "")
symbol = vt_symbol.split(".")[0] if vt_symbol else getattr(strategy, "symbol", "")
order = PaperOrder(
strategy_id=self.strategy_id,
symbol=symbol,
side=_direction_to_side(direction),
price=float(price),
volume=int(volume) * self.size, # vnpy 策略 volume 单位=手,转股(A 股 ×100)
is_market=True,
match_session=self.match_session,
listing_days=self.listing_days,
)
self.pending_orders.append(order)
return [f"paper.{self.strategy_id}.{len(self.pending_orders)}"]
def cancel_order(self, vt_orderid) -> None:
"""首版简化:取消从 pending 移除(未撮合的)。"""
if isinstance(vt_orderid, (list, tuple)):
for oid in vt_orderid:
self._cancel_one(oid)
else:
self._cancel_one(vt_orderid)
def cancel_all(self, vt_symbol: str | None = None) -> None:
"""撤销所有挂单(纸面:清 pendingCtaTemplate.on_bar 开头常调)。"""
self.pending_orders = []
def __getattr__(self, name: str):
"""兜底未实现的 cta_engine 方法(load_bar/put_event/write_log/send_email
/get_data/load_tick 等)返回 no-op,避免 CtaTemplate 调用时 AttributeError。"""
return lambda *a, **kw: None
def _cancel_one(self, vt_orderid: str) -> None:
try:
idx = int(vt_orderid.rsplit(".", 1)[-1]) - 1
if 0 <= idx < len(self.pending_orders):
self.pending_orders.pop(idx)
except (ValueError, IndexError):
logger.warning("cancel_order 找不到 %s", vt_orderid)
def on_bar(self, bar) -> None:
"""逐根 bar:清空上一根 pending,转发策略 on_bar(策略内调 send_order 收集新单)。"""
self.pending_orders = []
if self.strategy is not None:
self.strategy.on_bar(bar)
def pop_orders(self) -> list[PaperOrder]:
"""PaperEngine 撮合后取走 pending_orders。"""
orders = self.pending_orders
self.pending_orders = []
return orders