简单波动率策略,实现+网格搜索
This commit is contained in:
183
src/strategies/SimpleLimitBuyStrategy.py
Normal file
183
src/strategies/SimpleLimitBuyStrategy.py
Normal file
@@ -0,0 +1,183 @@
|
||||
# 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=7)
|
||||
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线时间
|
||||
|
||||
# --- 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[0]
|
||||
|
||||
# 计算历史 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.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)}")
|
||||
@@ -1,42 +1,31 @@
|
||||
# src/strategies/base_strategy.py
|
||||
|
||||
from abc import ABC, abstractmethod
|
||||
from typing import Dict, Any, Optional
|
||||
from datetime import datetime
|
||||
from typing import Dict, Any, Optional, List, TYPE_CHECKING
|
||||
|
||||
# 使用 TYPE_CHECKING 避免循环导入,但保留类型提示
|
||||
from ..backtest_context import BacktestContext # 转发引用 BacktestEngine
|
||||
from ..core_data import Bar, Order, Trade # 导入必要的类型
|
||||
|
||||
# 导入核心数据类
|
||||
from ..core_data import Bar, Order, Trade
|
||||
# 导入回测上下文 (注意相对导入路径的变化)
|
||||
from ..backtest_context import BacktestContext
|
||||
|
||||
class Strategy(ABC):
|
||||
"""
|
||||
策略抽象基类。所有具体策略都应继承此类,并实现 on_bar 方法。
|
||||
所有交易策略的抽象基类。
|
||||
策略通过 context 对象与回测引擎和模拟器进行交互,并提供辅助方法。
|
||||
"""
|
||||
def __init__(self, context: BacktestContext, **parameters: Any):
|
||||
"""
|
||||
初始化策略。
|
||||
|
||||
def __init__(self, context: 'BacktestContext', symbol: str, enable_log: bool = True, **params: Any):
|
||||
"""
|
||||
Args:
|
||||
context (BacktestContext): 回测上下文对象,用于与模拟器和数据管理器交互。
|
||||
**parameters (Any): 策略所需的任何自定义参数。
|
||||
context (BacktestEngine): 回测引擎实例,作为策略的上下文,提供与模拟器等的交互接口。
|
||||
symbol (str): 策略操作的合约Symbol。
|
||||
**params (Any): 其他策略特定参数。
|
||||
"""
|
||||
self.context = context
|
||||
self.parameters = parameters
|
||||
self.symbol = parameters.get('symbol', "DEFAULT_SYMBOL") # 策略操作的品种
|
||||
self.trade_volume = parameters.get('trade_volume', 100) # 每次下单的数量
|
||||
print(f"策略初始化: {self.__class__.__name__},参数: {self.parameters}")
|
||||
|
||||
@abstractmethod
|
||||
def on_bar(self, bar: Bar):
|
||||
"""
|
||||
每当接收到新的Bar数据时调用。
|
||||
具体策略逻辑在此方法中实现。
|
||||
|
||||
Args:
|
||||
bar (Bar): 当前的Bar数据对象。
|
||||
features (Dict[str, float]): 由数据处理模块计算并传入的特征字典。
|
||||
"""
|
||||
pass
|
||||
self.context = context # 存储 context 对象
|
||||
self.symbol = symbol # 策略操作的合约Symbol
|
||||
self.params = params
|
||||
self.enable_log = enable_log
|
||||
|
||||
def on_init(self):
|
||||
"""
|
||||
@@ -45,7 +34,7 @@ class Strategy(ABC):
|
||||
"""
|
||||
print(f"{self.__class__.__name__} 策略初始化回调被调用。")
|
||||
|
||||
def on_trade(self, trade: Trade):
|
||||
def on_trade(self, trade: 'Trade'):
|
||||
"""
|
||||
当模拟器成功执行一笔交易时调用。
|
||||
可用于更新策略内部持仓状态或记录交易。
|
||||
@@ -54,15 +43,94 @@ class Strategy(ABC):
|
||||
trade (Trade): 已完成的交易记录。
|
||||
"""
|
||||
# print(f"策略接收到交易: {trade.direction} {trade.volume} {trade.symbol} @ {trade.price:.2f}")
|
||||
pass # 默认不执行任何操作,具体策略可覆盖
|
||||
pass # 默认不执行任何操作,具体策略可覆盖
|
||||
|
||||
def on_order_status(self, order: Order, status: str):
|
||||
@abstractmethod
|
||||
def on_bar(self, bar: 'Bar'):
|
||||
"""
|
||||
当订单状态更新时调用 (例如,未成交,已提交等)。
|
||||
在简易回测中,可能不会频繁使用。
|
||||
|
||||
每当新的K线数据到来时调用此方法。
|
||||
Args:
|
||||
order (Order): 相关订单对象。
|
||||
status (str): 订单状态(例如 "FILLED", "PENDING", "CANCELLED")。
|
||||
bar (Bar): 当前的K线数据对象。
|
||||
next_bar_open (Optional[float]): 下一根K线的开盘价,如果存在的话。
|
||||
"""
|
||||
pass # 默认不执行任何操作
|
||||
pass
|
||||
|
||||
# --- 新增/修改的辅助方法 ---
|
||||
|
||||
def send_order(self, order: 'Order') -> Optional[Trade]:
|
||||
"""
|
||||
发送订单的辅助方法。
|
||||
会在 BaseStrategy 内部构建 Order 对象,并通过 context 转发给模拟器。
|
||||
"""
|
||||
return self.context.send_order(order)
|
||||
|
||||
def cancel_order(self, order_id: str) -> bool:
|
||||
"""
|
||||
取消指定ID的订单。
|
||||
通过 context 调用模拟器的 cancel_order 方法。
|
||||
"""
|
||||
|
||||
return self.context.cancel_order(order_id)
|
||||
|
||||
def cancel_all_pending_orders(self) -> int:
|
||||
"""
|
||||
取消所有当前策略的未决订单。
|
||||
返回成功取消的订单数量。
|
||||
"""
|
||||
pending_orders = self.get_pending_orders() # 调用 BaseStrategy 自己的 get_pending_orders
|
||||
cancelled_count = 0
|
||||
orders_to_cancel = [order.id for order in pending_orders.values() if order.symbol == self.symbol]
|
||||
for order_id in orders_to_cancel:
|
||||
if self.cancel_order(order_id): # 调用 BaseStrategy 自己的 cancel_order
|
||||
cancelled_count += 1
|
||||
return cancelled_count
|
||||
|
||||
def get_current_positions(self) -> Dict[str, int]:
|
||||
"""
|
||||
获取当前持仓。
|
||||
通过 context 调用模拟器的 get_positions 方法。
|
||||
"""
|
||||
return self.context._simulator.get_current_positions()
|
||||
|
||||
def get_pending_orders(self) -> Dict[str, 'Order']:
|
||||
"""
|
||||
获取当前所有待处理订单的副本。
|
||||
通过 context 调用模拟器的 get_pending_orders 方法。
|
||||
"""
|
||||
return self.context._simulator.get_pending_orders()
|
||||
|
||||
def get_average_position_price(self, symbol: str) -> Optional[float]:
|
||||
"""
|
||||
获取指定合约的平均持仓成本。
|
||||
通过 context 调用模拟器的 get_average_position_price 方法。
|
||||
"""
|
||||
return self.context._simulator.get_average_position_price(symbol)
|
||||
|
||||
# 你可以根据需要在这里添加更多辅助方法,如获取账户净值等
|
||||
def get_account_cash(self) -> float:
|
||||
"""获取当前账户现金余额。"""
|
||||
return self.context._simulator.cash
|
||||
|
||||
|
||||
def log(self, *args: Any, **kwargs: Any):
|
||||
"""
|
||||
统一的日志打印方法。
|
||||
如果 enable_log 为 True,则打印消息到控制台,并包含当前模拟时间。
|
||||
支持传入多个参数,像 print() 函数一样使用。
|
||||
"""
|
||||
if self.enable_log:
|
||||
# 尝试获取当前模拟时间,如果模拟器或时间不可用,则跳过时间前缀
|
||||
try:
|
||||
current_time_str = self.context._simulator.get_current_time().strftime('%Y-%m-%d %H:%M:%S')
|
||||
time_prefix = f"[{current_time_str}] "
|
||||
except AttributeError:
|
||||
# 如果获取不到时间(例如在策略初始化时,模拟器时间还未设置),则不加时间前缀
|
||||
time_prefix = ""
|
||||
|
||||
# 使用 f-string 结合 *args 来构建消息
|
||||
# print() 函数会将 *args 自动用空格分隔,这里我们模仿这个行为
|
||||
message = ' '.join(map(str, args))
|
||||
|
||||
# 你可以将其他 kwargs (如 sep, end, file, flush) 传递给 print,
|
||||
# 但通常日志方法不会频繁使用这些。这里只支持最基础的打印。
|
||||
print(f"{time_prefix}策略 ({self.symbol}): {message}", **kwargs)
|
||||
@@ -19,9 +19,10 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
|
||||
def __init__(self, context: Any, **parameters: Any): # context 类型提示可以为 BacktestContext
|
||||
super().__init__(context, **parameters)
|
||||
self.trade_volume = 1
|
||||
self.order_id_counter = 0
|
||||
self.limit_price_factor = parameters.get('limit_price_factor', 0.999) # 限价因子
|
||||
self.max_position = parameters.get('max_position', self.trade_volume * 2) # 最大持仓量
|
||||
self.max_position = parameters.get('max_position', self.trade_volume) # 最大持仓量
|
||||
|
||||
self._last_order_id: Optional[str] = None # 跟踪上一根Bar发出的订单ID
|
||||
self._current_long_position: int = 0 # 策略内部维护的当前持仓
|
||||
@@ -57,16 +58,14 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
每接收到一根Bar时,执行策略逻辑。
|
||||
"""
|
||||
current_portfolio_value = self.context.get_current_portfolio_value(bar)
|
||||
print(f"[{bar.datetime}] Strategy processing Bar. Current close price: {bar.close:.2f}. Current Portfolio Value: {current_portfolio_value:.2f}")
|
||||
# print(f"[{bar.datetime}] Strategy processing Bar. Current close price: {bar.close:.2f}. Current Portfolio Value: {current_portfolio_value:.2f}")
|
||||
|
||||
# 1. 撤销上一根K线未成交的订单
|
||||
if self._last_order_id:
|
||||
# 检查这个订单是否仍然在待处理订单列表中
|
||||
pending_orders = self.context._simulator.get_pending_orders() # 直接访问模拟器,或者通过context提供接口
|
||||
pending_orders = self.get_pending_orders() # 直接访问模拟器,或者通过context提供接口
|
||||
if self._last_order_id in pending_orders:
|
||||
success = self.context.send_order(Order(id=self._last_order_id, symbol=self.symbol,
|
||||
direction="CANCEL", volume=0,
|
||||
price_type="CANCEL")) # 使用一个特殊Order类型表示撤单
|
||||
success = self.cancel_order(self._last_order_id)
|
||||
# 这里发送的“撤单订单”会被simulator的send_order处理,并调用simulator.cancel_order
|
||||
if success: # simulator.send_order返回Trade或None,这里我们用一个特殊处理
|
||||
# Simulator的send_order返回的是Trade,如果实现撤单,最好Simulator的cancel_order返回bool
|
||||
@@ -109,10 +108,10 @@ class SimpleLimitBuyStrategy(Strategy):
|
||||
)
|
||||
|
||||
# 通过上下文发送订单
|
||||
trade = self.context.send_order(order)
|
||||
trade = self.send_order(order)
|
||||
if trade:
|
||||
print(
|
||||
f"[{bar.datetime}] 策略: 发送并立即成交限价买单 {trade.volume} 股 @ {trade.price:.2f} (订单ID: {order.id})")
|
||||
f"[{bar.datetime}] 策略: 发送并立即成交限价买单 {trade.volume} 股 @ {trade.price:.2f}(open:{bar.open}, close:{bar.close}) (订单ID: {order.id})")
|
||||
# 如果立即成交,_last_order_id 仍然保持 None
|
||||
else:
|
||||
# 如果未立即成交,将订单ID记录下来,以便下一根Bar撤销
|
||||
|
||||
Reference in New Issue
Block a user