Стратегия скользящей средней
Стратегия открывает длинную позицию, когда короткая скользящая средняя пересекает сверху длинную скользящую среднюю выбранного типа цены. Позиция закрывается, когда короткая средняя пересекает длинную сверху вниз.
Детали
- Вход: короткая средняя пересекает длинную снизу вверх.
- Выход: короткая средняя пересекает длинную сверху вниз.
- Индикаторы: SMA, EMA, DEMA, TEMA, WMA, VWMA.
- Тип цены: Close, High, Open, Low, Typical, Center.
- Стопы: нет.
- Значения по умолчанию:
MaType= EMAShortLength= 1LongLength= 20PriceType= TypicalCandleType= 1 minute
- Фильтры:
- Категория: Trend following
- Направление: Long only
- Индикаторы: Moving average
- Стопы: Нет
- Сложность: Простая
- Уровень риска: Средний
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>
/// Moving average crossover strategy.
/// A short and a long moving average of the selected type are calculated on the selected candle price.
/// A long opens when the short average crosses above the long one and closes when it crosses back below. Long only, no stops.
/// </summary>
public class MovingAverageStrategy : Strategy
{
/// <summary>
/// Moving average types.
/// </summary>
public enum MaTypes
{
/// <summary>Simple moving average.</summary>
SMA,
/// <summary>Exponential moving average.</summary>
EMA,
/// <summary>Double exponential moving average.</summary>
DEMA,
/// <summary>Triple exponential moving average.</summary>
TEMA,
/// <summary>Weighted moving average.</summary>
WMA,
/// <summary>Volume weighted moving average.</summary>
VWMA,
}
/// <summary>
/// Candle price used by the averages.
/// </summary>
public enum PriceTypes
{
/// <summary>Close price.</summary>
Close,
/// <summary>High price.</summary>
High,
/// <summary>Open price.</summary>
Open,
/// <summary>Low price.</summary>
Low,
/// <summary>(High + Low + Close) / 3.</summary>
Typical,
/// <summary>(High + Low) / 2.</summary>
Center,
}
private readonly StrategyParam<MaTypes> _maType;
private readonly StrategyParam<int> _shortLength;
private readonly StrategyParam<int> _longLength;
private readonly StrategyParam<PriceTypes> _priceType;
private readonly StrategyParam<DataType> _candleType;
private MovingAverageLine _shortMa;
private MovingAverageLine _longMa;
private decimal? _prevShort;
private decimal? _prevLong;
/// <summary>
/// Moving average type.
/// </summary>
public MaTypes MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Short moving average length.
/// </summary>
public int ShortLength
{
get => _shortLength.Value;
set => _shortLength.Value = value;
}
/// <summary>
/// Long moving average length.
/// </summary>
public int LongLength
{
get => _longLength.Value;
set => _longLength.Value = value;
}
/// <summary>
/// Candle price used by the averages.
/// </summary>
public PriceTypes PriceType
{
get => _priceType.Value;
set => _priceType.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public MovingAverageStrategy()
{
_maType = Param(nameof(MaType), MaTypes.EMA)
.SetDisplay("MA Type", "Moving average type", "Indicators");
_shortLength = Param(nameof(ShortLength), 1)
.SetGreaterThanZero()
.SetDisplay("Short Length", "Short moving average length", "Indicators");
_longLength = Param(nameof(LongLength), 20)
.SetGreaterThanZero()
.SetDisplay("Long Length", "Long moving average length", "Indicators");
_priceType = Param(nameof(PriceType), PriceTypes.Typical)
.SetDisplay("Price Type", "Candle price used by the averages", "Indicators");
_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();
_shortMa = null;
_longMa = null;
_prevShort = null;
_prevLong = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevShort = null;
_prevLong = null;
_shortMa = new MovingAverageLine(MaType, ShortLength);
_longMa = new MovingAverageLine(MaType, LongLength);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private decimal GetPrice(ICandleMessage candle)
{
return PriceType switch
{
PriceTypes.High => candle.HighPrice,
PriceTypes.Open => candle.OpenPrice,
PriceTypes.Low => candle.LowPrice,
PriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
PriceTypes.Center => (candle.HighPrice + candle.LowPrice) / 2m,
_ => candle.ClosePrice,
};
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = GetPrice(candle);
var shortValue = _shortMa.Process(price, candle.TotalVolume, candle.OpenTime);
var longValue = _longMa.Process(price, candle.TotalVolume, candle.OpenTime);
if (shortValue is not decimal shortMa || longValue is not decimal longMa)
return;
var prevShort = _prevShort;
var prevLong = _prevLong;
_prevShort = shortMa;
_prevLong = longMa;
if (prevShort is not decimal ps || prevLong is not decimal pl)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (ps <= pl && shortMa > longMa && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (ps >= pl && shortMa < longMa && Position > 0)
SellMarket(Position);
}
/// <summary>
/// A moving average fed with an arbitrary price; VWMA is calculated from the price and candle volume.
/// </summary>
private sealed class MovingAverageLine
{
private readonly IIndicator _indicator;
private readonly int _length;
private readonly Queue<(decimal price, decimal volume)> _window = new();
public MovingAverageLine(MaTypes type, int length)
{
_length = length;
_indicator = type switch
{
MaTypes.SMA => new SimpleMovingAverage { Length = length },
MaTypes.DEMA => new DoubleExponentialMovingAverage { Length = length },
MaTypes.TEMA => new TripleExponentialMovingAverage { Length = length },
MaTypes.WMA => new WeightedMovingAverage { Length = length },
MaTypes.VWMA => null,
_ => new ExponentialMovingAverage { Length = length },
};
}
public decimal? Process(decimal price, decimal volume, DateTime time)
{
if (_indicator == null)
{
_window.Enqueue((price, volume));
if (_window.Count > _length)
_window.Dequeue();
if (_window.Count < _length)
return null;
decimal sumPv = 0m, sumV = 0m;
foreach (var (p, v) in _window)
{
sumPv += p * v;
sumV += v;
}
return sumV == 0m ? price : sumPv / sumV;
}
var value = _indicator.Process(new DecimalIndicatorValue(_indicator, price, time) { IsFinal = true });
if (!_indicator.IsFormed || value.IsEmpty)
return null;
return value.ToDecimal();
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (SimpleMovingAverage, ExponentialMovingAverage, DoubleExponentialMovingAverage,
TripleExponentialMovingAverage, WeightedMovingAverage, DecimalIndicatorValue)
from StockSharp.Algo.Strategies import Strategy
class _moving_average_line(object):
"""A moving average fed with an arbitrary price; VWMA is calculated from the price and candle volume."""
def __init__(self, ma_type, length):
self._length = length
self._window = []
if ma_type == "SMA":
self._indicator = SimpleMovingAverage()
elif ma_type == "DEMA":
self._indicator = DoubleExponentialMovingAverage()
elif ma_type == "TEMA":
self._indicator = TripleExponentialMovingAverage()
elif ma_type == "WMA":
self._indicator = WeightedMovingAverage()
elif ma_type == "VWMA":
self._indicator = None
else:
self._indicator = ExponentialMovingAverage()
if self._indicator is not None:
self._indicator.Length = length
def process(self, price, volume, time):
if self._indicator is None:
self._window.append((price, volume))
if len(self._window) > self._length:
self._window.pop(0)
if len(self._window) < self._length:
return None
sum_pv = sum(p * v for p, v in self._window)
sum_v = sum(v for p, v in self._window)
return price if sum_v == 0 else sum_pv / sum_v
indicator_input = DecimalIndicatorValue(self._indicator, Decimal(price), time)
indicator_input.IsFinal = True
value = self._indicator.Process(indicator_input)
if not self._indicator.IsFormed or value.IsEmpty:
return None
return float(value.GetValue[Decimal](None))
class moving_average_strategy(Strategy):
"""
Moving average crossover strategy.
A short and a long moving average of the selected type are calculated on the selected candle price.
A long opens when the short average crosses above the long one and closes when it crosses back below. Long only, no stops.
"""
def __init__(self):
super(moving_average_strategy, self).__init__()
self._ma_type = self.Param("MaType", "EMA").SetDisplay("MA Type", "Moving average type: SMA, EMA, DEMA, TEMA, WMA or VWMA", "Indicators")
self._short_length = self.Param("ShortLength", 1).SetGreaterThanZero().SetDisplay("Short Length", "Short moving average length", "Indicators")
self._long_length = self.Param("LongLength", 20).SetGreaterThanZero().SetDisplay("Long Length", "Long moving average length", "Indicators")
self._price_type = self.Param("PriceType", "Typical").SetDisplay("Price Type", "Candle price: Close, High, Open, Low, Typical or Center", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._short_ma = None
self._long_ma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_short = None
self._prev_long = None
def OnReseted(self):
super(moving_average_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(moving_average_strategy, self).OnStarted2(time)
self._reset_state()
ma_type = str(self._ma_type.Value).upper()
self._short_ma = _moving_average_line(ma_type, self._short_length.Value)
self._long_ma = _moving_average_line(ma_type, self._long_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _get_price(self, candle):
price_type = str(self._price_type.Value)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
close = float(candle.ClosePrice)
if price_type == "High":
return high
if price_type == "Open":
return float(candle.OpenPrice)
if price_type == "Low":
return low
if price_type == "Typical":
return (high + low + close) / 3.0
if price_type == "Center":
return (high + low) / 2.0
return close
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = self._get_price(candle)
volume = float(candle.TotalVolume)
short_ma = self._short_ma.process(price, volume, candle.OpenTime)
long_ma = self._long_ma.process(price, volume, candle.OpenTime)
if short_ma is None or long_ma is None:
return
prev_short = self._prev_short
prev_long = self._prev_long
self._prev_short = short_ma
self._prev_long = long_ma
if prev_short is None or prev_long is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if prev_short <= prev_long and short_ma > long_ma and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif prev_short >= prev_long and short_ma < long_ma and self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return moving_average_strategy()