主力合约回测

This commit is contained in:
2025-06-23 22:21:59 +08:00
parent a81a32ce73
commit afed83f96f
12 changed files with 739 additions and 100713 deletions

View File

@@ -15,7 +15,13 @@ class Strategy(ABC):
策略通过 context 对象与回测引擎和模拟器进行交互,并提供辅助方法。
"""
def __init__(self, context: 'BacktestContext', symbol: str, enable_log: bool = True, **params: Any):
def __init__(
self,
context: "BacktestContext",
symbol: str,
enable_log: bool = True,
**params: Any,
):
"""
Args:
context (BacktestEngine): 回测引擎实例,作为策略的上下文,提供与模拟器等的交互接口。
@@ -34,7 +40,7 @@ class Strategy(ABC):
"""
print(f"{self.__class__.__name__} 策略初始化回调被调用。")
def on_trade(self, trade: 'Trade'):
def on_trade(self, trade: "Trade"):
"""
当模拟器成功执行一笔交易时调用。
可用于更新策略内部持仓状态或记录交易。
@@ -46,7 +52,7 @@ class Strategy(ABC):
pass # 默认不执行任何操作,具体策略可覆盖
@abstractmethod
def on_bar(self, bar: 'Bar'):
def on_bar(self, bar: "Bar"):
"""
每当新的K线数据到来时调用此方法。
Args:
@@ -57,11 +63,14 @@ class Strategy(ABC):
# --- 新增/修改的辅助方法 ---
def send_order(self, order: 'Order') -> Optional[Trade]:
def send_order(self, order: "Order") -> Optional[Order]:
"""
发送订单的辅助方法。
会在 BaseStrategy 内部构建 Order 对象,并通过 context 转发给模拟器。
"""
if self.context.is_rollover_bar:
self.log(f"当前是换月K线禁止开仓订单")
return None
return self.context.send_order(order)
def cancel_order(self, order_id: str) -> bool:
@@ -73,44 +82,37 @@ class Strategy(ABC):
return self.context.cancel_order(order_id)
def cancel_all_pending_orders(self) -> int:
"""
取消所有当前策略的未决订单。
返回成功取消的订单数量。
"""
pending_orders = self.get_pending_orders() # 调用 BaseStrategy 自己的 get_pending_orders
"""取消当前策略的未决订单仅限于当前策略关注的Symbol。"""
# 注意:在换月模式下,引擎会自动取消旧合约的挂单,这里是策略主动取消
pending_orders = self.get_pending_orders()
cancelled_count = 0
orders_to_cancel = [order.id for order in pending_orders.values() if order.symbol == self.symbol]
orders_to_cancel = [
order.id for order in pending_orders.values() if order.symbol == self.symbol
]
for order_id in orders_to_cancel:
if self.cancel_order(order_id): # 调用 BaseStrategy 自己的 cancel_order
if self.cancel_order(order_id):
cancelled_count += 1
return cancelled_count
def get_current_positions(self) -> Dict[str, int]:
"""
获取当前持仓。
通过 context 调用模拟器的 get_positions 方法。
"""
"""获取所有当前持仓 (可能包含多个合约)。"""
return self.context._simulator.get_current_positions()
def get_pending_orders(self) -> Dict[str, 'Order']:
"""
获取当前所有待处理订单的副本。
通过 context 调用模拟器的 get_pending_orders 方法。
"""
def get_pending_orders(self) -> Dict[str, "Order"]:
"""获取所有当前待处理订单的副本 (可能包含多个合约)。"""
return self.context._simulator.get_pending_orders()
def get_average_position_price(self, symbol: str) -> Optional[float]:
"""
获取指定合约的平均持仓成本。
通过 context 调用模拟器的 get_average_position_price 方法。
"""
"""获取指定合约的平均持仓成本。"""
return self.context._simulator.get_average_position_price(symbol)
# 你可以根据需要在这里添加更多辅助方法,如获取账户净值等
def get_account_cash(self) -> float:
"""获取当前账户现金余额。"""
return self.context._simulator.cash
def get_current_time(self) -> datetime:
"""获取模拟器当前时间。"""
return self.context._simulator.get_current_time()
def log(self, *args: Any, **kwargs: Any):
"""
@@ -121,7 +123,9 @@ class Strategy(ABC):
if self.enable_log:
# 尝试获取当前模拟时间,如果模拟器或时间不可用,则跳过时间前缀
try:
current_time_str = self.context._simulator.get_current_time().strftime('%Y-%m-%d %H:%M:%S')
current_time_str = self.context._simulator.get_current_time().strftime(
"%Y-%m-%d %H:%M:%S"
)
time_prefix = f"[{current_time_str}] "
except AttributeError:
# 如果获取不到时间(例如在策略初始化时,模拟器时间还未设置),则不加时间前缀
@@ -129,8 +133,21 @@ class Strategy(ABC):
# 使用 f-string 结合 *args 来构建消息
# print() 函数会将 *args 自动用空格分隔,这里我们模仿这个行为
message = ' '.join(map(str, args))
message = " ".join(map(str, args))
# 你可以将其他 kwargs (如 sep, end, file, flush) 传递给 print
# 但通常日志方法不会频繁使用这些。这里只支持最基础的打印。
print(f"{time_prefix}策略 ({self.symbol}): {message}", **kwargs)
print(f"{time_prefix}策略 ({self.symbol}): {message}", **kwargs)
def on_rollover(self, old_symbol: str, new_symbol: str):
"""
当回测的合约发生换月时调用此方法。
子类可以重写此方法来执行换月相关的逻辑(例如,调整目标仓位,清空历史数据)。
注意:在调用此方法前,引擎已强制平仓旧合约的所有仓位并取消所有挂单。
Args:
old_symbol (str): 旧的合约代码。
new_symbol (str): 新的合约代码。
"""
self.log(f"合约换月事件: 从 {old_symbol} 切换到 {new_symbol}")
# 默认实现可以为空,子类根据需要重写
pass