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>
/// Strategy that trades pullbacks using the Center of Gravity oscillator on two timeframes.
/// </summary>
public class CgOscillatorX2Strategy : Strategy
{
private readonly StrategyParam<DataType> _trendCandleType;
private readonly StrategyParam<DataType> _signalCandleType;
private readonly StrategyParam<int> _trendLength;
private readonly StrategyParam<int> _signalLength;
private readonly StrategyParam<bool> _buyOpen;
private readonly StrategyParam<bool> _sellOpen;
private readonly StrategyParam<bool> _buyClose;
private readonly StrategyParam<bool> _sellClose;
private readonly StrategyParam<bool> _buyCloseSignal;
private readonly StrategyParam<bool> _sellCloseSignal;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _signalCooldownBars;
private CenterOfGravityOscillator _trendIndicator;
private CenterOfGravityOscillator _signalIndicator;
private int _trendDirection;
private decimal? _trendPrevCg;
private decimal? _signalPrevCg;
private decimal? _signalPrevPrevCg;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
private int _cooldownRemaining;
public DataType TrendCandleType { get => _trendCandleType.Value; set => _trendCandleType.Value = value; }
public DataType SignalCandleType { get => _signalCandleType.Value; set => _signalCandleType.Value = value; }
public int TrendLength { get => _trendLength.Value; set => _trendLength.Value = value; }
public int SignalLength { get => _signalLength.Value; set => _signalLength.Value = value; }
public bool BuyOpen { get => _buyOpen.Value; set => _buyOpen.Value = value; }
public bool SellOpen { get => _sellOpen.Value; set => _sellOpen.Value = value; }
public bool BuyClose { get => _buyClose.Value; set => _buyClose.Value = value; }
public bool SellClose { get => _sellClose.Value; set => _sellClose.Value = value; }
public bool BuyCloseSignal { get => _buyCloseSignal.Value; set => _buyCloseSignal.Value = value; }
public bool SellCloseSignal { get => _sellCloseSignal.Value; set => _sellCloseSignal.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public int SignalCooldownBars { get => _signalCooldownBars.Value; set => _signalCooldownBars.Value = value; }
public CgOscillatorX2Strategy()
{
_trendCandleType = Param(nameof(TrendCandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Trend Candle Type", "Higher timeframe for trend detection", "General");
_signalCandleType = Param(nameof(SignalCandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Signal Candle Type", "Lower timeframe for trade execution", "General");
_trendLength = Param(nameof(TrendLength), 10)
.SetGreaterThanZero()
.SetDisplay("Trend Length", "CG length on the trend timeframe", "Indicator");
_signalLength = Param(nameof(SignalLength), 10)
.SetGreaterThanZero()
.SetDisplay("Signal Length", "CG length on the signal timeframe", "Indicator");
_buyOpen = Param(nameof(BuyOpen), true)
.SetDisplay("Allow Long Entries", "Enable long entries during uptrend", "Trading");
_sellOpen = Param(nameof(SellOpen), true)
.SetDisplay("Allow Short Entries", "Enable short entries during downtrend", "Trading");
_buyClose = Param(nameof(BuyClose), true)
.SetDisplay("Close Long On Trend Flip", "Exit long positions when higher trend turns bearish", "Trading");
_sellClose = Param(nameof(SellClose), true)
.SetDisplay("Close Short On Trend Flip", "Exit short positions when higher trend turns bullish", "Trading");
_buyCloseSignal = Param(nameof(BuyCloseSignal), false)
.SetDisplay("Close Long On Pullback", "Exit long positions when the oscillator confirms a bearish hook", "Trading");
_sellCloseSignal = Param(nameof(SellCloseSignal), false)
.SetDisplay("Close Short On Pullback", "Exit short positions when the oscillator confirms a bullish hook", "Trading");
_stopLoss = Param(nameof(StopLoss), 0m)
.SetNotNegative()
.SetDisplay("Stop Loss Distance", "Absolute stop-loss distance in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 0m)
.SetNotNegative()
.SetDisplay("Take Profit Distance", "Absolute take-profit distance in price units", "Risk");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 6)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed signal candles to wait before a new entry", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
yield return (Security, TrendCandleType);
if (!TrendCandleType.Equals(SignalCandleType))
yield return (Security, SignalCandleType);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_trendIndicator = new CenterOfGravityOscillator
{
Length = TrendLength
};
_signalIndicator = new CenterOfGravityOscillator
{
Length = SignalLength
};
SubscribeCandles(TrendCandleType)
.BindEx(_trendIndicator, ProcessTrend)
.Start();
SubscribeCandles(SignalCandleType)
.BindEx(_signalIndicator, ProcessSignal)
.Start();
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_trendDirection = 0;
_trendPrevCg = null;
_signalPrevCg = null;
_signalPrevPrevCg = null;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
_cooldownRemaining = 0;
}
private void ProcessTrend(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_trendIndicator.IsFormed)
return;
var cgValue = value.GetValue<decimal>();
var prevCg = _trendPrevCg;
_trendPrevCg = cgValue;
if (cgValue > 0)
_trendDirection = 1;
else if (cgValue < 0)
_trendDirection = -1;
else
_trendDirection = 0;
}
private void ProcessSignal(ICandleMessage candle, IIndicatorValue value)
{
if (candle.State != CandleStates.Finished)
return;
if (!_signalIndicator.IsFormed)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
var cgValue = value.GetValue<decimal>();
var prevCg = _signalPrevCg;
var prevPrevCg = _signalPrevPrevCg;
_signalPrevPrevCg = _signalPrevCg;
_signalPrevCg = cgValue;
if (prevCg is null)
return;
if (TryCloseByRisk(candle))
return;
var closeBuy = BuyCloseSignal && prevCg < 0;
var closeSell = SellCloseSignal && prevCg > 0;
var openBuy = false;
var openSell = false;
var bullishHook = prevPrevCg.HasValue && prevPrevCg.Value >= prevCg && cgValue > prevCg;
var bearishHook = prevPrevCg.HasValue && prevPrevCg.Value <= prevCg && cgValue < prevCg;
if (_trendDirection < 0)
{
if (BuyClose)
closeBuy = true;
if (_cooldownRemaining == 0 && SellOpen && bearishHook)
openSell = true;
}
else if (_trendDirection > 0)
{
if (SellClose)
closeSell = true;
if (_cooldownRemaining == 0 && BuyOpen && bullishHook)
openBuy = true;
}
if (closeBuy && Position > 0)
{
SellMarket(Position);
ResetRiskTargets();
}
if (closeSell && Position < 0)
{
BuyMarket(-Position);
ResetRiskTargets();
}
if (openBuy && Position <= 0)
{
var volume = Volume + (Position < 0 ? Math.Abs(Position) : 0m);
BuyMarket(volume);
SetRiskTargets(candle.ClosePrice, true);
_cooldownRemaining = SignalCooldownBars;
}
else if (openSell && Position >= 0)
{
var volume = Volume + (Position > 0 ? Math.Abs(Position) : 0m);
SellMarket(volume);
SetRiskTargets(candle.ClosePrice, false);
_cooldownRemaining = SignalCooldownBars;
}
}
private bool TryCloseByRisk(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket(Position);
ResetRiskTargets();
return true;
}
if (_takePrice is decimal take && candle.HighPrice >= take)
{
SellMarket(Position);
ResetRiskTargets();
return true;
}
}
else if (Position < 0)
{
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket(-Position);
ResetRiskTargets();
return true;
}
if (_takePrice is decimal take && candle.LowPrice <= take)
{
BuyMarket(-Position);
ResetRiskTargets();
return true;
}
}
return false;
}
private void SetRiskTargets(decimal entryPrice, bool isLong)
{
_entryPrice = entryPrice;
if (StopLoss > 0m)
_stopPrice = isLong ? entryPrice - StopLoss : entryPrice + StopLoss;
else
_stopPrice = null;
if (TakeProfit > 0m)
_takePrice = isLong ? entryPrice + TakeProfit : entryPrice - TakeProfit;
else
_takePrice = null;
}
private void ResetRiskTargets()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
}
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
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import CenterOfGravityOscillator
from StockSharp.Algo.Strategies import Strategy
class cg_oscillator_x2_strategy(Strategy):
def __init__(self):
super(cg_oscillator_x2_strategy, self).__init__()
self._trend_candle_type = self.Param("TrendCandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._signal_candle_type = self.Param("SignalCandleType", DataType.TimeFrame(TimeSpan.FromHours(2)))
self._trend_length = self.Param("TrendLength", 10)
self._signal_length = self.Param("SignalLength", 10)
self._buy_open = self.Param("BuyOpen", True)
self._sell_open = self.Param("SellOpen", True)
self._buy_close = self.Param("BuyClose", True)
self._sell_close = self.Param("SellClose", True)
self._buy_close_signal = self.Param("BuyCloseSignal", False)
self._sell_close_signal = self.Param("SellCloseSignal", False)
self._stop_loss = self.Param("StopLoss", 0.0)
self._take_profit = self.Param("TakeProfit", 0.0)
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 6)
self._trend_direction = 0
self._trend_prev_cg = None
self._signal_prev_cg = None
self._signal_prev_prev_cg = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._cooldown_remaining = 0
@property
def TrendCandleType(self):
return self._trend_candle_type.Value
@TrendCandleType.setter
def TrendCandleType(self, value):
self._trend_candle_type.Value = value
@property
def SignalCandleType(self):
return self._signal_candle_type.Value
@SignalCandleType.setter
def SignalCandleType(self, value):
self._signal_candle_type.Value = value
@property
def TrendLength(self):
return self._trend_length.Value
@TrendLength.setter
def TrendLength(self, value):
self._trend_length.Value = value
@property
def SignalLength(self):
return self._signal_length.Value
@SignalLength.setter
def SignalLength(self, value):
self._signal_length.Value = value
@property
def BuyOpen(self):
return self._buy_open.Value
@BuyOpen.setter
def BuyOpen(self, value):
self._buy_open.Value = value
@property
def SellOpen(self):
return self._sell_open.Value
@SellOpen.setter
def SellOpen(self, value):
self._sell_open.Value = value
@property
def BuyClose(self):
return self._buy_close.Value
@BuyClose.setter
def BuyClose(self, value):
self._buy_close.Value = value
@property
def SellClose(self):
return self._sell_close.Value
@SellClose.setter
def SellClose(self, value):
self._sell_close.Value = value
@property
def BuyCloseSignal(self):
return self._buy_close_signal.Value
@BuyCloseSignal.setter
def BuyCloseSignal(self, value):
self._buy_close_signal.Value = value
@property
def SellCloseSignal(self):
return self._sell_close_signal.Value
@SellCloseSignal.setter
def SellCloseSignal(self, value):
self._sell_close_signal.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
def OnStarted2(self, time):
super(cg_oscillator_x2_strategy, self).OnStarted2(time)
self._trend_indicator = CenterOfGravityOscillator()
self._trend_indicator.Length = self.TrendLength
self._signal_indicator = CenterOfGravityOscillator()
self._signal_indicator.Length = self.SignalLength
self.SubscribeCandles(self.TrendCandleType).BindEx(self._trend_indicator, self._process_trend).Start()
self.SubscribeCandles(self.SignalCandleType).BindEx(self._signal_indicator, self._process_signal).Start()
def _process_trend(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._trend_indicator.IsFormed:
return
cg_value = float(value)
self._trend_prev_cg = cg_value
if cg_value > 0:
self._trend_direction = 1
elif cg_value < 0:
self._trend_direction = -1
else:
self._trend_direction = 0
def _process_signal(self, candle, value):
if candle.State != CandleStates.Finished:
return
if not self._signal_indicator.IsFormed:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
cg_value = float(value)
prev_cg = self._signal_prev_cg
prev_prev_cg = self._signal_prev_prev_cg
self._signal_prev_prev_cg = self._signal_prev_cg
self._signal_prev_cg = cg_value
if prev_cg is None:
return
if self._try_close_by_risk(candle):
return
close_buy = self.BuyCloseSignal and prev_cg < 0
close_sell = self.SellCloseSignal and prev_cg > 0
open_buy = False
open_sell = False
bullish_hook = prev_prev_cg is not None and prev_prev_cg >= prev_cg and cg_value > prev_cg
bearish_hook = prev_prev_cg is not None and prev_prev_cg <= prev_cg and cg_value < prev_cg
if self._trend_direction < 0:
if self.BuyClose:
close_buy = True
if self._cooldown_remaining == 0 and self.SellOpen and bearish_hook:
open_sell = True
elif self._trend_direction > 0:
if self.SellClose:
close_sell = True
if self._cooldown_remaining == 0 and self.BuyOpen and bullish_hook:
open_buy = True
if close_buy and self.Position > 0:
self.SellMarket()
self._reset_risk_targets()
if close_sell and self.Position < 0:
self.BuyMarket()
self._reset_risk_targets()
close = float(candle.ClosePrice)
if open_buy and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self.BuyMarket()
self._set_risk_targets(close, True)
self._cooldown_remaining = int(self.SignalCooldownBars)
elif open_sell and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self.SellMarket()
self._set_risk_targets(close, False)
self._cooldown_remaining = int(self.SignalCooldownBars)
def _try_close_by_risk(self, candle):
high = float(candle.HighPrice)
low = float(candle.LowPrice)
if self.Position > 0:
if self._stop_price is not None and low <= self._stop_price:
self.SellMarket()
self._reset_risk_targets()
return True
if self._take_price is not None and high >= self._take_price:
self.SellMarket()
self._reset_risk_targets()
return True
elif self.Position < 0:
if self._stop_price is not None and high >= self._stop_price:
self.BuyMarket()
self._reset_risk_targets()
return True
if self._take_price is not None and low <= self._take_price:
self.BuyMarket()
self._reset_risk_targets()
return True
return False
def _set_risk_targets(self, entry_price, is_long):
self._entry_price = entry_price
sl = float(self.StopLoss)
tp = float(self.TakeProfit)
if sl > 0.0:
self._stop_price = entry_price - sl if is_long else entry_price + sl
else:
self._stop_price = None
if tp > 0.0:
self._take_price = entry_price + tp if is_long else entry_price - tp
else:
self._take_price = None
def _reset_risk_targets(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def OnReseted(self):
super(cg_oscillator_x2_strategy, self).OnReseted()
self._trend_direction = 0
self._trend_prev_cg = None
self._signal_prev_cg = None
self._signal_prev_prev_cg = None
self._entry_price = None
self._stop_price = None
self._take_price = None
self._cooldown_remaining = 0
def CreateClone(self):
return cg_oscillator_x2_strategy()