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>
/// Chaikin oscillator oversold/overbought strategy with optional reversal hedging and trailing management.
/// </summary>
public class PipsoverChaikinHedgeStrategy : Strategy
{
private readonly StrategyParam<decimal> _openLevel;
private readonly StrategyParam<decimal> _closeLevel;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _breakevenPips;
private readonly StrategyParam<int> _maPeriod;
private readonly StrategyParam<int> _maShift;
private readonly StrategyParam<MovingAverageTypeOptions> _maType;
private readonly StrategyParam<int> _chaikinFastPeriod;
private readonly StrategyParam<int> _chaikinSlowPeriod;
private readonly StrategyParam<MovingAverageTypeOptions> _chaikinMaType;
private readonly StrategyParam<DataType> _candleType;
private AccumulationDistributionLine _adLine = null!;
private IIndicator _priceMa = null!;
private IIndicator _chaikinFast = null!;
private IIndicator _chaikinSlow = null!;
private readonly Queue<decimal> _maValues = new();
private decimal _pipSize;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private decimal _prevOpen;
private decimal _prevClose;
private decimal _prevHigh;
private decimal _prevLow;
private bool _hasPrevCandle;
private decimal _prevChaikin;
private bool _hasPrevChaikin;
/// <summary>
/// Chaikin threshold for entries.
/// </summary>
public decimal OpenLevel
{
get => _openLevel.Value;
set => _openLevel.Value = value;
}
/// <summary>
/// Chaikin threshold for hedging reversals.
/// </summary>
public decimal CloseLevel
{
get => _closeLevel.Value;
set => _closeLevel.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Breakeven activation distance in pips.
/// </summary>
public decimal BreakevenPips
{
get => _breakevenPips.Value;
set => _breakevenPips.Value = value;
}
/// <summary>
/// Moving average length.
/// </summary>
public int MaPeriod
{
get => _maPeriod.Value;
set => _maPeriod.Value = value;
}
/// <summary>
/// Moving average shift in bars.
/// </summary>
public int MaShift
{
get => _maShift.Value;
set => _maShift.Value = value;
}
/// <summary>
/// Moving average type for price filter.
/// </summary>
public MovingAverageTypeOptions MaType
{
get => _maType.Value;
set => _maType.Value = value;
}
/// <summary>
/// Fast Chaikin moving average length.
/// </summary>
public int ChaikinFastPeriod
{
get => _chaikinFastPeriod.Value;
set => _chaikinFastPeriod.Value = value;
}
/// <summary>
/// Slow Chaikin moving average length.
/// </summary>
public int ChaikinSlowPeriod
{
get => _chaikinSlowPeriod.Value;
set => _chaikinSlowPeriod.Value = value;
}
/// <summary>
/// Moving average type used in Chaikin oscillator.
/// </summary>
public MovingAverageTypeOptions ChaikinMaType
{
get => _chaikinMaType.Value;
set => _chaikinMaType.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="PipsoverChaikinHedgeStrategy"/> class.
/// </summary>
public PipsoverChaikinHedgeStrategy()
{
_openLevel = Param(nameof(OpenLevel), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Open Level", "Chaikin level for entries", "Chaikin");
_closeLevel = Param(nameof(CloseLevel), 0.02m)
.SetGreaterThanZero()
.SetDisplay("Close Level", "Chaikin level for hedging", "Chaikin");
_stopLossPips = Param(nameof(StopLossPips), 65m)
.SetDisplay("Stop Loss (pips)", "Stop-loss distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 100m)
.SetDisplay("Take Profit (pips)", "Take-profit distance in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 30m)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_breakevenPips = Param(nameof(BreakevenPips), 15m)
.SetDisplay("Breakeven (pips)", "Breakeven activation distance", "Risk");
_maPeriod = Param(nameof(MaPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Period", "Price moving average length", "Trend");
_maShift = Param(nameof(MaShift), 0)
.SetDisplay("MA Shift", "Price moving average shift", "Trend");
_maType = Param(nameof(MaType), MovingAverageTypeOptions.Simple)
.SetDisplay("MA Type", "Price moving average type", "Trend");
_chaikinFastPeriod = Param(nameof(ChaikinFastPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Chaikin Fast", "Fast Chaikin length", "Chaikin");
_chaikinSlowPeriod = Param(nameof(ChaikinSlowPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("Chaikin Slow", "Slow Chaikin length", "Chaikin");
_chaikinMaType = Param(nameof(ChaikinMaType), MovingAverageTypeOptions.Exponential)
.SetDisplay("Chaikin MA Type", "Chaikin moving average type", "Chaikin");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Timeframe for analysis", "Data");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maValues.Clear();
_pipSize = 0m;
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_prevOpen = 0m;
_prevClose = 0m;
_prevHigh = 0m;
_prevLow = 0m;
_prevChaikin = 0m;
_hasPrevCandle = false;
_hasPrevChaikin = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pipSize = CalculatePipSize();
_adLine = new AccumulationDistributionLine();
_priceMa = CreateMovingAverage(MaType, MaPeriod);
_chaikinFast = CreateMovingAverage(ChaikinMaType, ChaikinFastPeriod);
_chaikinSlow = CreateMovingAverage(ChaikinMaType, ChaikinSlowPeriod);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_priceMa, _adLine, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal maValue, decimal adValue)
{
if (candle.State != CandleStates.Finished)
return;
var prevOpen = _prevOpen;
var prevClose = _prevClose;
var prevHigh = _prevHigh;
var prevLow = _prevLow;
var prevChaikin = _prevChaikin;
var hasPrevCandle = _hasPrevCandle;
var hasPrevChaikin = _hasPrevChaikin;
var fastValue = _chaikinFast.Process(new DecimalIndicatorValue(_chaikinFast, adValue, candle.OpenTime) { IsFinal = true });
var slowValue = _chaikinSlow.Process(new DecimalIndicatorValue(_chaikinSlow, adValue, candle.OpenTime) { IsFinal = true });
if (!fastValue.IsFinal || !slowValue.IsFinal)
{
_prevChaikin = fastValue.ToDecimal() - slowValue.ToDecimal();
_hasPrevChaikin = true;
StorePreviousCandle(candle);
return;
}
var chaikin = fastValue.ToDecimal() - slowValue.ToDecimal();
var shiftedMa = UpdateShiftedMa(maValue);
if (shiftedMa is null)
{
_prevChaikin = chaikin;
_hasPrevChaikin = true;
StorePreviousCandle(candle);
return;
}
var hasPrevData = hasPrevCandle && hasPrevChaikin;
var positionClosed = HandleStopsAndTargets(candle);
var reversed = false;
if (Position == 0m)
{
if (hasPrevData)
{
var bullishPrev = prevClose > prevOpen;
var bearishPrev = prevClose < prevOpen;
if (bullishPrev && prevLow < shiftedMa && prevChaikin < -OpenLevel)
{
BuyMarket(Volume);
SetupLongTargets(candle.ClosePrice);
}
else if (bearishPrev && prevHigh > shiftedMa && prevChaikin > OpenLevel)
{
SellMarket(Volume);
SetupShortTargets(candle.ClosePrice);
}
}
}
else if (!positionClosed)
{
if (hasPrevData)
{
var bearishPrev = prevClose < prevOpen;
var bullishPrev = prevClose > prevOpen;
if (Position > 0m && bearishPrev && prevHigh > shiftedMa && prevChaikin > CloseLevel)
{
var size = Math.Abs(Position) + Volume;
SellMarket(size);
SetupShortTargets(candle.ClosePrice);
reversed = true;
}
else if (Position < 0m && bullishPrev && prevLow < shiftedMa && prevChaikin < -CloseLevel)
{
var size = Math.Abs(Position) + Volume;
BuyMarket(size);
SetupLongTargets(candle.ClosePrice);
reversed = true;
}
}
if (!reversed)
UpdateTrailing(candle);
}
_prevChaikin = chaikin;
_hasPrevChaikin = true;
StorePreviousCandle(candle);
}
private decimal? UpdateShiftedMa(decimal maValue)
{
var shift = Math.Max(0, MaShift);
_maValues.Enqueue(maValue);
while (_maValues.Count > shift + 1)
_maValues.Dequeue();
var values = _maValues.ToArray();
if (values.Length < shift + 1)
return null;
return values[0];
}
private void StorePreviousCandle(ICandleMessage candle)
{
_prevOpen = candle.OpenPrice;
_prevClose = candle.ClosePrice;
_prevHigh = candle.HighPrice;
_prevLow = candle.LowPrice;
_hasPrevCandle = true;
}
private bool HandleStopsAndTargets(ICandleMessage candle)
{
if (Position > 0m)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return true;
}
if (_takeProfitPrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Math.Abs(Position));
ResetPositionState();
return true;
}
}
else if (Position < 0m)
{
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return true;
}
if (_takeProfitPrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(Math.Abs(Position));
ResetPositionState();
return true;
}
}
return false;
}
private void SetupLongTargets(decimal price)
{
_entryPrice = price;
if (StopLossPips > 0m)
_stopPrice = price - StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takeProfitPrice = price + TakeProfitPips * _pipSize;
else
_takeProfitPrice = null;
}
private void SetupShortTargets(decimal price)
{
_entryPrice = price;
if (StopLossPips > 0m)
_stopPrice = price + StopLossPips * _pipSize;
else
_stopPrice = null;
if (TakeProfitPips > 0m)
_takeProfitPrice = price - TakeProfitPips * _pipSize;
else
_takeProfitPrice = null;
}
private void UpdateTrailing(ICandleMessage candle)
{
if (_entryPrice is not decimal entry)
return;
var breakevenDist = BreakevenPips > 0m ? BreakevenPips * _pipSize : 0m;
var trailingDist = TrailingStopPips > 0m ? TrailingStopPips * _pipSize : 0m;
if (Position > 0m)
{
var move = candle.ClosePrice - entry;
if (breakevenDist > 0m && move > breakevenDist)
{
if (_stopPrice is null || _stopPrice < entry)
_stopPrice = entry;
}
if (trailingDist > 0m)
{
var activation = breakevenDist + trailingDist;
if (move > activation)
{
var newStop = candle.ClosePrice - trailingDist;
if (_stopPrice is null || newStop > _stopPrice)
_stopPrice = newStop;
}
}
}
else if (Position < 0m)
{
var move = entry - candle.ClosePrice;
if (breakevenDist > 0m && move > breakevenDist)
{
if (_stopPrice is null || _stopPrice > entry)
_stopPrice = entry;
}
if (trailingDist > 0m)
{
var activation = breakevenDist + trailingDist;
if (move > activation)
{
var newStop = candle.ClosePrice + trailingDist;
if (_stopPrice is null || newStop < _stopPrice)
_stopPrice = newStop;
}
}
}
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
}
private decimal CalculatePipSize()
{
var step = Security?.PriceStep ?? 0.0001m;
if (step <= 0m)
step = 0.0001m;
var tmp = step;
var decimals = 0;
while (tmp < 1m && decimals < 10)
{
tmp *= 10m;
decimals++;
}
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
private static IIndicator CreateMovingAverage(MovingAverageTypeOptions type, int length)
{
return type switch
{
MovingAverageTypeOptions.Simple => new SimpleMovingAverage { Length = length },
MovingAverageTypeOptions.Exponential => new ExponentialMovingAverage { Length = length },
MovingAverageTypeOptions.Smoothed => new SmoothedMovingAverage { Length = length },
MovingAverageTypeOptions.Weighted => new WeightedMovingAverage { Length = length },
_ => new SimpleMovingAverage { Length = length }
};
}
/// <summary>
/// Moving average options matching the MetaTrader configuration.
/// </summary>
public enum MovingAverageTypeOptions
{
Simple,
Exponential,
Smoothed,
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, ExponentialMovingAverage, AccumulationDistributionLine
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
from indicator_extensions import *
class pipsover_chaikin_hedge_strategy(Strategy):
def __init__(self):
super(pipsover_chaikin_hedge_strategy, self).__init__()
self._open_level = self.Param("OpenLevel", 0.01).SetGreaterThanZero().SetDisplay("Open Level", "Chaikin level for entries", "Chaikin")
self._close_level = self.Param("CloseLevel", 0.02).SetGreaterThanZero().SetDisplay("Close Level", "Chaikin level for hedging", "Chaikin")
self._sl_pips = self.Param("StopLossPips", 65.0).SetDisplay("Stop Loss (pips)", "Stop-loss distance", "Risk")
self._tp_pips = self.Param("TakeProfitPips", 100.0).SetDisplay("Take Profit (pips)", "Take-profit distance", "Risk")
self._ma_period = self.Param("MaPeriod", 20).SetGreaterThanZero().SetDisplay("MA Period", "Price MA length", "Trend")
self._chaikin_fast = self.Param("ChaikinFastPeriod", 3).SetGreaterThanZero().SetDisplay("Chaikin Fast", "Fast Chaikin length", "Chaikin")
self._chaikin_slow = self.Param("ChaikinSlowPeriod", 10).SetGreaterThanZero().SetDisplay("Chaikin Slow", "Slow Chaikin length", "Chaikin")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))).SetDisplay("Candle Type", "Timeframe", "Data")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(pipsover_chaikin_hedge_strategy, self).OnReseted()
self._pip_size = 0
self._entry_price = None
self._stop_price = None
self._tp_price = None
self._prev_open = 0
self._prev_close = 0
self._prev_high = 0
self._prev_low = 0
self._prev_chaikin = 0
self._has_prev = False
self._has_prev_chaikin = False
def OnStarted2(self, time):
super(pipsover_chaikin_hedge_strategy, self).OnStarted2(time)
self._pip_size = self._calc_pip_size()
self._entry_price = None
self._stop_price = None
self._tp_price = None
self._prev_open = 0
self._prev_close = 0
self._prev_high = 0
self._prev_low = 0
self._prev_chaikin = 0
self._has_prev = False
self._has_prev_chaikin = False
self._adl = AccumulationDistributionLine()
self._price_ma = SimpleMovingAverage()
self._price_ma.Length = self._ma_period.Value
self._ema_fast = ExponentialMovingAverage()
self._ema_fast.Length = self._chaikin_fast.Value
self._ema_slow = ExponentialMovingAverage()
self._ema_slow.Length = self._chaikin_slow.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._price_ma, self._adl, self.OnProcess).Start()
def _calc_pip_size(self):
step = 0.0001
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
return step
def OnProcess(self, candle, ma_val, ad_val):
if candle.State != CandleStates.Finished:
return
t = candle.ServerTime
fast_res = process_float(self._ema_fast, Decimal(float(ad_val)), t, True)
slow_res = process_float(self._ema_slow, Decimal(float(ad_val)), t, True)
chaikin = float(fast_res.Value) - float(slow_res.Value)
if not self._ema_fast.IsFormed or not self._ema_slow.IsFormed:
self._prev_chaikin = chaikin
self._has_prev_chaikin = True
self._store_candle(candle)
return
has_prev_data = self._has_prev and self._has_prev_chaikin
self._handle_stops(candle)
if self.Position == 0 and has_prev_data:
bullish = self._prev_close > self._prev_open
bearish = self._prev_close < self._prev_open
if bullish and self._prev_low < ma_val and self._prev_chaikin < -self._open_level.Value:
self.BuyMarket()
self._setup_long(candle.ClosePrice)
elif bearish and self._prev_high > ma_val and self._prev_chaikin > self._open_level.Value:
self.SellMarket()
self._setup_short(candle.ClosePrice)
elif self.Position != 0 and has_prev_data:
bearish = self._prev_close < self._prev_open
bullish = self._prev_close > self._prev_open
if self.Position > 0 and bearish and self._prev_high > ma_val and self._prev_chaikin > self._close_level.Value:
size = Math.Abs(self.Position) + self.Volume
self.SellMarket(size)
self._setup_short(candle.ClosePrice)
elif self.Position < 0 and bullish and self._prev_low < ma_val and self._prev_chaikin < -self._close_level.Value:
size = Math.Abs(self.Position) + self.Volume
self.BuyMarket(size)
self._setup_long(candle.ClosePrice)
self._prev_chaikin = chaikin
self._has_prev_chaikin = True
self._store_candle(candle)
def _store_candle(self, candle):
self._prev_open = float(candle.OpenPrice)
self._prev_close = float(candle.ClosePrice)
self._prev_high = float(candle.HighPrice)
self._prev_low = float(candle.LowPrice)
self._has_prev = True
def _handle_stops(self, candle):
if self.Position > 0:
if self._stop_price is not None and candle.LowPrice <= self._stop_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_state()
elif self._tp_price is not None and candle.HighPrice >= self._tp_price:
self.SellMarket(Math.Abs(self.Position))
self._reset_state()
elif self.Position < 0:
if self._stop_price is not None and candle.HighPrice >= self._stop_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_state()
elif self._tp_price is not None and candle.LowPrice <= self._tp_price:
self.BuyMarket(Math.Abs(self.Position))
self._reset_state()
def _setup_long(self, price):
self._entry_price = float(price)
self._stop_price = self._entry_price - self._sl_pips.Value * self._pip_size if self._sl_pips.Value > 0 else None
self._tp_price = self._entry_price + self._tp_pips.Value * self._pip_size if self._tp_pips.Value > 0 else None
def _setup_short(self, price):
self._entry_price = float(price)
self._stop_price = self._entry_price + self._sl_pips.Value * self._pip_size if self._sl_pips.Value > 0 else None
self._tp_price = self._entry_price - self._tp_pips.Value * self._pip_size if self._tp_pips.Value > 0 else None
def _reset_state(self):
self._entry_price = None
self._stop_price = None
self._tp_price = None
def CreateClone(self):
return pipsover_chaikin_hedge_strategy()