using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Moving average crossover strategy converted from the MetaTrader script "MA_cross_Method_PriceMode".
/// Allows selecting the smoothing method, applied price and horizontal shift for each average.
/// </summary>
public class MaCrossMethodPriceModeStrategy : Strategy
{
private readonly StrategyParam<int> _firstPeriod;
private readonly StrategyParam<int> _secondPeriod;
private readonly StrategyParam<MaMethods> _firstMethod;
private readonly StrategyParam<MaMethods> _secondMethod;
private readonly StrategyParam<AppliedPriceModes> _firstPriceMode;
private readonly StrategyParam<AppliedPriceModes> _secondPriceMode;
private readonly StrategyParam<int> _firstShift;
private readonly StrategyParam<int> _secondShift;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<DataType> _candleType;
private DecimalLengthIndicator _firstMa = null!;
private DecimalLengthIndicator _secondMa = null!;
private readonly List<decimal> _firstValues = new();
private readonly List<decimal> _secondValues = new();
/// <summary>
/// Initializes a new instance of <see cref="MaCrossMethodPriceModeStrategy"/>.
/// </summary>
public MaCrossMethodPriceModeStrategy()
{
_firstPeriod = Param(nameof(FirstPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Fast MA Period", "Length of the first moving average.", "Indicators")
.SetOptimize(2, 50, 1);
_secondPeriod = Param(nameof(SecondPeriod), 13)
.SetGreaterThanZero()
.SetDisplay("Slow MA Period", "Length of the second moving average.", "Indicators")
.SetOptimize(5, 100, 1);
_firstMethod = Param(nameof(FirstMethod), MaMethods.Simple)
.SetDisplay("Fast MA Method", "Smoothing method applied to the first moving average.", "Indicators")
;
_secondMethod = Param(nameof(SecondMethod), MaMethods.LinearWeighted)
.SetDisplay("Slow MA Method", "Smoothing method applied to the second moving average.", "Indicators")
;
_firstPriceMode = Param(nameof(FirstPriceMode), AppliedPriceModes.Close)
.SetDisplay("Fast MA Price", "Price source used for the first moving average.", "Indicators")
;
_secondPriceMode = Param(nameof(SecondPriceMode), AppliedPriceModes.Median)
.SetDisplay("Slow MA Price", "Price source used for the second moving average.", "Indicators")
;
_firstShift = Param(nameof(FirstShift), 0)
.SetNotNegative()
.SetDisplay("Fast MA Shift", "Horizontal shift (in bars) applied to the first moving average.", "Indicators");
_secondShift = Param(nameof(SecondShift), 0)
.SetNotNegative()
.SetDisplay("Slow MA Shift", "Horizontal shift (in bars) applied to the second moving average.", "Indicators");
_orderVolume = Param(nameof(OrderVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Order Volume", "Base order volume used for new entries.", "Trading")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for price processing.", "General");
}
/// <summary>
/// Period of the first moving average.
/// </summary>
public int FirstPeriod
{
get => _firstPeriod.Value;
set => _firstPeriod.Value = value;
}
/// <summary>
/// Period of the second moving average.
/// </summary>
public int SecondPeriod
{
get => _secondPeriod.Value;
set => _secondPeriod.Value = value;
}
/// <summary>
/// Smoothing method applied to the first moving average.
/// </summary>
public MaMethods FirstMethod
{
get => _firstMethod.Value;
set => _firstMethod.Value = value;
}
/// <summary>
/// Smoothing method applied to the second moving average.
/// </summary>
public MaMethods SecondMethod
{
get => _secondMethod.Value;
set => _secondMethod.Value = value;
}
/// <summary>
/// Applied price mode for the first moving average.
/// </summary>
public AppliedPriceModes FirstPriceMode
{
get => _firstPriceMode.Value;
set => _firstPriceMode.Value = value;
}
/// <summary>
/// Applied price mode for the second moving average.
/// </summary>
public AppliedPriceModes SecondPriceMode
{
get => _secondPriceMode.Value;
set => _secondPriceMode.Value = value;
}
/// <summary>
/// Shift (in bars) applied to the first moving average values.
/// </summary>
public int FirstShift
{
get => _firstShift.Value;
set => _firstShift.Value = value;
}
/// <summary>
/// Shift (in bars) applied to the second moving average values.
/// </summary>
public int SecondShift
{
get => _secondShift.Value;
set => _secondShift.Value = value;
}
/// <summary>
/// Base order volume used for new positions.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Candle type (timeframe) processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_firstMa = null!;
_secondMa = null!;
_firstValues.Clear();
_secondValues.Clear();
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_firstMa = CreateMovingAverage(FirstMethod, FirstPeriod);
_secondMa = CreateMovingAverage(SecondMethod, SecondPeriod);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_firstMa, _secondMa, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _firstMa);
DrawIndicator(area, _secondMa);
DrawOwnTrades(area);
}
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal firstDecimal, decimal secondDecimal)
{
if (candle.State != CandleStates.Finished)
return;
UpdateBuffer(_firstValues, firstDecimal, FirstShift);
UpdateBuffer(_secondValues, secondDecimal, SecondShift);
if (!TryGetShiftedValues(_firstValues, FirstShift, out var firstCurrent, out var firstPrevious))
return;
if (!TryGetShiftedValues(_secondValues, SecondShift, out var secondCurrent, out _))
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var bullishCross = IsBullishCross(firstPrevious, firstCurrent, secondCurrent);
var bearishCross = IsBearishCross(firstPrevious, firstCurrent, secondCurrent);
if (bullishCross && OrderVolume > 0m && Position <= 0m)
{
var volumeToBuy = OrderVolume + (Position < 0m ? Math.Abs(Position) : 0m);
BuyMarket(volumeToBuy);
}
else if (bearishCross && OrderVolume > 0m && Position >= 0m)
{
var volumeToSell = OrderVolume + (Position > 0m ? Position : 0m);
SellMarket(volumeToSell);
}
}
private static void UpdateBuffer(List<decimal> buffer, decimal value, int shift)
{
buffer.Add(value);
var maxCount = Math.Max(shift + 2, 2);
while (buffer.Count > maxCount)
{
buffer.RemoveAt(0);
}
}
private static bool TryGetShiftedValues(IReadOnlyList<decimal> buffer, int shift, out decimal current, out decimal previous)
{
var currentIndex = buffer.Count - 1 - shift;
var previousIndex = buffer.Count - 2 - shift;
if (previousIndex < 0 || currentIndex < 0 || currentIndex >= buffer.Count)
{
current = default;
previous = default;
return false;
}
current = buffer[currentIndex];
previous = buffer[previousIndex];
return true;
}
private static bool IsBullishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
{
return (previousFast <= currentSlow && currentFast > currentSlow)
|| (previousFast < currentSlow && currentFast >= currentSlow);
}
private static bool IsBearishCross(decimal previousFast, decimal currentFast, decimal currentSlow)
{
return (previousFast >= currentSlow && currentFast < currentSlow)
|| (previousFast > currentSlow && currentFast <= currentSlow);
}
private static decimal SelectPrice(ICandleMessage candle, AppliedPriceModes mode)
{
return mode switch
{
AppliedPriceModes.Close => candle.ClosePrice,
AppliedPriceModes.Open => candle.OpenPrice,
AppliedPriceModes.High => candle.HighPrice,
AppliedPriceModes.Low => candle.LowPrice,
AppliedPriceModes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceModes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceModes.Weighted => (candle.HighPrice + candle.LowPrice + (2m * candle.ClosePrice)) / 4m,
_ => candle.ClosePrice
};
}
private static DecimalLengthIndicator CreateMovingAverage(MaMethods method, int period)
{
return method switch
{
MaMethods.Simple => new SMA { Length = period },
MaMethods.Exponential => new EMA { Length = period },
MaMethods.Smoothed => new SmoothedMovingAverage { Length = period },
MaMethods.LinearWeighted => new WeightedMovingAverage { Length = period },
_ => new SMA { Length = period }
};
}
/// <summary>
/// Moving average smoothing methods that mirror the MetaTrader inputs.
/// </summary>
public enum MaMethods
{
Simple,
Exponential,
Smoothed,
LinearWeighted
}
/// <summary>
/// Applied price options equivalent to the MetaTrader constants.
/// </summary>
public enum AppliedPriceModes
{
Close,
Open,
High,
Low,
Median,
Typical,
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ma_cross_method_price_mode_strategy(Strategy):
"""
MA crossover strategy with configurable periods.
"""
def __init__(self):
super(ma_cross_method_price_mode_strategy, self).__init__()
self._first_period = self.Param("FirstPeriod", 3).SetDisplay("Fast MA", "Fast MA period", "Indicators")
self._second_period = self.Param("SecondPeriod", 13).SetDisplay("Slow MA", "Slow MA period", "Indicators")
self._order_volume = self.Param("OrderVolume", 0.1).SetDisplay("Order Volume", "Base order volume", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Timeframe", "General")
self._first_values = []
self._second_values = []
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ma_cross_method_price_mode_strategy, self).OnReseted()
self._first_values = []
self._second_values = []
def OnStarted2(self, time):
super(ma_cross_method_price_mode_strategy, self).OnStarted2(time)
first = SimpleMovingAverage()
first.Length = self._first_period.Value
second = WeightedMovingAverage()
second.Length = self._second_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(first, second, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, first)
self.DrawIndicator(area, second)
self.DrawOwnTrades(area)
def _process_candle(self, candle, first_val, second_val):
if candle.State != CandleStates.Finished:
return
f = float(first_val)
s = float(second_val)
self._first_values.append(f)
self._second_values.append(s)
if len(self._first_values) > 4:
self._first_values.pop(0)
if len(self._second_values) > 4:
self._second_values.pop(0)
if len(self._first_values) < 2:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
prev_f = self._first_values[-2]
curr_f = self._first_values[-1]
curr_s = self._second_values[-1]
bull = (prev_f <= curr_s and curr_f > curr_s) or (prev_f < curr_s and curr_f >= curr_s)
bear = (prev_f >= curr_s and curr_f < curr_s) or (prev_f > curr_s and curr_f <= curr_s)
vol = float(self._order_volume.Value)
pos = float(self.Position)
if bull and vol > 0 and pos <= 0:
buy_vol = vol + (Math.Abs(self.Position) if pos < 0 else 0)
self.BuyMarket(buy_vol)
elif bear and vol > 0 and pos >= 0:
sell_vol = vol + (self.Position if pos > 0 else 0)
self.SellMarket(sell_vol)
def CreateClone(self):
return ma_cross_method_price_mode_strategy()