Fast2 Crossover Strategy
Strategy based on the Fast2 histogram. The histogram combines the body of the last three candles with square‑root weights and applies two weighted moving averages. A long position is opened when the fast average crosses below the slow one, and a short position when it crosses above.
Details
- Entry Criteria:
- Long: fast WMA crosses below slow WMA
- Short: fast WMA crosses above slow WMA
- Long/Short: Both
- Exit Criteria:
- Opposite crossover
- Stops: None
- Default Values:
FastLength= 3SlowLength= 9CandleType= TimeSpan.FromHours(8).TimeFrame()
- Filters:
- Category: Crossover
- Direction: Both
- Indicators: WeightedMovingAverage
- Stops: No
- Complexity: Basic
- Timeframe: Mid-term
- 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>
/// Trading strategy based on Fast2 histogram moving average crossover.
/// Uses weighted candle body differences with WMA smoothing.
/// </summary>
public class Fast2CrossoverStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private decimal _prevFast;
private decimal _prevSlow;
private bool _hasPrevAverage;
private decimal _prevDiff1;
private decimal _prevDiff2;
private bool _hasPrevDiff1;
private bool _hasPrevDiff2;
/// <summary>Candle type.</summary>
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
/// <summary>Fast WMA length.</summary>
public int FastLength { get => _fastLength.Value; set => _fastLength.Value = value; }
/// <summary>Slow WMA length.</summary>
public int SlowLength { get => _slowLength.Value; set => _slowLength.Value = value; }
public Fast2CrossoverStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame());
_fastLength = Param(nameof(FastLength), 5).SetDisplay("Fast length", "Fast length", "General");
_slowLength = Param(nameof(SlowLength), 13).SetDisplay("Slow length", "Slow length", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = default;
_prevSlow = default;
_hasPrevAverage = default;
_prevDiff1 = default;
_prevDiff2 = default;
_hasPrevDiff1 = default;
_hasPrevDiff2 = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new WeightedMovingAverage { Length = FastLength };
var slow = new WeightedMovingAverage { Length = SlowLength };
Indicators.Add(fast);
Indicators.Add(slow);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(candle =>
{
if (candle.State != CandleStates.Finished)
return;
var diff = candle.ClosePrice - candle.OpenPrice;
var hist = diff;
if (_hasPrevDiff1)
hist += _prevDiff1 / (decimal)Math.Sqrt(2);
if (_hasPrevDiff2)
hist += _prevDiff2 / (decimal)Math.Sqrt(3);
var fastValue = fast.Process(hist, candle.OpenTime, true);
var slowValue = slow.Process(hist, candle.OpenTime, true);
_prevDiff2 = _prevDiff1;
_prevDiff1 = diff;
_hasPrevDiff2 = _hasPrevDiff1;
_hasPrevDiff1 = true;
if (fastValue.IsEmpty || slowValue.IsEmpty || !fast.IsFormed || !slow.IsFormed)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var f = fastValue.ToDecimal();
var s = slowValue.ToDecimal();
if (_hasPrevAverage)
{
if (_prevFast > _prevSlow && f < s && Position <= 0)
BuyMarket();
if (_prevFast < _prevSlow && f > s && Position >= 0)
SellMarket();
}
_prevFast = f;
_prevSlow = s;
_hasPrevAverage = true;
}).Start();
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class fast2_crossover_strategy(Strategy):
"""
Fast2 histogram moving average crossover.
Uses weighted candle body differences with WMA smoothing.
"""
def __init__(self):
super(fast2_crossover_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1))) \
.SetDisplay("Candle Type", "Candle type", "General")
self._fast_length = self.Param("FastLength", 5) \
.SetDisplay("Fast length", "Fast length", "General")
self._slow_length = self.Param("SlowLength", 13) \
.SetDisplay("Slow length", "Slow length", "General")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev_average = False
self._prev_diff1 = 0.0
self._prev_diff2 = 0.0
self._has_prev_diff1 = False
self._has_prev_diff2 = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fast2_crossover_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._has_prev_average = False
self._prev_diff1 = 0.0
self._prev_diff2 = 0.0
self._has_prev_diff1 = False
self._has_prev_diff2 = False
def OnStarted2(self, time):
super(fast2_crossover_strategy, self).OnStarted2(time)
self._fast = WeightedMovingAverage()
self._fast.Length = self._fast_length.Value
self._slow = WeightedMovingAverage()
self._slow.Length = self._slow_length.Value
self.Indicators.Add(self._fast)
self.Indicators.Add(self._slow)
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_p = float(candle.OpenPrice)
diff = close - open_p
hist = diff
if self._has_prev_diff1:
hist += self._prev_diff1 / math.sqrt(2)
if self._has_prev_diff2:
hist += self._prev_diff2 / math.sqrt(3)
fast_result = process_float(self._fast, hist, candle.OpenTime, True)
slow_result = process_float(self._slow, hist, candle.OpenTime, True)
self._prev_diff2 = self._prev_diff1
self._prev_diff1 = diff
self._has_prev_diff2 = self._has_prev_diff1
self._has_prev_diff1 = True
if fast_result.IsEmpty or slow_result.IsEmpty or not self._fast.IsFormed or not self._slow.IsFormed:
return
f = float(fast_result)
s = float(slow_result)
if self._has_prev_average:
if self._prev_fast > self._prev_slow and f < s and self.Position <= 0:
self.BuyMarket()
if self._prev_fast < self._prev_slow and f > s and self.Position >= 0:
self.SellMarket()
self._prev_fast = f
self._prev_slow = s
self._has_prev_average = True
def CreateClone(self):
return fast2_crossover_strategy()