Linear Continuation 策略
该示例将 TradingView 的 "Linear Continuation" 指标移植到 StockSharp。策略计算三条移动平均线并记录其线性延伸值,用于演示指标功能,不包含任何交易逻辑。
细节
- MA Type – 移动平均类型(SMA 或 EMA)
- MA1 Period – 第一条移动平均线周期
- MA2 Period – 第二条移动平均线周期
- MA3 Period – 第三条移动平均线周期
- Aggressive Mode – 切换投射距离
备注
- 不会提交任何订单。
- 仅用于演示。
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>
/// Triple moving average continuation strategy.
/// Opens positions when MA ordering indicates directional continuation.
/// </summary>
public class LinearContinuationStrategy : Strategy
{
private readonly StrategyParam<MovingAverageTypes> _maType;
private readonly StrategyParam<int> _ma1Period;
private readonly StrategyParam<int> _ma2Period;
private readonly StrategyParam<int> _ma3Period;
private readonly StrategyParam<decimal> _minSpreadPercent;
private readonly StrategyParam<int> _signalCooldownBars;
private readonly StrategyParam<bool> _aggressiveMode;
private readonly StrategyParam<DataType> _candleType;
private IIndicator _ma1;
private IIndicator _ma2;
private IIndicator _ma3;
private int _lastTrend;
private int _barsFromSignal;
/// <summary>
/// Moving average type (SMA or EMA).
/// </summary>
public MovingAverageTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// First moving average period.
/// </summary>
public int Ma1Period
{
get => _ma1Period.Value;
set => _ma1Period.Value = value;
}
/// <summary>
/// Second moving average period.
/// </summary>
public int Ma2Period
{
get => _ma2Period.Value;
set => _ma2Period.Value = value;
}
/// <summary>
/// Third moving average period.
/// </summary>
public int Ma3Period
{
get => _ma3Period.Value;
set => _ma3Period.Value = value;
}
/// <summary>
/// Minimum spread between fast and slow MA as percent of close price.
/// </summary>
public decimal MinSpreadPercent
{
get => _minSpreadPercent.Value;
set => _minSpreadPercent.Value = value;
}
/// <summary>
/// Minimum bars between new entry signals.
/// </summary>
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
/// <summary>
/// Use aggressive mode for continuation calculation.
/// </summary>
public bool AggressiveMode
{
get => _aggressiveMode.Value;
set => _aggressiveMode.Value = value;
}
/// <summary>
/// Candle type for subscription.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public LinearContinuationStrategy()
{
_maType = Param(nameof(MaType), MovingAverageTypes.Simple)
.SetDisplay("MA Type", "Moving average type", "General");
_ma1Period = Param(nameof(Ma1Period), 120)
.SetGreaterThanZero()
.SetDisplay("MA1 Period", "Period for MA1", "General")
.SetOptimize(60, 240, 10);
_ma2Period = Param(nameof(Ma2Period), 55)
.SetGreaterThanZero()
.SetDisplay("MA2 Period", "Period for MA2", "General")
.SetOptimize(20, 140, 5);
_ma3Period = Param(nameof(Ma3Period), 21)
.SetGreaterThanZero()
.SetDisplay("MA3 Period", "Period for MA3", "General")
.SetOptimize(8, 80, 4);
_minSpreadPercent = Param(nameof(MinSpreadPercent), 0.03m)
.SetGreaterThanZero()
.SetDisplay("Min Spread %", "Minimal fast/slow MA spread in percent", "General")
.SetOptimize(0.01m, 0.10m, 0.01m);
_signalCooldownBars = Param(nameof(SignalCooldownBars), 12)
.SetGreaterThanZero()
.SetDisplay("Signal Cooldown", "Minimum bars between signals", "General")
.SetOptimize(5, 30, 1);
_aggressiveMode = Param(nameof(AggressiveMode), true)
.SetDisplay("Aggressive Mode", "Use aggressive continuation", "General");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ma1 = null;
_ma2 = null;
_ma3 = null;
_lastTrend = 0;
_barsFromSignal = int.MaxValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ma1 = CreateMa(MaType, Ma1Period);
_ma2 = CreateMa(MaType, Ma2Period);
_ma3 = CreateMa(MaType, Ma3Period);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_ma1, _ma2, _ma3, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ma1);
DrawIndicator(area, _ma2);
DrawIndicator(area, _ma3);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal ma1Value, decimal ma2Value, decimal ma3Value)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_ma1 is null || _ma2 is null || _ma3 is null)
return;
if (!_ma1.IsFormed || !_ma2.IsFormed || !_ma3.IsFormed)
return;
_barsFromSignal++;
var isBullishContinuation = ma3Value > ma2Value && ma2Value > ma1Value;
var isBearishContinuation = ma3Value < ma2Value && ma2Value < ma1Value;
if (!isBullishContinuation && !isBearishContinuation)
return;
var closePrice = candle.ClosePrice;
if (closePrice <= 0)
return;
var spreadPercent = Math.Abs(ma3Value - ma1Value) / closePrice * 100m;
var minSpread = AggressiveMode ? MinSpreadPercent : MinSpreadPercent * 1.5m;
if (spreadPercent < minSpread)
return;
var cooldownBars = AggressiveMode ? SignalCooldownBars : SignalCooldownBars + GetBr(Ma2Period);
if (_barsFromSignal < cooldownBars)
return;
var trend = isBullishContinuation ? 1 : -1;
if (trend == _lastTrend)
return;
if (trend > 0 && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
_lastTrend = trend;
_barsFromSignal = 0;
return;
}
if (trend < 0 && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
_lastTrend = trend;
_barsFromSignal = 0;
}
}
private int GetBr(int period)
{
return AggressiveMode ? 1 : (int)Math.Round(period / 4.669m) + 1;
}
private static IIndicator CreateMa(MovingAverageTypes type, int length)
{
return type switch
{
MovingAverageTypes.Simple => new SMA { Length = length },
MovingAverageTypes.Exponential => new EMA { Length = length },
_ => throw new ArgumentOutOfRangeException(nameof(type), type, null),
};
}
/// <summary>
/// Moving average types.
/// </summary>
public enum MovingAverageTypes
{
/// <summary>Simple moving average.</summary>
Simple,
/// <summary>Exponential moving average.</summary>
Exponential,
}
}
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 SimpleMovingAverage, ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class linear_continuation_strategy(Strategy):
def __init__(self):
super(linear_continuation_strategy, self).__init__()
self._ma1_period = self.Param("Ma1Period", 120) \
.SetGreaterThanZero() \
.SetDisplay("MA1 Period", "Period for MA1", "General")
self._ma2_period = self.Param("Ma2Period", 55) \
.SetGreaterThanZero() \
.SetDisplay("MA2 Period", "Period for MA2", "General")
self._ma3_period = self.Param("Ma3Period", 21) \
.SetGreaterThanZero() \
.SetDisplay("MA3 Period", "Period for MA3", "General")
self._min_spread_pct = self.Param("MinSpreadPercent", 0.03) \
.SetGreaterThanZero() \
.SetDisplay("Min Spread Pct", "Minimal fast/slow MA spread in percent", "General")
self._signal_cooldown = self.Param("SignalCooldownBars", 12) \
.SetGreaterThanZero() \
.SetDisplay("Signal Cooldown", "Minimum bars between signals", "General")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))) \
.SetDisplay("Candle Type", "Type of candles to use", "General")
self._last_trend = 0
self._bars_from_signal = 999999
@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(linear_continuation_strategy, self).OnReseted()
self._last_trend = 0
self._bars_from_signal = 999999
def OnStarted2(self, time):
super(linear_continuation_strategy, self).OnStarted2(time)
ma1 = SimpleMovingAverage()
ma1.Length = self._ma1_period.Value
ma2 = SimpleMovingAverage()
ma2.Length = self._ma2_period.Value
ma3 = SimpleMovingAverage()
ma3.Length = self._ma3_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ma1, ma2, ma3, self.OnProcess).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ma1)
self.DrawIndicator(area, ma2)
self.DrawIndicator(area, ma3)
self.DrawOwnTrades(area)
def OnProcess(self, candle, ma1_val, ma2_val, ma3_val):
if candle.State != CandleStates.Finished:
return
self._bars_from_signal += 1
m1 = float(ma1_val)
m2 = float(ma2_val)
m3 = float(ma3_val)
close = float(candle.ClosePrice)
bullish = m3 > m2 and m2 > m1
bearish = m3 < m2 and m2 < m1
if not bullish and not bearish:
return
if close <= 0.0:
return
spread_pct = abs(m3 - m1) / close * 100.0
min_spread = float(self._min_spread_pct.Value)
if spread_pct < min_spread:
return
cd = self._signal_cooldown.Value
if self._bars_from_signal < cd:
return
trend = 1 if bullish else -1
if trend == self._last_trend:
return
if trend > 0 and self.Position <= 0:
self.BuyMarket()
self._last_trend = trend
self._bars_from_signal = 0
elif trend < 0 and self.Position >= 0:
self.SellMarket()
self._last_trend = trend
self._bars_from_signal = 0
def CreateClone(self):
return linear_continuation_strategy()