View on GitHub

JS MA Day Strategy

Overview

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

  1. Evaluate the daily simple moving average (SMA) and daily open prices.
  2. 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.
  3. 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.
  4. 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;
	}
}