Max Gain
Max Gain 通过比较在回溯期内从最低价到当前最高价的百分比上涨与从最高价到当前最低价的百分比下跌(调整后),当潜在涨幅大于调整后的跌幅时做多,否则做空。
细节
- 数据:价格K线。
- 入场条件:
- 多头:Max gain > adjusted max loss。
- 空头:Adjusted max loss > max gain。
- 出场条件:反向信号。
- 止损:无。
- 默认值:
PeriodLength= 30
- 过滤器:
- 类别:动量
- 方向:多头 & 空头
- 指标:Highest, Lowest
- 复杂度:低
- 风险等级:中等
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Compares upside/downside potential inside a rolling high-low range.
/// </summary>
public class MaxGainStrategy : Strategy
{
private readonly StrategyParam<int> _periodLength;
private readonly StrategyParam<decimal> _edgeMultiplier;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<DataType> _candleType;
private Highest _highest;
private Lowest _lowest;
private int _barsFromSignal;
/// <summary>
/// High/low lookback length.
/// </summary>
public int PeriodLength
{
get => _periodLength.Value;
set => _periodLength.Value = value;
}
/// <summary>
/// Minimum upside/downside ratio required for position change.
/// </summary>
public decimal EdgeMultiplier
{
get => _edgeMultiplier.Value;
set => _edgeMultiplier.Value = value;
}
/// <summary>
/// Minimum bars between market entries.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Candle timeframe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public MaxGainStrategy()
{
_periodLength = Param(nameof(PeriodLength), 64)
.SetGreaterThanZero()
.SetDisplay("Period Length", "Rolling high-low length", "General");
_edgeMultiplier = Param(nameof(EdgeMultiplier), 1.25m)
.SetGreaterThanZero()
.SetDisplay("Edge Multiplier", "Upside/downside ratio threshold", "General");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 14)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candles timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highest = null;
_lowest = null;
_barsFromSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
_highest = new() { Length = PeriodLength };
_lowest = new() { Length = PeriodLength };
_barsFromSignal = SignalCooldownBars;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_highest, _lowest, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal maxHigh, decimal minLow)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (!_highest.IsFormed || !_lowest.IsFormed)
return;
var close = candle.ClosePrice;
if (close <= 0m || maxHigh <= minLow)
return;
_barsFromSignal++;
if (_barsFromSignal < SignalCooldownBars)
return;
var upside = (maxHigh - close) / close;
var downside = (close - minLow) / close;
if (upside > downside * EdgeMultiplier && Position <= 0)
{
BuyMarket();
_barsFromSignal = 0;
}
else if (downside > upside * EdgeMultiplier && Position >= 0)
{
SellMarket();
_barsFromSignal = 0;
}
}
}
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 Highest, Lowest
from StockSharp.Algo.Strategies import Strategy
class max_gain_strategy(Strategy):
def __init__(self):
super(max_gain_strategy, self).__init__()
self._period_length = self.Param("PeriodLength", 64) \
.SetGreaterThanZero() \
.SetDisplay("Period Length", "Rolling high-low length", "General")
self._edge_multiplier = self.Param("EdgeMultiplier", 1.25) \
.SetGreaterThanZero() \
.SetDisplay("Edge Multiplier", "Upside/downside ratio threshold", "General")
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 14) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown Bars", "Minimum bars between entries", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15))) \
.SetDisplay("Candle Type", "Candles timeframe", "General")
self._bars_from_signal = 0
@property
def candle_type(self):
return self._candle_type.Value
@candle_type.setter
def candle_type(self, value):
self._candle_type.Value = value
def OnReseted(self):
super(max_gain_strategy, self).OnReseted()
self._bars_from_signal = 0
def OnStarted2(self, time):
super(max_gain_strategy, self).OnStarted2(time)
self._bars_from_signal = self._signal_cooldown_bars.Value
self._highest = Highest()
self._highest.Length = self._period_length.Value
self._lowest = Lowest()
self._lowest.Length = self._period_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._highest, self._lowest, self.OnProcess).Start()
def OnProcess(self, candle, max_high, min_low):
if candle.State != CandleStates.Finished:
return
if not self._highest.IsFormed or not self._lowest.IsFormed:
return
mh = float(max_high)
ml = float(min_low)
close = float(candle.ClosePrice)
if close <= 0.0 or mh <= ml:
return
self._bars_from_signal += 1
cd = self._signal_cooldown_bars.Value
if self._bars_from_signal < cd:
return
upside = (mh - close) / close
downside = (close - ml) / close
em = float(self._edge_multiplier.Value)
if upside > downside * em and self.Position <= 0:
self.BuyMarket()
self._bars_from_signal = 0
elif downside > upside * em and self.Position >= 0:
self.SellMarket()
self._bars_from_signal = 0
def CreateClone(self):
return max_gain_strategy()