feat: 添加边界防御机制使实盘行为与回测对齐

This commit is contained in:
2026-03-16 22:12:06 +08:00
parent 3c5a4c288a
commit a6aced2308
5 changed files with 3141 additions and 467 deletions

View File

@@ -236,12 +236,12 @@ if __name__ == "__main__":
# 这种方式适合获取相对较短或中等长度的历史K线数据。 # 这种方式适合获取相对较短或中等长度的历史K线数据。
df_if_backtest_daily = collect_and_save_tqsdk_data_stream( df_if_backtest_daily = collect_and_save_tqsdk_data_stream(
symbol="KQ.m@SHFE.br", symbol="KQ.m@CZCE.FG",
# symbol='SHFE.rb2510', # symbol='SHFE.rb2510',
# symbol='KQ.i@SHFE.bu', # symbol='KQ.i@SHFE.bu',
freq="min15", freq="min15",
start_date_str="2021-01-01", start_date_str="2020-01-01",
end_date_str="2025-12-01", end_date_str="2026-03-18",
mode="backtest", # 指定为回测模式 mode="backtest", # 指定为回测模式
tq_user=TQ_USER_NAME, tq_user=TQ_USER_NAME,
tq_pwd=TQ_PASSWORD, tq_pwd=TQ_PASSWORD,

File diff suppressed because one or more lines are too long

View File

@@ -118,6 +118,7 @@ class PragmaticCyberneticStrategy(Strategy):
self._closes.append(prev_bar.close) self._closes.append(prev_bar.close)
T, FV, FB, ATR = self._calculate_indicators() T, FV, FB, ATR = self._calculate_indicators()
if self.trading:
self.log(f'T: {T} FV: {FV} FB: {FB} ATR: {ATR}') self.log(f'T: {T} FV: {FV} FB: {FB} ATR: {ATR}')
if T is None or math.isnan(ATR): return if T is None or math.isnan(ATR): return
@@ -134,6 +135,7 @@ class PragmaticCyberneticStrategy(Strategy):
is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple()) is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
if self.trading:
self.log(f'is_met: {is_met}, pos: {pos}, current_fb: {current_fb}, prev_fb: {prev_fb}') self.log(f'is_met: {is_met}, pos: {pos}, current_fb: {current_fb}, prev_fb: {prev_fb}')
# ========================================== # ==========================================
@@ -192,6 +194,7 @@ class PragmaticCyberneticStrategy(Strategy):
# 核心二:经典入场逻辑 (完全不变) # 核心二:经典入场逻辑 (完全不变)
# ========================================== # ==========================================
elif pos == 0 and is_met: elif pos == 0 and is_met:
if self.trading:
self.log(f'{prev_bar.close}, {T} , {current_fb} , {self.fb_entry_threshold}') self.log(f'{prev_bar.close}, {T} , {current_fb} , {self.fb_entry_threshold}')
self.log(f'prev_bar.close > T: {prev_bar.close > T}, current_fb < -self.fb_entry_threshold : {current_fb < -self.fb_entry_threshold}') self.log(f'prev_bar.close > T: {prev_bar.close > T}, current_fb < -self.fb_entry_threshold : {current_fb < -self.fb_entry_threshold}')
self.log(f'prev_bar.close < T: {prev_bar.close < T}, current_fb > self.fb_entry_threshold: {current_fb > self.fb_entry_threshold}') self.log(f'prev_bar.close < T: {prev_bar.close < T}, current_fb > self.fb_entry_threshold: {current_fb > self.fb_entry_threshold}')
@@ -199,11 +202,13 @@ class PragmaticCyberneticStrategy(Strategy):
if "BUY" in self.order_direction: if "BUY" in self.order_direction:
long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR)) long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR))
self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN") self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN")
self.log(f"BUY LIMIT {long_limit}")
elif prev_bar.close < T and current_fb > self.fb_entry_threshold: elif prev_bar.close < T and current_fb > self.fb_entry_threshold:
if "SELL" in self.order_direction: if "SELL" in self.order_direction:
short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR)) short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR))
self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN") self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN")
self.log(f"SELL LIMIT {short_limit}")
# ========================================== # ==========================================
# 彻底隔离历史噪音:原生换月机制 # 彻底隔离历史噪音:原生换月机制

View File

@@ -118,6 +118,7 @@ class PragmaticCyberneticStrategy(Strategy):
self._closes.append(prev_bar.close) self._closes.append(prev_bar.close)
T, FV, FB, ATR = self._calculate_indicators() T, FV, FB, ATR = self._calculate_indicators()
if self.trading:
self.log(f'T: {T} FV: {FV} FB: {FB} ATR: {ATR}') self.log(f'T: {T} FV: {FV} FB: {FB} ATR: {ATR}')
if T is None or math.isnan(ATR): return if T is None or math.isnan(ATR): return
@@ -134,6 +135,7 @@ class PragmaticCyberneticStrategy(Strategy):
is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple()) is_met = self.indicator is None or self.indicator.is_condition_met(*self.get_indicator_tuple())
if self.trading:
self.log(f'is_met: {is_met}, pos: {pos}, current_fb: {current_fb}, prev_fb: {prev_fb}') self.log(f'is_met: {is_met}, pos: {pos}, current_fb: {current_fb}, prev_fb: {prev_fb}')
# ========================================== # ==========================================
@@ -192,6 +194,7 @@ class PragmaticCyberneticStrategy(Strategy):
# 核心二:经典入场逻辑 (完全不变) # 核心二:经典入场逻辑 (完全不变)
# ========================================== # ==========================================
elif pos == 0 and is_met: elif pos == 0 and is_met:
if self.trading:
self.log(f'{prev_bar.close}, {T} , {current_fb} , {self.fb_entry_threshold}') self.log(f'{prev_bar.close}, {T} , {current_fb} , {self.fb_entry_threshold}')
self.log(f'prev_bar.close > T: {prev_bar.close > T}, current_fb < -self.fb_entry_threshold : {current_fb < -self.fb_entry_threshold}') self.log(f'prev_bar.close > T: {prev_bar.close > T}, current_fb < -self.fb_entry_threshold : {current_fb < -self.fb_entry_threshold}')
self.log(f'prev_bar.close < T: {prev_bar.close < T}, current_fb > self.fb_entry_threshold: {current_fb > self.fb_entry_threshold}') self.log(f'prev_bar.close < T: {prev_bar.close < T}, current_fb > self.fb_entry_threshold: {current_fb > self.fb_entry_threshold}')
@@ -199,11 +202,13 @@ class PragmaticCyberneticStrategy(Strategy):
if "BUY" in self.order_direction: if "BUY" in self.order_direction:
long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR)) long_limit = self.round_to_tick(FV - (self.limit_offset_mult * ATR))
self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN") self.send_limit_order(long_limit, "BUY", self.trade_volume, "OPEN")
self.log(f"BUY LIMIT {long_limit}")
elif prev_bar.close < T and current_fb > self.fb_entry_threshold: elif prev_bar.close < T and current_fb > self.fb_entry_threshold:
if "SELL" in self.order_direction: if "SELL" in self.order_direction:
short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR)) short_limit = self.round_to_tick(FV + (self.limit_offset_mult * ATR))
self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN") self.send_limit_order(short_limit, "SELL", self.trade_volume, "OPEN")
self.log(f"SELL LIMIT {short_limit}")
# ========================================== # ==========================================
# 彻底隔离历史噪音:原生换月机制 # 彻底隔离历史噪音:原生换月机制

View File

@@ -7,7 +7,11 @@ import pandas as pd
import time import time
# 导入你提供的 core_data 中的类型 # 导入你提供的 core_data 中的类型
from src.common_utils import is_bar_pre_close_period, is_futures_trading_time, generate_strategy_identifier from src.common_utils import (
is_bar_pre_close_period,
is_futures_trading_time,
generate_strategy_identifier,
)
from src.core_data import Bar, Order, Trade, PortfolioSnapshot from src.core_data import Bar, Order, Trade, PortfolioSnapshot
# 导入 Tqsdk 的核心类型 # 导入 Tqsdk 的核心类型
@@ -24,6 +28,7 @@ from tqsdk import (
) )
from src.state_repo import JsonFileStateRepository from src.state_repo import JsonFileStateRepository
# 导入 TqsdkContext 和 BaseStrategy # 导入 TqsdkContext 和 BaseStrategy
from src.tqsdk_real_context import TqsdkContext from src.tqsdk_real_context import TqsdkContext
from src.strategies.base_strategy import Strategy # 假设你的策略基类在此路径 from src.strategies.base_strategy import Strategy # 假设你的策略基类在此路径
@@ -91,7 +96,9 @@ class TqsdkEngine:
# 初始化上下文 # 初始化上下文
identifier = generate_strategy_identifier(strategy_class, strategy_params) identifier = generate_strategy_identifier(strategy_class, strategy_params)
self._context: TqsdkContext = TqsdkContext(api=self._api, state_repository=JsonFileStateRepository(identifier)) # 实例化策略,并将上下文传递给它 self._context: TqsdkContext = TqsdkContext(
api=self._api, state_repository=JsonFileStateRepository(identifier)
) # 实例化策略,并将上下文传递给它
self._strategy: Strategy = self.strategy_class( self._strategy: Strategy = self.strategy_class(
context=self._context, **self.strategy_params context=self._context, **self.strategy_params
) )
@@ -117,7 +124,9 @@ class TqsdkEngine:
self.quote = None self.quote = None
self.quote = api.get_quote(symbol) self.quote = api.get_quote(symbol)
self.klines = api.get_kline_serial( self.klines = api.get_kline_serial(
self.quote.underlying_symbol, duration_seconds, data_length=history_length + 2 self.quote.underlying_symbol,
duration_seconds,
data_length=history_length + 2,
) )
self.klines_1min = api.get_kline_serial(self.quote.underlying_symbol, 60) self.klines_1min = api.get_kline_serial(self.quote.underlying_symbol, 60)
@@ -126,6 +135,9 @@ class TqsdkEngine:
self.target_pos_dict = {} self.target_pos_dict = {}
# 边界检测状态记录上一根bar结束时是否有持仓
self.prev_bar_had_position: bool = False
print("TqsdkEngine: 初始化完成。") print("TqsdkEngine: 初始化完成。")
@property @property
@@ -172,16 +184,20 @@ class TqsdkEngine:
current_pos_volume = current_positions.get(order_to_send.symbol, 0) current_pos_volume = current_positions.get(order_to_send.symbol, 0)
target_volume = None target_volume = None
if order_to_send.direction == 'CLOSE_LONG': if order_to_send.direction == "CLOSE_LONG":
target_volume = current_pos_volume - order_to_send.volume target_volume = current_pos_volume - order_to_send.volume
elif order_to_send.direction == 'CLOSE_SHORT': elif order_to_send.direction == "CLOSE_SHORT":
target_volume = current_pos_volume + order_to_send.volume target_volume = current_pos_volume + order_to_send.volume
if target_volume is not None: if target_volume is not None:
if order_to_send.symbol not in self.target_pos_dict: if order_to_send.symbol not in self.target_pos_dict:
self.target_pos_dict[order_to_send.symbol] = TargetPosTask(self._api, order_to_send.symbol) self.target_pos_dict[order_to_send.symbol] = TargetPosTask(
self._api, order_to_send.symbol
)
self.target_pos_dict[order_to_send.symbol].set_target_volume(target_volume) self.target_pos_dict[order_to_send.symbol].set_target_volume(
target_volume
)
else: else:
try: try:
tq_order = self._api.insert_order( tq_order = self._api.insert_order(
@@ -347,6 +363,15 @@ class TqsdkEngine:
f"TqsdkEngine: self._last_underlying_symbol:{self._last_underlying_symbol}, is_trading_time:{is_trading_time}" f"TqsdkEngine: self._last_underlying_symbol:{self._last_underlying_symbol}, is_trading_time:{is_trading_time}"
) )
# 初始化边界检测状态:根据实际持仓设置(处理引擎重启情况)
current_positions = self._context.get_current_positions()
self.prev_bar_had_position = (
current_positions.get(self.quote.underlying_symbol, 0) != 0
)
print(
f"TqsdkEngine: 边界检测状态初始化完成prev_bar_had_position={self.prev_bar_had_position}"
)
# 初始化策略 (如果策略有 on_init 方法) # 初始化策略 (如果策略有 on_init 方法)
if hasattr(self._strategy, "on_init"): if hasattr(self._strategy, "on_init"):
self._strategy.on_init() self._strategy.on_init()
@@ -362,6 +387,7 @@ class TqsdkEngine:
kline_dt = kline_dt.tz_convert(BEIJING_TZ) kline_dt = kline_dt.tz_convert(BEIJING_TZ)
print(f"TqsdkEngine: 当前是交易时间处理最新一根k线datetime:{kline_dt}") print(f"TqsdkEngine: 当前是交易时间处理最新一根k线datetime:{kline_dt}")
self._check_boundary_and_close() # 边界检测上一根bar新开仓且触及边界价则平仓
self.main(self.klines.iloc[-1], self.klines.iloc[-2]) self.main(self.klines.iloc[-1], self.klines.iloc[-2])
new_bar = True new_bar = True
@@ -375,7 +401,9 @@ class TqsdkEngine:
for bar in self.all_bars[-5:]: for bar in self.all_bars[-5:]:
print(bar) print(bar)
print(f"TqsdkEngine: 开始等待最新数据, all bars -1:{self.all_bars[-1].datetime}") print(
f"TqsdkEngine: 开始等待最新数据, all bars -1:{self.all_bars[-1].datetime}"
)
last_min_k = None last_min_k = None
while True: while True:
@@ -388,11 +416,16 @@ class TqsdkEngine:
self._check_roll_over() self._check_roll_over()
self.is_checked_rollover = True self.is_checked_rollover = True
if new_bar and (last_min_k is None or last_min_k.datetime != self.klines_1min.iloc[-1].datetime): if new_bar and (
last_min_k is None
or last_min_k.datetime != self.klines_1min.iloc[-1].datetime
):
last_min_k = self.klines_1min.iloc[-1] last_min_k = self.klines_1min.iloc[-1]
if self.kline_row is not None: if self.kline_row is not None:
kline_dt = pd.to_datetime(self.kline_row.datetime, unit="ns", utc=True) kline_dt = pd.to_datetime(
self.kline_row.datetime, unit="ns", utc=True
)
kline_dt = kline_dt.tz_convert(BEIJING_TZ) kline_dt = kline_dt.tz_convert(BEIJING_TZ)
is_close_bar = is_bar_pre_close_period( is_close_bar = is_bar_pre_close_period(
@@ -410,8 +443,10 @@ class TqsdkEngine:
# if self._api.is_changing(self.klines.iloc[-1], "open"): # if self._api.is_changing(self.klines.iloc[-1], "open"):
# print(f"TqsdkEngine: open change!, open:{self.klines.iloc[-1].open}, now: {datetime.now()}") # print(f"TqsdkEngine: open change!, open:{self.klines.iloc[-1].open}, now: {datetime.now()}")
if self.kline_row is None or self.kline_row.datetime != self.klines.iloc[-1].datetime: if (
self.kline_row is None
or self.kline_row.datetime != self.klines.iloc[-1].datetime
):
# 到这里一定满足“整点-00/30 且秒>1” # 到这里一定满足“整点-00/30 且秒>1”
kline_row = self.klines.iloc[-1] kline_row = self.klines.iloc[-1]
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True) kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
@@ -433,12 +468,16 @@ class TqsdkEngine:
# 小粒度休眠,防止 CPU 空转 # 小粒度休眠,防止 CPU 空转
self._api.wait_update() self._api.wait_update()
if kline_dt.hour != self.all_bars[-1].datetime.hour or kline_dt.minute != self.all_bars[ if (
-1].datetime.minute: kline_dt.hour != self.all_bars[-1].datetime.hour
or kline_dt.minute != self.all_bars[-1].datetime.minute
):
print( print(
f"TqsdkEngine: 新k线产生, k line datetime:{kline_dt}, now: {datetime.now()}, open: {self.klines.iloc[-1].open}") f"TqsdkEngine: 新k线产生, k line datetime:{kline_dt}, now: {datetime.now()}, open: {self.klines.iloc[-1].open}"
)
self.kline_row = self.klines.iloc[-1] self.kline_row = self.klines.iloc[-1]
self._check_boundary_and_close() # 边界检测上一根bar新开仓且触及边界价则平仓
self.main(self.klines.iloc[-1], self.klines.iloc[-2]) self.main(self.klines.iloc[-1], self.klines.iloc[-2])
new_bar = True new_bar = True
@@ -468,7 +507,6 @@ class TqsdkEngine:
# 处理订单和取消请求 # 处理订单和取消请求
self._process_queued_requests() self._process_queued_requests()
def _check_roll_over(self, timeout_seconds: int = 120): def _check_roll_over(self, timeout_seconds: int = 120):
""" """
[最安全版] 检查并处理实盘持仓换月,此函数会阻塞直到换月成功或超时。 [最安全版] 检查并处理实盘持仓换月,此函数会阻塞直到换月成功或超时。
@@ -500,9 +538,11 @@ class TqsdkEngine:
# 条件一: 是本引擎负责的品种 (e.g., "CZCE.FG605".startswith("CZCE.FG")) # 条件一: 是本引擎负责的品种 (e.g., "CZCE.FG605".startswith("CZCE.FG"))
# 条件二: 不是当前最新的主力合约 # 条件二: 不是当前最新的主力合约
# 条件三: 有实际持仓 # 条件三: 有实际持仓
if (pos_symbol.startswith(self.product_id) and if (
pos_symbol != current_dominant_symbol and pos_symbol.startswith(self.product_id)
quantity != 0): and pos_symbol != current_dominant_symbol
and quantity != 0
):
old_contracts_to_rollover[pos_symbol] = quantity old_contracts_to_rollover[pos_symbol] = quantity
# 如果没有需要处理的旧合约,直接返回 # 如果没有需要处理的旧合约,直接返回
@@ -532,10 +572,16 @@ class TqsdkEngine:
# 5.2 在新合约上建立合并后的总目标仓位 # 5.2 在新合约上建立合并后的总目标仓位
if current_dominant_symbol not in self.target_pos_dict: if current_dominant_symbol not in self.target_pos_dict:
self.target_pos_dict[current_dominant_symbol] = TargetPosTask(self._api, current_dominant_symbol) self.target_pos_dict[current_dominant_symbol] = TargetPosTask(
self.target_pos_dict[current_dominant_symbol].set_target_volume(total_target_quantity) self._api, current_dominant_symbol
)
self.target_pos_dict[current_dominant_symbol].set_target_volume(
total_target_quantity
)
print(f" - [移仓指令已发送] 正在处理 {len(old_contracts_to_rollover)} 个旧合约的平仓...") print(
f" - [移仓指令已发送] 正在处理 {len(old_contracts_to_rollover)} 个旧合约的平仓..."
)
# 6. 进入等待循环,直到所有换月操作完成或超时 # 6. 进入等待循环,直到所有换月操作完成或超时
while True: while True:
@@ -557,26 +603,120 @@ class TqsdkEngine:
latest_positions = self._context.get_current_positions() latest_positions = self._context.get_current_positions()
# 检查所有旧合约仓位是否已归零 # 检查所有旧合约仓位是否已归零
all_old_cleared = all(latest_positions.get(s, 0) == 0 for s in old_contracts_to_rollover) all_old_cleared = all(
latest_positions.get(s, 0) == 0 for s in old_contracts_to_rollover
)
# 检查新合约仓位是否已达到目标 # 检查新合约仓位是否已达到目标
new_pos_correct = latest_positions.get(current_dominant_symbol, 0) == total_target_quantity new_pos_correct = (
latest_positions.get(current_dominant_symbol, 0)
== total_target_quantity
)
if all_old_cleared and new_pos_correct: if all_old_cleared and new_pos_correct:
print("-" * 70) print("-" * 70)
print(f"TqsdkEngine ({self.product_id}): [换月成功] 移仓操作已确认完成。") print(
f"TqsdkEngine ({self.product_id}): [换月成功] 移仓操作已确认完成。"
)
print(f" - 所有旧合约持仓已清零。") print(f" - 所有旧合约持仓已清零。")
print(f" - 新合约 {current_dominant_symbol} 持仓: {total_target_quantity}") print(
f" - 新合约 {current_dominant_symbol} 持仓: {total_target_quantity}"
)
print("-" * 70) print("-" * 70)
# 6.3 通知策略层 (只需通知一次) # 6.3 通知策略层 (只需通知一次)
if hasattr(self._strategy, "on_rollover"): if hasattr(self._strategy, "on_rollover"):
# 传递第一个旧合约符号作为代表 # 传递第一个旧合约符号作为代表
representative_old_symbol = list(old_contracts_to_rollover.keys())[0] representative_old_symbol = list(old_contracts_to_rollover.keys())[
self._strategy.on_rollover(representative_old_symbol, current_dominant_symbol) 0
]
self._strategy.on_rollover(
representative_old_symbol, current_dominant_symbol
)
break # 成功,跳出循环 break # 成功,跳出循环
def _check_boundary_and_close(self):
"""
检查上一根bar是否新开仓且触及边界价如果是则平仓。
只在实盘循环中调用,避免预热阶段误平仓。
使用 self.klines.iloc[-2] 获取上一根K线数据。
"""
import math
import time
from datetime import datetime
# 获取上一根K线数据
prev_kline = self.klines.iloc[-2]
current_positions = self._context.get_current_positions()
current_qty = current_positions.get(self._last_underlying_symbol, 0)
# 检测是否是上一根bar新开仓上一根bar没有持仓但现在有持仓
if not self.prev_bar_had_position and current_qty != 0:
avg_price = self._context.get_average_position_price(
self._last_underlying_symbol
)
if avg_price is not None:
# 修复1使用 math.isclose 解决浮点数精度问题
is_long_boundary = (current_qty > 0) and math.isclose(
avg_price, prev_kline.low, abs_tol=1e-5
)
is_short_boundary = (current_qty < 0) and math.isclose(
avg_price, prev_kline.high, abs_tol=1e-5
)
if is_long_boundary or is_short_boundary:
direction = "CLOSE_LONG" if current_qty > 0 else "CLOSE_SHORT"
boundary_price = (
prev_kline.low if current_qty > 0 else prev_kline.high
)
print("=" * 60)
print(f"🚨 [务实对齐] 触发边界防御机制!")
print(
f" - 持仓方向: {'多仓' if current_qty > 0 else '空仓'} ({current_qty}手)"
)
print(f" - 物理均价: {avg_price} == 上根K线极值: {boundary_price}")
print(
f" - 说明: 极大概率在回测中不会成交,立即物理对齐以保护策略状态!"
)
print("=" * 60)
# 修复2补全 Order 必需参数,防止框架报错
close_order = Order(
id=f"SYS_ALIGN_CLOSE_{int(time.time() * 1000)}",
symbol=self._last_underlying_symbol,
direction=direction,
volume=abs(current_qty),
price_type="MARKET",
offset="CLOSE",
submitted_time=datetime.now(),
)
self._context.send_order(close_order)
self._process_queued_requests()
# 修复3阻塞等待 TargetPosTask 执行完毕,防止策略复读
wait_timeout = time.monotonic() + 10 # 最大等待 10 秒
while True:
self._api.wait_update()
temp_qty = self._context.get_current_positions().get(
self._last_underlying_symbol, 0
)
if temp_qty == 0:
print("✅ [务实对齐] 平仓确认完成,账户已归零。")
current_qty = 0 # 同步本地状态
break
if time.monotonic() > wait_timeout:
print(
"⚠️ [务实对齐] 警告: 平仓确认超时,策略可能陷入混乱状态!"
)
break
# 更新状态记录本根bar开始时的持仓情况供下一根bar检测使用
self.prev_bar_had_position = current_qty != 0
def main(self, kline_row, prev_kline_row): def main(self, kline_row, prev_kline_row):
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True) kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
@@ -584,7 +724,9 @@ class TqsdkEngine:
if self.partial_bar is not None: if self.partial_bar is not None:
last_bar = Bar( last_bar = Bar(
datetime=pd.to_datetime(prev_kline_row.datetime, unit="ns", utc=True).tz_convert(BEIJING_TZ), datetime=pd.to_datetime(
prev_kline_row.datetime, unit="ns", utc=True
).tz_convert(BEIJING_TZ),
symbol=self.partial_bar.symbol, symbol=self.partial_bar.symbol,
open=prev_kline_row.open, open=prev_kline_row.open,
high=prev_kline_row.high, high=prev_kline_row.high,
@@ -605,7 +747,6 @@ class TqsdkEngine:
self.last_processed_bar = last_bar self.last_processed_bar = last_bar
self._strategy.on_open_bar(kline_row.open, self._last_underlying_symbol) self._strategy.on_open_bar(kline_row.open, self._last_underlying_symbol)
# 处理订单和取消请求 # 处理订单和取消请求
if self._strategy.trading is True: if self._strategy.trading is True: