162 lines
5.7 KiB
Python
162 lines
5.7 KiB
Python
#!/usr/bin/env python
|
|
# -*- coding: utf-8 -*-
|
|
"""
|
|
纯突破量化策略
|
|
N日新高放量突破买入,严格止损止盈
|
|
"""
|
|
import pandas as pd
|
|
import numpy as np
|
|
from vnpy.app.cta_strategy import CtaTemplate
|
|
from vnpy.trader.object import BarData
|
|
|
|
|
|
class PureBreakoutStrategy(CtaTemplate):
|
|
"""
|
|
纯突破策略
|
|
- N日新高放量突破买入
|
|
- 止损:跌破突破日最低价的-5%
|
|
- 跟踪止盈:从买入后高点回落10%止盈
|
|
- 均线止盈:跌破均线卖出
|
|
- 最长持有到期自动卖出
|
|
- 单票最大仓位5%
|
|
"""
|
|
|
|
author = "翼德"
|
|
parameters = [
|
|
"breakout_days",
|
|
"volume_multiple",
|
|
"stop_loss_pct",
|
|
"trailing_stop_pct",
|
|
"ma_period",
|
|
"max_holding_days",
|
|
"max_position_pct",
|
|
]
|
|
|
|
variables = [
|
|
]
|
|
|
|
def __init__(self, cta_engine, strategy_name, setting_dict):
|
|
super().__init__(cta_engine, strategy_name, setting_dict)
|
|
|
|
# 默认参数
|
|
self.breakout_days = getattr(self, 'breakout_days', 60)
|
|
self.volume_multiple = getattr(self, 'volume_multiple', 1.5)
|
|
self.stop_loss_pct = getattr(self, 'stop_loss_pct', 0.05)
|
|
self.trailing_stop_pct = getattr(self, 'trailing_stop_pct', 0.10)
|
|
self.ma_period = getattr(self, 'ma_period', 20)
|
|
self.max_holding_days = getattr(self, 'max_holding_days', 60)
|
|
self.max_position_pct = getattr(self, 'max_position_pct', 0.05)
|
|
|
|
# 持仓信息
|
|
self.in_market = False
|
|
self.entry_price = 0
|
|
self.breakout_low = 0
|
|
self.highest_price = 0
|
|
self.entry_date = None
|
|
self.holding_days = 0
|
|
|
|
def on_init(self):
|
|
self.write_log("策略初始化完成")
|
|
self.load_bar(self.breakout_days + self.max_holding_days)
|
|
|
|
def on_start(self):
|
|
self.write_log("策略启动")
|
|
|
|
def on_stop(self):
|
|
self.write_log("策略停止")
|
|
|
|
def on_bar(self, bar: BarData):
|
|
if self.in_market:
|
|
self.holding_days += 1
|
|
|
|
# 更新最高价
|
|
if bar.close > self.highest_price:
|
|
self.highest_price = bar.close
|
|
|
|
# 检查卖出条件
|
|
exit_signal = False
|
|
|
|
# 1. 止损检查 - 跌破突破低价*(1-stop_loss_pct)
|
|
stop_price = self.breakout_low * (1 - self.stop_loss_pct)
|
|
if bar.low <= stop_price:
|
|
exit_signal = True
|
|
self.write_log(f"触发止损,价格{bar.low:.2f} <= 止损价{stop_price:.2f}")
|
|
|
|
# 2. 跟踪止盈 - 从最高点回落超过trailing_stop_pct
|
|
if not exit_signal:
|
|
trailing_price = self.highest_price * (1 - self.trailing_stop_pct)
|
|
if bar.close <= trailing_price:
|
|
exit_signal = True
|
|
self.write_log(f"触发跟踪止盈,价格{bar.close:.2f} <= 止盈价{trailing_price:.2f}")
|
|
|
|
# 3. 均线止盈 - 收盘价跌破均线
|
|
if not exit_signal:
|
|
closes = self.get_bars(self.ma_period)
|
|
if len(closes) >= self.ma_period:
|
|
ma = np.mean([b.close for b in closes])
|
|
if bar.close < ma:
|
|
exit_signal = True
|
|
self.write_log(f"触发均线止盈,价格{bar.close:.2f} < MA{self.ma_period}={ma:.2f}")
|
|
|
|
# 4. 持有到期
|
|
if not exit_signal and self.holding_days >= self.max_holding_days:
|
|
exit_signal = True
|
|
self.write_log(f"持有到期{self.holding_days}天,自动卖出")
|
|
|
|
if exit_signal:
|
|
# 全部卖出
|
|
position = self.get_position(self.vt_symbol)
|
|
if position and position.volume > 0:
|
|
self.sell(self.vt_symbol, bar.close, position.volume)
|
|
self.in_market = False
|
|
return
|
|
|
|
# 如果没持仓,检查突破信号
|
|
else:
|
|
# 获取最近N日数据
|
|
bars = self.get_bars(self.breakout_days + 1)
|
|
if len(bars) < self.breakout_days + 1:
|
|
return
|
|
|
|
# 计算N日最高价
|
|
highest = max(b.close for b in bars[:-1])
|
|
current_close = bar.close
|
|
current_volume = bar.volume
|
|
|
|
# 计算N日平均成交量
|
|
avg_volume = np.mean(b.volume for b in bars[:-1])
|
|
|
|
# 突破条件:收盘价创新高 + 成交量放量
|
|
if current_close > highest and current_volume >= avg_volume * self.volume_multiple:
|
|
# 突破买入
|
|
# 计算目标仓位
|
|
target_value = self.balance * self.max_position_pct
|
|
target_volume = int(target_value / bar.open / 100) * 100
|
|
|
|
if target_volume > 0:
|
|
self.buy(self.vt_symbol, bar.open, target_volume)
|
|
self.in_market = True
|
|
self.entry_price = bar.open
|
|
self.breakout_low = bar.low
|
|
self.highest_price = bar.close
|
|
self.entry_date = bar.datetime
|
|
self.holding_days = 0
|
|
self.write_log(f"突破买入,价格{bar.open:.2f},数量{target_volume}")
|
|
|
|
return
|
|
|
|
def _need_rebalance(self, current_date):
|
|
if self.last_rebalance_date is None:
|
|
return True
|
|
|
|
if self.rebalance_freq == 'M':
|
|
if current_date.month != self.last_rebalance_date.month:
|
|
return True
|
|
elif self.rebalance_freq == 'W':
|
|
current_week = current_date.isocalendar()[1]
|
|
last_week = self.last_rebalance_date.isocalendar()[1]
|
|
if current_week != last_week:
|
|
return True
|
|
|
|
return False
|