Files
NewQuant/src/strategies/SimpleLimitBuyStrategy.py
2025-06-23 22:21:59 +08:00

195 lines
9.7 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
# 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
self._bar_history: deque[Bar] = deque(maxlen=10)
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线时间
self.symbol = bar.symbol
# --- 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]
bar_7_ago = self._bar_history[-8]
# 计算历史 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:
self._last_order_id = new_order.id
self.log(f"[{current_datetime}] 策略: 发送限价买入订单 {self._last_order_id} @ {target_buy_price:.2f}")
else:
self.log(f"[{current_datetime}] 策略: 发送订单失败。")
# else:
# 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准备新合约交易。")