主力合约回测
This commit is contained in:
@@ -41,7 +41,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
|
||||
self.order_id_counter = 0
|
||||
|
||||
self._bar_history: deque[Bar] = deque(maxlen=7)
|
||||
self._bar_history: deque[Bar] = deque(maxlen=10)
|
||||
self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID
|
||||
|
||||
self.log(f"策略初始化: symbol={self.symbol}, trade_volume={self.trade_volume}, "
|
||||
@@ -58,6 +58,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
||||
"""
|
||||
current_datetime = bar.datetime # 获取当前K线时间
|
||||
self.symbol = bar.symbol
|
||||
|
||||
# --- 1. 撤销上一根K线未成交的订单 ---
|
||||
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
||||
@@ -87,7 +88,6 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
# --- 3. 平仓逻辑 (止损/止盈) ---
|
||||
# 只有当有持仓时才考虑平仓
|
||||
if current_pos_volume > 0: # 假设只做多,所以持仓量 > 0
|
||||
|
||||
avg_entry_price = self.get_average_position_price(self.symbol)
|
||||
if avg_entry_price is not None:
|
||||
pnl_per_unit = bar.close - avg_entry_price # 当前浮动盈亏(以收盘价计算)
|
||||
@@ -140,7 +140,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
|
||||
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
||||
bar_1_ago = self._bar_history[-2]
|
||||
bar_7_ago = self._bar_history[0]
|
||||
bar_7_ago = self._bar_history[-8]
|
||||
|
||||
# 计算历史 K 线的 Range
|
||||
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
||||
@@ -174,10 +174,21 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
new_order = self.send_order(order)
|
||||
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
||||
if new_order:
|
||||
self._last_order_id = new_order.order_id
|
||||
self._last_order_id = new_order.id
|
||||
self.log(f"[{current_datetime}] 策略: 发送限价买入订单 {self._last_order_id} @ {target_buy_price:.2f}")
|
||||
else:
|
||||
self.log(f"[{current_datetime}] 策略: 发送订单失败。")
|
||||
|
||||
# else:
|
||||
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(self._bar_history)}")
|
||||
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(self._bar_history)}")
|
||||
|
||||
def on_rollover(self, old_symbol: str, new_symbol: str):
|
||||
"""
|
||||
在合约换月时清空历史K线数据和上次订单ID,避免使用旧合约数据进行计算。
|
||||
"""
|
||||
super().on_rollover(old_symbol, new_symbol) # 调用基类方法打印日志
|
||||
self._bar_history.clear() # 清空历史K线
|
||||
self._last_order_id = None # 清空上次订单ID,因为旧合约订单已取消
|
||||
|
||||
self.log(f"换月完成,清空历史K线数据和上次订单ID,准备新合约交易。")
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -57,7 +57,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
"""
|
||||
每接收到一根Bar时,执行策略逻辑。
|
||||
"""
|
||||
current_portfolio_value = self.context.get_current_portfolio_value(bar)
|
||||
current_portfolio_value = self.context.get_account_cash()
|
||||
# print(f"[{bar.datetime}] Strategy processing Bar. Current close price: {bar.close:.2f}. Current Portfolio Value: {current_portfolio_value:.2f}")
|
||||
|
||||
# 1. 撤销上一根K线未成交的订单
|
||||
@@ -108,16 +108,19 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
)
|
||||
|
||||
# 通过上下文发送订单
|
||||
trade = self.send_order(order)
|
||||
if trade:
|
||||
print(
|
||||
f"[{bar.datetime}] 策略: 发送并立即成交限价买单 {trade.volume} 股 @ {trade.price:.2f}(open:{bar.open}, close:{bar.close}) (订单ID: {order.id})")
|
||||
# 如果立即成交,_last_order_id 仍然保持 None
|
||||
else:
|
||||
# 如果未立即成交,将订单ID记录下来,以便下一根Bar撤销
|
||||
self._last_order_id = order.id
|
||||
print(
|
||||
f"[{bar.datetime}] 策略: 发送限价买单 {trade_volume} 股 @ {limit_price:.2f} (未成交,订单ID: {order.id} 已挂单)")
|
||||
# trade = self.send_order(order)
|
||||
# if trade:
|
||||
# print(
|
||||
# f"[{bar.datetime}] 策略: 发送并立即成交限价买单 {trade.volume} 股 (open:{bar.open}, close:{bar.close}) (订单ID: {order.id})")
|
||||
# # 如果立即成交,_last_order_id 仍然保持 None
|
||||
# else:
|
||||
# # 如果未立即成交,将订单ID记录下来,以便下一根Bar撤销
|
||||
# self._last_order_id = order.id
|
||||
# print(
|
||||
# f"[{bar.datetime}] 策略: 发送限价买单 {trade_volume} 股 @ {limit_price:.2f} (未成交,订单ID: {order.id} 已挂单)")
|
||||
order = self.send_order(order)
|
||||
if order:
|
||||
print(f"[{bar.datetime}]发送订单 {order.id}, direction {order.direction}")
|
||||
else:
|
||||
# print(f"[{bar.datetime}] 策略: 当前已有持仓或有未成交订单,不重复下单。")
|
||||
pass
|
||||
Reference in New Issue
Block a user