2025-06-22 23:03:50 +08:00
|
|
|
|
# src/strategies/simple_limit_buy_strategy.py
|
|
|
|
|
|
|
|
|
|
|
|
from .base_strategy import Strategy
|
|
|
|
|
|
from ..core_data import Bar, Order
|
|
|
|
|
|
from typing import Optional, Dict, Any
|
|
|
|
|
|
from collections import deque
|
|
|
|
|
|
|
|
|
|
|
|
class SimpleLimitBuyStrategy(Strategy):
|
|
|
|
|
|
"""
|
|
|
|
|
|
一个基于当前K线Open、前1根和前7根K线Range计算优势价格进行限价买入的策略。
|
|
|
|
|
|
具备以下特点:
|
|
|
|
|
|
- 每根K线开始时取消上一根K线未成交的订单。
|
|
|
|
|
|
- 最多只能有一个开仓挂单和一个持仓。
|
|
|
|
|
|
- 包含简单的止损和止盈逻辑。
|
|
|
|
|
|
"""
|
|
|
|
|
|
def __init__(self, simulator: Any, symbol: str, enable_log: bool, trade_volume: int,
|
|
|
|
|
|
open_range_factor_1_ago: float,
|
|
|
|
|
|
open_range_factor_7_ago: float,
|
|
|
|
|
|
max_position: int,
|
|
|
|
|
|
stop_loss_points: float = 10, # 新增:止损点数
|
|
|
|
|
|
take_profit_points: float = 10): # 新增:止盈点数
|
|
|
|
|
|
"""
|
|
|
|
|
|
初始化策略。
|
|
|
|
|
|
Args:
|
|
|
|
|
|
simulator: 模拟器实例。
|
|
|
|
|
|
symbol (str): 交易合约代码。
|
|
|
|
|
|
trade_volume (int): 单笔交易量。
|
|
|
|
|
|
open_range_factor_1_ago (float): 前1根K线Range的权重因子,用于从Open价向下偏移。
|
|
|
|
|
|
open_range_factor_7_ago (float): 前7根K线Range的权重因子,用于从Open价向下偏移。
|
|
|
|
|
|
max_position (int): 最大持仓量(此处为1,因为只允许一个持仓)。
|
|
|
|
|
|
stop_loss_points (float): 止损点数(例如,亏损达到此点数则止损)。
|
|
|
|
|
|
take_profit_points (float): 止盈点数(例如,盈利达到此点数则止盈)。
|
|
|
|
|
|
"""
|
|
|
|
|
|
super().__init__(simulator, symbol, enable_log)
|
|
|
|
|
|
self.trade_volume = trade_volume
|
|
|
|
|
|
self.open_range_factor_1_ago = open_range_factor_1_ago
|
|
|
|
|
|
self.open_range_factor_7_ago = open_range_factor_7_ago
|
|
|
|
|
|
self.max_position = max_position # 理论上这里应为1
|
|
|
|
|
|
self.stop_loss_points = stop_loss_points
|
|
|
|
|
|
self.take_profit_points = take_profit_points
|
|
|
|
|
|
|
|
|
|
|
|
self.order_id_counter = 0
|
|
|
|
|
|
|
2025-06-23 22:21:59 +08:00
|
|
|
|
self._bar_history: deque[Bar] = deque(maxlen=10)
|
2025-06-22 23:03:50 +08:00
|
|
|
|
self._last_order_id: Optional[str] = None # 用于跟踪上一根K线发出的订单ID
|
|
|
|
|
|
|
|
|
|
|
|
self.log(f"策略初始化: symbol={self.symbol}, trade_volume={self.trade_volume}, "
|
|
|
|
|
|
f"open_range_factor_1_ago={self.open_range_factor_1_ago}, "
|
|
|
|
|
|
f"open_range_factor_7_ago={self.open_range_factor_7_ago}, "
|
|
|
|
|
|
f"max_position={self.max_position}, "
|
|
|
|
|
|
f"止损点={self.stop_loss_points}, 止盈点={self.take_profit_points}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_bar(self, bar: Bar, next_bar_open: Optional[float] = None):
|
|
|
|
|
|
"""
|
|
|
|
|
|
每当新的K线数据到来时调用。
|
|
|
|
|
|
Args:
|
|
|
|
|
|
bar (Bar): 当前的K线数据对象。
|
|
|
|
|
|
next_bar_open (Optional[float]): 下一根K线的开盘价,此处策略未使用。
|
|
|
|
|
|
"""
|
|
|
|
|
|
current_datetime = bar.datetime # 获取当前K线时间
|
2025-06-23 22:21:59 +08:00
|
|
|
|
self.symbol = bar.symbol
|
2025-06-22 23:03:50 +08:00
|
|
|
|
|
|
|
|
|
|
# --- 1. 撤销上一根K线未成交的订单 ---
|
|
|
|
|
|
# 检查是否记录了上一笔订单ID,并且该订单仍然在待处理列表中
|
|
|
|
|
|
if self._last_order_id:
|
|
|
|
|
|
pending_orders = self.get_pending_orders()
|
|
|
|
|
|
if self._last_order_id in pending_orders:
|
|
|
|
|
|
success = self.cancel_order(self._last_order_id) # 直接调用基类的取消方法
|
|
|
|
|
|
if success:
|
|
|
|
|
|
self.log(f"[{current_datetime}] 策略: 成功撤销上一根K线未成交订单 {self._last_order_id}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.log(f"[{current_datetime}] 策略: 尝试撤销订单 {self._last_order_id} 失败(可能已成交或不存在)")
|
|
|
|
|
|
# 无论撤销成功与否,既然我们尝试了撤销,就清除记录
|
|
|
|
|
|
self._last_order_id = None
|
|
|
|
|
|
# else:
|
|
|
|
|
|
# self.log(f"[{current_datetime}] 策略: 无上一根K线未成交订单需要撤销。")
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# 2. 更新K线历史
|
|
|
|
|
|
self._bar_history.append(bar)
|
|
|
|
|
|
trade_volume = self.trade_volume
|
|
|
|
|
|
|
|
|
|
|
|
# 获取当前持仓和未决订单(在取消之后获取,确保是最新的状态)
|
|
|
|
|
|
current_positions = self.get_current_positions()
|
|
|
|
|
|
current_pos_volume = current_positions.get(self.symbol, 0)
|
|
|
|
|
|
pending_orders_after_cancel = self.get_pending_orders() # 再次获取,此时应已取消旧订单
|
|
|
|
|
|
|
|
|
|
|
|
# --- 3. 平仓逻辑 (止损/止盈) ---
|
|
|
|
|
|
# 只有当有持仓时才考虑平仓
|
|
|
|
|
|
if current_pos_volume > 0: # 假设只做多,所以持仓量 > 0
|
|
|
|
|
|
avg_entry_price = self.get_average_position_price(self.symbol)
|
|
|
|
|
|
if avg_entry_price is not None:
|
|
|
|
|
|
pnl_per_unit = bar.close - avg_entry_price # 当前浮动盈亏(以收盘价计算)
|
|
|
|
|
|
|
|
|
|
|
|
# 止盈条件
|
|
|
|
|
|
if pnl_per_unit >= self.take_profit_points:
|
|
|
|
|
|
self.log(f"[{current_datetime}] 止盈信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {self.take_profit_points:.2f}")
|
|
|
|
|
|
|
|
|
|
|
|
order_id = f"{self.symbol}_BUY_{bar.datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
|
|
|
|
|
self.order_id_counter += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 创建一个限价多单
|
|
|
|
|
|
order = Order(
|
|
|
|
|
|
id=order_id,
|
|
|
|
|
|
symbol=self.symbol,
|
|
|
|
|
|
direction="CLOSE_LONG",
|
|
|
|
|
|
volume=trade_volume,
|
|
|
|
|
|
price_type="MARKET",
|
|
|
|
|
|
# limit_price=limit_price,
|
|
|
|
|
|
submitted_time=bar.datetime
|
|
|
|
|
|
)
|
|
|
|
|
|
trade = self.send_order(order)
|
|
|
|
|
|
return # 平仓后本K线不再进行开仓判断
|
|
|
|
|
|
|
|
|
|
|
|
# 止损条件
|
|
|
|
|
|
elif pnl_per_unit <= -self.stop_loss_points:
|
|
|
|
|
|
self.log(f"[{current_datetime}] 止损信号 - PnL per unit: {pnl_per_unit:.2f}, 目标: {-self.stop_loss_points:.2f}")
|
|
|
|
|
|
# 发送市价卖出订单平仓,确保立即成交
|
|
|
|
|
|
order_id = f"{self.symbol}_BUY_{bar.datetime.strftime('%Y%m%d%H%M%S')}_{self.order_id_counter}"
|
|
|
|
|
|
self.order_id_counter += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 创建一个限价多单
|
|
|
|
|
|
order = Order(
|
|
|
|
|
|
id=order_id,
|
|
|
|
|
|
symbol=self.symbol,
|
|
|
|
|
|
direction="CLOSE_LONG",
|
|
|
|
|
|
volume=trade_volume,
|
|
|
|
|
|
price_type="MARKET",
|
|
|
|
|
|
# limit_price=limit_price,
|
|
|
|
|
|
submitted_time=bar.datetime
|
|
|
|
|
|
)
|
|
|
|
|
|
trade = self.send_order(order)
|
|
|
|
|
|
return # 平仓后本K线不再进行开仓判断
|
|
|
|
|
|
|
|
|
|
|
|
# --- 4. 开仓逻辑 (只考虑做多 BUY 方向) ---
|
|
|
|
|
|
# 只有在没有持仓 (current_pos_volume == 0) 且没有待处理订单 (not pending_orders_after_cancel)
|
|
|
|
|
|
# 且K线历史足够长时才考虑开仓
|
|
|
|
|
|
if current_pos_volume == 0 and \
|
|
|
|
|
|
len(self._bar_history) == self._bar_history.maxlen:
|
|
|
|
|
|
|
|
|
|
|
|
# 获取前1根K线 (倒数第二根) 和前7根K线 (队列中最老的一根)
|
|
|
|
|
|
bar_1_ago = self._bar_history[-2]
|
2025-06-23 22:21:59 +08:00
|
|
|
|
bar_7_ago = self._bar_history[-8]
|
2025-06-22 23:03:50 +08:00
|
|
|
|
|
|
|
|
|
|
# 计算历史 K 线的 Range
|
|
|
|
|
|
range_1_ago = bar_1_ago.high - bar_1_ago.low
|
|
|
|
|
|
range_7_ago = bar_7_ago.high - bar_7_ago.low
|
|
|
|
|
|
|
|
|
|
|
|
# 根据策略逻辑计算目标买入价格
|
|
|
|
|
|
# 目标买入价 = 当前K线Open - (前1根Range * 因子1 + 前7根Range * 因子2)
|
|
|
|
|
|
self.log(bar.open ,range_1_ago * self.open_range_factor_1_ago, range_7_ago * self.open_range_factor_7_ago)
|
|
|
|
|
|
target_buy_price = bar.open - (range_1_ago * self.open_range_factor_1_ago + range_7_ago * self.open_range_factor_7_ago)
|
|
|
|
|
|
|
|
|
|
|
|
# 确保目标买入价格有效,例如不能是负数
|
|
|
|
|
|
target_buy_price = max(0.01, target_buy_price)
|
|
|
|
|
|
|
|
|
|
|
|
self.log(f"[{current_datetime}] 开多仓信号 - 当前Open={bar.open:.2f}, "
|
|
|
|
|
|
f"前1Range={range_1_ago:.2f}, 前7Range={range_7_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}"
|
|
|
|
|
|
self.order_id_counter += 1
|
|
|
|
|
|
|
|
|
|
|
|
# 创建一个限价多单
|
|
|
|
|
|
order = Order(
|
|
|
|
|
|
id=order_id,
|
|
|
|
|
|
symbol=self.symbol,
|
|
|
|
|
|
direction="BUY",
|
|
|
|
|
|
volume=trade_volume,
|
|
|
|
|
|
price_type="LIMIT",
|
|
|
|
|
|
limit_price=target_buy_price,
|
|
|
|
|
|
submitted_time=bar.datetime
|
|
|
|
|
|
)
|
|
|
|
|
|
new_order = self.send_order(order)
|
|
|
|
|
|
# 记录下这个订单的ID,以便在下一根K线开始时进行撤销
|
|
|
|
|
|
if new_order:
|
2025-06-23 22:21:59 +08:00
|
|
|
|
self._last_order_id = new_order.id
|
2025-06-22 23:03:50 +08:00
|
|
|
|
self.log(f"[{current_datetime}] 策略: 发送限价买入订单 {self._last_order_id} @ {target_buy_price:.2f}")
|
|
|
|
|
|
else:
|
|
|
|
|
|
self.log(f"[{current_datetime}] 策略: 发送订单失败。")
|
|
|
|
|
|
|
|
|
|
|
|
# else:
|
2025-06-23 22:21:59 +08:00
|
|
|
|
# self.log(f"[{current_datetime}] 不满足开仓条件:持仓={current_pos_volume}, 待处理订单={len(pending_orders_after_cancel)}, K线历史长度={len(self._bar_history)}")
|
|
|
|
|
|
|
|
|
|
|
|
def on_rollover(self, old_symbol: str, new_symbol: str):
|
|
|
|
|
|
"""
|
|
|
|
|
|
在合约换月时清空历史K线数据和上次订单ID,避免使用旧合约数据进行计算。
|
|
|
|
|
|
"""
|
|
|
|
|
|
super().on_rollover(old_symbol, new_symbol) # 调用基类方法打印日志
|
|
|
|
|
|
self._bar_history.clear() # 清空历史K线
|
|
|
|
|
|
self._last_order_id = None # 清空上次订单ID,因为旧合约订单已取消
|
|
|
|
|
|
|
|
|
|
|
|
self.log(f"换月完成,清空历史K线数据和上次订单ID,准备新合约交易。")
|
|
|
|
|
|
|