This strategy trades based on a smoothed Force Index crossing user-defined levels. When the indicator rises above the high level or falls below the low level, the strategy opens or closes positions depending on the selected trading mode. The Force Index is calculated from the price change and volume and smoothed with an EMA.
Details
Entry Criteria
Direct mode:
Long: indicator crosses above HighLevel.
Short: indicator crosses below LowLevel.
Against mode:
Long: indicator crosses below LowLevel.
Short: indicator crosses above HighLevel.
Exit Criteria
Direct mode:
Long: cross below LowLevel.
Short: cross above HighLevel.
Against mode:
Long: cross above HighLevel.
Short: cross below LowLevel.
Stops: No.
Default Values:
Period = 30
HighLevel = 0
LowLevel = 0
Candle Type = 4-hour
Filters:
Category: Momentum
Direction: Both
Indicators: Force Index
Stops: No
Complexity: Medium
Timeframe: Medium-term
Seasonality: No
Neural networks: No
Divergence: No
Risk level: Medium
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>
/// Fractal Force Index strategy.
/// Uses EMA of price changes as a momentum measure.
/// Opens or closes positions based on indicator level crossovers.
/// </summary>
public class FractalForceIndexStrategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<DataType> _candleType;
private decimal _prevEma;
private decimal _prevClose;
private bool _hasPrev;
/// <summary>
/// EMA smoothing period.
/// </summary>
public int Period
{
get => _period.Value;
set => _period.Value = value;
}
/// <summary>
/// The type of candles used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="FractalForceIndexStrategy"/>.
/// </summary>
public FractalForceIndexStrategy()
{
_period = Param(nameof(Period), 21)
.SetGreaterThanZero()
.SetDisplay("Period", "EMA length", "Indicator")
.SetOptimize(5, 30, 5);
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for indicator", "General");
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevEma = 0m;
_prevClose = 0m;
_hasPrev = false;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_hasPrev = false;
var ema = new ExponentialMovingAverage { Length = Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ema, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, ema);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
if (_hasPrev)
{
// Force-like momentum: price relative to EMA direction
var crossedAbove = _prevClose <= _prevEma && close > emaValue;
var crossedBelow = _prevClose >= _prevEma && close < emaValue;
if (crossedAbove && Position <= 0)
{
BuyMarket(Volume + Math.Abs(Position));
}
else if (crossedBelow && Position >= 0)
{
SellMarket(Volume + Math.Abs(Position));
}
}
_prevClose = close;
_prevEma = emaValue;
_hasPrev = true;
}
}
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
class fractal_force_index_strategy(Strategy):
"""
Fractal Force Index: EMA crossover momentum strategy.
Opens positions when close crosses above/below EMA.
"""
def __init__(self):
super(fractal_force_index_strategy, self).__init__()
self._period = self.Param("Period", 21) \
.SetDisplay("Period", "EMA length", "Indicator")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Timeframe for indicator", "General")
self._prev_ema = 0.0
self._prev_close = 0.0
self._has_prev = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(fractal_force_index_strategy, self).OnReseted()
self._prev_ema = 0.0
self._prev_close = 0.0
self._has_prev = False
def OnStarted2(self, time):
super(fractal_force_index_strategy, self).OnStarted2(time)
self._has_prev = False
ema = ExponentialMovingAverage()
ema.Length = self._period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
ema = float(ema_value)
if self._has_prev:
crossed_above = self._prev_close <= self._prev_ema and close > ema
crossed_below = self._prev_close >= self._prev_ema and close < ema
if crossed_above and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
elif crossed_below and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._prev_close = close
self._prev_ema = ema
self._has_prev = True
def CreateClone(self):
return fractal_force_index_strategy()