每周回撤通道策略
概述
每周回撤通道策略复现了 MetaTrader 4 专家顾问 2_Otkat_Sys_v1_1 的核心思想。系统寻找前一次收盘价与 24 根 K 线之前的开盘价之间的大幅差距,当差距超过设定的通道阈值并且满足指定的交易日条件时,会在新交易日的最初几分钟入场。策略会设置止损/止盈,并在交易日结束前强制平仓。
交易逻辑
- 数据准备
- 默认使用 1 分钟 K 线,亦可自定义其他周期。
- 保存上一根 K 线的收盘价,并使用循环缓冲区记录 24 根之前的开盘价。
- 信号判定
- 在指定的交易日(MetaTrader 编号:
0 = 周日,6 = 周六),当本地时间处于 00:00 至 00:03 之间时评估信号。 - 计算历史开盘价(24 根之前)与最新收盘价之间的差值;当差值超过通道阈值时发出市价单:
- 多头:历史开盘价减去上一收盘价大于阈值。
- 空头:上一收盘价减去历史开盘价大于阈值。
- 每个交易日最多开仓一次。
- 在指定的交易日(MetaTrader 编号:
- 仓位管理
- 止损、止盈以点值表示,根据合约最小报价单位转换成价格偏移。
- 多头止盈额外增加原 MT4 程序中的 3 个点距离。
- 持仓期间持续监控 K 线高低点,若触发止损或止盈则以市价平仓。
- 若到本地时间 22:45 仍有持仓,将立即平仓,以模拟原始程序的收盘平仓规则。
参数
| 名称 | 说明 | 默认值 |
|---|---|---|
TakeProfitPoints |
止盈点数,多头会额外增加 3 个点。 | 5 |
StopLossPoints |
止损点数。 | 49 |
TradeVolume |
市价单下单手数,将根据合约最小手数自动对齐。 | 1 |
CorridorPoints |
历史开盘价与最新收盘价之间所需的最小差值。 | 10 |
TradeDayOfWeek |
交易日(MetaTrader 编号,0 = 周日 … 6 = 周六)。 |
5(周五) |
CandleType |
用于分析的 K 线类型。 | 1 分钟 |
说明
- 策略仅使用已完成的 K 线,符合项目对数据处理的要求。
- 需要确保标的具备足够的历史数据,以建立 24 根 K 线的缓冲区后才可能产生信号。
- 建议根据标的的最小报价单位、最小手数和交易时间调整各项参数。
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
public class WeeklyReboundCorridorStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<decimal> _oversold;
private readonly StrategyParam<decimal> _overbought;
private readonly StrategyParam<int> _cooldownCandles;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevRsi;
private bool _hasPrev;
private int _cooldownRemaining;
public int RsiPeriod { get => _rsiPeriod.Value; set => _rsiPeriod.Value = value; }
public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
public decimal Oversold { get => _oversold.Value; set => _oversold.Value = value; }
public decimal Overbought { get => _overbought.Value; set => _overbought.Value = value; }
public int CooldownCandles { get => _cooldownCandles.Value; set => _cooldownCandles.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public WeeklyReboundCorridorStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14).SetDisplay("RSI Period", "RSI lookback", "Indicators");
_emaPeriod = Param(nameof(EmaPeriod), 50).SetDisplay("EMA Period", "EMA filter", "Indicators");
_oversold = Param(nameof(Oversold), 25m).SetDisplay("Oversold", "RSI oversold level", "Levels");
_overbought = Param(nameof(Overbought), 75m).SetDisplay("Overbought", "RSI overbought level", "Levels");
_cooldownCandles = Param(nameof(CooldownCandles), 50).SetDisplay("Cooldown", "Candles between signals", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame()).SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevRsi = default;
_hasPrev = default;
_cooldownRemaining = default;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = 0;
_hasPrev = false;
_cooldownRemaining = 0;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
var ema = new ExponentialMovingAverage { Length = EmaPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(rsi, ema, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal rsi, decimal ema)
{
if (candle.State != CandleStates.Finished) return;
var close = candle.ClosePrice;
if (!_hasPrev) { _prevRsi = rsi; _hasPrev = true; return; }
if (_cooldownRemaining > 0)
{
_cooldownRemaining--;
_prevRsi = rsi;
return;
}
var oversold = Oversold;
var overbought = Overbought;
if (_prevRsi >= oversold && rsi < oversold && Position <= 0)
{
if (Position < 0) BuyMarket();
BuyMarket();
_cooldownRemaining = CooldownCandles;
}
else if (_prevRsi <= overbought && rsi > overbought && Position >= 0)
{
if (Position > 0) SellMarket();
SellMarket();
_cooldownRemaining = CooldownCandles;
}
_prevRsi = rsi;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import RelativeStrengthIndex, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class weekly_rebound_corridor_strategy(Strategy):
def __init__(self):
super(weekly_rebound_corridor_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14) \
.SetDisplay("RSI Period", "RSI lookback", "Indicators")
self._ema_period = self.Param("EmaPeriod", 50) \
.SetDisplay("EMA Period", "EMA filter", "Indicators")
self._oversold = self.Param("Oversold", 25.0) \
.SetDisplay("Oversold", "RSI oversold level", "Levels")
self._overbought = self.Param("Overbought", 75.0) \
.SetDisplay("Overbought", "RSI overbought level", "Levels")
self._cooldown_candles = self.Param("CooldownCandles", 50) \
.SetDisplay("Cooldown", "Candles between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Candle timeframe", "General")
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
@property
def rsi_period(self):
return self._rsi_period.Value
@property
def ema_period(self):
return self._ema_period.Value
@property
def oversold(self):
return self._oversold.Value
@property
def overbought(self):
return self._overbought.Value
@property
def cooldown_candles(self):
return self._cooldown_candles.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(weekly_rebound_corridor_strategy, self).OnReseted()
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
def OnStarted2(self, time):
super(weekly_rebound_corridor_strategy, self).OnStarted2(time)
self._prev_rsi = 0.0
self._has_prev = False
self._cooldown_remaining = 0
rsi = RelativeStrengthIndex()
rsi.Length = self.rsi_period
ema = ExponentialMovingAverage()
ema.Length = self.ema_period
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(rsi, ema, self.process_candle).Start()
def process_candle(self, candle, rsi, ema):
if candle.State != CandleStates.Finished:
return
rsi_val = float(rsi)
if not self._has_prev:
self._prev_rsi = rsi_val
self._has_prev = True
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._prev_rsi = rsi_val
return
oversold = float(self.oversold)
overbought = float(self.overbought)
if self._prev_rsi >= oversold and rsi_val < oversold and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._cooldown_remaining = self.cooldown_candles
elif self._prev_rsi <= overbought and rsi_val > overbought and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._cooldown_remaining = self.cooldown_candles
self._prev_rsi = rsi_val
def CreateClone(self):
return weekly_rebound_corridor_strategy()