The JS MA Day Strategy trades based on a simple moving average calculated on daily candles using the median price. The strategy compares the position of the moving average relative to each day's open price and opens positions when the trend of the moving average confirms a crossover of the open price.
Indicators
Simple Moving Average (median price)
Parameters
Name
Description
Default
MaPeriod
Period of the simple moving average.
3
Reverse
Reverse trading signals. When enabled, buy signals become sell signals and vice versa.
false
CandleType
Candle type used for calculations. Default is daily timeframe candles.
TimeFrame(1 day)
Entry Rules
Evaluate the daily simple moving average (SMA) and daily open prices.
Buy when:
Current SMA is below the previous SMA.
Current SMA is above today's open price.
Previous SMA is below the SMA two days ago.
Previous SMA is above the previous day's open price.
Sell when:
Current SMA is above the previous SMA.
Current SMA is below today's open price.
Previous SMA is above the SMA two days ago.
Previous SMA is below the previous day's open price.
If Reverse is enabled, buy and sell conditions are swapped.
Exit Rules
Positions are closed by calling StartProtection, which allows configuring protective orders such as stop loss or take profit through the platform settings.
Notes
The strategy processes only completed candles.
The volume of orders is defined by the Volume property of the base class.
There is no Python version of this strategy yet.
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>
/// Daily SMA strategy comparing moving average to open price.
/// Opens position when the moving average crosses daily open in trend direction.
/// </summary>
public class JsMaDayStrategy : Strategy
{
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<bool> _reverse;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _prices = new();
private decimal? _prevMa;
private decimal? _prevOpen;
/// <summary>
/// Moving average period.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Reverse trading signals.
/// </summary>
public bool Reverse
{
get => _reverse.Value;
set => _reverse.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public JsMaDayStrategy()
{
_maPeriod = Param(nameof(MaPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("MA Period", "SMA period on daily candles", "Parameters")
.SetOptimize(2, 20, 1);
_reverse = Param(nameof(Reverse), false)
.SetDisplay("Reverse Signals", "Reverse entry direction", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Type of candles for moving average", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_prevMa = null;
_prevOpen = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
new Unit(2000m, UnitTypes.Absolute),
new Unit(1000m, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_prices.Add(candle.ClosePrice);
if (_prices.Count > MaPeriod)
_prices.RemoveAt(0);
if (_prices.Count < MaPeriod)
return;
var sum = 0m;
foreach (var price in _prices)
sum += price;
var ma = sum / _prices.Count;
var open = candle.OpenPrice;
if (_prevMa is decimal prevMa && _prevOpen is decimal prevOpen)
{
var buyCondition = ma > open && prevMa <= prevOpen;
var sellCondition = ma < open && prevMa >= prevOpen;
if (buyCondition)
{
if (!Reverse && Position <= 0)
BuyMarket();
else if (Reverse && Position >= 0)
SellMarket();
}
else if (sellCondition)
{
if (!Reverse && Position >= 0)
SellMarket();
else if (Reverse && Position <= 0)
BuyMarket();
}
}
_prevMa = ma;
_prevOpen = open;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class js_ma_day_strategy(Strategy):
"""
Daily SMA strategy comparing manual moving average to open price.
Trades when MA crosses daily open. Uses StartProtection for SL/TP.
"""
def __init__(self):
super(js_ma_day_strategy, self).__init__()
self._ma_period = self.Param("MaPeriod", 5) \
.SetDisplay("MA Period", "SMA period on daily candles", "Parameters")
self._reverse = self.Param("Reverse", False) \
.SetDisplay("Reverse Signals", "Reverse entry direction", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Type of candles for moving average", "General")
self._prices = []
self._prev_ma = None
self._prev_open = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(js_ma_day_strategy, self).OnReseted()
self._prices = []
self._prev_ma = None
self._prev_open = None
def OnStarted2(self, time):
super(js_ma_day_strategy, self).OnStarted2(time)
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
period = self._ma_period.Value
self._prices.append(close)
if len(self._prices) > period:
self._prices.pop(0)
if len(self._prices) < period:
return
ma = sum(self._prices) / len(self._prices)
if self._prev_ma is not None and self._prev_open is not None:
buy_cond = ma > open_price and self._prev_ma <= self._prev_open
sell_cond = ma < open_price and self._prev_ma >= self._prev_open
reverse = self._reverse.Value
if buy_cond:
if not reverse and self.Position <= 0:
self.BuyMarket()
elif reverse and self.Position >= 0:
self.SellMarket()
elif sell_cond:
if not reverse and self.Position >= 0:
self.SellMarket()
elif reverse and self.Position <= 0:
self.BuyMarket()
self._prev_ma = ma
self._prev_open = open_price
def CreateClone(self):
return js_ma_day_strategy()