修复未来函数bug

This commit is contained in:
2025-07-15 22:45:51 +08:00
parent 5de1a43b02
commit 5a2d0fb984
19 changed files with 15019 additions and 9388 deletions

File diff suppressed because one or more lines are too long

View File

@@ -236,11 +236,11 @@ if __name__ == "__main__":
# 这种方式适合获取相对较短或中等长度的历史K线数据。
df_if_backtest_daily = collect_and_save_tqsdk_data_stream(
symbol="KQ.m@CZCE.MA",
symbol="KQ.m@DCE.jm",
# symbol='SHFE.rb2510',
# symbol='KQ.i@SHFE.bu',
freq="min60",
start_date_str="2022-01-01",
start_date_str="2021-01-01",
end_date_str="2025-07-11",
mode="backtest", # 指定为回测模式
tq_user=TQ_USER_NAME,

File diff suppressed because one or more lines are too long

File diff suppressed because one or more lines are too long

View File

@@ -2,6 +2,7 @@ from datetime import timedelta
from src.analysis.result_analyzer import ResultAnalyzer
# 导入 TqsdkEngine而不是原来的 BacktestEngine
from src.indicators.indicators import RSI, HistoricalRange
from src.tqsdk_real_engine import TqsdkEngine
# 导入你的策略类
@@ -17,29 +18,34 @@ initial_capital = 100000.0
slippage_rate = 0.000 # 在 Tqsdk 模拟中,滑点通常由 TqSim 处理或在策略中手动模拟
commission_rate = 0.0001 # 同上
# 主力合约的 symbol
main_symbol = "KQ.m@CZCE.MA"
main_symbol = "KQ.m@DCE.jm"
strategy_parameters = {
'symbol': main_symbol, # 根据您的数据文件中的品种名称调整
'trade_volume': 1,
'range_factor': 1.8, # 示例值,需要通过网格搜索优化
'profit_factor': 2.8, # 示例值
# 'range_factor': 0.7, # 示例值,需要通过网格搜索优化
# 'profit_factor': 71, # 示例值
# 'range_factor_l': 2, # 示例值,需要通过网格搜索优化
# 'profit_factor_l': 3, # 示例值
# 'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化
# 'profit_factor_s': 5.6, # 示例值
'lag': 1,
# 'range_factor': 1.3, # 示例值,需要通过网格搜索优化
# 'profit_factor': 4.8, # 示例值
# 'range_factor': 1.1, # 示例值,需要通过网格搜索优化
# 'profit_factor': 4.9, # 示例值
'range_factor_l': 1.3, # 示例值,需要通过网格搜索优化
'profit_factor_l': 4.8, # 示例值
'range_factor_s': 1.1, # 示例值,需要通过网格搜索优化
'profit_factor_s': 4.9, # 示例值
'max_position': 10,
'enable_log': True,
'stop_loss_points': 20,
'use_indicator': True
'use_indicator': True,
# 'indicator': RSI(5, 63, 95),
# 'indicator': RSI(5, 5, 25),
'indicator_l': RSI(5, 63, 95),
'indicator_s': RSI(5, 0, 100),
}
api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
# --- 1. 初始化回测引擎并运行 ---
print("\n初始化 Tqsdk 回测引擎...")
engine = TqsdkEngine(
strategy_class=SimpleLimitBuyStrategyLong,
strategy_class=SimpleLimitBuyStrategy,
strategy_params=strategy_parameters,
api=api,
symbol=main_symbol,

View File

@@ -70,7 +70,7 @@ class ResultAnalyzer:
for trade in self.trade_history:
# 调整输出格式,显示实现盈亏
pnl_display = (
f" | Indicators:{trade.indicator_dict} | PnL: {trade.realized_pnl:.2f}"
f" | PnL: {trade.realized_pnl:.2f}"
if trade.is_close_trade
else ""
)
@@ -145,7 +145,8 @@ class ResultAnalyzer:
# 确保 trade.indicator_dict 中包含当前指标的值
# 并且这个值是可用的非None或NaN
if (
indicator_name in trade.indicator_dict
trade.indicator_dict is not None
and indicator_name in trade.indicator_dict
and trade.indicator_dict[indicator_name] is not None
):
# 检查是否为 NaN如果使用 np.nan则需要 isinstance(value, float) and np.isnan(value)

View File

@@ -107,13 +107,6 @@ class BacktestEngine:
if current_bar is None:
break # 没有更多数据,回测结束
self.all_bars.append(current_bar)
self.close_list.append(current_bar.close)
self.open_list.append(current_bar.open)
self.high_list.append(current_bar.high)
self.low_list.append(current_bar.low)
self.volume_list.append(current_bar.volume)
if self.start_time and current_bar.datetime < self.start_time:
continue
@@ -163,14 +156,7 @@ class BacktestEngine:
# 3. 更新策略关注的当前合约 symbol
self.strategy.symbol = current_bar.symbol
# 5. 更新引擎内部的历史 Bar 缓存
self._history_bars.append(current_bar)
if len(self._history_bars) > self._max_history_bars:
self._history_bars.pop(0)
# 6. 处理待撮合订单 (在调用策略 on_bar 之前,确保订单在当前 K 线开盘价撮合)
# self.simulator.process_pending_orders(current_bar)
self.strategy.on_open_bar(current_bar)
self.strategy.on_open_bar(current_bar.open, current_bar.symbol)
current_indicator_dict = {}
close_array = np.array(self.close_list)
@@ -187,10 +173,18 @@ class BacktestEngine:
low_array,
volume_array
)
self.simulator.process_pending_orders(current_bar, current_indicator_dict)
self.all_bars.append(current_bar)
self.close_list.append(current_bar.close)
self.open_list.append(current_bar.open)
self.high_list.append(current_bar.high)
self.low_list.append(current_bar.low)
self.volume_list.append(current_bar.volume)
# 7. 调用策略的 on_bar 方法
# self.strategy.on_bar(current_bar)
self.simulator.process_pending_orders(current_bar, current_indicator_dict)
self.strategy.on_close_bar(current_bar)
self.simulator.process_pending_orders(current_bar, current_indicator_dict)
@@ -277,4 +271,5 @@ class BacktestEngine:
return self.low_list
elif key == 'volume':
return self.volume_list
return None

View File

@@ -1,9 +1,24 @@
# src/common_utils.py
from typing import List, Union
from typing import List, Tuple, Union
from datetime import time
BEIJING_TZ = "Asia/Shanghai"
import pytz
BEIJING_TZ = pytz.timezone("Asia/Shanghai")
# 定义每日交易时段(以 datetime.time 对象表示,不含日期)
# 每个元组包含 (会话开始时间, 会话结束时间)
# 这里假设了常见的上午盘和下午盘,如果您的市场有夜盘或特殊时段,请在此处添加
# 注意:结束时间通常是不包含的,例如 11:30:00 表示在该时间点收盘
TRADING_SESSIONS_TIMES: List[Tuple[time, time]] = [
(time(9, 0, 0), time(11, 30, 0)), # 上午盘09:00:00 到 11:30:00
(time(13, 30, 0), time(15, 0, 0)), # 下午盘13:30:00 到 15:00:00
(time(21, 0, 0), time(23, 0, 0))
# 如果有夜盘且跨日,例如 21:00 到次日 02:30需要更复杂的逻辑来处理日期转换
# 例如:(time(21, 0, 0), time(2, 30, 0))
]
def generate_parameter_range(start: Union[int, float], end: Union[int, float], step: Union[int, float]) -> List[Union[int, float]]:
"""
@@ -77,6 +92,60 @@ def is_futures_trading_time(current_dt: Optional[datetime] = None) -> bool:
return False
def round_time_up_to_nearest_minute(dt_obj: datetime) -> datetime:
"""
将 datetime 对象向上取整到最近的分钟。
例如09:58:30.123456 -> 09:59:00.000000
09:58:00.000001 -> 09:59:00.000000 (即使秒数为0微秒不为0也进位)
09:58:00.000000 -> 09:58:00.000000 (已经是整分钟,不进位)
保留原始时区信息。
"""
# 如果已经是整分钟且没有微秒,直接返回
if dt_obj.second == 0 and dt_obj.microsecond == 0:
return dt_obj
# 将秒和微秒归零,得到当前分钟的开始时间
rounded_dt = dt_obj.replace(second=0, microsecond=0)
# 如果原始时间有秒数或微秒数(即不是整分钟),则进位到下一分钟
if dt_obj.second > 0 or dt_obj.microsecond > 0:
rounded_dt += timedelta(minutes=1)
return rounded_dt
def get_session_market_close_time(dt: datetime) -> Optional[datetime]:
"""
根据传入的 datetime 对象,获取其所在交易时段的实际收盘时间。
dt 必须是时区感知的 (BEIJING_TZ)。
Args:
dt (datetime): 一个时区感知的 datetime 对象。
Returns:
datetime: 如果 dt 落在定义的交易时段内,则返回该时段的结束时间(时区感知)。
否则返回 None。
"""
target_date = dt.date()
target_time = dt.time()
for session_start_t, session_end_t in TRADING_SESSIONS_TIMES:
# 构建当前日期的会话开始和结束的 datetime 对象,并应用时区
session_start_dt_aware = BEIJING_TZ.localize(datetime.combine(target_date, session_start_t))
session_end_dt_aware = BEIJING_TZ.localize(datetime.combine(target_date, session_end_t))
# TODO: 如果有跨日的夜盘,例如从 21:00 到次日 02:30需要在此处添加处理
# 例如if session_end_t < session_start_t: session_end_dt_aware += timedelta(days=1)
# 判断传入的 dt 是否在这个会话区间内
# dt 应该大于等于会话开始时间,且小于会话结束时间(通常结束时间不包含在区间内)
if session_start_dt_aware <= dt < session_end_dt_aware:
return session_end_dt_aware
# 如果传入的 dt 不在任何已定义的交易时段内,则返回 None
return None
def is_bar_pre_close_period(
bar_start_time: datetime,
bar_duration_seconds: int,
@@ -85,31 +154,61 @@ def is_bar_pre_close_period(
) -> bool:
"""
判断当前系统时间是否在一根K线的即将结束时间段内。
K线的时区和所有内部时间判断的基准时区固定为 "Asia/Shanghai"
优化考虑市场实际收盘时间K线结束时间不会晚于其所属交易时段的实际收盘时间。
Args:
bar_start_time (datetime): 当前K线的开始时间例如bar.datetime
bar_start_time (datetime): 当前K线的开始时间。它必须是时区感知的 (offset-aware)
且应是 "Asia/Shanghai" 时区。
可以是 datetime.datetime 对象或 pandas.Timestamp 对象。
bar_duration_seconds (int): 该K线的持续时间以秒为单位。
对于1小时K线传入 3600。
pre_close_minutes (int): K线结束前多少分钟被认为是“即将结束”状态。例如,传入 3 表示结束前3分钟。
pre_close_minutes (int): K线结束前多少分钟被认为是“即将结束”状态。
current_system_time (Optional[datetime]): 用于判断的当前时间。
如果为 None则默认使用 `datetime.now()`。
此时间在函数内部会向上取整到最近的分钟。
Returns:
bool: 如果当前时间在K线结束前 `pre_close_minutes` 的窗口内,则返回 True否则返回 False。
"""
# 1. 统一 current_system_time 的时区感知性到 BEIJING_TZ并向上取整
if current_system_time is None:
current_system_time = datetime.now(BEIJING_TZ)
else:
if current_system_time.tzinfo is None: # 如果传入的是 naive 时间
current_system_time = BEIJING_TZ.localize(current_system_time, is_dst=None)
else: # 如果传入的是 aware 时间,则将其转换到北京时区
current_system_time = current_system_time.astimezone(BEIJING_TZ)
# 1. 计算K线的精确结束时间
# K线结束时间 = K线开始时间 + K线持续时间
bar_end_time = bar_start_time + timedelta(seconds=bar_duration_seconds)
# 对 current_system_time 进行向上取整,避免毫秒级偏差
current_system_time = round_time_up_to_nearest_minute(current_system_time)
# 2. 计算“即将结束”窗口的开始时间
# 这个窗口从 (K线结束时间 - pre_close_minutes) 开始,到 K线结束时间 结束
pre_close_window_start_time = bar_end_time - timedelta(minutes=pre_close_minutes)
# 2. 确保 bar_duration_seconds 是 int 类型
duration_as_int = int(bar_duration_seconds)
# 3. 判断当前系统时间是否在这个窗口内
# 窗口定义为 [pre_close_window_start_time, bar_end_time),即包含开始时间,不包含结束时间
print(pre_close_window_start_time, current_system_time, bar_end_time)
return pre_close_window_start_time <= current_system_time < bar_end_time
# 3. 计算K线的理论结束时间不考虑市场收盘
calculated_bar_end_time = bar_start_time + timedelta(seconds=duration_as_int)
# 4. 获取实际的市场会话收盘时间
actual_session_close = get_session_market_close_time(bar_start_time)
final_bar_end_time: datetime
if actual_session_close:
# K线的实际结束时间不能晚于其所在交易时段的收盘时间
# 例如11:00开始的1小时K线理论结束12:00但上午盘11:30结束所以实际结束是11:30
final_bar_end_time = min(calculated_bar_end_time, actual_session_close)
else:
# 如果 bar_start_time 不在任何已定义的交易时段内
# 通常这表示 K线数据有问题或在非交易时间生成。
# 这里为了兼容性,暂时使用计算出的理论结束时间,但会打印警告。
# 在严谨的系统中,您可能需要抛出异常或直接返回 False。
# print(f"警告K线开始时间 {bar_start_time} 不在已定义的交易时段内。将使用理论结束时间 {calculated_bar_end_time}。")
final_bar_end_time = calculated_bar_end_time
# 5. 计算“即将结束”窗口的开始时间
# 这个窗口从 (final_bar_end_time - pre_close_minutes) 开始,到 final_bar_end_time 结束
pre_close_window_start_time = final_bar_end_time - timedelta(minutes=pre_close_minutes)
# 6. 判断当前系统时间是否在这个窗口内
# 窗口定义为 [pre_close_window_start_time, final_bar_end_time),即包含开始时间,不包含结束时间
return pre_close_window_start_time <= current_system_time < final_bar_end_time

View File

@@ -339,7 +339,8 @@ class ExecutionSimulator:
# 这里直接调用 _execute_single_order 确保强制平仓立即成交
trade = self._execute_single_order(rollover_order, closing_bar)
if trade:
closed_trades.append(trade)
# closed_trades.append(trade)
self.trade_log.append(trade)
else:
print(
f"[{closing_bar.datetime}] 警告: 强制平仓 {symbol_to_close} 失败!"

View File

@@ -8,8 +8,10 @@ from src.core_data import Bar
class Indicator(ABC):
def __init__(self):
pass
def __init__(self, down_bound:float = None, up_bound:float = None, shift_window: int = 0):
self.down_bound =down_bound
self.up_bound =up_bound
self.shift_window = shift_window
@abstractmethod
def get_values(self, close: np.array, open: np.array, high: np.array, low: np.array, volume: np.array):
@@ -17,7 +19,27 @@ class Indicator(ABC):
def get_latest_value(self, close: np.array, open: np.array, high: np.array, low: np.array, volume: np.array):
return self.get_values(close, open, high, low, volume)[-1].item()
values = self.get_values(close, open, high, low, volume)
return values[-(self.shift_window + 1)].item() if len(values) > self.shift_window + 1 else None
def is_condition_met(self,
close: np.array,
open: np.array,
high: np.array,
low: np.array,
volume: np.array):
value = self.get_latest_value(close, open, high, low, volume)
condition_met = True
if value is None:
return False
if self.up_bound is None and self.down_bound is None:
condition_met = False
if self.up_bound is not None:
condition_met = condition_met and (value < self.up_bound)
if self.down_bound is not None:
condition_met = condition_met and (value > self.down_bound)
return condition_met
@abstractmethod
def get_name(self):

View File

@@ -1,17 +1,33 @@
from src.indicators.indicators import RSI, HistoricalRange
from src.indicators.indicators import RSI, HistoricalRange, DifferencedVolumeIndicator, StochasticOscillator, \
RateOfChange, NormalizedATR
INDICATOR_LIST = [
RSI(5),
RSI(7),
RSI(10),
RSI(14),
RSI(15),
RSI(20),
RSI(25),
RSI(30),
RSI(35),
RSI(40),
HistoricalRange(1),
HistoricalRange(8),
HistoricalRange(15),
HistoricalRange(21),
HistoricalRange(shift_window=0),
HistoricalRange(shift_window=6),
HistoricalRange(shift_window=13),
HistoricalRange(shift_window=20),
# DifferencedVolumeIndicator(shift_window=0),
# DifferencedVolumeIndicator(shift_window=6),
# DifferencedVolumeIndicator(shift_window=13),
# DifferencedVolumeIndicator(shift_window=20),
StochasticOscillator(fastk_period=14, slowd_period=3, slowk_period=3),
StochasticOscillator(fastk_period=5, slowd_period=3, slowk_period=3),
StochasticOscillator(fastk_period=21, slowd_period=5, slowk_period=5),
RateOfChange(window=5),
RateOfChange(window=10),
RateOfChange(window=15),
RateOfChange(window=20),
NormalizedATR(window=5),
NormalizedATR(window=14),
NormalizedATR(window=21)
]

View File

@@ -10,21 +10,25 @@ class RSI(Indicator):
相对强弱指数 (RSI) 指标实现,使用 TA-Lib 简化计算。
"""
def __init__(self, window: int = 14):
"""
初始化RSI指标。
Args:
window (int): RSI的计算周期默认为14。
"""
super().__init__()
def __init__(
self,
window: int = 14,
down_bound: float = None,
up_bound: float = None,
shift_window: int = 0,
):
super().__init__(down_bound, up_bound)
self.window = window
self.shift_window = shift_window
def get_values(self,
def get_values(
self,
close: np.array,
open: np.array, # 不使用
high: np.array, # 不使用
low: np.array, # 不使用
volume: np.array) -> np.array: # 不使用
volume: np.array,
) -> np.array: # 不使用
"""
根据收盘价列表计算RSI值使用 TA-Lib。
Args:
@@ -41,8 +45,7 @@ class RSI(Indicator):
return rsi_values
def get_name(self):
return f'rsi_{self.window}'
return f"rsi_{self.window}"
class HistoricalRange(Indicator):
@@ -50,21 +53,20 @@ class HistoricalRange(Indicator):
历史波动幅度指标:计算过去 N 日的 (最高价 - 最低价) 的简单移动平均。
"""
def __init__(self, window: int = 20):
"""
初始化历史波动幅度指标。
Args:
window (int): 计算范围平均值的周期默认为20。
"""
super().__init__()
self.window = window
def __init__(
self, down_bound: float = None, up_bound: float = None, shift_window: int = 0
):
super().__init__(down_bound, up_bound)
self.shift_window = shift_window
def get_values(self,
def get_values(
self,
close: np.array, # 不使用
open: np.array, # 不使用
high: np.array,
low: np.array,
volume: np.array) -> np.array: # 不使用
volume: np.array,
) -> np.array: # 不使用
"""
根据最高价和最低价列表计算过去 N 日的 (high - low) 值的简单移动平均。
Args:
@@ -82,7 +84,188 @@ class HistoricalRange(Indicator):
daily_ranges = high - low
# 将 numpy 数组转换为 list 并返回
return np.roll(daily_ranges, self.window)
return daily_ranges
def get_name(self):
return f'range_{self.window}'
return f"range_{self.shift_window}"
class DifferencedVolumeIndicator(Indicator):
"""
计算当前交易量与前一交易量的差值。
volume[t] - volume[t-1]。
用于识别交易量变化的趋势,常用于平稳化交易量序列。
"""
def __init__(
self, down_bound: float = None, up_bound: float = None, shift_window: int = 0
):
# 差值没有固定上下界,取决于实际交易量
super().__init__(down_bound, up_bound)
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:
"""
根据交易量计算其差分值。
Args:
volume (np.array): 交易量列表。
其他 OHLCV 参数在此指标中不使用。
Returns:
np.array: 交易量差分值列表。第一个值为NaN。
"""
if not isinstance(volume, np.ndarray) or len(volume) < 2:
return np.full_like(
volume if isinstance(volume, np.ndarray) else [], np.nan, dtype=float
)
# 计算相邻交易量的差值
# np.diff(volume) 会比原数组少一个元素,前面补 NaN
diff_volume = np.concatenate(([np.nan], np.diff(volume)))
return diff_volume
def get_name(self) -> str:
return f"differenced_volume_{self.shift_window}"
class StochasticOscillator(Indicator):
"""
随机摆动指标 (%K),衡量收盘价在近期价格高低区间内的位置。
这是一个平稳的动量摆动指标,值域在 [0, 100] 之间。
"""
def __init__(
self,
fastk_period: int = 14,
slowk_period: int = 3,
slowd_period: int = 3, # 在此实现中未使用 slowd但保留以符合标准
down_bound: float = None,
up_bound: float = None,
shift_window: int = 0,
):
super().__init__(down_bound, up_bound)
self.fastk_period = fastk_period
self.slowk_period = slowk_period
self.slowd_period = slowd_period
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:
"""
根据最高价、最低价和收盘价计算随机摆动指标 %K 的值。
Args:
high (np.array): 最高价列表。
low (np.array): 最低价列表。
close (np.array): 收盘价列表。
Returns:
np.array: 慢速 %K 线的值列表。
"""
# TA-Lib 的 STOCH 函数返回 slowk 和 slowd 两条线
# 我们通常使用 slowk 作为主要的摆动指标
slowk, _ = talib.STOCH(
high,
low,
close,
fastk_period=self.fastk_period,
slowk_period=self.slowk_period,
slowk_matype=0, # 使用 SMA
slowd_period=self.slowd_period,
slowd_matype=0, # 使用 SMA
)
return slowk
def get_name(self):
return f"stoch_k_{self.fastk_period}_{self.slowk_period}"
class RateOfChange(Indicator):
"""
价格变化率 (ROC),衡量当前价格与 N 期前价格的百分比变化。
这是一个平稳的动量指标,围绕 0 波动。
"""
def __init__(
self,
window: int = 10,
down_bound: float = None,
up_bound: float = None,
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:
"""
根据收盘价计算 ROC 值。
Args:
close (np.array): 收盘价列表。
Returns:
np.array: ROC 值列表。
"""
roc_values = talib.ROC(close, timeperiod=self.window)
return roc_values
def get_name(self):
return f"roc_{self.window}"
class NormalizedATR(Indicator):
"""
归一化平均真实波幅 (NATR),即 ATR / Close * 100。
将绝对波动幅度转换为相对波动百分比,使其成为一个更平稳的波动率指标。
"""
def __init__(
self,
window: int = 14,
down_bound: float = None,
up_bound: float = None,
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:
"""
根据最高价、最低价和收盘价计算 NATR 值。
Args:
high (np.array): 最高价列表。
low (np.array): 最低价列表。
close (np.array): 收盘价列表。
Returns:
np.array: NATR 值列表。
"""
# 使用 TA-Lib 直接计算 NATR
natr_values = talib.NATR(high, low, close, timeperiod=self.window)
return natr_values
def get_name(self):
return f"natr_{self.window}"

View File

@@ -2,6 +2,7 @@
from tkinter import N
import numpy as np
from src.indicators.base_indicators import Indicator
from src.indicators.indicators import RSI, HistoricalRange
from .base_strategy import Strategy
from ..core_data import Bar, Order
@@ -30,6 +31,8 @@ class SimpleLimitBuyStrategyLong(Strategy):
stop_loss_points: float = 10, # 新增:止损点数
take_profit_points: float = 10,
use_indicator: bool = False,
indicator: Indicator = None,
lag: int = 7,
): # 新增:止盈点数
"""
初始化策略。
@@ -51,6 +54,8 @@ class SimpleLimitBuyStrategyLong(Strategy):
self.stop_loss_points = stop_loss_points
self.take_profit_points = take_profit_points
self.use_indicator = use_indicator
self.indicator = indicator
self.lag = lag
self.order_id_counter = 0
@@ -67,18 +72,17 @@ class SimpleLimitBuyStrategyLong(Strategy):
def on_init(self):
super().on_init()
count = self.cancel_all_pending_orders()
self.log(f'取消{count}笔订单')
self.log(f"取消{count}笔订单")
def on_open_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
def on_open_bar(self, open: float, symbol: str):
"""
每当新的K线数据到来时调用。
Args:
bar (Bar): 当前的K线数据对象。
next_bar_open (Optional[float]): 下一根K线的开盘价此处策略未使用。
"""
current_datetime = bar.datetime # 获取当前K线时间
self.symbol = bar.symbol
current_datetime = self.get_current_time() # 获取当前K线时间
self.symbol = symbol
# --- 1. 撤销上一根K线未成交的订单 ---
# 检查是否记录了上一笔订单ID并且该订单仍然在待处理列表中
@@ -112,18 +116,15 @@ class SimpleLimitBuyStrategyLong(Strategy):
) # 再次获取,此时应已取消旧订单
range_1_ago = None
range_7_ago = None
bar_history = self.get_bar_history()
if len(bar_history) > 16:
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
bar_1_ago = bar_history[-8]
bar_7_ago = bar_history[-15]
bar_1_ago = bar_history[-self.lag]
# 计算历史 K 线的 Range
range_1_ago = bar_1_ago.high - bar_1_ago.low
range_7_ago = bar_7_ago.high - bar_7_ago.low
# for i in range(1, 9, 1):
# print(bar_history[-i].datetime)
@@ -135,18 +136,16 @@ class SimpleLimitBuyStrategyLong(Strategy):
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = (
bar.open - avg_entry_price
) # 当前浮动盈亏(以收盘价计算)
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
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}, 目标: {range_1_ago * self.profit_factor:.2f}"
)
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor:
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -157,7 +156,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -169,7 +168,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -180,7 +179,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -190,36 +189,37 @@ class SimpleLimitBuyStrategyLong(Strategy):
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
# 且K线历史足够长时才考虑开仓
# rsi = RSI(5).get_latest_value(np.array(self.get_price_history('close')), None, None, None, None)
indicator_value = HistoricalRange(21).get_latest_value(
None,
None,
np.array(self.get_price_history("high")),
np.array(self.get_price_history("low")),
None,
)
if (
current_pos_volume == 0
and range_1_ago is not None
and (not self.use_indicator or 10 < indicator_value < 25)
and (
not self.use_indicator
or self.indicator.is_condition_met(
np.array(self.get_price_history("close")),
np.array(self.get_price_history("open")),
np.array(self.get_price_history("high")),
np.array(self.get_price_history("low")),
np.array(self.get_price_history("volume")),
)
)
):
# if current_pos_volume == 0 and range_1_ago is not None:
# 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
self.log(bar.open, range_1_ago * self.range_factor)
target_buy_price = bar.open - (range_1_ago * self.range_factor)
self.log(open, range_1_ago, self.range_factor)
target_buy_price = open - (range_1_ago * self.range_factor)
# 确保目标买入价格有效,例如不能是负数
target_buy_price = max(0.01, target_buy_price)
self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}"
)
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -230,7 +230,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
volume=trade_volume,
price_type="LIMIT",
limit_price=target_buy_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
)
new_order = self.send_order(order)
# 记录下这个订单的ID以便在下一根K线开始时进行撤销
@@ -279,6 +279,8 @@ class SimpleLimitBuyStrategyShort(Strategy):
stop_loss_points: float = 10, # 新增:止损点数
take_profit_points: float = 10,
use_indicator: bool = False,
indicator: Indicator = None,
lag: int = 7,
): # 新增:止盈点数
"""
初始化策略。
@@ -300,6 +302,8 @@ class SimpleLimitBuyStrategyShort(Strategy):
self.stop_loss_points = stop_loss_points
self.take_profit_points = take_profit_points
self.use_indicator = use_indicator
self.indicator = indicator
self.lag = lag
self.order_id_counter = 0
@@ -313,15 +317,20 @@ class SimpleLimitBuyStrategyShort(Strategy):
f"止损点={self.stop_loss_points}, 止盈点={self.take_profit_points}"
)
def on_open_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
def on_init(self):
super().on_init()
count = self.cancel_all_pending_orders()
self.log(f"取消{count}笔订单")
def on_open_bar(self, open: float, symbol: str):
"""
每当新的K线数据到来时调用。
Args:
bar (Bar): 当前的K线数据对象。
next_bar_open (Optional[float]): 下一根K线的开盘价此处策略未使用。
"""
current_datetime = bar.datetime # 获取当前K线时间
self.symbol = bar.symbol
current_datetime = self.get_current_time() # 获取当前K线时间
self.symbol = symbol
# --- 1. 撤销上一根K线未成交的订单 ---
# 检查是否记录了上一笔订单ID并且该订单仍然在待处理列表中
@@ -355,18 +364,15 @@ class SimpleLimitBuyStrategyShort(Strategy):
) # 再次获取,此时应已取消旧订单
range_1_ago = None
range_7_ago = None
bar_history = self.get_bar_history()
if len(bar_history) > 16:
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
bar_1_ago = bar_history[-8]
bar_7_ago = bar_history[-15]
bar_1_ago = bar_history[-self.lag]
# 计算历史 K 线的 Range
range_1_ago = bar_1_ago.high - bar_1_ago.low
range_7_ago = bar_7_ago.high - bar_7_ago.low
# --- 3. 平仓逻辑 (止损/止盈) ---
# 只有当有持仓时才考虑平仓
@@ -375,9 +381,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = (
avg_entry_price - bar.open
) # 当前浮动盈亏(以收盘价计算)
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}"
@@ -386,7 +390,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor:
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -397,7 +401,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -409,7 +413,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -420,7 +424,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -430,28 +434,35 @@ class SimpleLimitBuyStrategyShort(Strategy):
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
# 且K线历史足够长时才考虑开仓
# rsi = RSI(5).get_latest_value(np.array(self.get_price_history('close')), None, None, None, None)
indicator_value = RSI(5).get_latest_value(np.array(self.get_price_history('close')), None, None, None, None)
if (
current_pos_volume == 0
and range_1_ago is not None
and (not self.use_indicator or 20 < indicator_value < 60)
and (
not self.use_indicator
or self.indicator.is_condition_met(
np.array(self.get_price_history("close")),
np.array(self.get_price_history("open")),
np.array(self.get_price_history("high")),
np.array(self.get_price_history("low")),
np.array(self.get_price_history("volume")),
)
)
):
# 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
self.log(bar.open, range_1_ago * self.range_factor)
target_buy_price = bar.open + (range_1_ago * self.range_factor)
target_buy_price = open + (range_1_ago * self.range_factor)
# 确保目标买入价格有效,例如不能是负数
target_buy_price = max(0.01, target_buy_price)
self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}"
)
order_id = f"{self.symbol}_BUY_{bar.datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
order_id = f"{self.symbol}_SELL_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -462,7 +473,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
volume=trade_volume,
price_type="LIMIT",
limit_price=target_buy_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
)
new_order = self.send_order(order)
# 记录下这个订单的ID以便在下一根K线开始时进行撤销
@@ -513,6 +524,9 @@ class SimpleLimitBuyStrategy(Strategy):
stop_loss_points: float = 10, # 新增:止损点数
take_profit_points: float = 10,
use_indicator: bool = False,
indicator_l: Indicator = None,
indicator_s: Indicator = None,
lag: int = 7,
): # 新增:止盈点数
"""
初始化策略。
@@ -536,6 +550,9 @@ class SimpleLimitBuyStrategy(Strategy):
self.stop_loss_points = stop_loss_points
self.take_profit_points = take_profit_points
self.use_indicator = use_indicator
self.indicator_l = indicator_l
self.indicator_s = indicator_s
self.lag = lag
self.order_id_counter = 0
@@ -551,15 +568,20 @@ class SimpleLimitBuyStrategy(Strategy):
f"止损点={self.stop_loss_points}, 止盈点={self.take_profit_points}"
)
def on_open_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
def on_init(self):
super().on_init()
count = self.cancel_all_pending_orders()
self.log(f"取消{count}笔订单")
def on_open_bar(self, open, symbol):
"""
每当新的K线数据到来时调用。
Args:
bar (Bar): 当前的K线数据对象。
next_bar_open (Optional[float]): 下一根K线的开盘价此处策略未使用。
"""
current_datetime = bar.datetime # 获取当前K线时间
self.symbol = bar.symbol
self.symbol = symbol
current_datetime = self.get_current_time()
# --- 1. 撤销上一根K线未成交的订单 ---
# 检查是否记录了上一笔订单ID并且该订单仍然在待处理列表中
@@ -593,18 +615,15 @@ class SimpleLimitBuyStrategy(Strategy):
) # 再次获取,此时应已取消旧订单
range_1_ago = None
range_7_ago = None
bar_history = self.get_bar_history()
if len(bar_history) > 16:
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
bar_1_ago = bar_history[-8]
bar_7_ago = bar_history[-15]
bar_1_ago = bar_history[-self.lag]
# 计算历史 K 线的 Range
range_1_ago = bar_1_ago.high - bar_1_ago.low
range_7_ago = bar_7_ago.high - bar_7_ago.low
# --- 3. 平仓逻辑 (止损/止盈) ---
# 只有当有持仓时才考虑平仓
@@ -613,9 +632,7 @@ class SimpleLimitBuyStrategy(Strategy):
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = (
avg_entry_price - bar.open
) # 当前浮动盈亏(以收盘价计算)
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}"
@@ -624,7 +641,7 @@ class SimpleLimitBuyStrategy(Strategy):
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor_s:
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -635,7 +652,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -647,7 +664,7 @@ class SimpleLimitBuyStrategy(Strategy):
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -658,7 +675,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -669,9 +686,7 @@ class SimpleLimitBuyStrategy(Strategy):
): # 假设只做多,所以持仓量 > 0
avg_entry_price = self.get_average_position_price(self.symbol)
if avg_entry_price is not None:
pnl_per_unit = (
bar.open - avg_entry_price
) # 当前浮动盈亏(以收盘价计算)
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
self.log(
f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}"
@@ -680,7 +695,7 @@ class SimpleLimitBuyStrategy(Strategy):
# 止盈条件
if pnl_per_unit >= range_1_ago * self.profit_factor_l:
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -691,7 +706,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -703,7 +718,7 @@ class SimpleLimitBuyStrategy(Strategy):
f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}"
)
# 发送市价卖出订单平仓,确保立即成交
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -714,7 +729,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="MARKET",
# limit_price=limit_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
offset="CLOSE",
)
trade = self.send_order(order)
@@ -724,34 +739,30 @@ class SimpleLimitBuyStrategy(Strategy):
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
# 且K线历史足够长时才考虑开仓
if (
current_pos_volume == 0
and range_1_ago is not None
):
if current_pos_volume == 0 and range_1_ago is not None:
indicator_value = HistoricalRange(21).get_latest_value(
None,
None,
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("open")),
np.array(self.get_price_history("high")),
np.array(self.get_price_history("low")),
None,
)
if (not self.use_indicator or 10 < indicator_value < 25):
np.array(self.get_price_history("volume")),
):
# 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
self.log(bar.open, range_1_ago * self.range_factor_l)
target_buy_price = bar.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 = max(0.01, target_buy_price)
self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}"
)
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -762,7 +773,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="LIMIT",
limit_price=target_buy_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
)
new_order = self.send_order(order)
# 记录下这个订单的ID以便在下一根K线开始时进行撤销
@@ -774,24 +785,31 @@ class SimpleLimitBuyStrategy(Strategy):
else:
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 20 < indicator_value < 60):
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(
np.array(self.get_price_history("close")),
np.array(self.get_price_history("open")),
np.array(self.get_price_history("high")),
np.array(self.get_price_history("low")),
np.array(self.get_price_history("volume")),
):
# 根据策略逻辑计算目标买入价格
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
self.log(bar.open, range_1_ago * self.range_factor_s)
target_buy_price = bar.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 = max(0.01, target_buy_price)
self.log(
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
f"前1Range={range_1_ago:.2f}, "
f"计算目标买入价={target_buy_price:.2f}"
)
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_{current_datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
self.order_id_counter += 1
# 创建一个限价多单
@@ -802,7 +820,7 @@ class SimpleLimitBuyStrategy(Strategy):
volume=trade_volume,
price_type="LIMIT",
limit_price=target_buy_price,
submitted_time=bar.datetime,
submitted_time=current_datetime,
)
new_order = self.send_order(order)
# 记录下这个订单的ID以便在下一根K线开始时进行撤销

View File

@@ -54,7 +54,7 @@ class Strategy(ABC):
pass # 默认不执行任何操作,具体策略可覆盖
@abstractmethod
def on_open_bar(self, bar: "Bar"):
def on_open_bar(self, open: float, symbol: str):
"""
每当新的K线数据到来时调用此方法。
Args:

View File

@@ -165,7 +165,11 @@ class TqsdkEngine:
order_to_send.limit_price
if order_to_send.price_type == "LIMIT"
# else self.quote.bid_price1 + (1 if tqsdk_direction == "BUY" else -1)
else self.quote.bid_price1 if tqsdk_direction == "SELL" else self.quote.ask_price1
else (
self.quote.bid_price1
if tqsdk_direction == "SELL"
else self.quote.ask_price1
)
),
)
# 更新原始 Order 对象与 Tqsdk 的订单ID和状态
@@ -358,7 +362,7 @@ class TqsdkEngine:
self.now = now_dt
if self._api.is_changing(self.klines):
if self._api.is_changing(self.klines.iloc[-1]):
kline_row = self.klines.iloc[-1]
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
@@ -382,11 +386,6 @@ class TqsdkEngine:
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
if (
self.last_processed_bar is None
or self.last_processed_bar.datetime != kline_dt
):
# 创建 core_data.Bar 对象
current_bar = Bar(
datetime=kline_dt,
symbol=self._last_underlying_symbol,
@@ -399,6 +398,11 @@ class TqsdkEngine:
close_oi=kline_row.close_oi,
)
if (
self.last_processed_bar is None
or self.last_processed_bar.datetime != kline_dt
):
# 设置当前 Bar 到 Context
self._context.set_current_bar(current_bar)
@@ -423,19 +427,10 @@ class TqsdkEngine:
else:
self._is_rollover_bar = False
self.all_bars.append(current_bar)
self.close_list.append(current_bar.close)
self.open_list.append(current_bar.open)
self.high_list.append(current_bar.high)
self.low_list.append(current_bar.low)
self.volume_list.append(current_bar.volume)
self.last_processed_bar = current_bar
# 调用策略的 on_bar 方法
self._strategy.on_open_bar(current_bar)
self._strategy.on_open_bar(current_bar.open, current_bar.symbol)
# 处理订单和取消请求
self._process_queued_requests()
@@ -443,25 +438,13 @@ class TqsdkEngine:
# 记录投资组合快照
self._record_portfolio_snapshot(current_bar.datetime)
else:
# 创建 core_data.Bar 对象
current_bar = Bar(
datetime=kline_dt,
symbol=self._last_underlying_symbol,
open=kline_row.open,
high=kline_row.high,
low=kline_row.low,
close=kline_row.close,
volume=kline_row.volume,
open_oi=kline_row.open_oi,
close_oi=kline_row.close_oi,
)
self.all_bars[-1] = current_bar
self.all_bars.append(current_bar)
self.close_list[-1] = current_bar.close
self.open_list[-1] = current_bar.open
self.high_list[-1] = current_bar.high
self.low_list[-1] = current_bar.low
self.volume_list[-1] = current_bar.volume
self.close_list.append(current_bar.close)
self.open_list.append(current_bar.open)
self.high_list.append(current_bar.high)
self.low_list.append(current_bar.low)
self.volume_list.append(current_bar.volume)
self.last_processed_bar = current_bar
@@ -513,13 +496,13 @@ class TqsdkEngine:
return self.all_bars
def get_price_history(self, key: str):
if key == 'close':
if key == "close":
return self.close_list
elif key == 'open':
elif key == "open":
return self.open_list
elif key == 'high':
elif key == "high":
return self.high_list
elif key == 'low':
elif key == "low":
return self.low_list
elif key == 'volume':
elif key == "volume":
return self.volume_list

View File

@@ -112,15 +112,13 @@ class TqsdkEngine:
self.klines = api.get_kline_serial(
symbol, duration_seconds, data_length=history_length + 2
)
self.klines_1min = api.get_kline_serial(
symbol, 60
)
self.klines_1min = api.get_kline_serial(symbol, 60)
self.now = None
self.quote = None
if roll_over_mode:
self.quote = api.get_quote(symbol)
self.kline_row = None
self.partial_bar: Bar = None
print("TqsdkEngine: 初始化完成。")
@@ -345,7 +343,12 @@ class TqsdkEngine:
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
self.main(kline_row, self.klines.iloc[-i - 1])
print(f"TqsdkEngine: 加载历史k线完成, bars数量:{len(self.all_bars)},last bar datetime:{self.all_bars[-1].datetime}")
print(
f"TqsdkEngine: 加载历史k线完成, bars数量:{len(self.all_bars)},last bar datetime:{self.all_bars[-1].datetime}"
)
for bar in self.all_bars[-5:]:
print(bar)
self._strategy.trading = True
self._last_underlying_symbol = self.quote.underlying_symbol
@@ -358,6 +361,8 @@ class TqsdkEngine:
if hasattr(self._strategy, "on_init"):
self._strategy.on_init()
new_bar = False
if is_trading_time:
print(f"TqsdkEngine: 当前是交易时间处理最新一根k线")
@@ -367,10 +372,12 @@ class TqsdkEngine:
self.kline_row = kline_row
self.main(self.klines.iloc[-1], self.klines.iloc[-2])
new_bar = True
# 迭代 K 线数据
# 使用 self._api.get_kline_serial 获取到的 K 线是 Pandas DataFrame
# 直接迭代其行Bar更符合回测逻辑
print(f"TqsdkEngine: 开始等待最新数据")
while True:
# Tqsdk API 的 wait_update() 确保数据更新
@@ -383,15 +390,22 @@ class TqsdkEngine:
self._last_underlying_symbol = self.quote.underlying_symbol
if self._api.is_changing(self.klines_1min.iloc[-1], "datetime"):
if self.kline_row is not None:
kline_dt = pd.to_datetime(self.kline_row.datetime, unit="ns", utc=True)
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
is_close_bar = is_bar_pre_close_period(kline_dt, int(self.kline_row.duration), pre_close_minutes=3)
is_close_bar = is_bar_pre_close_period(
kline_dt, int(self.kline_row.duration), pre_close_minutes=3
)
if is_close_bar:
print(f'TqsdkEngine: close bar, kline_dt:{kline_dt}, now: {datetime.now()}')
if is_close_bar and new_bar:
print(
f"TqsdkEngine: close bar, kline_dt:{kline_dt}, now: {datetime.now()}"
)
self.close_bar(kline_row)
new_bar = False
if self._api.is_changing(self.klines.iloc[-1], "datetime"):
kline_row = self.klines.iloc[-1]
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
@@ -403,6 +417,8 @@ class TqsdkEngine:
)
self.main(kline_row, self.klines.iloc[-2])
new_bar = True
def close_bar(self, kline_row):
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
@@ -419,13 +435,6 @@ class TqsdkEngine:
open_oi=kline_row.open_oi,
close_oi=kline_row.close_oi,
)
self.all_bars[-1] = current_bar
self.close_list[-1] = current_bar.close
self.open_list[-1] = current_bar.open
self.high_list[-1] = current_bar.high
self.low_list[-1] = current_bar.low
self.volume_list[-1] = current_bar.volume
self.last_processed_bar = current_bar
@@ -436,14 +445,13 @@ class TqsdkEngine:
self._process_queued_requests()
def main(self, kline_row, prev_kline_row):
if True:
kline_dt = pd.to_datetime(prev_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)
if len(self.all_bars) > 0:
# 创建 core_data.Bar 对象
current_bar = Bar(
datetime=kline_dt,
symbol=self._last_underlying_symbol,
if self.partial_bar is not None:
last_bar = Bar(
datetime=pd.to_datetime(prev_kline_row.datetime, unit="ns", utc=True).tz_convert(BEIJING_TZ),
symbol=self.partial_bar.symbol,
open=prev_kline_row.open,
high=prev_kline_row.high,
low=prev_kline_row.low,
@@ -452,48 +460,22 @@ class TqsdkEngine:
open_oi=prev_kline_row.open_oi,
close_oi=prev_kline_row.close_oi,
)
self.all_bars[-1] = current_bar
self.close_list[-1] = current_bar.close
self.open_list[-1] = current_bar.open
self.high_list[-1] = current_bar.high
self.low_list[-1] = current_bar.low
self.volume_list[-1] = current_bar.volume
self.all_bars.append(last_bar)
self.last_processed_bar = current_bar
self.close_list.append(last_bar.close)
self.open_list.append(last_bar.open)
self.high_list.append(last_bar.high)
self.low_list.append(last_bar.low)
self.volume_list.append(last_bar.volume)
# if self._strategy.trading is True:
# self._strategy.on_close_bar(current_bar)
self.last_processed_bar = last_bar
# # 处理订单和取消请求
# self._process_queued_requests()
# on open bar --------------------------------------
# 创建 core_data.Bar 对象
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
current_bar = Bar(
datetime=kline_dt,
symbol=self._last_underlying_symbol,
open=kline_row.open,
high=kline_row.high,
low=kline_row.low,
close=kline_row.close,
volume=kline_row.volume,
open_oi=kline_row.open_oi,
close_oi=kline_row.close_oi,
)
# 设置当前 Bar 到 Context
self._context.set_current_bar(current_bar)
# Tqsdk 的 is_changing 用于判断数据是否有变化,对于回测遍历 K 线,每次迭代都算作新 Bar
# 如果 kline_row.datetime 与上次不同,则认为是新 Bar
if (
self.roll_over_mode
and self.last_processed_bar is not None
and self._last_underlying_symbol != self.last_processed_bar.symbol
and self._strategy.trading is True
and self._strategy.trading
):
self._is_rollover_bar = True
print(
@@ -507,27 +489,22 @@ class TqsdkEngine:
self.last_processed_bar.symbol, self._last_underlying_symbol
)
else:
self._is_rollover_bar = False
self.all_bars.append(current_bar)
self.close_list.append(current_bar.close)
self.open_list.append(current_bar.open)
self.high_list.append(current_bar.high)
self.low_list.append(current_bar.low)
self.volume_list.append(current_bar.volume)
self.last_processed_bar = current_bar
# 调用策略的 on_bar 方法
self._strategy.on_open_bar(current_bar)
self._strategy.on_open_bar(kline_row.open, self._last_underlying_symbol)
# 处理订单和取消请求
if self._strategy.trading is True:
self._process_queued_requests()
# 记录投资组合快照
self._record_portfolio_snapshot(current_bar.datetime)
self.partial_bar = Bar(
datetime=kline_dt,
symbol=self.quote.underlying_symbol,
open=0,
high=0,
low=0,
close=0,
volume=0,
open_oi=0,
close_oi=0,
)
def run(self):
"""

0
test
View File

70
test.txt Normal file
View File

@@ -0,0 +1,70 @@
INFO - 时间: 2024-06-24 09:00:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2482.000,手续费: 2.00
INFO - 时间: 2024-06-25 11:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2526.000,手续费: 2.00
INFO - 时间: 2024-06-25 13:30:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2510.000,手续费: 2.00
INFO - 时间: 2024-06-28 09:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2523.000,手续费: 2.00
INFO - 时间: 2024-07-01 09:07:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2531.000,手续费: 2.00
INFO - 时间: 2024-07-02 13:30:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2559.000,手续费: 2.00
INFO - 时间: 2024-07-08 09:43:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2527.000,手续费: 2.00
INFO - 时间: 2024-07-08 22:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2547.000,手续费: 2.00
INFO - 时间: 2024-07-15 13:39:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2566.000,手续费: 2.00
INFO - 时间: 2024-07-17 14:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2538.000,手续费: 2.00
INFO - 时间: 2024-07-23 14:57:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2500.000,手续费: 2.00
INFO - 时间: 2024-07-29 11:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2477.000,手续费: 2.00
INFO - 时间: 2024-07-29 14:48:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2481.000,手续费: 2.00
INFO - 时间: 2024-07-30 10:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2460.000,手续费: 2.00
INFO - 时间: 2024-08-08 14:35:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2453.000,手续费: 2.00
INFO - 时间: 2024-08-09 22:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2420.000,手续费: 2.00
INFO - 时间: 2024-08-15 09:18:59.999999, 合约: CZCE.MA409, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2410.000,手续费: 2.00
INFO - 时间: 2024-08-15 21:00:00.000000, 合约: CZCE.MA409, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2416.000,手续费: 2.00
INFO - 时间: 2024-08-28 14:51:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2536.000,手续费: 2.00
INFO - 时间: 2024-08-30 22:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2507.000,手续费: 2.00
INFO - 时间: 2024-09-04 09:11:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2434.000,手续费: 2.00
INFO - 时间: 2024-09-04 21:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2403.000,手续费: 2.00
INFO - 时间: 2024-09-09 13:48:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2336.000,手续费: 2.00
INFO - 时间: 2024-09-10 22:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2308.000,手续费: 2.00
INFO - 时间: 2024-09-18 09:32:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2367.000,手续费: 2.00
INFO - 时间: 2024-09-19 13:30:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2394.000,手续费: 2.00
INFO - 时间: 2024-10-08 09:00:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2596.000,手续费: 2.00
INFO - 时间: 2024-10-08 11:00:59.999999, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2568.000,手续费: 2.00
INFO - 时间: 2024-10-08 21:44:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2542.000,手续费: 2.00
INFO - 时间: 2024-10-09 21:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2507.000,手续费: 2.00
INFO - 时间: 2024-10-21 09:17:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2397.000,手续费: 2.00
INFO - 时间: 2024-10-23 14:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2431.000,手续费: 2.00
INFO - 时间: 2024-11-15 14:17:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2494.000,手续费: 2.00
INFO - 时间: 2024-11-18 11:00:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2528.000,手续费: 2.00
INFO - 时间: 2024-12-02 21:12:59.999999, 合约: CZCE.MA501, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2543.000,手续费: 2.00
INFO - 时间: 2024-12-04 13:30:00.000000, 合约: CZCE.MA501, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2520.000,手续费: 2.00
INFO - 时间: 2024-12-17 14:50:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2602.000,手续费: 2.00
INFO - 时间: 2024-12-23 22:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2640.000,手续费: 2.00
INFO - 时间: 2025-01-03 14:52:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2652.000,手续费: 2.00
INFO - 时间: 2025-01-06 10:00:59.999999, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2614.000,手续费: 2.00
INFO - 时间: 2025-01-08 21:03:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2571.000,手续费: 2.00
INFO - 时间: 2025-01-10 21:00:59.999999, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2620.000,手续费: 2.00
INFO - 时间: 2025-01-16 09:58:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2636.000,手续费: 2.00
INFO - 时间: 2025-01-16 22:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2602.000,手续费: 2.00
INFO - 时间: 2025-01-21 21:24:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2570.000,手续费: 2.00
INFO - 时间: 2025-01-23 10:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2547.000,手续费: 2.00
INFO - 时间: 2025-01-23 14:58:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2546.000,手续费: 2.00
INFO - 时间: 2025-01-27 14:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2565.000,手续费: 2.00
INFO - 时间: 2025-02-10 21:33:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2598.000,手续费: 2.00
INFO - 时间: 2025-02-12 21:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2577.000,手续费: 2.00
INFO - 时间: 2025-02-14 14:50:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2530.000,手续费: 2.00
INFO - 时间: 2025-02-18 13:30:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2550.000,手续费: 2.00
INFO - 时间: 2025-02-26 09:01:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2534.000,手续费: 2.00
INFO - 时间: 2025-02-27 09:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2553.000,手续费: 2.00
INFO - 时间: 2025-03-24 21:31:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2572.000,手续费: 2.00
INFO - 时间: 2025-03-25 11:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2546.000,手续费: 2.00
INFO - 时间: 2025-03-31 09:03:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2517.000,手续费: 2.00
INFO - 时间: 2025-03-31 10:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2495.000,手续费: 2.00
INFO - 时间: 2025-04-09 09:13:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2322.000,手续费: 2.00
INFO - 时间: 2025-04-09 14:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2350.000,手续费: 2.00
INFO - 时间: 2025-04-14 09:05:59.999999, 合约: CZCE.MA505, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2367.000,手续费: 2.00
INFO - 时间: 2025-04-16 21:00:00.000000, 合约: CZCE.MA505, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2374.000,手续费: 2.00
INFO - 时间: 2025-04-21 09:31:59.999999, 合约: CZCE.MA509, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2264.000,手续费: 2.00
INFO - 时间: 2025-04-23 14:00:00.000000, 合约: CZCE.MA509, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2294.000,手续费: 2.00
INFO - 时间: 2025-05-08 09:09:59.999999, 合约: CZCE.MA509, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2210.000,手续费: 2.00
INFO - 时间: 2025-05-09 11:00:00.000000, 合约: CZCE.MA509, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2234.000,手续费: 2.00
INFO - 时间: 2025-05-19 09:21:59.999999, 合约: CZCE.MA509, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2276.000,手续费: 2.00
INFO - 时间: 2025-05-20 14:00:00.000000, 合约: CZCE.MA509, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2250.000,手续费: 2.00
INFO - 时间: 2025-05-23 14:22:59.999999, 合约: CZCE.MA509, 开平: OPEN, 方向: BUY, 手数: 1, 价格: 2226.000,手续费: 2.00
INFO - 时间: 2025-05-26 22:00:00.000000, 合约: CZCE.MA509, 开平: CLOSE, 方向: SELL, 手数: 1, 价格: 2190.000,手续费: 2.00

File diff suppressed because one or more lines are too long