修复回测bug
This commit is contained in:
9826
main.ipynb
9826
main.ipynb
File diff suppressed because one or more lines are too long
@@ -1,5 +1,5 @@
|
|||||||
# src/backtest_engine.py
|
# src/backtest_engine.py
|
||||||
|
from datetime import datetime
|
||||||
from typing import Type, Dict, Any, List, Optional
|
from typing import Type, Dict, Any, List, Optional
|
||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
@@ -22,7 +22,10 @@ class BacktestEngine:
|
|||||||
initial_capital: float = 100000.0,
|
initial_capital: float = 100000.0,
|
||||||
slippage_rate: float = 0.0001,
|
slippage_rate: float = 0.0001,
|
||||||
commission_rate: float = 0.0002,
|
commission_rate: float = 0.0002,
|
||||||
roll_over_mode: bool = False): # 新增换月模式参数
|
roll_over_mode: bool = False,
|
||||||
|
start_time: Optional[datetime] = None, # 新增开始时间
|
||||||
|
end_time: Optional[datetime] = None # 新增结束时间
|
||||||
|
): # 新增换月模式参数
|
||||||
"""
|
"""
|
||||||
初始化回测引擎。
|
初始化回测引擎。
|
||||||
|
|
||||||
@@ -63,6 +66,10 @@ class BacktestEngine:
|
|||||||
self._last_processed_bar_symbol: Optional[str] = None # 记录上一根 K 线的 symbol
|
self._last_processed_bar_symbol: Optional[str] = None # 记录上一根 K 线的 symbol
|
||||||
self.is_rollover_bar: bool = False # 标记当前 K 线是否为换月 K 线(禁止开仓)
|
self.is_rollover_bar: bool = False # 标记当前 K 线是否为换月 K 线(禁止开仓)
|
||||||
|
|
||||||
|
# 新增时间过滤属性
|
||||||
|
self.start_time = start_time
|
||||||
|
self.end_time = end_time
|
||||||
|
|
||||||
print("\n--- 回测引擎初始化完成 ---")
|
print("\n--- 回测引擎初始化完成 ---")
|
||||||
print(f" 策略: {strategy_class.__name__}")
|
print(f" 策略: {strategy_class.__name__}")
|
||||||
print(f" 初始资金: {initial_capital:.2f}")
|
print(f" 初始资金: {initial_capital:.2f}")
|
||||||
@@ -85,6 +92,14 @@ class BacktestEngine:
|
|||||||
if current_bar is None:
|
if current_bar is None:
|
||||||
break # 没有更多数据,回测结束
|
break # 没有更多数据,回测结束
|
||||||
|
|
||||||
|
if self.start_time and current_bar.datetime < self.start_time:
|
||||||
|
continue
|
||||||
|
|
||||||
|
# 如果设置了结束时间,且当前K线在结束时间之后,则终止回测
|
||||||
|
if self.end_time and current_bar.datetime >= self.end_time:
|
||||||
|
print(f"到达结束时间 {self.end_time},回测终止。")
|
||||||
|
break
|
||||||
|
|
||||||
# --- 换月逻辑判断和处理 (在处理 current_bar 之前进行) ---
|
# --- 换月逻辑判断和处理 (在处理 current_bar 之前进行) ---
|
||||||
# 1. 重置 is_rollover_bar 标记
|
# 1. 重置 is_rollover_bar 标记
|
||||||
self.is_rollover_bar = False
|
self.is_rollover_bar = False
|
||||||
|
|||||||
@@ -5,10 +5,12 @@ from typing import Dict, List, Optional
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
from .core_data import Order, Trade, Bar, PortfolioSnapshot
|
from .core_data import Order, Trade, Bar, PortfolioSnapshot
|
||||||
|
|
||||||
|
|
||||||
class ExecutionSimulator:
|
class ExecutionSimulator:
|
||||||
"""
|
"""
|
||||||
模拟交易执行和管理账户资金、持仓。
|
模拟交易执行和管理账户资金、持仓。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, initial_capital: float,
|
def __init__(self, initial_capital: float,
|
||||||
slippage_rate: float = 0.0001,
|
slippage_rate: float = 0.0001,
|
||||||
commission_rate: float = 0.0002,
|
commission_rate: float = 0.0002,
|
||||||
@@ -49,7 +51,7 @@ class ExecutionSimulator:
|
|||||||
"""
|
"""
|
||||||
fill_price = -1.0 # 默认未成交
|
fill_price = -1.0 # 默认未成交
|
||||||
|
|
||||||
base_price = current_bar.open # 所有成交都以当前K线的开盘价为基准
|
base_price = current_bar.open # 所有成交都以当前K线的开盘价为基准
|
||||||
|
|
||||||
if order.price_type == "MARKET":
|
if order.price_type == "MARKET":
|
||||||
# 市价单:直接以开盘价成交,考虑滑点
|
# 市价单:直接以开盘价成交,考虑滑点
|
||||||
@@ -58,11 +60,11 @@ class ExecutionSimulator:
|
|||||||
elif order.direction == "SELL" or order.direction == "CLOSE_LONG": # 卖出/平多:向下偏离(少收)
|
elif order.direction == "SELL" or order.direction == "CLOSE_LONG": # 卖出/平多:向下偏离(少收)
|
||||||
fill_price = base_price * (1 - self.slippage_rate)
|
fill_price = base_price * (1 - self.slippage_rate)
|
||||||
else:
|
else:
|
||||||
fill_price = base_price # 理论上不发生
|
fill_price = base_price # 理论上不发生
|
||||||
|
|
||||||
elif order.price_type == "LIMIT" and order.limit_price is not None:
|
elif order.price_type == "LIMIT" and order.limit_price is not None:
|
||||||
limit_price = order.limit_price
|
limit_price = order.limit_price
|
||||||
|
|
||||||
# 限价单:判断开盘价是否满足限价条件,如果满足,则以开盘价成交(考虑滑点)
|
# 限价单:判断开盘价是否满足限价条件,如果满足,则以开盘价成交(考虑滑点)
|
||||||
if order.direction == "BUY" or order.direction == "CLOSE_SHORT": # 限价买入/平空
|
if order.direction == "BUY" or order.direction == "CLOSE_SHORT": # 限价买入/平空
|
||||||
# 买单只有当开盘价低于或等于限价时才可能成交
|
# 买单只有当开盘价低于或等于限价时才可能成交
|
||||||
@@ -76,7 +78,7 @@ class ExecutionSimulator:
|
|||||||
if base_price >= limit_price:
|
if base_price >= limit_price:
|
||||||
fill_price = base_price * (1 - self.slippage_rate)
|
fill_price = base_price * (1 - self.slippage_rate)
|
||||||
# else: 未满足限价条件,不成交
|
# else: 未满足限价条件,不成交
|
||||||
|
|
||||||
# 最终检查成交价是否有效且合理(大于0)
|
# 最终检查成交价是否有效且合理(大于0)
|
||||||
if fill_price <= 0:
|
if fill_price <= 0:
|
||||||
return -1.0 # 未成交或价格无效
|
return -1.0 # 未成交或价格无效
|
||||||
@@ -94,28 +96,28 @@ class ExecutionSimulator:
|
|||||||
self.pending_orders[order.id] = order
|
self.pending_orders[order.id] = order
|
||||||
# print(f"订单 {order.id} 加入待处理队列。")
|
# print(f"订单 {order.id} 加入待处理队列。")
|
||||||
return order
|
return order
|
||||||
|
|
||||||
def process_pending_orders(self, current_bar: Bar):
|
def process_pending_orders(self, current_bar: Bar):
|
||||||
"""
|
"""
|
||||||
处理所有待撮合的订单。在每个K线数据到来时调用。
|
处理所有待撮合的订单。在每个K线数据到来时调用。
|
||||||
"""
|
"""
|
||||||
# 复制一份待处理订单的键,防止在迭代时修改字典
|
# 复制一份待处理订单的键,防止在迭代时修改字典
|
||||||
order_ids_to_process = list(self.pending_orders.keys())
|
order_ids_to_process = list(self.pending_orders.keys())
|
||||||
|
|
||||||
for order_id in order_ids_to_process:
|
for order_id in order_ids_to_process:
|
||||||
if order_id not in self.pending_orders: # 订单可能已被取消
|
if order_id not in self.pending_orders: # 订单可能已被取消
|
||||||
continue
|
continue
|
||||||
|
|
||||||
order = self.pending_orders[order_id]
|
order = self.pending_orders[order_id]
|
||||||
|
|
||||||
# 只有当订单的symbol与当前bar的symbol一致时才尝试撮合
|
# 只有当订单的symbol与当前bar的symbol一致时才尝试撮合
|
||||||
# 这样确保了在换月后,旧合约的挂单不会被尝试撮合 (尽管换月时会强制取消)
|
# 这样确保了在换月后,旧合约的挂单不会被尝试撮合 (尽管换月时会强制取消)
|
||||||
if order.symbol != current_bar.symbol:
|
if order.symbol != current_bar.symbol:
|
||||||
# 这种情况理论上应该被换月逻辑清理掉的旧合约挂单,
|
# 这种情况理论上应该被换月逻辑清理掉的旧合约挂单,
|
||||||
# 如果因为某种原因漏掉了,这里直接跳过,避免异常。
|
# 如果因为某种原因漏掉了,这里直接跳过,避免异常。
|
||||||
continue
|
continue
|
||||||
|
|
||||||
# 尝试成交订单
|
# 尝试成交订单
|
||||||
self._execute_single_order(order, current_bar)
|
self._execute_single_order(order, current_bar)
|
||||||
|
|
||||||
def _execute_single_order(self, order: Order, current_bar: Bar) -> Optional[Trade]:
|
def _execute_single_order(self, order: Order, current_bar: Bar) -> Optional[Trade]:
|
||||||
@@ -124,106 +126,154 @@ class ExecutionSimulator:
|
|||||||
由 send_order 或 process_pending_orders 调用。
|
由 send_order 或 process_pending_orders 调用。
|
||||||
"""
|
"""
|
||||||
# --- 处理撤单指令 ---
|
# --- 处理撤单指令 ---
|
||||||
if order.direction == "CANCEL": # 策略主动发起撤单
|
if order.direction == "CANCEL": # 策略主动发起撤单
|
||||||
success = self.cancel_order(order.id)
|
success = self.cancel_order(order.id)
|
||||||
if success:
|
if success:
|
||||||
# print(f"[{current_bar.datetime}] 模拟器: 收到并成功处理撤单指令 for Order ID: {order.id}")
|
# print(f"[{current_bar.datetime}] 模拟器: 收到并成功处理撤单指令 for Order ID: {order.id}")
|
||||||
pass
|
pass
|
||||||
return None # 撤单操作不返回Trade
|
return None # 撤单操作不返回Trade
|
||||||
|
|
||||||
symbol = order.symbol
|
symbol = order.symbol
|
||||||
volume = order.volume
|
volume = order.volume
|
||||||
|
|
||||||
# 尝试计算成交价格
|
# 尝试计算成交价格
|
||||||
fill_price = self._calculate_fill_price(order, current_bar)
|
fill_price = self._calculate_fill_price(order, current_bar)
|
||||||
|
|
||||||
if fill_price <= 0: # 未成交或不满足限价条件
|
if fill_price <= 0: # 未成交或不满足限价条件
|
||||||
return None
|
return None
|
||||||
|
|
||||||
# --- 以下是订单成功成交的逻辑 ---
|
# --- 以下是订单成功成交前的预检查逻辑 ---
|
||||||
trade_value = volume * fill_price
|
trade_value = volume * fill_price
|
||||||
commission = trade_value * self.commission_rate
|
commission = trade_value * self.commission_rate
|
||||||
|
|
||||||
current_position = self.positions.get(symbol, 0)
|
current_position = self.positions.get(symbol, 0)
|
||||||
current_average_cost = self.average_costs.get(symbol, 0.0)
|
current_average_cost = self.average_costs.get(symbol, 0.0)
|
||||||
|
|
||||||
realized_pnl = 0.0
|
realized_pnl = 0.0 # 预先计算的实现盈亏
|
||||||
|
|
||||||
# 根据 direction 判断开平仓意图
|
|
||||||
# 如果 direction 是 CLOSE_LONG 或 CLOSE_SELL (平多), CLOSE_SHORT (平空) 则是平仓交易
|
|
||||||
is_close_trade = order.direction in ["CLOSE_LONG", "CLOSE_SELL", "CLOSE_SHORT"]
|
|
||||||
# 如果 direction 是 BUY 或 SELL 且不是平仓意图,则是开仓交易
|
|
||||||
is_open_trade = (order.direction in ["BUY", "SELL"]) and (not is_close_trade)
|
|
||||||
|
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
# 精确判断 is_open_trade 和 is_close_trade
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
is_trade_a_close_operation = False
|
||||||
|
is_trade_an_open_operation = False
|
||||||
|
|
||||||
# 区分实际的买卖方向
|
# 1. 判断是否为平仓操作
|
||||||
|
# 显式平仓指令
|
||||||
|
if order.direction in ["CLOSE_LONG", "CLOSE_SELL", "CLOSE_SHORT"]:
|
||||||
|
is_trade_a_close_operation = True
|
||||||
|
# 隐式平仓 (例如,持有空头时买入,或持有多头时卖出)
|
||||||
|
elif order.direction == "BUY" and current_position < 0: # 买入平空
|
||||||
|
is_trade_a_close_operation = True
|
||||||
|
elif order.direction == "SELL" and current_position > 0: # 卖出平多
|
||||||
|
is_trade_a_close_operation = True
|
||||||
|
|
||||||
|
# 2. 判断是否为开仓操作
|
||||||
|
if order.direction == "BUY":
|
||||||
|
# 买入开多: 如果当前持有多头或无仓位,或者从空头转为多头
|
||||||
|
if current_position >= 0 or (current_position < 0 and (current_position + volume) > 0):
|
||||||
|
is_trade_an_open_operation = True
|
||||||
|
elif order.direction == "SELL":
|
||||||
|
# 卖出开空: 如果当前持有空头或无仓位,或者从多头转为空头
|
||||||
|
if current_position <= 0 or (current_position > 0 and (current_position - volume) < 0):
|
||||||
|
is_trade_an_open_operation = True
|
||||||
|
|
||||||
|
# -----------------------------------------------------------
|
||||||
|
|
||||||
|
# 区分实际的买卖方向 (用于资金和持仓计算)
|
||||||
actual_execution_direction = ""
|
actual_execution_direction = ""
|
||||||
if order.direction == "BUY" or order.direction == "CLOSE_SHORT":
|
if order.direction == "BUY" or order.direction == "CLOSE_SHORT":
|
||||||
actual_execution_direction = "BUY"
|
actual_execution_direction = "BUY"
|
||||||
elif order.direction == "SELL" or order.direction == "CLOSE_LONG" or order.direction == "CLOSE_SELL":
|
elif order.direction == "SELL" or order.direction == "CLOSE_LONG" or order.direction == "CLOSE_SELL":
|
||||||
actual_execution_direction = "SELL"
|
actual_execution_direction = "SELL"
|
||||||
else:
|
else:
|
||||||
print(f"[{current_bar.datetime}] 模拟器: 收到未知订单方向 {order.direction} for Order ID: {order.id}. 订单未处理。")
|
print(
|
||||||
|
f"[{current_bar.datetime}] 模拟器: 收到未知订单方向 {order.direction} for Order ID: {order.id}. 订单未处理。")
|
||||||
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
||||||
return None
|
return None
|
||||||
|
|
||||||
|
# --- 临时变量,用于预计算新的资金和持仓状态 ---
|
||||||
|
temp_cash = self.cash
|
||||||
|
temp_positions = self.positions.copy()
|
||||||
|
temp_average_costs = self.average_costs.copy()
|
||||||
|
|
||||||
|
# 根据实际执行方向进行预计算和资金检查
|
||||||
if actual_execution_direction == "BUY": # 处理实际的买入 (开多 / 平空)
|
if actual_execution_direction == "BUY": # 处理实际的买入 (开多 / 平空)
|
||||||
if current_position >= 0: # 当前持有多仓或无仓位 (开多)
|
if current_position >= 0: # 当前持有多仓或无仓位 (开多)
|
||||||
new_total_cost = (current_average_cost * current_position) + (fill_price * volume)
|
required_cash = trade_value + commission
|
||||||
new_total_volume = current_position + volume
|
if temp_cash < required_cash:
|
||||||
self.average_costs[symbol] = new_total_cost / new_total_volume if new_total_volume > 0 else 0.0
|
print(
|
||||||
self.positions[symbol] = new_total_volume
|
f"[{current_bar.datetime}] 模拟器: 资金不足 (开多), 无法执行买入 {volume} {symbol} @ {fill_price:.2f}. 需要: {required_cash:.2f}, 当前: {temp_cash:.2f}")
|
||||||
else: # 当前持有空仓 (平空)
|
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
||||||
pnl_per_share = current_average_cost - fill_price # 空头平仓盈亏
|
return None
|
||||||
|
|
||||||
|
temp_cash -= required_cash
|
||||||
|
new_total_cost = (temp_average_costs.get(symbol, 0.0) * temp_positions.get(symbol, 0)) + (
|
||||||
|
fill_price * volume)
|
||||||
|
new_total_volume = temp_positions.get(symbol, 0) + volume
|
||||||
|
temp_average_costs[symbol] = new_total_cost / new_total_volume if new_total_volume > 0 else 0.0
|
||||||
|
temp_positions[symbol] = new_total_volume
|
||||||
|
|
||||||
|
else: # 当前持有空仓 (平空) - 平仓交易,佣金从交易价值中扣除,不单独检查现金余额
|
||||||
|
pnl_per_share = current_average_cost - fill_price # 空头平仓盈亏
|
||||||
realized_pnl = pnl_per_share * volume
|
realized_pnl = pnl_per_share * volume
|
||||||
|
|
||||||
self.positions[symbol] += volume
|
temp_cash -= commission # 扣除佣金
|
||||||
if self.positions[symbol] == 0:
|
temp_cash += trade_value # 回收平仓价值
|
||||||
del self.positions[symbol]
|
temp_cash += realized_pnl # 计入实现盈亏
|
||||||
if symbol in self.average_costs: del self.average_costs[symbol]
|
|
||||||
elif self.positions[symbol] > 0 and current_position < 0: # 空转多
|
|
||||||
self.average_costs[symbol] = fill_price # 新多头仓位成本以成交价为准
|
|
||||||
|
|
||||||
if self.cash < trade_value + commission:
|
temp_positions[symbol] += volume
|
||||||
print(f"[{current_bar.datetime}] 模拟器: 资金不足,无法执行买入 {volume} {symbol} @ {fill_price:.2f}")
|
if temp_positions[symbol] == 0:
|
||||||
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
del temp_positions[symbol]
|
||||||
return None
|
if symbol in temp_average_costs: del temp_average_costs[symbol]
|
||||||
self.cash -= (trade_value + commission)
|
elif current_position < 0 and temp_positions[symbol] > 0: # 发生空转多
|
||||||
|
temp_average_costs[symbol] = fill_price # 新多头仓位成本以成交价为准
|
||||||
|
|
||||||
|
|
||||||
elif actual_execution_direction == "SELL": # 处理实际的卖出 (开空 / 平多)
|
elif actual_execution_direction == "SELL": # 处理实际的卖出 (开空 / 平多)
|
||||||
if current_position <= 0: # 当前持有空仓或无仓位 (开空)
|
if current_position <= 0: # 当前持有空仓或无仓位 (开空)
|
||||||
new_total_value = (current_average_cost * abs(current_position)) + (fill_price * volume)
|
# 开空主要检查佣金是否足够
|
||||||
new_total_volume = abs(current_position) + volume
|
if temp_cash < commission:
|
||||||
self.average_costs[symbol] = new_total_value / new_total_volume if new_total_volume > 0 else 0.0
|
print(
|
||||||
self.positions[symbol] -= volume
|
f"[{current_bar.datetime}] 模拟器: 资金不足 (开空佣金), 无法执行卖出 {volume} {symbol} @ {fill_price:.2f}. 佣金: {commission:.2f}, 当前: {temp_cash:.2f}")
|
||||||
else: # 当前持有多仓 (平多)
|
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
||||||
pnl_per_share = fill_price - current_average_cost # 多头平仓盈亏
|
return None
|
||||||
|
|
||||||
|
temp_cash -= commission
|
||||||
|
new_total_value = (temp_average_costs.get(symbol, 0.0) * abs(temp_positions.get(symbol, 0))) + (
|
||||||
|
fill_price * volume)
|
||||||
|
new_total_volume = abs(temp_positions.get(symbol, 0)) + volume
|
||||||
|
temp_average_costs[symbol] = new_total_value / new_total_volume if new_total_volume > 0 else 0.0 # 平均成本
|
||||||
|
temp_positions[symbol] -= volume
|
||||||
|
|
||||||
|
else: # 当前持有多仓 (平多) - 平仓交易,佣金从交易价值中扣除,不单独检查现金余额
|
||||||
|
pnl_per_share = fill_price - current_average_cost # 多头平仓盈亏
|
||||||
realized_pnl = pnl_per_share * volume
|
realized_pnl = pnl_per_share * volume
|
||||||
|
|
||||||
self.positions[symbol] -= volume
|
temp_cash -= commission # 扣除佣金
|
||||||
if self.positions[symbol] == 0:
|
temp_cash += trade_value # 回收平仓价值
|
||||||
del self.positions[symbol]
|
temp_cash += realized_pnl # 计入实现盈亏
|
||||||
if symbol in self.average_costs: del self.average_costs[symbol]
|
|
||||||
elif self.positions[symbol] < 0 and current_position > 0: # 多转空
|
temp_positions[symbol] -= volume
|
||||||
self.average_costs[symbol] = fill_price # 新空头仓位成本以成交价为准
|
if temp_positions[symbol] == 0:
|
||||||
|
del temp_positions[symbol]
|
||||||
if self.cash < commission: # 卖出交易,佣金先扣
|
if symbol in temp_average_costs: del temp_average_costs[symbol]
|
||||||
print(f"[{current_bar.datetime}] 模拟器: 资金不足(佣金),无法执行卖出 {volume} {symbol} @ {fill_price:.2f}")
|
elif current_position > 0 and temp_positions[symbol] < 0: # 发生多转空
|
||||||
if order.id in self.pending_orders: del self.pending_orders[order.id]
|
temp_average_costs[symbol] = fill_price # 新空头仓位成本以成交价为准
|
||||||
return None
|
|
||||||
self.cash -= commission
|
# --- 所有检查通过后,才正式更新模拟器状态 ---
|
||||||
self.cash += trade_value
|
self.cash = temp_cash
|
||||||
|
self.positions = temp_positions
|
||||||
|
self.average_costs = temp_average_costs
|
||||||
|
|
||||||
# 创建 Trade 对象时,direction 使用原始订单的 direction
|
# 创建 Trade 对象时,direction 使用原始订单的 direction
|
||||||
executed_trade = Trade(
|
executed_trade = Trade(
|
||||||
order_id=order.id, fill_time=current_bar.datetime, symbol=symbol,
|
order_id=order.id, fill_time=current_bar.datetime, symbol=symbol,
|
||||||
direction=order.direction, # 使用原始订单的 direction
|
direction=order.direction, # 使用原始订单的 direction
|
||||||
volume=volume, price=fill_price, commission=commission,
|
volume=volume, price=fill_price, commission=commission,
|
||||||
cash_after_trade=self.cash, positions_after_trade=self.positions.copy(),
|
cash_after_trade=self.cash, positions_after_trade=self.positions.copy(),
|
||||||
realized_pnl=realized_pnl,
|
realized_pnl=realized_pnl,
|
||||||
is_open_trade=is_open_trade,
|
is_open_trade=is_trade_an_open_operation, # 使用更精确的判断
|
||||||
is_close_trade=is_close_trade
|
is_close_trade=is_trade_a_close_operation # 使用更精确的判断
|
||||||
)
|
)
|
||||||
self.trade_log.append(executed_trade)
|
self.trade_log.append(executed_trade)
|
||||||
|
|
||||||
@@ -232,6 +282,7 @@ class ExecutionSimulator:
|
|||||||
del self.pending_orders[order.id]
|
del self.pending_orders[order.id]
|
||||||
|
|
||||||
return executed_trade
|
return executed_trade
|
||||||
|
|
||||||
def cancel_order(self, order_id: str) -> bool:
|
def cancel_order(self, order_id: str) -> bool:
|
||||||
"""
|
"""
|
||||||
尝试取消一个待处理订单。
|
尝试取消一个待处理订单。
|
||||||
@@ -240,7 +291,7 @@ class ExecutionSimulator:
|
|||||||
del self.pending_orders[order_id]
|
del self.pending_orders[order_id]
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
# --- 新增:强制平仓指定合约的所有持仓 ---
|
# --- 新增:强制平仓指定合约的所有持仓 ---
|
||||||
def force_close_all_positions_for_symbol(self, symbol_to_close: str, closing_bar: Bar) -> List[Trade]:
|
def force_close_all_positions_for_symbol(self, symbol_to_close: str, closing_bar: Bar) -> List[Trade]:
|
||||||
"""
|
"""
|
||||||
@@ -252,14 +303,14 @@ class ExecutionSimulator:
|
|||||||
List[Trade]: 因强制平仓而产生的交易记录。
|
List[Trade]: 因强制平仓而产生的交易记录。
|
||||||
"""
|
"""
|
||||||
closed_trades: List[Trade] = []
|
closed_trades: List[Trade] = []
|
||||||
|
|
||||||
# 仅处理指定symbol的持仓
|
# 仅处理指定symbol的持仓
|
||||||
if symbol_to_close in self.positions and self.positions[symbol_to_close] != 0:
|
if symbol_to_close in self.positions and self.positions[symbol_to_close] != 0:
|
||||||
volume_to_close = self.positions[symbol_to_close]
|
volume_to_close = self.positions[symbol_to_close]
|
||||||
|
|
||||||
# 根据持仓方向决定平仓订单的方向
|
# 根据持仓方向决定平仓订单的方向
|
||||||
direction = "SELL" if volume_to_close > 0 else "BUY" # 多头平仓是卖出,空头平仓是买入
|
direction = "CLOSE_LONG" if volume_to_close > 0 else "CLOSE_SELL" # 多头平仓是卖出,空头平仓是买入
|
||||||
|
|
||||||
# 构造一个市价平仓订单
|
# 构造一个市价平仓订单
|
||||||
rollover_order = Order(
|
rollover_order = Order(
|
||||||
id=f"FORCE_CLOSE_{symbol_to_close}_{closing_bar.datetime.strftime('%Y%m%d%H%M%S%f')}",
|
id=f"FORCE_CLOSE_{symbol_to_close}_{closing_bar.datetime.strftime('%Y%m%d%H%M%S%f')}",
|
||||||
@@ -270,7 +321,7 @@ class ExecutionSimulator:
|
|||||||
limit_price=None,
|
limit_price=None,
|
||||||
submitted_time=closing_bar.datetime,
|
submitted_time=closing_bar.datetime,
|
||||||
)
|
)
|
||||||
|
|
||||||
# 使用内部的执行逻辑进行撮合
|
# 使用内部的执行逻辑进行撮合
|
||||||
trade = self._execute_single_order(rollover_order, closing_bar)
|
trade = self._execute_single_order(rollover_order, closing_bar)
|
||||||
if trade:
|
if trade:
|
||||||
@@ -291,7 +342,7 @@ class ExecutionSimulator:
|
|||||||
if order.symbol == symbol_to_cancel
|
if order.symbol == symbol_to_cancel
|
||||||
]
|
]
|
||||||
for order_id in order_ids_to_cancel:
|
for order_id in order_ids_to_cancel:
|
||||||
if self.cancel_order(order_id): # 调用现有的 cancel_order 方法
|
if self.cancel_order(order_id): # 调用现有的 cancel_order 方法
|
||||||
cancelled_count += 1
|
cancelled_count += 1
|
||||||
return cancelled_count
|
return cancelled_count
|
||||||
|
|
||||||
@@ -347,10 +398,10 @@ class ExecutionSimulator:
|
|||||||
self.cash = new_initial_capital if new_initial_capital is not None else self.initial_capital
|
self.cash = new_initial_capital if new_initial_capital is not None else self.initial_capital
|
||||||
self.positions = new_initial_positions.copy() if new_initial_positions is not None else {}
|
self.positions = new_initial_positions.copy() if new_initial_positions is not None else {}
|
||||||
self.average_costs = {}
|
self.average_costs = {}
|
||||||
for symbol, qty in self.positions.items(): # 重置平均成本
|
for symbol, qty in self.positions.items(): # 重置平均成本
|
||||||
self.average_costs[symbol] = 0.0
|
self.average_costs[symbol] = 0.0
|
||||||
self.trade_log = []
|
self.trade_log = []
|
||||||
self.pending_orders = {} # 清空挂单
|
self.pending_orders = {} # 清空挂单
|
||||||
self._current_time = None
|
self._current_time = None
|
||||||
|
|
||||||
# Removed clear_trade_history as trade_log is cleared in reset
|
# Removed clear_trade_history as trade_log is cleared in reset
|
||||||
@@ -358,4 +409,4 @@ class ExecutionSimulator:
|
|||||||
def get_average_position_price(self, symbol: str) -> Optional[float]:
|
def get_average_position_price(self, symbol: str) -> Optional[float]:
|
||||||
if symbol in self.positions and self.positions[symbol] != 0:
|
if symbol in self.positions and self.positions[symbol] != 0:
|
||||||
return self.average_costs.get(symbol)
|
return self.average_costs.get(symbol)
|
||||||
return None
|
return None
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
@@ -157,6 +157,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
self.log(f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
self.log(f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
||||||
f"前1Range={range_1_ago:.2f}, 前7Range={range_7_ago:.2f}, "
|
f"前1Range={range_1_ago:.2f}, 前7Range={range_7_ago:.2f}, "
|
||||||
f"计算目标买入价={target_buy_price:.2f}")
|
f"计算目标买入价={target_buy_price:.2f}")
|
||||||
|
self.log(f'{self.context._simulator.get_current_positions()}')
|
||||||
|
|
||||||
order_id = f"{self.symbol}_BUY_{bar.datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
order_id = f"{self.symbol}_BUY_{bar.datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
||||||
self.order_id_counter += 1
|
self.order_id_counter += 1
|
||||||
|
|||||||
Reference in New Issue
Block a user