1、新增SMCPureH1LongStrategy策略

2、修复实盘bug
This commit is contained in:
2025-07-28 14:36:58 +08:00
parent 2fa952a3da
commit 52c5ec18d8
12 changed files with 982 additions and 852 deletions

File diff suppressed because one or more lines are too long

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@DCE.jm", symbol="KQ.m@SHFE.rb",
# symbol='SHFE.rb2510', # symbol='SHFE.rb2510',
# symbol='KQ.i@SHFE.bu', # symbol='KQ.i@SHFE.bu',
freq="min60", freq="min15",
start_date_str="2021-01-01", start_date_str="2021-01-01",
end_date_str="2025-07-11", end_date_str="2025-07-22",
mode="backtest", # 指定为回测模式 mode="backtest", # 指定为回测模式
tq_user=TQ_USER_NAME, tq_user=TQ_USER_NAME,
tq_pwd=TQ_PASSWORD, tq_pwd=TQ_PASSWORD,

View File

@@ -6,8 +6,8 @@
"id": "initial_id", "id": "initial_id",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-07-12T14:39:30.843472Z", "end_time": "2025-07-22T07:44:51.375234Z",
"start_time": "2025-07-12T14:39:30.823755Z" "start_time": "2025-07-22T07:44:51.352161Z"
}, },
"collapsed": true "collapsed": true
}, },
@@ -20,54 +20,44 @@
}, },
{ {
"cell_type": "code", "cell_type": "code",
"execution_count": null, "execution_count": 2,
"id": "a559dfcf", "id": "a559dfcf",
"metadata": { "metadata": {
"ExecuteTime": { "ExecuteTime": {
"end_time": "2025-07-12T14:39:35.610573Z", "end_time": "2025-07-22T07:44:56.927700Z",
"start_time": "2025-07-12T14:39:30.855663Z" "start_time": "2025-07-22T07:44:51.391111Z"
} }
}, },
"outputs": [ "outputs": [
{ {
"name": "stdout", "ename": "ModuleNotFoundError",
"output_type": "stream", "evalue": "No module named 'src'",
"text": [ "output_type": "error",
"初始化数据管理器...\n", "traceback": [
"数据加载成功: /mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\n", "\u001b[31m---------------------------------------------------------------------------\u001b[39m",
"数据范围从 2021-12-31 14:00:00 到 2025-07-10 09:00:00\n", "\u001b[31mModuleNotFoundError\u001b[39m Traceback (most recent call last)",
"总计 5904 条记录。\n", "\u001b[36mCell\u001b[39m\u001b[36m \u001b[39m\u001b[32mIn[2]\u001b[39m\u001b[32m, line 2\u001b[39m\n\u001b[32m 1\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01mturtle\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m down\n\u001b[32m----> \u001b[39m\u001b[32m2\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msrc\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01manalysis\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mresult_analyzer\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m ResultAnalyzer\n\u001b[32m 3\u001b[39m \u001b[38;5;66;03m# 导入所有必要的模块\u001b[39;00m\n\u001b[32m 4\u001b[39m \u001b[38;5;28;01mfrom\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[34;01msrc\u001b[39;00m\u001b[34;01m.\u001b[39;00m\u001b[34;01mdata_manager\u001b[39;00m\u001b[38;5;250m \u001b[39m\u001b[38;5;28;01mimport\u001b[39;00m DataManager\n",
"\n", "\u001b[31mModuleNotFoundError\u001b[39m: No module named 'src'"
"初始化回测引擎...\n",
"模拟器初始化:初始资金=100000.00, 滑点率=0.0, 佣金率=0.0001\n",
"\n",
"--- 回测引擎初始化完成 ---\n",
" 策略: SimpleLimitBuyStrategyShort\n",
" 初始资金: 100000.00\n",
" 换月模式: 启用\n",
"\n",
"开始运行回测...\n",
"\n",
"--- 回测开始 ---\n",
"SimpleLimitBuyStrategyShort 策略初始化回调被调用。\n",
"开始将 DataFrame 转换为 Bar 对象流...\n"
] ]
} }
], ],
"source": [ "source": [
"\n", "\n",
"from turtle import down\n",
"from src.analysis.result_analyzer import ResultAnalyzer\n", "from src.analysis.result_analyzer import ResultAnalyzer\n",
"# 导入所有必要的模块\n", "# 导入所有必要的模块\n",
"from src.data_manager import DataManager\n", "from src.data_manager import DataManager\n",
"from src.backtest_engine import BacktestEngine\n", "from src.backtest_engine import BacktestEngine\n",
"from src.indicators.indicator_list import INDICATOR_LIST\n", "from src.indicators.indicator_list import INDICATOR_LIST\n",
"from src.indicators.indicators import RSI, HistoricalRange, StochasticOscillator\n", "from src.indicators.indicators import RSI, BollingerBandwidth, HistoricalRange, NormalizedATR, RateOfChange, StochasticOscillator\n",
"from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy\n", "from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy\n",
"\n", "\n",
"\n", "\n",
"# --- 配置参数 ---\n", "# --- 配置参数 ---\n",
"# 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n", "# 获取当前脚本所在目录,假设数据文件在项目根目录下的 data 文件夹内\n",
"# data_file_path = '/mnt/d/PyProject/NewQuant/data/data/SHFE_rb2510/SHFE_rb2510_min60.csv'\n", "# data_file_path = '/mnt/d/PyProject/NewQuant/data/data/SHFE_rb2510/SHFE_rb2510_min60.csv'\n",
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n",
"# data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@SHFE_rb/KQ_m@SHFE_rb_min60.csv\"\n",
"data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n", "data_file_path = \"/mnt/d/PyProject/NewQuant/data/data/KQ_m@CZCE_MA/KQ_m@CZCE_MA_min60.csv\"\n",
"\n", "\n",
"initial_capital = 100000.0\n", "initial_capital = 100000.0\n",
@@ -78,31 +68,33 @@
" 'symbol': 'KQ_m@CZCE_MA',\n", " 'symbol': 'KQ_m@CZCE_MA',\n",
"}\n", "}\n",
"\n", "\n",
"# Short 可用\n",
"strategy_parameters = {\n", "strategy_parameters = {\n",
" # 'symbol': \"SHFE_rb2501\", # 根据您的数据文件中的品种名称调整\n", " 'main_symbol': \"MA\", # 根据您的数据文件中的品种名称调整\n",
" 'trade_volume': 1,\n", " 'trade_volume': 1,\n",
" 'lag': 7,\n",
" # 'range_factor': 1.8, # 示例值,需要通过网格搜索优化\n", " # 'range_factor': 1.8, # 示例值,需要通过网格搜索优化\n",
" # 'profit_factor': 2.3, # 示例值\n", " # 'profit_factor': 2.8, # 示例值\n",
" 'range_factor': 1.6, # 示例值,需要通过网格搜索优化\n", " # 'range_factor': 1.6, # 示例值,需要通过网格搜索优化\n",
" 'profit_factor': 2.1, # 示例值\n", " # 'profit_factor': 2.1, # 示例值\n",
" # 'range_factor_l': 1.8, # 示例值,需要通过网格搜索优化\n", " 'range_factor_l': 1.8, # 示例值,需要通过网格搜索优化\n",
" # 'profit_factor_l': 2.8, # 示例值\n", " 'profit_factor_l': 2.8, # 示例值\n",
" # 'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化\n", " 'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化\n",
" # 'profit_factor_s': 2.1, # 示例值\n", " 'profit_factor_s': 2.1, # 示例值\n",
" 'max_position': 10,\n", " 'max_position': 10,\n",
" 'enable_log': False,\n", " 'enable_log': True,\n",
" 'stop_loss_points': 15,\n", " 'stop_loss_points': 20,\n",
" 'use_indicator': True,\n", " 'use_indicator': True,\n",
" # 'indicator': HistoricalRange(shift_window=20, down_bound=10, up_bound=24),\n", " # 'indicator': HistoricalRange(11, 25, 20),\n",
" 'indicator': RSI(5, 15, 60),\n", " # 'indicator': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),\n",
" # 'indicator_l': HistoricalRange(21, 10, 25),\n", " 'indicator_l': HistoricalRange(11, 25, 20),\n",
" # 'indicator_s': RSI(5, 20, 60),\n", " 'indicator_s': BollingerBandwidth(window=20, nbdev=2.0, down_bound=1.9, up_bound=3.25),\n",
"}\n", "}\n",
"start_time = datetime(2022, 1, 1)\n", "start_time = datetime(2021, 1, 1)\n",
"end_time = datetime(2024, 6, 1)\n", "end_time = datetime(2024, 6, 1)\n",
"\n", "\n",
"# start_time = datetime(2024, 6, 1)\n", "start_time = datetime(2024, 6, 1)\n",
"# end_time = datetime(2025, 6, 1)\n", "end_time = datetime(2025, 8, 1)\n",
"\n", "\n",
"\n", "\n",
"# --- 1. 初始化数据管理器 ---\n", "# --- 1. 初始化数据管理器 ---\n",
@@ -115,7 +107,7 @@
"print(\"\\n初始化回测引擎...\")\n", "print(\"\\n初始化回测引擎...\")\n",
"engine = BacktestEngine(\n", "engine = BacktestEngine(\n",
" data_manager=data_manager,\n", " data_manager=data_manager,\n",
" strategy_class=SimpleLimitBuyStrategyShort,\n", " strategy_class=SimpleLimitBuyStrategy,\n",
" # current_segment_symbol=strategy_parameters['symbol'],\n", " # current_segment_symbol=strategy_parameters['symbol'],\n",
" strategy_params=strategy_parameters,\n", " strategy_params=strategy_parameters,\n",
" initial_capital=initial_capital,\n", " initial_capital=initial_capital,\n",

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -8,15 +8,11 @@ from src.tqsdk_real_engine import TqsdkEngine
# 导入你的策略类 # 导入你的策略类
from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy from src.strategies.OpenTwoFactorStrategy import SimpleLimitBuyStrategyLong, SimpleLimitBuyStrategyShort, SimpleLimitBuyStrategy
from tqsdk import TqApi, TqBacktest, TqAuth, TqKq from tqsdk import TqApi, TqBacktest, TqAuth, TqKq, TqAccount
# --- 配置参数 --- # --- 配置参数 ---
# Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv # Tqsdk 的本地数据文件路径,注意 Tqsdk 要求文件名为 KQ_m@交易所_品种名_周期.csv
# 例如: KQ_m@SHFE_rb_min60.csv
initial_capital = 100000.0
slippage_rate = 0.000 # 在 Tqsdk 模拟中,滑点通常由 TqSim 处理或在策略中手动模拟
commission_rate = 0.0001 # 同上
# 主力合约的 symbol # 主力合约的 symbol
main_symbol = "KQ.m@DCE.jm" main_symbol = "KQ.m@DCE.jm"
strategy_parameters = { strategy_parameters = {
@@ -41,7 +37,9 @@ strategy_parameters = {
'indicator_s': RSI(5, 0, 100), 'indicator_s': RSI(5, 0, 100),
} }
api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg")) # api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
api = TqApi(TqAccount('H宏源期货', '903308830', 'lzr520102'), auth=TqAuth("emanresu", "dfgvfgdfgg"))
# --- 1. 初始化回测引擎并运行 --- # --- 1. 初始化回测引擎并运行 ---
print("\n初始化 Tqsdk 回测引擎...") print("\n初始化 Tqsdk 回测引擎...")
engine = TqsdkEngine( engine = TqsdkEngine(

View File

@@ -56,7 +56,7 @@ class BacktestEngine:
# self.current_segment_symbol = current_segment_symbol # 此行移除或作为内部变量动态管理 # self.current_segment_symbol = current_segment_symbol # 此行移除或作为内部变量动态管理
# 实例化策略。初始 symbol 会在 run_backtest 中根据第一根 Bar 动态设置。 # 实例化策略。初始 symbol 会在 run_backtest 中根据第一根 Bar 动态设置。
self.strategy = strategy_class(self.context, symbol="INITIAL_PLACEHOLDER_SYMBOL", **strategy_params) self.strategy = strategy_class(self.context, **strategy_params)
self.indicators = indicators self.indicators = indicators

View File

@@ -1,5 +1,6 @@
from src.indicators.indicators import RSI, HistoricalRange, DifferencedVolumeIndicator, StochasticOscillator, \ from curses import window
RateOfChange, NormalizedATR
from src.indicators.indicators import *
INDICATOR_LIST = [ INDICATOR_LIST = [
RSI(5), RSI(5),
@@ -29,5 +30,11 @@ INDICATOR_LIST = [
RateOfChange(window=20), RateOfChange(window=20),
NormalizedATR(window=5), NormalizedATR(window=5),
NormalizedATR(window=14), NormalizedATR(window=14),
NormalizedATR(window=21) NormalizedATR(window=21),
ADX(window=7),
ADX(window=14),
ADX(window=30),
BollingerBandwidth(window=10, nbdev=1.5),
BollingerBandwidth(window=20, nbdev=2.0),
BollingerBandwidth(window=50, nbdev=2.5),
] ]

View File

@@ -269,3 +269,85 @@ class NormalizedATR(Indicator):
def get_name(self): def get_name(self):
return f"natr_{self.window}" return f"natr_{self.window}"
class ADX(Indicator):
"""
平均趋向指标 (ADX),用于衡量趋势的强度而非方向。
是区分趋势行情和震荡行情的核心过滤指标。
"""
def __init__(
self,
window: int = 14,
down_bound: float = None, # 例如,设置 down_bound=25 可过滤出强趋势行情
up_bound: float = None, # 例如,设置 up_bound=20 可过滤出震荡行情
shift_window: int = 0,
):
super().__init__(down_bound, up_bound)
self.window = window
self.shift_window = shift_window
def get_values(
self,
close: np.array,
open: np.array, # 不使用
high: np.array,
low: np.array,
volume: np.array, # 不使用
) -> np.array:
"""
根据最高价、最低价和收盘价计算ADX值。
"""
adx_values = talib.ADX(high, low, close, timeperiod=self.window)
return adx_values
def get_name(self):
return f"adx_{self.window}"
class BollingerBandwidth(Indicator):
"""
布林带宽度,计算公式为 (上轨 - 下轨) / 中轨。
这是一个归一化的波动率指标用于识别波动性的收缩Squeeze和扩张。
"""
def __init__(
self,
window: int = 20,
nbdev: float = 2.0, # 标准差倍数
down_bound: float = None,
up_bound: float = None,
shift_window: int = 0,
):
super().__init__(down_bound, up_bound)
self.window = window
self.nbdev = nbdev
self.shift_window = shift_window
def get_values(
self,
close: np.array,
open: np.array, # 不使用
high: np.array, # 不使用
low: np.array, # 不使用
volume: np.array, # 不使用
) -> np.array:
"""
根据收盘价计算布林带宽度。
"""
upper, middle, lower = talib.BBANDS(
close,
timeperiod=self.window,
nbdevup=self.nbdev,
nbdevdn=self.nbdev,
matype=0 # 使用SMA
)
# 为避免除以0在 middle 为0或NaN的地方带宽也设为NaN
bandwidth = np.full_like(middle, np.nan)
mask = (middle > 0)
bandwidth[mask] = (upper[mask] - lower[mask]) / middle[mask] * 100
return bandwidth
def get_name(self):
return f"bbw_{self.window}_{int(self.nbdev*10)}"

View File

@@ -22,7 +22,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
def __init__( def __init__(
self, self,
context: Any, context: Any,
symbol: str, main_symbol: str,
enable_log: bool, enable_log: bool,
trade_volume: int, trade_volume: int,
range_factor: float, range_factor: float,
@@ -46,7 +46,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。 stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。
take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。 take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。
""" """
super().__init__(context, symbol, enable_log) super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume self.trade_volume = trade_volume
self.range_factor = range_factor self.range_factor = range_factor
self.profit_factor = profit_factor self.profit_factor = profit_factor
@@ -57,6 +57,8 @@ class SimpleLimitBuyStrategyLong(Strategy):
self.indicator = indicator self.indicator = indicator
self.lag = lag self.lag = lag
self.main_symbol = main_symbol
self.order_id_counter = 0 self.order_id_counter = 0
self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID
@@ -71,7 +73,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
def on_init(self): def on_init(self):
super().on_init() super().on_init()
count = self.cancel_all_pending_orders() count = self.cancel_all_pending_orders(self.main_symbol)
self.log(f"取消{count}笔订单") self.log(f"取消{count}笔订单")
def on_open_bar(self, open: float, symbol: str): def on_open_bar(self, open: float, symbol: str):
@@ -138,12 +140,15 @@ class SimpleLimitBuyStrategyLong(Strategy):
if avg_entry_price is not None: if avg_entry_price is not None:
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算) pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
stop_position_points = range_1_ago * self.profit_factor
stop_position_points = 20
self.log( self.log(
f"[{current_datetime}] PnL per unit: {pnl_per_unit:.2f}, 目标: {range_1_ago * self.profit_factor:.2f}" f"[{current_datetime}] PnL per unit: {pnl_per_unit:.2f}, 目标: {stop_position_points:.2f}"
) )
# 止盈条件 # 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor: if pnl_per_unit >= stop_position_points:
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}" order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1 self.order_id_counter += 1
@@ -246,7 +251,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(bar_history)}") # self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(bar_history)}")
def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None): def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
self.cancel_all_pending_orders() self.cancel_all_pending_orders(self.main_symbol)
def on_rollover(self, old_symbol: str, new_symbol: str): def on_rollover(self, old_symbol: str, new_symbol: str):
""" """
@@ -270,7 +275,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
def __init__( def __init__(
self, self,
context: Any, context: Any,
symbol: str, main_symbol: str,
enable_log: bool, enable_log: bool,
trade_volume: int, trade_volume: int,
range_factor: float, range_factor: float,
@@ -294,7 +299,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。 stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。
take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。 take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。
""" """
super().__init__(context, symbol, enable_log) super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume self.trade_volume = trade_volume
self.range_factor = range_factor self.range_factor = range_factor
self.profit_factor = profit_factor self.profit_factor = profit_factor
@@ -305,6 +310,8 @@ class SimpleLimitBuyStrategyShort(Strategy):
self.indicator = indicator self.indicator = indicator
self.lag = lag self.lag = lag
self.main_symbol = main_symbol
self.order_id_counter = 0 self.order_id_counter = 0
self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID
@@ -319,7 +326,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
def on_init(self): def on_init(self):
super().on_init() super().on_init()
count = self.cancel_all_pending_orders() count = self.cancel_all_pending_orders(self.main_symbol)
self.log(f"取消{count}笔订单") self.log(f"取消{count}笔订单")
def on_open_bar(self, open: float, symbol: str): def on_open_bar(self, open: float, symbol: str):
@@ -489,7 +496,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(bar_history)}") # self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(bar_history)}")
def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None): def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
self.cancel_all_pending_orders() self.cancel_all_pending_orders(self.main_symbol)
def on_rollover(self, old_symbol: str, new_symbol: str): def on_rollover(self, old_symbol: str, new_symbol: str):
""" """
@@ -513,7 +520,7 @@ class SimpleLimitBuyStrategy(Strategy):
def __init__( def __init__(
self, self,
context: Any, context: Any,
symbol: str, main_symbol: str,
enable_log: bool, enable_log: bool,
trade_volume: int, trade_volume: int,
range_factor_l: float, range_factor_l: float,
@@ -527,6 +534,8 @@ class SimpleLimitBuyStrategy(Strategy):
indicator_l: Indicator = None, indicator_l: Indicator = None,
indicator_s: Indicator = None, indicator_s: Indicator = None,
lag: int = 7, lag: int = 7,
lag_l: int = None,
lag_s: int = None,
): # 新增:止盈点数 ): # 新增:止盈点数
""" """
初始化策略。 初始化策略。
@@ -540,7 +549,7 @@ class SimpleLimitBuyStrategy(Strategy):
stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。 stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。
take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。 take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。
""" """
super().__init__(context, symbol, enable_log) super().__init__(context, main_symbol, enable_log)
self.trade_volume = trade_volume self.trade_volume = trade_volume
self.range_factor_l = range_factor_l self.range_factor_l = range_factor_l
self.profit_factor_l = profit_factor_l self.profit_factor_l = profit_factor_l
@@ -552,7 +561,16 @@ class SimpleLimitBuyStrategy(Strategy):
self.use_indicator = use_indicator self.use_indicator = use_indicator
self.indicator_l = indicator_l self.indicator_l = indicator_l
self.indicator_s = indicator_s self.indicator_s = indicator_s
self.lag = lag
self.main_symbol = main_symbol
self.lag_l = lag_l
self.lag_s = lag_s
if self.lag_l is None:
self.lag_l = lag
if self.lag_s is None:
self.lag_s = lag
self.order_id_counter = 0 self.order_id_counter = 0
@@ -570,7 +588,7 @@ class SimpleLimitBuyStrategy(Strategy):
def on_init(self): def on_init(self):
super().on_init() super().on_init()
count = self.cancel_all_pending_orders() count = self.cancel_all_pending_orders(self.main_symbol)
self.log(f"取消{count}笔订单") self.log(f"取消{count}笔订单")
def on_open_bar(self, open, symbol): def on_open_bar(self, open, symbol):
@@ -583,24 +601,30 @@ class SimpleLimitBuyStrategy(Strategy):
self.symbol = symbol self.symbol = symbol
current_datetime = self.get_current_time() current_datetime = self.get_current_time()
self.log(
f"[{current_datetime}] - 当前Open={open:.2f}, "
)
# --- 1. 撤销上一根K线未成交的订单 --- # --- 1. 撤销上一根K线未成交的订单 ---
count = self.cancel_all_pending_orders(self.main_symbol)
self.log(f"取消{count}笔订单")
# 检查是否记录了上一笔订单ID并且该订单仍然在待处理列表中 # 检查是否记录了上一笔订单ID并且该订单仍然在待处理列表中
if self._last_order_id: # if self._last_order_id:
pending_orders = self.get_pending_orders() # pending_orders = self.get_pending_orders()
if self._last_order_id in pending_orders: # if self._last_order_id in pending_orders:
success = self.cancel_order( # success = self.cancel_order(
self._last_order_id # self._last_order_id
) # 直接调用基类的取消方法 # ) # 直接调用基类的取消方法
if success: # if success:
self.log( # self.log(
f"[{current_datetime}] 策略: 成功撤销上一根K线未成交订单 {self._last_order_id}" # f"[{current_datetime}] 策略: 成功撤销上一根K线未成交订单 {self._last_order_id}"
) # )
else: # else:
self.log( # self.log(
f"[{current_datetime}] 策略: 尝试撤销订单 {self._last_order_id} 失败(可能已成交或不存在)" # f"[{current_datetime}] 策略: 尝试撤销订单 {self._last_order_id} 失败(可能已成交或不存在)"
) # )
# 无论撤销成功与否,既然我们尝试了撤销,就清除记录 # # 无论撤销成功与否,既然我们尝试了撤销,就清除记录
self._last_order_id = None # self._last_order_id = None
# else: # else:
# self.log(f"[{current_datetime}] 策略: 无上一根K线未成交订单需要撤销。") # self.log(f"[{current_datetime}] 策略: 无上一根K线未成交订单需要撤销。")
@@ -610,6 +634,7 @@ class SimpleLimitBuyStrategy(Strategy):
# 获取当前持仓和未决订单(在取消之后获取,确保是最新的状态) # 获取当前持仓和未决订单(在取消之后获取,确保是最新的状态)
current_positions = self.get_current_positions() current_positions = self.get_current_positions()
current_pos_volume = current_positions.get(self.symbol, 0) current_pos_volume = current_positions.get(self.symbol, 0)
self.log(f'current_pos_volume: {current_pos_volume}')
pending_orders_after_cancel = ( pending_orders_after_cancel = (
self.get_pending_orders() self.get_pending_orders()
) # 再次获取,此时应已取消旧订单 ) # 再次获取,此时应已取消旧订单
@@ -617,79 +642,21 @@ class SimpleLimitBuyStrategy(Strategy):
range_1_ago = None range_1_ago = None
bar_history = self.get_bar_history() bar_history = self.get_bar_history()
if len(bar_history) > 16:
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根) # 持有多仓
bar_1_ago = bar_history[-self.lag] if (
current_pos_volume > 0 and len(bar_history) > 16
): # 假设只做多,所以持仓量 > 0
# 计算历史 K 线的 Range bar_1_ago = bar_history[-self.lag_l]
range_1_ago = bar_1_ago.high - bar_1_ago.low range_1_ago = bar_1_ago.high - bar_1_ago.low
# --- 3. 平仓逻辑 (止损/止盈) ---
# 只有当有持仓时才考虑平仓
if (
current_pos_volume < 0 and range_1_ago is not None
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}"
)
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor_s:
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
order = Order(
id=order_id,
symbol=self.symbol,
direction="CLOSE_SHORT",
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
return # 平仓后本K线不再进行开仓判断
# 止损条件
elif pnl_per_unit <= -self.stop_loss_points:
self.log(
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
order = Order(
id=order_id,
symbol=self.symbol,
direction="CLOSE_SHORT",
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
return # 平仓后本K线不再进行开仓判断
if (
current_pos_volume > 0 and range_1_ago is not None
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol) avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None: if avg_entry_price is not None:
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算) pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
self.log( self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}" f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}, 止损: {-self.stop_loss_points:.2f}"
) )
# 止盈条件 # 止盈条件
@@ -703,7 +670,7 @@ class SimpleLimitBuyStrategy(Strategy):
id=order_id, id=order_id,
symbol=self.symbol, symbol=self.symbol,
direction="CLOSE_LONG", direction="CLOSE_LONG",
volume=trade_volume, volume=abs(current_pos_volume),
price_type="MARKET", price_type="MARKET",
# limit_price=limit_price, # limit_price=limit_price,
submitted_time=current_datetime, submitted_time=current_datetime,
@@ -714,9 +681,6 @@ class SimpleLimitBuyStrategy(Strategy):
# 止损条件 # 止损条件
elif pnl_per_unit <= -self.stop_loss_points: elif pnl_per_unit <= -self.stop_loss_points:
self.log(
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交 # 发送市价卖出订单平仓,确保立即成交
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}" order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1 self.order_id_counter += 1
@@ -726,7 +690,63 @@ class SimpleLimitBuyStrategy(Strategy):
id=order_id, id=order_id,
symbol=self.symbol, symbol=self.symbol,
direction="CLOSE_LONG", direction="CLOSE_LONG",
volume=trade_volume, volume=abs(current_pos_volume),
price_type="MARKET",
# limit_price=limit_price,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
return # 平仓后本K线不再进行开仓判断
# 持有空仓
if (
current_pos_volume < 0 and len(bar_history) > 16
): # 假设只做多,所以持仓量 > 0
bar_1_ago = bar_history[-self.lag_s]
range_1_ago = bar_1_ago.high - bar_1_ago.low
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}, 止损: {-self.stop_loss_points:.2f}"
)
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor_s:
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
order = Order(
id=order_id,
symbol=self.symbol,
direction="CLOSE_SHORT",
volume=abs(current_pos_volume),
price_type="MARKET",
# limit_price=limit_price,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
return # 平仓后本K线不再进行开仓判断
# 止损条件
elif pnl_per_unit <= -self.stop_loss_points:
# 发送市价卖出订单平仓,确保立即成交
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
order = Order(
id=order_id,
symbol=self.symbol,
direction="CLOSE_SHORT",
volume=abs(current_pos_volume),
price_type="MARKET", price_type="MARKET",
# limit_price=limit_price, # limit_price=limit_price,
submitted_time=current_datetime, submitted_time=current_datetime,
@@ -739,7 +759,7 @@ class SimpleLimitBuyStrategy(Strategy):
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel) # 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
# 且K线历史足够长时才考虑开仓 # 且K线历史足够长时才考虑开仓
if current_pos_volume == 0 and range_1_ago is not None: if current_pos_volume == 0 and len(bar_history) > 16:
if not self.use_indicator or self.indicator_l.is_condition_met( if not self.use_indicator or self.indicator_l.is_condition_met(
np.array(self.get_price_history("close")), np.array(self.get_price_history("close")),
@@ -750,6 +770,9 @@ class SimpleLimitBuyStrategy(Strategy):
): ):
# 根据策略逻辑计算目标买入价格 # 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2) # 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
bar_1_ago = bar_history[-self.lag_l]
range_1_ago = bar_1_ago.high - bar_1_ago.low
self.log(open, range_1_ago * self.range_factor_l) self.log(open, range_1_ago * self.range_factor_l)
target_buy_price = open - (range_1_ago * self.range_factor_l) target_buy_price = open - (range_1_ago * self.range_factor_l)
@@ -757,9 +780,9 @@ class SimpleLimitBuyStrategy(Strategy):
target_buy_price = max(0.01, target_buy_price) target_buy_price = max(0.01, target_buy_price)
self.log( self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, " f"[{current_datetime}] LONG信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, " f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}" f"计算目标LONG价={target_buy_price:.2f}"
) )
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}" order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
@@ -780,14 +803,11 @@ class SimpleLimitBuyStrategy(Strategy):
if new_order: if new_order:
self._last_order_id = new_order.id self._last_order_id = new_order.id
self.log( self.log(
f"[{current_datetime}] 策略: 发送限价买入订单 {self._last_order_id} @ {target_buy_price:.2f}" f"[{current_datetime}] 策略: 发送限价LONG订单 {self._last_order_id} @ {target_buy_price:.2f}"
) )
else: else:
self.log(f"[{current_datetime}] 策略: 发送订单失败。") self.log(f"[{current_datetime}] 策略: 发送订单失败。")
indicator_value = RSI(5).get_latest_value(
np.array(self.get_price_history("close")), None, None, None, None
)
if not self.use_indicator or self.indicator_s.is_condition_met( if not self.use_indicator or self.indicator_s.is_condition_met(
np.array(self.get_price_history("close")), np.array(self.get_price_history("close")),
np.array(self.get_price_history("open")), np.array(self.get_price_history("open")),
@@ -797,6 +817,9 @@ class SimpleLimitBuyStrategy(Strategy):
): ):
# 根据策略逻辑计算目标买入价格 # 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2) # 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
bar_1_ago = bar_history[-self.lag_s]
range_1_ago = bar_1_ago.high - bar_1_ago.low
self.log(open, range_1_ago * self.range_factor_s) self.log(open, range_1_ago * self.range_factor_s)
target_buy_price = open + (range_1_ago * self.range_factor_s) target_buy_price = open + (range_1_ago * self.range_factor_s)
@@ -804,9 +827,9 @@ class SimpleLimitBuyStrategy(Strategy):
target_buy_price = max(0.01, target_buy_price) target_buy_price = max(0.01, target_buy_price)
self.log( self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, " f"[{current_datetime}] SHORT信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, " f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}" f"计算目标SHORT价={target_buy_price:.2f}"
) )
order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}" order_id = f"{self.symbol}_BUY_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
@@ -827,15 +850,13 @@ class SimpleLimitBuyStrategy(Strategy):
if new_order: if new_order:
self._last_order_id = new_order.id self._last_order_id = new_order.id
self.log( self.log(
f"[{current_datetime}] 策略: 发送限价买入订单 {self._last_order_id} @ {target_buy_price:.2f}" f"[{current_datetime}] 策略: 发送限价SHORT订单 {self._last_order_id} @ {target_buy_price:.2f}"
) )
else: else:
self.log(f"[{current_datetime}] 策略: 发送订单失败。") self.log(f"[{current_datetime}] 策略: 发送订单失败。")
# else:
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(bar_history)}")
def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None): def on_close_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
self.cancel_all_pending_orders() self.cancel_all_pending_orders(self.main_symbol)
def on_rollover(self, old_symbol: str, new_symbol: str): def on_rollover(self, old_symbol: str, new_symbol: str):
""" """

View File

@@ -112,7 +112,8 @@ class Strategy(ABC):
return self.context.cancel_order(order_id) return self.context.cancel_order(order_id)
def cancel_all_pending_orders(self) -> int:
def cancel_all_pending_orders(self, main_symbol = None) -> int:
"""取消当前策略的未决订单仅限于当前策略关注的Symbol。""" """取消当前策略的未决订单仅限于当前策略关注的Symbol。"""
# 注意:在换月模式下,引擎会自动取消旧合约的挂单,这里是策略主动取消 # 注意:在换月模式下,引擎会自动取消旧合约的挂单,这里是策略主动取消
if not self.trading: if not self.trading:
@@ -123,6 +124,11 @@ class Strategy(ABC):
# orders_to_cancel = [ # orders_to_cancel = [
# order.id for order in pending_orders.values() if order.symbol == self.symbol # order.id for order in pending_orders.values() if order.symbol == self.symbol
# ] # ]
if main_symbol is not None:
orders_to_cancel = [
order.id for order in pending_orders.values() if main_symbol in order.symbol
]
else:
orders_to_cancel = [ orders_to_cancel = [
order.id for order in pending_orders.values() order.id for order in pending_orders.values()
] ]

View File

@@ -4,7 +4,7 @@ import asyncio
from datetime import date, datetime, timedelta from datetime import date, datetime, timedelta
from typing import Literal, Type, Dict, Any, List, Optional from typing import Literal, Type, Dict, Any, List, Optional
import pandas as pd import pandas as pd
import uuid import time
# 导入你提供的 core_data 中的类型 # 导入你提供的 core_data 中的类型
from src.common_utils import is_bar_pre_close_period, is_futures_trading_time from src.common_utils import is_bar_pre_close_period, is_futures_trading_time
@@ -74,9 +74,10 @@ class TqsdkEngine:
self._api: TqApi = api self._api: TqApi = api
# 从策略参数中获取主symbolTqsdkContext 需要知道它 # 从策略参数中获取主symbolTqsdkContext 需要知道它
self.symbol: str = strategy_params.get("symbol") # self.symbol: str = strategy_params.get("symbol")
if not self.symbol: # if not self.symbol:
raise ValueError("strategy_params 必须包含 'symbol' 字段") # raise ValueError("strategy_params 必须包含 'symbol' 字段")
self.symbol = symbol
# 获取 K 线数据Tqsdk 自动处理) # 获取 K 线数据Tqsdk 自动处理)
# 这里假设策略所需 K 线周期在 strategy_params 中否则默认60秒1分钟K线 # 这里假设策略所需 K 线周期在 strategy_params 中否则默认60秒1分钟K线
@@ -119,6 +120,9 @@ class TqsdkEngine:
self.quote = api.get_quote(symbol) self.quote = api.get_quote(symbol)
self.partial_bar: Bar = None self.partial_bar: Bar = None
self.kline_row = None
self.target_pos_dict = {}
print("TqsdkEngine: 初始化完成。") print("TqsdkEngine: 初始化完成。")
@@ -161,6 +165,22 @@ class TqsdkEngine:
if "SHFE" in order_to_send.symbol: if "SHFE" in order_to_send.symbol:
tqsdk_offset = "OPEN" tqsdk_offset = "OPEN"
if "CLOSE" in order_to_send.direction:
current_positions = self._context.get_current_positions()
current_pos_volume = current_positions.get(order_to_send.symbol, 0)
target_volume = None
if order_to_send.direction == 'CLOSE_LONG':
target_volume = current_pos_volume - order_to_send.volume
elif order_to_send.direction == 'CLOSE_SHORT':
target_volume = current_pos_volume + order_to_send.volume
if target_volume is not None:
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].set_target_volume(target_volume)
else:
try: try:
tq_order = self._api.insert_order( tq_order = self._api.insert_order(
symbol=order_to_send.symbol, symbol=order_to_send.symbol,
@@ -187,39 +207,8 @@ class TqsdkEngine:
tq_order.insert_date_time, unit="ns", utc=True tq_order.insert_date_time, unit="ns", utc=True
) )
# 等待订单状态更新(成交/撤销/报错)
# 在 Tqsdk 中,订单和成交是独立的,通常在 wait_update() 循环中通过 api.is_changing() 检查
# 这里为了模拟同步处理,直接等待订单状态最终确定
# 注意:实际回测中,不应在这里长时间阻塞,而应在主循环中持续 wait_update
# 为了简化适配,这里模拟即时处理,但可能与真实异步行为有差异。
# 更健壮的方式是在主循环中通过订单状态回调更新
# 这里我们假设订单会很快更新状态,或者在下一个 wait_update() 周期中被检测到
self._api.wait_update() # 等待一次更新 self._api.wait_update() # 等待一次更新
# # 检查最终订单状态和成交
# if tq_order.status == "FINISHED":
# # 查找对应的成交记录
# for trade_id, tq_trade in self._api.get_trade().items():
# if tq_trade.order_id == tq_order.order_id and tq_trade.volume > 0: # 确保是实际成交
# # 创建 core_data.Trade 对象
# trade = Trade(
# order_id=tq_trade.order_id,
# fill_time=tafunc.get_datetime_from_timestamp(tq_trade.trade_date_time) if tq_trade.trade_date_time else datetime.now(),
# symbol=order_to_send.symbol, # 使用 Context 中的 symbol
# direction=tq_trade.direction, # 实际成交方向
# volume=tq_trade.volume,
# price=tq_trade.price,
# commission=tq_trade.commission,
# cash_after_trade=self._api.get_account().available,
# positions_after_trade=self._context.get_current_positions(),
# realized_pnl=tq_trade.realized_pnl, # Tqsdk TqTrade 对象有 realized_pnl
# is_open_trade=tq_trade.offset == "OPEN",
# is_close_trade=tq_trade.offset in ["CLOSE", "CLOSETODAY", "CLOSEYESTERDAY"]
# )
# self.trade_history.append(trade)
# print(f"Engine: 成交记录: {trade}")
# break # 找到成交就跳出
# order_to_send.status = tq_order.status # 更新最终状态
except Exception as e: except Exception as e:
print(f"Engine: 发送订单 {order_to_send.id} 失败: {e}") print(f"Engine: 发送订单 {order_to_send.id} 失败: {e}")
# order_to_send.status = "ERROR" # order_to_send.status = "ERROR"
@@ -406,15 +395,28 @@ class TqsdkEngine:
new_bar = False new_bar = False
if self._api.is_changing(self.klines.iloc[-1], "open"):
print(f"TqsdkEngine: open change!, open:{self.klines.iloc[-1].open}, now: {datetime.now()}")
if self._api.is_changing(self.klines.iloc[-1], "datetime"): if self._api.is_changing(self.klines.iloc[-1], "datetime"):
# 等待到整点-00 或 30 分且秒>1
while True:
now = datetime.now()
minute = now.minute
second = now.second
if (minute % 30 == 0) and (second > 1):
break
# 小粒度休眠,防止 CPU 空转
time.sleep(0.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)
kline_dt = kline_dt.tz_convert(BEIJING_TZ) kline_dt = kline_dt.tz_convert(BEIJING_TZ)
self.kline_row = kline_row self.kline_row = kline_row
print( print(f"TqsdkEngine: 新k线产生, k line datetime:{kline_dt}, now: {datetime.now()}")
f"TqsdkEngine: 新k线产生,k line datetime:{kline_dt}, now: {datetime.now()}"
)
self.main(kline_row, self.klines.iloc[-2]) self.main(kline_row, self.klines.iloc[-2])
new_bar = True new_bar = True