La Color Coppock Strategy implementa un sistema de trading basado en un oscilador Coppock modificado. El oscilador suma dos valores de Rate of Change (ROC) y suaviza el resultado con una media móvil. El momentum ascendente genera señales largas, mientras que el momentum descendente genera señales cortas.
Cómo Funciona
Calcular dos valores ROC con diferentes períodos.
Sumar ambos valores ROC y aplicar una Media Móvil Simple para el suavizado.
Comparar el valor actual del oscilador con los dos valores anteriores:
Si el oscilador gira hacia arriba después de descender, la estrategia entra en una posición larga o cierra la posición corta existente.
Si el oscilador gira hacia abajo después de subir, la estrategia entra en una posición corta o cierra la posición larga existente.
El volumen de la posición se toma de la propiedad Volume de la estrategia.
Parámetros
Nombre
Descripción
Roc1Period
Período para el primer cálculo ROC.
Roc2Period
Período para el segundo cálculo ROC.
SmoothingPeriod
Período SMA aplicado a la suma de ambos valores ROC.
CandleType
Tipo de vela utilizado para los cálculos del indicador.
Uso
Adjuntar la estrategia a un valor y establecer los parámetros deseados.
La estrategia se suscribe a las velas especificadas y procesa solo las velas finalizadas.
Las operaciones se ejecutan con órdenes de mercado usando el volumen predeterminado.
Notas
La estrategia utiliza solo llamadas de API de alto nivel como SubscribeCandles y ayudantes de órdenes de mercado.
Todos los comentarios dentro del código están escritos en inglés.
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 the Coppock Curve oscillator.
/// Uses ROC sum smoothed by SMA.
/// </summary>
public class ColorCoppockStrategy : Strategy
{
private readonly StrategyParam<int> _roc1Period;
private readonly StrategyParam<int> _roc2Period;
private readonly StrategyParam<int> _smoothingPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closes = new();
private readonly List<decimal> _coppockValues = new();
private decimal? _prevCoppock;
private decimal? _prevPrevCoppock;
public int Roc1Period { get => _roc1Period.Value; set => _roc1Period.Value = value; }
public int Roc2Period { get => _roc2Period.Value; set => _roc2Period.Value = value; }
public int SmoothingPeriod { get => _smoothingPeriod.Value; set => _smoothingPeriod.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ColorCoppockStrategy()
{
_roc1Period = Param(nameof(Roc1Period), 14)
.SetDisplay("ROC1 Period", "First ROC calculation period", "Parameters");
_roc2Period = Param(nameof(Roc2Period), 10)
.SetDisplay("ROC2 Period", "Second ROC calculation period", "Parameters");
_smoothingPeriod = Param(nameof(SmoothingPeriod), 10)
.SetDisplay("Smoothing Period", "SMA period for ROC sum", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame for processing", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_closes.Clear();
_coppockValues.Clear();
_prevCoppock = _prevPrevCoppock = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var sma = new ExponentialMovingAverage { Length = Roc1Period };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(sma, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal smaValue)
{
if (candle.State != CandleStates.Finished)
return;
_closes.Add(candle.ClosePrice);
var maxPeriod = Math.Max(Roc1Period, Roc2Period);
if (_closes.Count > maxPeriod + SmoothingPeriod + 5)
_closes.RemoveAt(0);
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_closes.Count <= maxPeriod)
return;
// Calculate ROC values
var idx = _closes.Count - 1;
decimal roc1 = 0, roc2 = 0;
if (idx >= Roc1Period && _closes[idx - Roc1Period] != 0)
roc1 = (_closes[idx] - _closes[idx - Roc1Period]) / _closes[idx - Roc1Period] * 100m;
if (idx >= Roc2Period && _closes[idx - Roc2Period] != 0)
roc2 = (_closes[idx] - _closes[idx - Roc2Period]) / _closes[idx - Roc2Period] * 100m;
_coppockValues.Add(roc1 + roc2);
if (_coppockValues.Count > SmoothingPeriod + 5)
_coppockValues.RemoveAt(0);
if (_coppockValues.Count < SmoothingPeriod)
return;
// SMA of ROC sum
var coppock = _coppockValues.Skip(_coppockValues.Count - SmoothingPeriod).Average();
if (_prevCoppock is decimal prev && _prevPrevCoppock is decimal prevPrev)
{
// Buy when Coppock turns up from a bottom
if (prev < prevPrev && coppock > prev && Position <= 0)
BuyMarket();
// Sell when Coppock turns down from a top
else if (prev > prevPrev && coppock < prev && Position >= 0)
SellMarket();
}
_prevPrevCoppock = _prevCoppock;
_prevCoppock = coppock;
}
}
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
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class color_coppock_strategy(Strategy):
def __init__(self):
super(color_coppock_strategy, self).__init__()
self._roc1_period = self.Param("Roc1Period", 14) \
.SetDisplay("ROC1 Period", "First ROC calculation period", "Parameters")
self._roc2_period = self.Param("Roc2Period", 10) \
.SetDisplay("ROC2 Period", "Second ROC calculation period", "Parameters")
self._smoothing_period = self.Param("SmoothingPeriod", 10) \
.SetDisplay("Smoothing Period", "SMA period for ROC sum", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Time frame for processing", "General")
self._closes = []
self._coppock_values = []
self._prev_coppock = None
self._prev_prev_coppock = None
@property
def roc1_period(self):
return self._roc1_period.Value
@property
def roc2_period(self):
return self._roc2_period.Value
@property
def smoothing_period(self):
return self._smoothing_period.Value
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(color_coppock_strategy, self).OnReseted()
self._closes = []
self._coppock_values = []
self._prev_coppock = None
self._prev_prev_coppock = None
def OnStarted2(self, time):
super(color_coppock_strategy, self).OnStarted2(time)
sma = ExponentialMovingAverage()
sma.Length = int(self.roc1_period)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(sma, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def process_candle(self, candle, sma_value):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
self._closes.append(close)
r1p = int(self.roc1_period)
r2p = int(self.roc2_period)
sp = int(self.smoothing_period)
max_period = max(r1p, r2p)
if len(self._closes) > max_period + sp + 5:
self._closes.pop(0)
if len(self._closes) <= max_period:
return
idx = len(self._closes) - 1
roc1 = 0.0
roc2 = 0.0
if idx >= r1p and self._closes[idx - r1p] != 0.0:
roc1 = (self._closes[idx] - self._closes[idx - r1p]) / self._closes[idx - r1p] * 100.0
if idx >= r2p and self._closes[idx - r2p] != 0.0:
roc2 = (self._closes[idx] - self._closes[idx - r2p]) / self._closes[idx - r2p] * 100.0
self._coppock_values.append(roc1 + roc2)
if len(self._coppock_values) > sp + 5:
self._coppock_values.pop(0)
if len(self._coppock_values) < sp:
return
# SMA of ROC sum
coppock = sum(self._coppock_values[-sp:]) / sp
if self._prev_coppock is not None and self._prev_prev_coppock is not None:
# Buy when Coppock turns up from bottom
if self._prev_coppock < self._prev_prev_coppock and coppock > self._prev_coppock and self.Position <= 0:
self.BuyMarket()
# Sell when Coppock turns down from top
elif self._prev_coppock > self._prev_prev_coppock and coppock < self._prev_coppock and self.Position >= 0:
self.SellMarket()
self._prev_prev_coppock = self._prev_coppock
self._prev_coppock = coppock
def CreateClone(self):
return color_coppock_strategy()