Q2MA Cross Strategy trades based on the crossover of smoothed moving averages built on candle close and open prices. A long position is opened when the close average falls below the open average after being above, while a short position is opened on the opposite crossover. Positions are closed when an opposite trend appears. The strategy also applies stop loss and take profit levels measured in ticks.
Details
Entry Criteria: crossover between moving averages of close and open prices
Long/Short: both directions
Exit Criteria: opposite crossover or stop loss/take profit
Stops: yes
Default Values:
Length = 8
StopLoss = 1000
TakeProfit = 2000
CandleType = TimeSpan.FromHours(4).TimeFrame()
Volume = 1
BuyPosOpen = true
SellPosOpen = true
BuyPosClose = true
SellPosClose = true
Invert = false
Filters:
Category: Trend
Direction: Both
Indicators: Moving Average
Stops: Yes
Complexity: Intermediate
Timeframe: H4
Seasonality: No
Neural networks: No
Divergence: No
Risk level: Medium
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>
/// Q2MA cross strategy based on open and close moving averages.
/// Buys when close MA crosses above open MA, sells on opposite.
/// </summary>
public class Q2maCrossStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _closeMa;
private ExponentialMovingAverage _openMa;
private decimal? _prevUp;
private decimal? _prevDn;
public int Length { get => _length.Value; set => _length.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public Q2maCrossStrategy()
{
_length = Param(nameof(Length), 8)
.SetDisplay("Length", "Moving average length", "Indicator");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Indicator timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_closeMa = null;
_openMa = null;
_prevUp = null;
_prevDn = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevUp = null;
_prevDn = null;
_closeMa = new ExponentialMovingAverage { Length = Length };
_openMa = new ExponentialMovingAverage { Length = Length };
Indicators.Add(_closeMa);
Indicators.Add(_openMa);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var t = candle.ServerTime;
var upResult = _closeMa.Process(new DecimalIndicatorValue(_closeMa, candle.ClosePrice, t) { IsFinal = true });
var dnResult = _openMa.Process(new DecimalIndicatorValue(_openMa, candle.OpenPrice, t) { IsFinal = true });
if (!_closeMa.IsFormed || !_openMa.IsFormed)
return;
var up = upResult.GetValue<decimal>();
var dn = dnResult.GetValue<decimal>();
if (_prevUp is null || _prevDn is null)
{
_prevUp = up;
_prevDn = dn;
return;
}
// Close MA crosses above Open MA -> buy signal
if (_prevUp <= _prevDn && up > dn && Position <= 0)
BuyMarket();
// Close MA crosses below Open MA -> sell signal
else if (_prevUp >= _prevDn && up < dn && Position >= 0)
SellMarket();
_prevUp = up;
_prevDn = dn;
}
}
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 ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class q2ma_cross_strategy(Strategy):
def __init__(self):
super(q2ma_cross_strategy, self).__init__()
self._length = self.Param("Length", 8) \
.SetDisplay("Length", "Moving average length", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Indicator timeframe", "General")
self._close_ma = None
self._open_ma = None
self._prev_up = None
self._prev_dn = None
@property
def length(self):
return self._length.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(q2ma_cross_strategy, self).OnReseted()
self._close_ma = None
self._open_ma = None
self._prev_up = None
self._prev_dn = None
def OnStarted2(self, time):
super(q2ma_cross_strategy, self).OnStarted2(time)
self._prev_up = None
self._prev_dn = None
self._close_ma = ExponentialMovingAverage()
self._close_ma.Length = self.length
self._open_ma = ExponentialMovingAverage()
self._open_ma.Length = self.length
self.Indicators.Add(self._close_ma)
self.Indicators.Add(self._open_ma)
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 process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
up_result = process_float(self._close_ma, float(candle.ClosePrice), t, True)
dn_result = process_float(self._open_ma, float(candle.OpenPrice), t, True)
if not self._close_ma.IsFormed or not self._open_ma.IsFormed:
return
up = float(up_result)
dn = float(dn_result)
if self._prev_up is None or self._prev_dn is None:
self._prev_up = up
self._prev_dn = dn
return
if self._prev_up <= self._prev_dn and up > dn and self.Position <= 0:
self.BuyMarket()
elif self._prev_up >= self._prev_dn and up < dn and self.Position >= 0:
self.SellMarket()
self._prev_up = up
self._prev_dn = dn
def CreateClone(self):
return q2ma_cross_strategy()