Die Color Coppock Strategy implementiert ein Handelssystem auf Basis eines modifizierten Coppock-Oszillators. Der Oszillator summiert zwei Rate-of-Change-Werte (ROC) und glättet das Ergebnis mit einem gleitenden Durchschnitt. Steigendes Momentum erzeugt Long-Signale, fallendes Momentum erzeugt Short-Signale.
Funktionsweise
Berechnung von zwei ROC-Werten mit unterschiedlichen Perioden.
Summierung beider ROC-Werte und Anwendung eines einfachen gleitenden Durchschnitts zur Glättung.
Vergleich des aktuellen Oszillatorwerts mit den zwei vorherigen Werten:
Wenn der Oszillator nach einem Rückgang nach oben dreht, eröffnet die Strategie eine Long-Position oder schließt eine bestehende Short-Position.
Wenn der Oszillator nach einem Anstieg nach unten dreht, eröffnet die Strategie eine Short-Position oder schließt eine bestehende Long-Position.
Das Positionsvolumen wird aus der Volume-Eigenschaft der Strategie entnommen.
Parameter
Name
Beschreibung
Roc1Period
Periode für die erste ROC-Berechnung.
Roc2Period
Periode für die zweite ROC-Berechnung.
SmoothingPeriod
SMA-Periode für die Summe beider ROC-Werte.
CandleType
Kerzentyp für die Indikatorberechnungen.
Verwendung
Strategie einem Wertpapier zuweisen und gewünschte Parameter setzen.
Die Strategie abonniert die angegebenen Kerzen und verarbeitet nur abgeschlossene Kerzen.
Trades werden mit Marktorders unter Verwendung des Standardvolumens ausgeführt.
Hinweise
Die Strategie verwendet nur High-Level-API-Aufrufe wie SubscribeCandles und Marktorder-Helfer.
Alle Kommentare im Code sind auf Englisch verfasst.
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()