Adaptive CG Oscillator X2 Strategy
Uses the Adaptive CG Oscillator on two different timeframes. The higher timeframe defines the prevailing trend while the lower timeframe handles actual entries and exits based on oscillator crossovers.
Details
- Entry Criteria:
- Long: oscillator crosses below its signal line while global trend is up
- Short: oscillator crosses above its signal line while global trend is down
- Long/Short: Both
- Exit Criteria: Opposite signal or explicit close flags
- Stops: No
- Default Values:
TrendAlpha= 0.07mSignalAlpha= 0.07mTrendCandleType= TimeSpan.FromHours(6).TimeFrame()SignalCandleType= TimeSpan.FromMinutes(30).TimeFrame()
- Filters:
- Category: Oscillator
- Direction: Both
- Indicators: Adaptive CG Oscillator
- Stops: No
- Complexity: Intermediate
- Timeframe: Multi-timeframe
- 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>
/// Strategy based on Adaptive Center of Gravity Oscillator.
/// Computes CG oscillator inline and trades on crossovers of CG with its prior value (signal).
/// </summary>
public class AdaptiveCgOscillatorX2Strategy : Strategy
{
private readonly StrategyParam<int> _period;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _prices = new();
private decimal _prevCg;
private decimal _prevPrevCg;
private int _count;
private int _barsSinceSignal;
public int Period { get => _period.Value; set => _period.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public AdaptiveCgOscillatorX2Strategy()
{
_period = Param(nameof(Period), 20)
.SetGreaterThanZero()
.SetDisplay("Period", "Lookback period for CG oscillator", "Parameters")
.SetOptimize(5, 20, 1);
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prices.Clear();
_prevCg = 0m;
_prevPrevCg = 0m;
_count = 0;
_barsSinceSignal = int.MaxValue;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
new Unit(TakeProfit, UnitTypes.Absolute),
new Unit(StopLoss, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
_barsSinceSignal++;
_prices.Add(price);
if (_prices.Count > Period)
_prices.RemoveAt(0);
if (_prices.Count < Period)
return;
// Compute Center of Gravity
decimal num = 0m;
decimal denom = 0m;
for (int i = 0; i < _prices.Count; i++)
{
num += (1 + i) * _prices[i];
denom += _prices[i];
}
var cg = denom != 0 ? -num / denom + (Period + 1m) / 2m : 0m;
_count++;
if (_count < 3)
{
_prevPrevCg = _prevCg;
_prevCg = cg;
return;
}
var longSignal = cg > 0m && cg > _prevCg && _prevCg <= _prevPrevCg;
var shortSignal = cg < 0m && cg < _prevCg && _prevCg >= _prevPrevCg;
if (longSignal && _barsSinceSignal >= 12 && Position <= 0)
{
BuyMarket();
_barsSinceSignal = 0;
}
else if (shortSignal && _barsSinceSignal >= 12 && Position >= 0)
{
SellMarket();
_barsSinceSignal = 0;
}
_prevPrevCg = _prevCg;
_prevCg = cg;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class adaptive_cg_oscillator_x2_strategy(Strategy):
def __init__(self):
super(adaptive_cg_oscillator_x2_strategy, self).__init__()
self._period = self.Param("Period", 20)
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._prices = []
self._prev_cg = 0.0
self._prev_prev_cg = 0.0
self._count = 0
self._bars_since_signal = 999999
@property
def Period(self):
return self._period.Value
@Period.setter
def Period(self, value):
self._period.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 CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(adaptive_cg_oscillator_x2_strategy, self).OnStarted2(time)
self._prices = []
self._prev_cg = 0.0
self._prev_prev_cg = 0.0
self._count = 0
self._bars_since_signal = 999999
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
period = int(self.Period)
self._bars_since_signal += 1
self._prices.append(price)
if len(self._prices) > period:
self._prices.pop(0)
if len(self._prices) < period:
return
num = 0.0
denom = 0.0
for i in range(len(self._prices)):
num += (1 + i) * self._prices[i]
denom += self._prices[i]
if denom != 0.0:
cg = -num / denom + (period + 1.0) / 2.0
else:
cg = 0.0
self._count += 1
if self._count < 3:
self._prev_prev_cg = self._prev_cg
self._prev_cg = cg
return
long_signal = cg > 0.0 and cg > self._prev_cg and self._prev_cg <= self._prev_prev_cg
short_signal = cg < 0.0 and cg < self._prev_cg and self._prev_cg >= self._prev_prev_cg
if long_signal and self._bars_since_signal >= 12 and self.Position <= 0:
self.BuyMarket()
self._bars_since_signal = 0
elif short_signal and self._bars_since_signal >= 12 and self.Position >= 0:
self.SellMarket()
self._bars_since_signal = 0
self._prev_prev_cg = self._prev_cg
self._prev_cg = cg
def OnReseted(self):
super(adaptive_cg_oscillator_x2_strategy, self).OnReseted()
self._prices = []
self._prev_cg = 0.0
self._prev_prev_cg = 0.0
self._count = 0
self._bars_since_signal = 999999
def CreateClone(self):
return adaptive_cg_oscillator_x2_strategy()