修复未来函数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线数据。
|
||||
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
788
main2.ipynb
788
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
|
||||
|
||||
# 导入 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,
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -105,14 +105,7 @@ class BacktestEngine:
|
||||
current_bar = self.data_manager.get_next_bar()
|
||||
|
||||
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)
|
||||
break # 没有更多数据,回测结束
|
||||
|
||||
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)
|
||||
@@ -178,7 +164,7 @@ class BacktestEngine:
|
||||
high_array = np.array(self.high_list)
|
||||
low_array = np.array(self.low_list)
|
||||
volume_array = np.array(self.volume_list)
|
||||
|
||||
|
||||
for indicator in self.indicators:
|
||||
current_indicator_dict[indicator.get_name()] = indicator.get_latest_value(
|
||||
close_array,
|
||||
@@ -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
|
||||
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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} 失败!"
|
||||
|
||||
@@ -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):
|
||||
|
||||
@@ -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)
|
||||
]
|
||||
|
||||
@@ -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,
|
||||
close: np.array,
|
||||
open: np.array, # 不使用
|
||||
high: np.array, # 不使用
|
||||
low: np.array, # 不使用
|
||||
volume: np.array) -> np.array: # 不使用
|
||||
def get_values(
|
||||
self,
|
||||
close: np.array,
|
||||
open: np.array, # 不使用
|
||||
high: np.array, # 不使用
|
||||
low: np.array, # 不使用
|
||||
volume: np.array,
|
||||
) -> np.array: # 不使用
|
||||
"""
|
||||
根据收盘价列表计算RSI值,使用 TA-Lib。
|
||||
Args:
|
||||
@@ -39,10 +43,9 @@ class RSI(Indicator):
|
||||
|
||||
# 将 numpy 数组转换为 list 并返回
|
||||
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,
|
||||
close: np.array, # 不使用
|
||||
open: np.array, # 不使用
|
||||
high: np.array,
|
||||
low: np.array,
|
||||
volume: np.array) -> np.array: # 不使用
|
||||
def get_values(
|
||||
self,
|
||||
close: np.array, # 不使用
|
||||
open: np.array, # 不使用
|
||||
high: np.array,
|
||||
low: 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}"
|
||||
|
||||
@@ -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)
|
||||
@@ -723,35 +738,31 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
# --- 4. 开仓逻辑 (只考虑做多 BUY 方向) ---
|
||||
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
||||
# 且K线历史足够长时才考虑开仓
|
||||
|
||||
if (
|
||||
current_pos_volume == 0
|
||||
and range_1_ago is not 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 (not self.use_indicator or 10 < indicator_value < 25):
|
||||
if current_pos_volume == 0 and range_1_ago is not 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")),
|
||||
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线开始时进行撤销
|
||||
|
||||
@@ -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:
|
||||
|
||||
@@ -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,22 +386,22 @@ class TqsdkEngine:
|
||||
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,
|
||||
)
|
||||
|
||||
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,
|
||||
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)
|
||||
@@ -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
|
||||
|
||||
@@ -511,15 +494,15 @@ class TqsdkEngine:
|
||||
|
||||
def get_bar_history(self):
|
||||
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
|
||||
|
||||
@@ -66,7 +66,7 @@ class TqsdkEngine:
|
||||
self.roll_over_mode = roll_over_mode
|
||||
self.history_length = history_length
|
||||
self.close_bar_delta = close_bar_delta
|
||||
|
||||
|
||||
self.next_close_time = None
|
||||
|
||||
# Tqsdk API 和模拟器
|
||||
@@ -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: 初始化完成。")
|
||||
|
||||
@@ -338,14 +336,19 @@ class TqsdkEngine:
|
||||
self._strategy.trading = False
|
||||
|
||||
is_trading_time = is_futures_trading_time()
|
||||
|
||||
|
||||
for i in range(self.history_length + 1, 0 if not is_trading_time else 1, -1):
|
||||
kline_row = self.klines.iloc[-i]
|
||||
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
|
||||
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
|
||||
@@ -357,7 +360,9 @@ class TqsdkEngine:
|
||||
# 初始化策略 (如果策略有 on_init 方法)
|
||||
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,14 +390,21 @@ class TqsdkEngine:
|
||||
self._last_underlying_symbol = self.quote.underlying_symbol
|
||||
|
||||
if self._api.is_changing(self.klines_1min.iloc[-1], "datetime"):
|
||||
kline_dt = pd.to_datetime(self.kline_row.datetime, unit="ns", utc=True)
|
||||
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
||||
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()}')
|
||||
self.close_bar(kline_row)
|
||||
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]
|
||||
@@ -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,64 +445,37 @@ 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 = 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,
|
||||
open=prev_kline_row.open,
|
||||
high=prev_kline_row.high,
|
||||
low=prev_kline_row.low,
|
||||
close=prev_kline_row.close,
|
||||
volume=prev_kline_row.volume,
|
||||
open_oi=prev_kline_row.open_oi,
|
||||
close_oi=prev_kline_row.close_oi,
|
||||
)
|
||||
self.all_bars[-1] = current_bar
|
||||
kline_dt = pd.to_datetime(kline_row.datetime, unit="ns", utc=True)
|
||||
kline_dt = kline_dt.tz_convert(BEIJING_TZ)
|
||||
|
||||
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
|
||||
|
||||
# if self._strategy.trading is True:
|
||||
# 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,
|
||||
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,
|
||||
close=prev_kline_row.close,
|
||||
volume=prev_kline_row.volume,
|
||||
open_oi=prev_kline_row.open_oi,
|
||||
close_oi=prev_kline_row.close_oi,
|
||||
)
|
||||
|
||||
# 设置当前 Bar 到 Context
|
||||
self._context.set_current_bar(current_bar)
|
||||
self.all_bars.append(last_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)
|
||||
|
||||
self.last_processed_bar = last_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._strategy.on_open_bar(kline_row.open, self._last_underlying_symbol)
|
||||
# 处理订单和取消请求
|
||||
if self._strategy.trading is True:
|
||||
self._process_queued_requests()
|
||||
|
||||
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:
|
||||
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):
|
||||
"""
|
||||
|
||||
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