修复未来函数bug
This commit is contained in:
File diff suppressed because one or more lines are too long
@@ -236,11 +236,11 @@ 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@CZCE.MA",
|
symbol="KQ.m@DCE.jm",
|
||||||
# symbol='SHFE.rb2510',
|
# symbol='SHFE.rb2510',
|
||||||
# symbol='KQ.i@SHFE.bu',
|
# symbol='KQ.i@SHFE.bu',
|
||||||
freq="min60",
|
freq="min60",
|
||||||
start_date_str="2022-01-01",
|
start_date_str="2021-01-01",
|
||||||
end_date_str="2025-07-11",
|
end_date_str="2025-07-11",
|
||||||
mode="backtest", # 指定为回测模式
|
mode="backtest", # 指定为回测模式
|
||||||
tq_user=TQ_USER_NAME,
|
tq_user=TQ_USER_NAME,
|
||||||
|
|||||||
File diff suppressed because one or more lines are too long
786
main2.ipynb
786
main2.ipynb
File diff suppressed because one or more lines are too long
@@ -2,6 +2,7 @@ from datetime import timedelta
|
|||||||
from src.analysis.result_analyzer import ResultAnalyzer
|
from src.analysis.result_analyzer import ResultAnalyzer
|
||||||
|
|
||||||
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
# 导入 TqsdkEngine,而不是原来的 BacktestEngine
|
||||||
|
from src.indicators.indicators import RSI, HistoricalRange
|
||||||
from src.tqsdk_real_engine import TqsdkEngine
|
from src.tqsdk_real_engine import TqsdkEngine
|
||||||
|
|
||||||
# 导入你的策略类
|
# 导入你的策略类
|
||||||
@@ -17,29 +18,34 @@ initial_capital = 100000.0
|
|||||||
slippage_rate = 0.000 # 在 Tqsdk 模拟中,滑点通常由 TqSim 处理或在策略中手动模拟
|
slippage_rate = 0.000 # 在 Tqsdk 模拟中,滑点通常由 TqSim 处理或在策略中手动模拟
|
||||||
commission_rate = 0.0001 # 同上
|
commission_rate = 0.0001 # 同上
|
||||||
# 主力合约的 symbol
|
# 主力合约的 symbol
|
||||||
main_symbol = "KQ.m@CZCE.MA"
|
main_symbol = "KQ.m@DCE.jm"
|
||||||
strategy_parameters = {
|
strategy_parameters = {
|
||||||
'symbol': main_symbol, # 根据您的数据文件中的品种名称调整
|
'symbol': main_symbol, # 根据您的数据文件中的品种名称调整
|
||||||
'trade_volume': 1,
|
'trade_volume': 1,
|
||||||
'range_factor': 1.8, # 示例值,需要通过网格搜索优化
|
'lag': 1,
|
||||||
'profit_factor': 2.8, # 示例值
|
# 'range_factor': 1.3, # 示例值,需要通过网格搜索优化
|
||||||
# 'range_factor': 0.7, # 示例值,需要通过网格搜索优化
|
# 'profit_factor': 4.8, # 示例值
|
||||||
# 'profit_factor': 71, # 示例值
|
# 'range_factor': 1.1, # 示例值,需要通过网格搜索优化
|
||||||
# 'range_factor_l': 2, # 示例值,需要通过网格搜索优化
|
# 'profit_factor': 4.9, # 示例值
|
||||||
# 'profit_factor_l': 3, # 示例值
|
'range_factor_l': 1.3, # 示例值,需要通过网格搜索优化
|
||||||
# 'range_factor_s': 1.6, # 示例值,需要通过网格搜索优化
|
'profit_factor_l': 4.8, # 示例值
|
||||||
# 'profit_factor_s': 5.6, # 示例值
|
'range_factor_s': 1.1, # 示例值,需要通过网格搜索优化
|
||||||
|
'profit_factor_s': 4.9, # 示例值
|
||||||
'max_position': 10,
|
'max_position': 10,
|
||||||
'enable_log': True,
|
'enable_log': True,
|
||||||
'stop_loss_points': 20,
|
'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"))
|
api = TqApi(TqKq(), auth=TqAuth("emanresu", "dfgvfgdfgg"))
|
||||||
# --- 1. 初始化回测引擎并运行 ---
|
# --- 1. 初始化回测引擎并运行 ---
|
||||||
print("\n初始化 Tqsdk 回测引擎...")
|
print("\n初始化 Tqsdk 回测引擎...")
|
||||||
engine = TqsdkEngine(
|
engine = TqsdkEngine(
|
||||||
strategy_class=SimpleLimitBuyStrategyLong,
|
strategy_class=SimpleLimitBuyStrategy,
|
||||||
strategy_params=strategy_parameters,
|
strategy_params=strategy_parameters,
|
||||||
api=api,
|
api=api,
|
||||||
symbol=main_symbol,
|
symbol=main_symbol,
|
||||||
|
|||||||
@@ -70,7 +70,7 @@ class ResultAnalyzer:
|
|||||||
for trade in self.trade_history:
|
for trade in self.trade_history:
|
||||||
# 调整输出格式,显示实现盈亏
|
# 调整输出格式,显示实现盈亏
|
||||||
pnl_display = (
|
pnl_display = (
|
||||||
f" | Indicators:{trade.indicator_dict} | PnL: {trade.realized_pnl:.2f}"
|
f" | PnL: {trade.realized_pnl:.2f}"
|
||||||
if trade.is_close_trade
|
if trade.is_close_trade
|
||||||
else ""
|
else ""
|
||||||
)
|
)
|
||||||
@@ -145,7 +145,8 @@ class ResultAnalyzer:
|
|||||||
# 确保 trade.indicator_dict 中包含当前指标的值
|
# 确保 trade.indicator_dict 中包含当前指标的值
|
||||||
# 并且这个值是可用的(非None或NaN)
|
# 并且这个值是可用的(非None或NaN)
|
||||||
if (
|
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
|
and trade.indicator_dict[indicator_name] is not None
|
||||||
):
|
):
|
||||||
# 检查是否为 NaN,如果使用 np.nan,则需要 isinstance(value, float) and np.isnan(value)
|
# 检查是否为 NaN,如果使用 np.nan,则需要 isinstance(value, float) and np.isnan(value)
|
||||||
|
|||||||
@@ -107,13 +107,6 @@ class BacktestEngine:
|
|||||||
if current_bar is None:
|
if current_bar is None:
|
||||||
break # 没有更多数据,回测结束
|
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:
|
if self.start_time and current_bar.datetime < self.start_time:
|
||||||
continue
|
continue
|
||||||
|
|
||||||
@@ -163,14 +156,7 @@ class BacktestEngine:
|
|||||||
# 3. 更新策略关注的当前合约 symbol
|
# 3. 更新策略关注的当前合约 symbol
|
||||||
self.strategy.symbol = current_bar.symbol
|
self.strategy.symbol = current_bar.symbol
|
||||||
|
|
||||||
# 5. 更新引擎内部的历史 Bar 缓存
|
self.strategy.on_open_bar(current_bar.open, current_bar.symbol)
|
||||||
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)
|
|
||||||
|
|
||||||
current_indicator_dict = {}
|
current_indicator_dict = {}
|
||||||
close_array = np.array(self.close_list)
|
close_array = np.array(self.close_list)
|
||||||
@@ -187,10 +173,18 @@ class BacktestEngine:
|
|||||||
low_array,
|
low_array,
|
||||||
volume_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 方法
|
# 7. 调用策略的 on_bar 方法
|
||||||
# self.strategy.on_bar(current_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.strategy.on_close_bar(current_bar)
|
||||||
self.simulator.process_pending_orders(current_bar, current_indicator_dict)
|
self.simulator.process_pending_orders(current_bar, current_indicator_dict)
|
||||||
@@ -277,4 +271,5 @@ class BacktestEngine:
|
|||||||
return self.low_list
|
return self.low_list
|
||||||
elif key == 'volume':
|
elif key == 'volume':
|
||||||
return self.volume_list
|
return self.volume_list
|
||||||
|
return None
|
||||||
|
|
||||||
|
|||||||
@@ -1,9 +1,24 @@
|
|||||||
# src/common_utils.py
|
# 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]]:
|
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
|
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(
|
def is_bar_pre_close_period(
|
||||||
bar_start_time: datetime,
|
bar_start_time: datetime,
|
||||||
bar_duration_seconds: int,
|
bar_duration_seconds: int,
|
||||||
@@ -85,31 +154,61 @@ def is_bar_pre_close_period(
|
|||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
判断当前系统时间是否在一根K线的即将结束时间段内。
|
判断当前系统时间是否在一根K线的即将结束时间段内。
|
||||||
|
K线的时区和所有内部时间判断的基准时区固定为 "Asia/Shanghai"。
|
||||||
|
优化:考虑市场实际收盘时间,K线结束时间不会晚于其所属交易时段的实际收盘时间。
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
bar_start_time (datetime): 当前K线的开始时间(例如:bar.datetime)。
|
bar_start_time (datetime): 当前K线的开始时间。它必须是时区感知的 (offset-aware),
|
||||||
|
且应是 "Asia/Shanghai" 时区。
|
||||||
可以是 datetime.datetime 对象或 pandas.Timestamp 对象。
|
可以是 datetime.datetime 对象或 pandas.Timestamp 对象。
|
||||||
bar_duration_seconds (int): 该K线的持续时间,以秒为单位。
|
bar_duration_seconds (int): 该K线的持续时间,以秒为单位。
|
||||||
对于1小时K线,传入 3600。
|
对于1小时K线,传入 3600。
|
||||||
pre_close_minutes (int): K线结束前多少分钟被认为是“即将结束”状态。例如,传入 3 表示结束前3分钟。
|
pre_close_minutes (int): K线结束前多少分钟被认为是“即将结束”状态。
|
||||||
current_system_time (Optional[datetime]): 用于判断的当前时间。
|
current_system_time (Optional[datetime]): 用于判断的当前时间。
|
||||||
如果为 None,则默认使用 `datetime.now()`。
|
如果为 None,则默认使用 `datetime.now()`。
|
||||||
|
此时间在函数内部会向上取整到最近的分钟。
|
||||||
|
|
||||||
Returns:
|
Returns:
|
||||||
bool: 如果当前时间在K线结束前 `pre_close_minutes` 的窗口内,则返回 True,否则返回 False。
|
bool: 如果当前时间在K线结束前 `pre_close_minutes` 的窗口内,则返回 True,否则返回 False。
|
||||||
"""
|
"""
|
||||||
|
# 1. 统一 current_system_time 的时区感知性到 BEIJING_TZ,并向上取整
|
||||||
if current_system_time is None:
|
if current_system_time is None:
|
||||||
current_system_time = datetime.now(BEIJING_TZ)
|
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线的精确结束时间
|
# 对 current_system_time 进行向上取整,避免毫秒级偏差
|
||||||
# K线结束时间 = K线开始时间 + K线持续时间
|
current_system_time = round_time_up_to_nearest_minute(current_system_time)
|
||||||
bar_end_time = bar_start_time + timedelta(seconds=bar_duration_seconds)
|
|
||||||
|
|
||||||
# 2. 计算“即将结束”窗口的开始时间
|
# 2. 确保 bar_duration_seconds 是 int 类型
|
||||||
# 这个窗口从 (K线结束时间 - pre_close_minutes) 开始,到 K线结束时间 结束
|
duration_as_int = int(bar_duration_seconds)
|
||||||
pre_close_window_start_time = bar_end_time - timedelta(minutes=pre_close_minutes)
|
|
||||||
|
|
||||||
# 3. 判断当前系统时间是否在这个窗口内
|
# 3. 计算K线的理论结束时间(不考虑市场收盘)
|
||||||
# 窗口定义为 [pre_close_window_start_time, bar_end_time),即包含开始时间,不包含结束时间
|
calculated_bar_end_time = bar_start_time + timedelta(seconds=duration_as_int)
|
||||||
print(pre_close_window_start_time, current_system_time, bar_end_time)
|
|
||||||
return pre_close_window_start_time <= current_system_time < bar_end_time
|
# 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
|
||||||
|
|||||||
@@ -339,7 +339,8 @@ class ExecutionSimulator:
|
|||||||
# 这里直接调用 _execute_single_order 确保强制平仓立即成交
|
# 这里直接调用 _execute_single_order 确保强制平仓立即成交
|
||||||
trade = self._execute_single_order(rollover_order, closing_bar)
|
trade = self._execute_single_order(rollover_order, closing_bar)
|
||||||
if trade:
|
if trade:
|
||||||
closed_trades.append(trade)
|
# closed_trades.append(trade)
|
||||||
|
self.trade_log.append(trade)
|
||||||
else:
|
else:
|
||||||
print(
|
print(
|
||||||
f"[{closing_bar.datetime}] 警告: 强制平仓 {symbol_to_close} 失败!"
|
f"[{closing_bar.datetime}] 警告: 强制平仓 {symbol_to_close} 失败!"
|
||||||
|
|||||||
@@ -8,8 +8,10 @@ from src.core_data import Bar
|
|||||||
|
|
||||||
class Indicator(ABC):
|
class Indicator(ABC):
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(self, down_bound:float = None, up_bound:float = None, shift_window: int = 0):
|
||||||
pass
|
self.down_bound =down_bound
|
||||||
|
self.up_bound =up_bound
|
||||||
|
self.shift_window = shift_window
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def get_values(self, close: np.array, open: np.array, high: np.array, low: np.array, volume: np.array):
|
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):
|
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
|
@abstractmethod
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
|
|||||||
@@ -1,17 +1,33 @@
|
|||||||
from src.indicators.indicators import RSI, HistoricalRange
|
from src.indicators.indicators import RSI, HistoricalRange, DifferencedVolumeIndicator, StochasticOscillator, \
|
||||||
|
RateOfChange, NormalizedATR
|
||||||
|
|
||||||
INDICATOR_LIST = [
|
INDICATOR_LIST = [
|
||||||
RSI(5),
|
RSI(5),
|
||||||
|
RSI(7),
|
||||||
RSI(10),
|
RSI(10),
|
||||||
|
RSI(14),
|
||||||
RSI(15),
|
RSI(15),
|
||||||
RSI(20),
|
RSI(20),
|
||||||
RSI(25),
|
RSI(25),
|
||||||
RSI(30),
|
RSI(30),
|
||||||
RSI(35),
|
RSI(35),
|
||||||
RSI(40),
|
RSI(40),
|
||||||
HistoricalRange(1),
|
HistoricalRange(shift_window=0),
|
||||||
HistoricalRange(8),
|
HistoricalRange(shift_window=6),
|
||||||
HistoricalRange(15),
|
HistoricalRange(shift_window=13),
|
||||||
HistoricalRange(21),
|
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)
|
||||||
]
|
]
|
||||||
@@ -10,21 +10,25 @@ class RSI(Indicator):
|
|||||||
相对强弱指数 (RSI) 指标实现,使用 TA-Lib 简化计算。
|
相对强弱指数 (RSI) 指标实现,使用 TA-Lib 简化计算。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, window: int = 14):
|
def __init__(
|
||||||
"""
|
self,
|
||||||
初始化RSI指标。
|
window: int = 14,
|
||||||
Args:
|
down_bound: float = None,
|
||||||
window (int): RSI的计算周期,默认为14。
|
up_bound: float = None,
|
||||||
"""
|
shift_window: int = 0,
|
||||||
super().__init__()
|
):
|
||||||
|
super().__init__(down_bound, up_bound)
|
||||||
self.window = window
|
self.window = window
|
||||||
|
self.shift_window = shift_window
|
||||||
|
|
||||||
def get_values(self,
|
def get_values(
|
||||||
|
self,
|
||||||
close: np.array,
|
close: np.array,
|
||||||
open: np.array, # 不使用
|
open: np.array, # 不使用
|
||||||
high: np.array, # 不使用
|
high: np.array, # 不使用
|
||||||
low: np.array, # 不使用
|
low: np.array, # 不使用
|
||||||
volume: np.array) -> np.array: # 不使用
|
volume: np.array,
|
||||||
|
) -> np.array: # 不使用
|
||||||
"""
|
"""
|
||||||
根据收盘价列表计算RSI值,使用 TA-Lib。
|
根据收盘价列表计算RSI值,使用 TA-Lib。
|
||||||
Args:
|
Args:
|
||||||
@@ -41,8 +45,7 @@ class RSI(Indicator):
|
|||||||
return rsi_values
|
return rsi_values
|
||||||
|
|
||||||
def get_name(self):
|
def get_name(self):
|
||||||
return f'rsi_{self.window}'
|
return f"rsi_{self.window}"
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
class HistoricalRange(Indicator):
|
class HistoricalRange(Indicator):
|
||||||
@@ -50,21 +53,20 @@ class HistoricalRange(Indicator):
|
|||||||
历史波动幅度指标:计算过去 N 日的 (最高价 - 最低价) 的简单移动平均。
|
历史波动幅度指标:计算过去 N 日的 (最高价 - 最低价) 的简单移动平均。
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def __init__(self, window: int = 20):
|
def __init__(
|
||||||
"""
|
self, down_bound: float = None, up_bound: float = None, shift_window: int = 0
|
||||||
初始化历史波动幅度指标。
|
):
|
||||||
Args:
|
super().__init__(down_bound, up_bound)
|
||||||
window (int): 计算范围平均值的周期,默认为20。
|
self.shift_window = shift_window
|
||||||
"""
|
|
||||||
super().__init__()
|
|
||||||
self.window = window
|
|
||||||
|
|
||||||
def get_values(self,
|
def get_values(
|
||||||
|
self,
|
||||||
close: np.array, # 不使用
|
close: np.array, # 不使用
|
||||||
open: np.array, # 不使用
|
open: np.array, # 不使用
|
||||||
high: np.array,
|
high: np.array,
|
||||||
low: np.array,
|
low: np.array,
|
||||||
volume: np.array) -> np.array: # 不使用
|
volume: np.array,
|
||||||
|
) -> np.array: # 不使用
|
||||||
"""
|
"""
|
||||||
根据最高价和最低价列表计算过去 N 日的 (high - low) 值的简单移动平均。
|
根据最高价和最低价列表计算过去 N 日的 (high - low) 值的简单移动平均。
|
||||||
Args:
|
Args:
|
||||||
@@ -82,7 +84,188 @@ class HistoricalRange(Indicator):
|
|||||||
daily_ranges = high - low
|
daily_ranges = high - low
|
||||||
|
|
||||||
# 将 numpy 数组转换为 list 并返回
|
# 将 numpy 数组转换为 list 并返回
|
||||||
return np.roll(daily_ranges, self.window)
|
return daily_ranges
|
||||||
|
|
||||||
def get_name(self):
|
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}"
|
||||||
|
|||||||
@@ -2,6 +2,7 @@
|
|||||||
|
|
||||||
from tkinter import N
|
from tkinter import N
|
||||||
import numpy as np
|
import numpy as np
|
||||||
|
from src.indicators.base_indicators import Indicator
|
||||||
from src.indicators.indicators import RSI, HistoricalRange
|
from src.indicators.indicators import RSI, HistoricalRange
|
||||||
from .base_strategy import Strategy
|
from .base_strategy import Strategy
|
||||||
from ..core_data import Bar, Order
|
from ..core_data import Bar, Order
|
||||||
@@ -30,6 +31,8 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
stop_loss_points: float = 10, # 新增:止损点数
|
stop_loss_points: float = 10, # 新增:止损点数
|
||||||
take_profit_points: float = 10,
|
take_profit_points: float = 10,
|
||||||
use_indicator: bool = False,
|
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.stop_loss_points = stop_loss_points
|
||||||
self.take_profit_points = take_profit_points
|
self.take_profit_points = take_profit_points
|
||||||
self.use_indicator = use_indicator
|
self.use_indicator = use_indicator
|
||||||
|
self.indicator = indicator
|
||||||
|
self.lag = lag
|
||||||
|
|
||||||
self.order_id_counter = 0
|
self.order_id_counter = 0
|
||||||
|
|
||||||
@@ -67,18 +72,17 @@ 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.log(f'取消{count}笔订单')
|
self.log(f"取消{count}笔订单")
|
||||||
|
|
||||||
|
def on_open_bar(self, open: float, symbol: str):
|
||||||
def on_open_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
|
|
||||||
"""
|
"""
|
||||||
每当新的K线数据到来时调用。
|
每当新的K线数据到来时调用。
|
||||||
Args:
|
Args:
|
||||||
bar (Bar): 当前的K线数据对象。
|
bar (Bar): 当前的K线数据对象。
|
||||||
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
||||||
"""
|
"""
|
||||||
current_datetime = bar.datetime # 获取当前K线时间
|
current_datetime = self.get_current_time() # 获取当前K线时间
|
||||||
self.symbol = bar.symbol
|
self.symbol = symbol
|
||||||
|
|
||||||
# --- 1. 撤销上一根K线未成交的订单 ---
|
# --- 1. 撤销上一根K线未成交的订单 ---
|
||||||
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
||||||
@@ -112,18 +116,15 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
) # 再次获取,此时应已取消旧订单
|
) # 再次获取,此时应已取消旧订单
|
||||||
|
|
||||||
range_1_ago = None
|
range_1_ago = None
|
||||||
range_7_ago = None
|
|
||||||
|
|
||||||
bar_history = self.get_bar_history()
|
bar_history = self.get_bar_history()
|
||||||
if len(bar_history) > 16:
|
if len(bar_history) > 16:
|
||||||
|
|
||||||
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
||||||
bar_1_ago = bar_history[-8]
|
bar_1_ago = bar_history[-self.lag]
|
||||||
bar_7_ago = bar_history[-15]
|
|
||||||
|
|
||||||
# 计算历史 K 线的 Range
|
# 计算历史 K 线的 Range
|
||||||
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
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):
|
# for i in range(1, 9, 1):
|
||||||
# print(bar_history[-i].datetime)
|
# print(bar_history[-i].datetime)
|
||||||
@@ -135,18 +136,16 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
): # 假设只做多,所以持仓量 > 0
|
): # 假设只做多,所以持仓量 > 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 = (
|
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
|
||||||
bar.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}, 目标: {range_1_ago * self.profit_factor:.2f}"
|
||||||
)
|
)
|
||||||
|
|
||||||
# 止盈条件
|
# 止盈条件
|
||||||
if pnl_per_unit >= range_1_ago * self.profit_factor:
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -157,7 +156,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
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}"
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -180,7 +179,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
trade = self.send_order(order)
|
||||||
@@ -190,36 +189,37 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
||||||
# 且K线历史足够长时才考虑开仓
|
# 且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 (
|
if (
|
||||||
current_pos_volume == 0
|
current_pos_volume == 0
|
||||||
and range_1_ago is not None
|
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:
|
# if current_pos_volume == 0 and range_1_ago is not None:
|
||||||
|
|
||||||
# 根据策略逻辑计算目标买入价格
|
# 根据策略逻辑计算目标买入价格
|
||||||
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
||||||
self.log(bar.open, range_1_ago * self.range_factor)
|
self.log(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)
|
target_buy_price = max(0.01, target_buy_price)
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
|
||||||
f"前1Range={range_1_ago:.2f}, "
|
f"前1Range={range_1_ago:.2f}, "
|
||||||
f"计算目标买入价={target_buy_price:.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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -230,7 +230,7 @@ class SimpleLimitBuyStrategyLong(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="LIMIT",
|
price_type="LIMIT",
|
||||||
limit_price=target_buy_price,
|
limit_price=target_buy_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
)
|
)
|
||||||
new_order = self.send_order(order)
|
new_order = self.send_order(order)
|
||||||
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
||||||
@@ -279,6 +279,8 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
stop_loss_points: float = 10, # 新增:止损点数
|
stop_loss_points: float = 10, # 新增:止损点数
|
||||||
take_profit_points: float = 10,
|
take_profit_points: float = 10,
|
||||||
use_indicator: bool = False,
|
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.stop_loss_points = stop_loss_points
|
||||||
self.take_profit_points = take_profit_points
|
self.take_profit_points = take_profit_points
|
||||||
self.use_indicator = use_indicator
|
self.use_indicator = use_indicator
|
||||||
|
self.indicator = indicator
|
||||||
|
self.lag = lag
|
||||||
|
|
||||||
self.order_id_counter = 0
|
self.order_id_counter = 0
|
||||||
|
|
||||||
@@ -313,15 +317,20 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
f"止损点={self.stop_loss_points}, 止盈点={self.take_profit_points}"
|
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线数据到来时调用。
|
每当新的K线数据到来时调用。
|
||||||
Args:
|
Args:
|
||||||
bar (Bar): 当前的K线数据对象。
|
bar (Bar): 当前的K线数据对象。
|
||||||
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
||||||
"""
|
"""
|
||||||
current_datetime = bar.datetime # 获取当前K线时间
|
current_datetime = self.get_current_time() # 获取当前K线时间
|
||||||
self.symbol = bar.symbol
|
self.symbol = symbol
|
||||||
|
|
||||||
# --- 1. 撤销上一根K线未成交的订单 ---
|
# --- 1. 撤销上一根K线未成交的订单 ---
|
||||||
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
||||||
@@ -355,18 +364,15 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
) # 再次获取,此时应已取消旧订单
|
) # 再次获取,此时应已取消旧订单
|
||||||
|
|
||||||
range_1_ago = None
|
range_1_ago = None
|
||||||
range_7_ago = None
|
|
||||||
|
|
||||||
bar_history = self.get_bar_history()
|
bar_history = self.get_bar_history()
|
||||||
if len(bar_history) > 16:
|
if len(bar_history) > 16:
|
||||||
|
|
||||||
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
||||||
bar_1_ago = bar_history[-8]
|
bar_1_ago = bar_history[-self.lag]
|
||||||
bar_7_ago = bar_history[-15]
|
|
||||||
|
|
||||||
# 计算历史 K 线的 Range
|
# 计算历史 K 线的 Range
|
||||||
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
||||||
range_7_ago = bar_7_ago.high - bar_7_ago.low
|
|
||||||
|
|
||||||
# --- 3. 平仓逻辑 (止损/止盈) ---
|
# --- 3. 平仓逻辑 (止损/止盈) ---
|
||||||
# 只有当有持仓时才考虑平仓
|
# 只有当有持仓时才考虑平仓
|
||||||
@@ -375,9 +381,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
): # 假设只做多,所以持仓量 > 0
|
): # 假设只做多,所以持仓量 > 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 = (
|
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
|
||||||
avg_entry_price - bar.open
|
|
||||||
) # 当前浮动盈亏(以收盘价计算)
|
|
||||||
|
|
||||||
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}"
|
||||||
@@ -386,7 +390,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
# 止盈条件
|
# 止盈条件
|
||||||
if pnl_per_unit >= range_1_ago * self.profit_factor:
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -397,7 +401,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
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}"
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -420,7 +424,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
trade = self.send_order(order)
|
||||||
@@ -430,28 +434,35 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
||||||
# 且K线历史足够长时才考虑开仓
|
# 且K线历史足够长时才考虑开仓
|
||||||
# rsi = RSI(5).get_latest_value(np.array(self.get_price_history('close')), None, None, None, None)
|
# 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 (
|
if (
|
||||||
current_pos_volume == 0
|
current_pos_volume == 0
|
||||||
and range_1_ago is not None
|
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)
|
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
||||||
self.log(bar.open, range_1_ago * self.range_factor)
|
target_buy_price = open + (range_1_ago * self.range_factor)
|
||||||
target_buy_price = bar.open + (range_1_ago * self.range_factor)
|
|
||||||
|
|
||||||
# 确保目标买入价格有效,例如不能是负数
|
# 确保目标买入价格有效,例如不能是负数
|
||||||
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={bar.open:.2f}, "
|
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
|
||||||
f"前1Range={range_1_ago:.2f}, "
|
f"前1Range={range_1_ago:.2f}, "
|
||||||
f"计算目标买入价={target_buy_price:.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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -462,7 +473,7 @@ class SimpleLimitBuyStrategyShort(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="LIMIT",
|
price_type="LIMIT",
|
||||||
limit_price=target_buy_price,
|
limit_price=target_buy_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
)
|
)
|
||||||
new_order = self.send_order(order)
|
new_order = self.send_order(order)
|
||||||
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
||||||
@@ -513,6 +524,9 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
stop_loss_points: float = 10, # 新增:止损点数
|
stop_loss_points: float = 10, # 新增:止损点数
|
||||||
take_profit_points: float = 10,
|
take_profit_points: float = 10,
|
||||||
use_indicator: bool = False,
|
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.stop_loss_points = stop_loss_points
|
||||||
self.take_profit_points = take_profit_points
|
self.take_profit_points = take_profit_points
|
||||||
self.use_indicator = use_indicator
|
self.use_indicator = use_indicator
|
||||||
|
self.indicator_l = indicator_l
|
||||||
|
self.indicator_s = indicator_s
|
||||||
|
self.lag = lag
|
||||||
|
|
||||||
self.order_id_counter = 0
|
self.order_id_counter = 0
|
||||||
|
|
||||||
@@ -551,15 +568,20 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
f"止损点={self.stop_loss_points}, 止盈点={self.take_profit_points}"
|
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线数据到来时调用。
|
每当新的K线数据到来时调用。
|
||||||
Args:
|
Args:
|
||||||
bar (Bar): 当前的K线数据对象。
|
bar (Bar): 当前的K线数据对象。
|
||||||
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
||||||
"""
|
"""
|
||||||
current_datetime = bar.datetime # 获取当前K线时间
|
self.symbol = symbol
|
||||||
self.symbol = bar.symbol
|
current_datetime = self.get_current_time()
|
||||||
|
|
||||||
# --- 1. 撤销上一根K线未成交的订单 ---
|
# --- 1. 撤销上一根K线未成交的订单 ---
|
||||||
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
||||||
@@ -593,18 +615,15 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
) # 再次获取,此时应已取消旧订单
|
) # 再次获取,此时应已取消旧订单
|
||||||
|
|
||||||
range_1_ago = None
|
range_1_ago = None
|
||||||
range_7_ago = None
|
|
||||||
|
|
||||||
bar_history = self.get_bar_history()
|
bar_history = self.get_bar_history()
|
||||||
if len(bar_history) > 16:
|
if len(bar_history) > 16:
|
||||||
|
|
||||||
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
||||||
bar_1_ago = bar_history[-8]
|
bar_1_ago = bar_history[-self.lag]
|
||||||
bar_7_ago = bar_history[-15]
|
|
||||||
|
|
||||||
# 计算历史 K 线的 Range
|
# 计算历史 K 线的 Range
|
||||||
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
||||||
range_7_ago = bar_7_ago.high - bar_7_ago.low
|
|
||||||
|
|
||||||
# --- 3. 平仓逻辑 (止损/止盈) ---
|
# --- 3. 平仓逻辑 (止损/止盈) ---
|
||||||
# 只有当有持仓时才考虑平仓
|
# 只有当有持仓时才考虑平仓
|
||||||
@@ -613,9 +632,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
): # 假设只做多,所以持仓量 > 0
|
): # 假设只做多,所以持仓量 > 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 = (
|
pnl_per_unit = avg_entry_price - open # 当前浮动盈亏(以收盘价计算)
|
||||||
avg_entry_price - bar.open
|
|
||||||
) # 当前浮动盈亏(以收盘价计算)
|
|
||||||
|
|
||||||
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}"
|
||||||
@@ -624,7 +641,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
# 止盈条件
|
# 止盈条件
|
||||||
if pnl_per_unit >= range_1_ago * self.profit_factor_s:
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -635,7 +652,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
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}"
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -658,7 +675,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
trade = self.send_order(order)
|
||||||
@@ -669,9 +686,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
): # 假设只做多,所以持仓量 > 0
|
): # 假设只做多,所以持仓量 > 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 = (
|
pnl_per_unit = open - avg_entry_price # 当前浮动盈亏(以收盘价计算)
|
||||||
bar.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}"
|
||||||
@@ -680,7 +695,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
# 止盈条件
|
# 止盈条件
|
||||||
if pnl_per_unit >= range_1_ago * self.profit_factor_l:
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -691,7 +706,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
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}"
|
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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -714,7 +729,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="MARKET",
|
price_type="MARKET",
|
||||||
# limit_price=limit_price,
|
# limit_price=limit_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
offset="CLOSE",
|
offset="CLOSE",
|
||||||
)
|
)
|
||||||
trade = self.send_order(order)
|
trade = self.send_order(order)
|
||||||
@@ -724,34 +739,30 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
||||||
# 且K线历史足够长时才考虑开仓
|
# 且K线历史足够长时才考虑开仓
|
||||||
|
|
||||||
if (
|
if current_pos_volume == 0 and range_1_ago is not None:
|
||||||
current_pos_volume == 0
|
|
||||||
and range_1_ago is not None
|
|
||||||
):
|
|
||||||
|
|
||||||
indicator_value = HistoricalRange(21).get_latest_value(
|
if not self.use_indicator or self.indicator_l.is_condition_met(
|
||||||
None,
|
np.array(self.get_price_history("close")),
|
||||||
None,
|
np.array(self.get_price_history("open")),
|
||||||
np.array(self.get_price_history("high")),
|
np.array(self.get_price_history("high")),
|
||||||
np.array(self.get_price_history("low")),
|
np.array(self.get_price_history("low")),
|
||||||
None,
|
np.array(self.get_price_history("volume")),
|
||||||
)
|
):
|
||||||
if (not self.use_indicator or 10 < indicator_value < 25):
|
|
||||||
# 根据策略逻辑计算目标买入价格
|
# 根据策略逻辑计算目标买入价格
|
||||||
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
||||||
self.log(bar.open, range_1_ago * self.range_factor_l)
|
self.log(open, range_1_ago * self.range_factor_l)
|
||||||
target_buy_price = bar.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)
|
target_buy_price = max(0.01, target_buy_price)
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
|
||||||
f"前1Range={range_1_ago:.2f}, "
|
f"前1Range={range_1_ago:.2f}, "
|
||||||
f"计算目标买入价={target_buy_price:.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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -762,7 +773,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="LIMIT",
|
price_type="LIMIT",
|
||||||
limit_price=target_buy_price,
|
limit_price=target_buy_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
)
|
)
|
||||||
new_order = self.send_order(order)
|
new_order = self.send_order(order)
|
||||||
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
||||||
@@ -774,24 +785,31 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
else:
|
else:
|
||||||
self.log(f"[{current_datetime}] 策略: 发送订单失败。")
|
self.log(f"[{current_datetime}] 策略: 发送订单失败。")
|
||||||
|
|
||||||
|
indicator_value = RSI(5).get_latest_value(
|
||||||
indicator_value = RSI(5).get_latest_value(np.array(self.get_price_history('close')), None, None, None, None)
|
np.array(self.get_price_history("close")), None, None, None, None
|
||||||
if (not self.use_indicator or 20 < indicator_value < 60):
|
)
|
||||||
|
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)
|
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
||||||
self.log(bar.open, range_1_ago * self.range_factor_s)
|
self.log(open, range_1_ago * self.range_factor_s)
|
||||||
target_buy_price = bar.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)
|
target_buy_price = max(0.01, target_buy_price)
|
||||||
|
|
||||||
self.log(
|
self.log(
|
||||||
f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
f"[{current_datetime}] 开多仓信号 - 当前Open={open:.2f}, "
|
||||||
f"前1Range={range_1_ago:.2f}, "
|
f"前1Range={range_1_ago:.2f}, "
|
||||||
f"计算目标买入价={target_buy_price:.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
|
self.order_id_counter += 1
|
||||||
|
|
||||||
# 创建一个限价多单
|
# 创建一个限价多单
|
||||||
@@ -802,7 +820,7 @@ class SimpleLimitBuyStrategy(Strategy):
|
|||||||
volume=trade_volume,
|
volume=trade_volume,
|
||||||
price_type="LIMIT",
|
price_type="LIMIT",
|
||||||
limit_price=target_buy_price,
|
limit_price=target_buy_price,
|
||||||
submitted_time=bar.datetime,
|
submitted_time=current_datetime,
|
||||||
)
|
)
|
||||||
new_order = self.send_order(order)
|
new_order = self.send_order(order)
|
||||||
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
||||||
|
|||||||
@@ -54,7 +54,7 @@ class Strategy(ABC):
|
|||||||
pass # 默认不执行任何操作,具体策略可覆盖
|
pass # 默认不执行任何操作,具体策略可覆盖
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def on_open_bar(self, bar: "Bar"):
|
def on_open_bar(self, open: float, symbol: str):
|
||||||
"""
|
"""
|
||||||
每当新的K线数据到来时调用此方法。
|
每当新的K线数据到来时调用此方法。
|
||||||
Args:
|
Args:
|
||||||
|
|||||||
@@ -165,7 +165,11 @@ class TqsdkEngine:
|
|||||||
order_to_send.limit_price
|
order_to_send.limit_price
|
||||||
if order_to_send.price_type == "LIMIT"
|
if order_to_send.price_type == "LIMIT"
|
||||||
# else self.quote.bid_price1 + (1 if tqsdk_direction == "BUY" else -1)
|
# 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和状态
|
# 更新原始 Order 对象与 Tqsdk 的订单ID和状态
|
||||||
@@ -358,7 +362,7 @@ class TqsdkEngine:
|
|||||||
|
|
||||||
self.now = now_dt
|
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_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)
|
||||||
@@ -382,11 +386,6 @@ class TqsdkEngine:
|
|||||||
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)
|
||||||
|
|
||||||
if (
|
|
||||||
self.last_processed_bar is None
|
|
||||||
or self.last_processed_bar.datetime != kline_dt
|
|
||||||
):
|
|
||||||
# 创建 core_data.Bar 对象
|
|
||||||
current_bar = Bar(
|
current_bar = Bar(
|
||||||
datetime=kline_dt,
|
datetime=kline_dt,
|
||||||
symbol=self._last_underlying_symbol,
|
symbol=self._last_underlying_symbol,
|
||||||
@@ -399,6 +398,11 @@ class TqsdkEngine:
|
|||||||
close_oi=kline_row.close_oi,
|
close_oi=kline_row.close_oi,
|
||||||
)
|
)
|
||||||
|
|
||||||
|
if (
|
||||||
|
self.last_processed_bar is None
|
||||||
|
or self.last_processed_bar.datetime != kline_dt
|
||||||
|
):
|
||||||
|
|
||||||
# 设置当前 Bar 到 Context
|
# 设置当前 Bar 到 Context
|
||||||
self._context.set_current_bar(current_bar)
|
self._context.set_current_bar(current_bar)
|
||||||
|
|
||||||
@@ -423,19 +427,10 @@ class TqsdkEngine:
|
|||||||
else:
|
else:
|
||||||
self._is_rollover_bar = False
|
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
|
self.last_processed_bar = current_bar
|
||||||
|
|
||||||
|
|
||||||
# 调用策略的 on_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()
|
self._process_queued_requests()
|
||||||
@@ -443,25 +438,13 @@ class TqsdkEngine:
|
|||||||
# 记录投资组合快照
|
# 记录投资组合快照
|
||||||
self._record_portfolio_snapshot(current_bar.datetime)
|
self._record_portfolio_snapshot(current_bar.datetime)
|
||||||
else:
|
else:
|
||||||
# 创建 core_data.Bar 对象
|
self.all_bars.append(current_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.close_list[-1] = current_bar.close
|
self.close_list.append(current_bar.close)
|
||||||
self.open_list[-1] = current_bar.open
|
self.open_list.append(current_bar.open)
|
||||||
self.high_list[-1] = current_bar.high
|
self.high_list.append(current_bar.high)
|
||||||
self.low_list[-1] = current_bar.low
|
self.low_list.append(current_bar.low)
|
||||||
self.volume_list[-1] = current_bar.volume
|
self.volume_list.append(current_bar.volume)
|
||||||
|
|
||||||
self.last_processed_bar = current_bar
|
self.last_processed_bar = current_bar
|
||||||
|
|
||||||
@@ -513,13 +496,13 @@ class TqsdkEngine:
|
|||||||
return self.all_bars
|
return self.all_bars
|
||||||
|
|
||||||
def get_price_history(self, key: str):
|
def get_price_history(self, key: str):
|
||||||
if key == 'close':
|
if key == "close":
|
||||||
return self.close_list
|
return self.close_list
|
||||||
elif key == 'open':
|
elif key == "open":
|
||||||
return self.open_list
|
return self.open_list
|
||||||
elif key == 'high':
|
elif key == "high":
|
||||||
return self.high_list
|
return self.high_list
|
||||||
elif key == 'low':
|
elif key == "low":
|
||||||
return self.low_list
|
return self.low_list
|
||||||
elif key == 'volume':
|
elif key == "volume":
|
||||||
return self.volume_list
|
return self.volume_list
|
||||||
|
|||||||
@@ -112,15 +112,13 @@ class TqsdkEngine:
|
|||||||
self.klines = api.get_kline_serial(
|
self.klines = api.get_kline_serial(
|
||||||
symbol, duration_seconds, data_length=history_length + 2
|
symbol, duration_seconds, data_length=history_length + 2
|
||||||
)
|
)
|
||||||
self.klines_1min = api.get_kline_serial(
|
self.klines_1min = api.get_kline_serial(symbol, 60)
|
||||||
symbol, 60
|
|
||||||
)
|
|
||||||
self.now = None
|
self.now = None
|
||||||
self.quote = None
|
self.quote = None
|
||||||
if roll_over_mode:
|
if roll_over_mode:
|
||||||
self.quote = api.get_quote(symbol)
|
self.quote = api.get_quote(symbol)
|
||||||
|
|
||||||
self.kline_row = None
|
self.partial_bar: Bar = None
|
||||||
|
|
||||||
print("TqsdkEngine: 初始化完成。")
|
print("TqsdkEngine: 初始化完成。")
|
||||||
|
|
||||||
@@ -345,7 +343,12 @@ class TqsdkEngine:
|
|||||||
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
||||||
self.main(kline_row, self.klines.iloc[-i - 1])
|
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._strategy.trading = True
|
||||||
self._last_underlying_symbol = self.quote.underlying_symbol
|
self._last_underlying_symbol = self.quote.underlying_symbol
|
||||||
@@ -358,6 +361,8 @@ class TqsdkEngine:
|
|||||||
if hasattr(self._strategy, "on_init"):
|
if hasattr(self._strategy, "on_init"):
|
||||||
self._strategy.on_init()
|
self._strategy.on_init()
|
||||||
|
|
||||||
|
new_bar = False
|
||||||
|
|
||||||
if is_trading_time:
|
if is_trading_time:
|
||||||
print(f"TqsdkEngine: 当前是交易时间,处理最新一根k线")
|
print(f"TqsdkEngine: 当前是交易时间,处理最新一根k线")
|
||||||
|
|
||||||
@@ -367,10 +372,12 @@ class TqsdkEngine:
|
|||||||
self.kline_row = kline_row
|
self.kline_row = kline_row
|
||||||
|
|
||||||
self.main(self.klines.iloc[-1], self.klines.iloc[-2])
|
self.main(self.klines.iloc[-1], self.klines.iloc[-2])
|
||||||
|
new_bar = True
|
||||||
|
|
||||||
# 迭代 K 线数据
|
# 迭代 K 线数据
|
||||||
# 使用 self._api.get_kline_serial 获取到的 K 线是 Pandas DataFrame,
|
# 使用 self._api.get_kline_serial 获取到的 K 线是 Pandas DataFrame,
|
||||||
# 直接迭代其行(Bar)更符合回测逻辑
|
# 直接迭代其行(Bar)更符合回测逻辑
|
||||||
|
|
||||||
print(f"TqsdkEngine: 开始等待最新数据")
|
print(f"TqsdkEngine: 开始等待最新数据")
|
||||||
while True:
|
while True:
|
||||||
# Tqsdk API 的 wait_update() 确保数据更新
|
# Tqsdk API 的 wait_update() 确保数据更新
|
||||||
@@ -383,15 +390,22 @@ class TqsdkEngine:
|
|||||||
self._last_underlying_symbol = self.quote.underlying_symbol
|
self._last_underlying_symbol = self.quote.underlying_symbol
|
||||||
|
|
||||||
if self._api.is_changing(self.klines_1min.iloc[-1], "datetime"):
|
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 = 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(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:
|
if is_close_bar and new_bar:
|
||||||
print(f'TqsdkEngine: close bar, kline_dt:{kline_dt}, now: {datetime.now()}')
|
print(
|
||||||
|
f"TqsdkEngine: close bar, kline_dt:{kline_dt}, now: {datetime.now()}"
|
||||||
|
)
|
||||||
self.close_bar(kline_row)
|
self.close_bar(kline_row)
|
||||||
|
|
||||||
|
new_bar = False
|
||||||
|
|
||||||
if self._api.is_changing(self.klines.iloc[-1], "datetime"):
|
if self._api.is_changing(self.klines.iloc[-1], "datetime"):
|
||||||
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)
|
||||||
@@ -403,6 +417,8 @@ class TqsdkEngine:
|
|||||||
)
|
)
|
||||||
self.main(kline_row, self.klines.iloc[-2])
|
self.main(kline_row, self.klines.iloc[-2])
|
||||||
|
|
||||||
|
new_bar = True
|
||||||
|
|
||||||
def close_bar(self, kline_row):
|
def close_bar(self, 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)
|
||||||
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
||||||
@@ -419,13 +435,6 @@ class TqsdkEngine:
|
|||||||
open_oi=kline_row.open_oi,
|
open_oi=kline_row.open_oi,
|
||||||
close_oi=kline_row.close_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
|
self.last_processed_bar = current_bar
|
||||||
|
|
||||||
@@ -436,14 +445,13 @@ class TqsdkEngine:
|
|||||||
self._process_queued_requests()
|
self._process_queued_requests()
|
||||||
|
|
||||||
def main(self, kline_row, prev_kline_row):
|
def main(self, kline_row, prev_kline_row):
|
||||||
if True:
|
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
|
||||||
kline_dt = pd.to_datetime(prev_kline_row.datetime, unit="ns", utc=True)
|
|
||||||
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
||||||
if len(self.all_bars) > 0:
|
|
||||||
# 创建 core_data.Bar 对象
|
if self.partial_bar is not None:
|
||||||
current_bar = Bar(
|
last_bar = Bar(
|
||||||
datetime=kline_dt,
|
datetime=pd.to_datetime(prev_kline_row.datetime, unit="ns", utc=True).tz_convert(BEIJING_TZ),
|
||||||
symbol=self._last_underlying_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,
|
||||||
low=prev_kline_row.low,
|
low=prev_kline_row.low,
|
||||||
@@ -452,48 +460,22 @@ class TqsdkEngine:
|
|||||||
open_oi=prev_kline_row.open_oi,
|
open_oi=prev_kline_row.open_oi,
|
||||||
close_oi=prev_kline_row.close_oi,
|
close_oi=prev_kline_row.close_oi,
|
||||||
)
|
)
|
||||||
self.all_bars[-1] = current_bar
|
|
||||||
|
|
||||||
self.close_list[-1] = current_bar.close
|
self.all_bars.append(last_bar)
|
||||||
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
|
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.last_processed_bar = last_bar
|
||||||
# self._strategy.on_close_bar(current_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 (
|
if (
|
||||||
self.roll_over_mode
|
self.roll_over_mode
|
||||||
and self.last_processed_bar is not None
|
and self.last_processed_bar is not None
|
||||||
and self._last_underlying_symbol != self.last_processed_bar.symbol
|
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
|
self._is_rollover_bar = True
|
||||||
print(
|
print(
|
||||||
@@ -507,27 +489,22 @@ class TqsdkEngine:
|
|||||||
self.last_processed_bar.symbol, self._last_underlying_symbol
|
self.last_processed_bar.symbol, self._last_underlying_symbol
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
self._is_rollover_bar = False
|
self._strategy.on_open_bar(kline_row.open, self._last_underlying_symbol)
|
||||||
|
|
||||||
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)
|
|
||||||
|
|
||||||
# 处理订单和取消请求
|
# 处理订单和取消请求
|
||||||
if self._strategy.trading is True:
|
if self._strategy.trading is True:
|
||||||
self._process_queued_requests()
|
self._process_queued_requests()
|
||||||
|
|
||||||
# 记录投资组合快照
|
self.partial_bar = Bar(
|
||||||
self._record_portfolio_snapshot(current_bar.datetime)
|
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):
|
def run(self):
|
||||||
"""
|
"""
|
||||||
|
|||||||
70
test.txt
Normal file
70
test.txt
Normal 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
|
||||||
22003
tqsdk_main2.ipynb
22003
tqsdk_main2.ipynb
File diff suppressed because one or more lines are too long
Reference in New Issue
Block a user