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 synthetic candles generated by Center of Gravity calculation.
/// Uses SMA and LWMA products smoothed to create synthetic OHLC, then trades color changes.
/// </summary>
public class CenterOfGravityCandleStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _period;
private readonly StrategyParam<int> _smoothPeriod;
// Internal indicators for computing synthetic candle
private SimpleMovingAverage _openSma;
private SimpleMovingAverage _highSma;
private SimpleMovingAverage _lowSma;
private SimpleMovingAverage _closeSma;
private WeightedMovingAverage _openLwma;
private WeightedMovingAverage _highLwma;
private WeightedMovingAverage _lowLwma;
private WeightedMovingAverage _closeLwma;
private SimpleMovingAverage _openSmooth;
private SimpleMovingAverage _highSmooth;
private SimpleMovingAverage _lowSmooth;
private SimpleMovingAverage _closeSmooth;
private int _prevColor; // 0=neutral, 1=bullish, -1=bearish
private bool _hasPrev;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Period { get => _period.Value; set => _period.Value = value; }
public int SmoothPeriod { get => _smoothPeriod.Value; set => _smoothPeriod.Value = value; }
public CenterOfGravityCandleStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_period = Param(nameof(Period), 10)
.SetGreaterThanZero()
.SetDisplay("CoG Period", "Center of gravity period", "Indicator");
_smoothPeriod = Param(nameof(SmoothPeriod), 1)
.SetGreaterThanZero()
.SetDisplay("Smooth Period", "Smoothing period", "Indicator");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevColor = 0;
_hasPrev = false;
_openSma = null;
_highSma = null;
_lowSma = null;
_closeSma = null;
_openLwma = null;
_highLwma = null;
_lowLwma = null;
_closeLwma = null;
_openSmooth = null;
_highSmooth = null;
_lowSmooth = null;
_closeSmooth = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var len = Period;
var sLen = SmoothPeriod;
_openSma = new SMA { Length = len };
_highSma = new SMA { Length = len };
_lowSma = new SMA { Length = len };
_closeSma = new SMA { Length = len };
_openLwma = new WeightedMovingAverage { Length = len };
_highLwma = new WeightedMovingAverage { Length = len };
_lowLwma = new WeightedMovingAverage { Length = len };
_closeLwma = new WeightedMovingAverage { Length = len };
_openSmooth = new SMA { Length = sLen };
_highSmooth = new SMA { Length = sLen };
_lowSmooth = new SMA { Length = sLen };
_closeSmooth = new SMA { Length = sLen };
_prevColor = 0;
_hasPrev = false;
var sub = SubscribeCandles(CandleType);
sub.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, sub);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var t = candle.OpenTime;
var point = Security?.PriceStep ?? 1m;
if (point <= 0m) point = 1m;
// Compute SMA and LWMA for each OHLC component
var oSma = _openSma.Process(candle.OpenPrice, t, true);
var oLwma = _openLwma.Process(candle.OpenPrice, t, true);
var hSma = _highSma.Process(candle.HighPrice, t, true);
var hLwma = _highLwma.Process(candle.HighPrice, t, true);
var lSma = _lowSma.Process(candle.LowPrice, t, true);
var lLwma = _lowLwma.Process(candle.LowPrice, t, true);
var cSma = _closeSma.Process(candle.ClosePrice, t, true);
var cLwma = _closeLwma.Process(candle.ClosePrice, t, true);
if (!_openSma.IsFormed || !_openLwma.IsFormed ||
!_highSma.IsFormed || !_highLwma.IsFormed ||
!_lowSma.IsFormed || !_lowLwma.IsFormed ||
!_closeSma.IsFormed || !_closeLwma.IsFormed)
return;
// Use average of SMA and LWMA instead of product (avoids overflow with high prices)
var openProd = (oSma.ToDecimal() + oLwma.ToDecimal()) / 2m;
var highProd = (hSma.ToDecimal() + hLwma.ToDecimal()) / 2m;
var lowProd = (lSma.ToDecimal() + lLwma.ToDecimal()) / 2m;
var closeProd = (cSma.ToDecimal() + cLwma.ToDecimal()) / 2m;
// Smooth
var oSmooth = _openSmooth.Process(openProd, t, true);
var hSmooth = _highSmooth.Process(highProd, t, true);
var lSmooth = _lowSmooth.Process(lowProd, t, true);
var cSmooth = _closeSmooth.Process(closeProd, t, true);
if (!_openSmooth.IsFormed || !_highSmooth.IsFormed ||
!_lowSmooth.IsFormed || !_closeSmooth.IsFormed)
return;
var openVal = oSmooth.ToDecimal();
var closeVal = cSmooth.ToDecimal();
// Determine color
int color;
if (openVal < closeVal)
color = 1; // bullish
else if (openVal > closeVal)
color = -1; // bearish
else
color = 0; // neutral
if (_hasPrev)
{
// Bullish color change
if (color == 1 && _prevColor != 1 && Position <= 0)
BuyMarket();
// Bearish color change
else if (color == -1 && _prevColor != -1 && Position >= 0)
SellMarket();
}
_prevColor = color;
_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 SimpleMovingAverage, WeightedMovingAverage
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class center_of_gravity_candle_strategy(Strategy):
"""
Trades synthetic candles generated by Center of Gravity calculation.
Uses SMA and LWMA averages smoothed to create synthetic OHLC, then trades color changes.
"""
def __init__(self):
super(center_of_gravity_candle_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._period = self.Param("Period", 10) \
.SetDisplay("CoG Period", "Center of gravity period", "Indicator")
self._smooth_period = self.Param("SmoothPeriod", 1) \
.SetDisplay("Smooth Period", "Smoothing period", "Indicator")
self._prev_color = 0
self._has_prev = False
self._open_sma = None
self._high_sma = None
self._low_sma = None
self._close_sma = None
self._open_lwma = None
self._high_lwma = None
self._low_lwma = None
self._close_lwma = None
self._open_smooth = None
self._high_smooth = None
self._low_smooth = None
self._close_smooth = None
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(center_of_gravity_candle_strategy, self).OnReseted()
self._prev_color = 0
self._has_prev = False
self._open_sma = None
self._high_sma = None
self._low_sma = None
self._close_sma = None
self._open_lwma = None
self._high_lwma = None
self._low_lwma = None
self._close_lwma = None
self._open_smooth = None
self._high_smooth = None
self._low_smooth = None
self._close_smooth = None
def OnStarted2(self, time):
super(center_of_gravity_candle_strategy, self).OnStarted2(time)
length = self._period.Value
s_len = self._smooth_period.Value
self._open_sma = SimpleMovingAverage()
self._open_sma.Length = length
self._high_sma = SimpleMovingAverage()
self._high_sma.Length = length
self._low_sma = SimpleMovingAverage()
self._low_sma.Length = length
self._close_sma = SimpleMovingAverage()
self._close_sma.Length = length
self._open_lwma = WeightedMovingAverage()
self._open_lwma.Length = length
self._high_lwma = WeightedMovingAverage()
self._high_lwma.Length = length
self._low_lwma = WeightedMovingAverage()
self._low_lwma.Length = length
self._close_lwma = WeightedMovingAverage()
self._close_lwma.Length = length
self._open_smooth = SimpleMovingAverage()
self._open_smooth.Length = s_len
self._high_smooth = SimpleMovingAverage()
self._high_smooth.Length = s_len
self._low_smooth = SimpleMovingAverage()
self._low_smooth.Length = s_len
self._close_smooth = SimpleMovingAverage()
self._close_smooth.Length = s_len
self._prev_color = 0
self._has_prev = False
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self.on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def on_process(self, candle):
if candle.State != CandleStates.Finished:
return
t = candle.OpenTime
def _proc(ind, val):
return process_float(ind, val, t, True)
o_sma = _proc(self._open_sma, candle.OpenPrice)
o_lwma = _proc(self._open_lwma, candle.OpenPrice)
h_sma = _proc(self._high_sma, candle.HighPrice)
h_lwma = _proc(self._high_lwma, candle.HighPrice)
l_sma = _proc(self._low_sma, candle.LowPrice)
l_lwma = _proc(self._low_lwma, candle.LowPrice)
c_sma = _proc(self._close_sma, candle.ClosePrice)
c_lwma = _proc(self._close_lwma, candle.ClosePrice)
if (not self._open_sma.IsFormed or not self._open_lwma.IsFormed or
not self._high_sma.IsFormed or not self._high_lwma.IsFormed or
not self._low_sma.IsFormed or not self._low_lwma.IsFormed or
not self._close_sma.IsFormed or not self._close_lwma.IsFormed):
return
open_prod = (float(o_sma) + float(o_lwma)) / 2.0
high_prod = (float(h_sma) + float(h_lwma)) / 2.0
low_prod = (float(l_sma) + float(l_lwma)) / 2.0
close_prod = (float(c_sma) + float(c_lwma)) / 2.0
o_smooth = _proc(self._open_smooth, open_prod)
h_smooth = _proc(self._high_smooth, high_prod)
l_smooth = _proc(self._low_smooth, low_prod)
c_smooth = _proc(self._close_smooth, close_prod)
if (not self._open_smooth.IsFormed or not self._high_smooth.IsFormed or
not self._low_smooth.IsFormed or not self._close_smooth.IsFormed):
return
open_val = float(o_smooth)
close_val = float(c_smooth)
if open_val < close_val:
color = 1
elif open_val > close_val:
color = -1
else:
color = 0
if self._has_prev:
if color == 1 and self._prev_color != 1 and self.Position <= 0:
self.BuyMarket()
elif color == -1 and self._prev_color != -1 and self.Position >= 0:
self.SellMarket()
self._prev_color = color
self._has_prev = True
def CreateClone(self):
return center_of_gravity_candle_strategy()