COSTAR-Strategie
Die COSTAR-Strategie erstellt eine lineare Regression der Schlusskurse und misst die Standardabweichung der Residuen. Obere und untere Bänder werden gebildet, indem die mit einem Faktor multiplizierte Abweichung addiert und subtrahiert wird. Trades versuchen, extreme Abweichungen gegenzuhandeln, und werden geschlossen, wenn der Kurs zur Regressionslinie zurückkehrt.
Details
- Einstiegskriterien:
- Long: Kurs kreuzt über das untere Band.
- Short: Kurs kreuzt unter das obere Band.
- Long/Short: Beide Seiten.
- Ausstiegskriterien:
- Kurs kreuzt zurück durch die Regressionslinie.
- Stops: Keine.
- Standardwerte:
Length= 100Multiplier= 1
- Filter:
- Kategorie: Mean Reversion
- Richtung: Beide
- Indikatoren: Linear Regression, Standard Deviation
- Stops: Nein
- Komplexität: Niedrig
- Zeitrahmen: Beliebig
- Saisonalität: Nein
- Neuronale Netze: Nein
- Divergenz: Nein
- Risikolevel: Mittel
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// COSTAR strategy.
/// A least-squares line is fitted to the last Length closes and the standard deviation of its residuals, times Multiplier,
/// builds bands around the line's current value. A close crossing back above the lower band buys and a close crossing back
/// below the upper band sells short, reversing an opposite position. A long closes when the close crosses above the
/// regression line and a short when it crosses below it.
/// </summary>
public class CostarStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _multiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<decimal> _closes = new();
private decimal? _prevClose;
private decimal? _prevLine;
private decimal? _prevUpper;
private decimal? _prevLower;
/// <summary>
/// Regression length.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Residual deviation multiplier for the bands.
/// </summary>
public decimal Multiplier
{
get => _multiplier.Value;
set => _multiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public CostarStrategy()
{
_length = Param(nameof(Length), 100)
.SetRange(2, 10000)
.SetDisplay("Length", "Regression length", "Indicators");
_multiplier = Param(nameof(Multiplier), 1m)
.SetGreaterThanZero()
.SetDisplay("Multiplier", "Residual deviation multiplier for the bands", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_closes.Clear();
_prevClose = null;
_prevLine = null;
_prevUpper = null;
_prevLower = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
_closes.Enqueue(close);
while (_closes.Count > Length)
_closes.Dequeue();
if (_closes.Count < Length)
{
_prevClose = close;
return;
}
var (line, deviation) = CalculateRegression(_closes.ToArray());
var upper = line + deviation * Multiplier;
var lower = line - deviation * Multiplier;
var prevClose = _prevClose;
var prevLine = _prevLine;
var prevUpper = _prevUpper;
var prevLower = _prevLower;
_prevClose = close;
_prevLine = line;
_prevUpper = upper;
_prevLower = lower;
if (prevClose is not decimal pc || prevLine is not decimal pl || prevUpper is not decimal pu || prevLower is not decimal plo)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var crossAboveLower = pc <= plo && close > lower;
var crossBelowUpper = pc >= pu && close < upper;
if (crossAboveLower && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (crossBelowUpper && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
else if (Position > 0 && pc <= pl && close > line)
SellMarket(Position);
else if (Position < 0 && pc >= pl && close < line)
BuyMarket(-Position);
}
private static (decimal line, decimal deviation) CalculateRegression(decimal[] values)
{
var n = values.Length;
double sumX = 0, sumY = 0, sumXy = 0, sumXx = 0;
for (var i = 0; i < n; i++)
{
var y = (double)values[i];
sumX += i;
sumY += y;
sumXy += i * y;
sumXx += (double)i * i;
}
var denominator = n * sumXx - sumX * sumX;
var slope = denominator == 0 ? 0 : (n * sumXy - sumX * sumY) / denominator;
var intercept = (sumY - slope * sumX) / n;
var sumSquares = 0.0;
for (var i = 0; i < n; i++)
{
var residual = (double)values[i] - (intercept + slope * i);
sumSquares += residual * residual;
}
var line = intercept + slope * (n - 1);
return ((decimal)line, (decimal)Math.Sqrt(sumSquares / n));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
import math
from collections import deque
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class costar_strategy(Strategy):
"""
COSTAR strategy.
A least-squares line is fitted to the last Length closes and the standard deviation of its residuals, times Multiplier,
builds bands around the line's current value. A close crossing back above the lower band buys and a close crossing back
below the upper band sells short, reversing an opposite position. A long closes when the close crosses above the
regression line and a short when it crosses below it.
"""
def __init__(self):
super(costar_strategy, self).__init__()
self._length = self.Param("Length", 100).SetRange(2, 10000).SetDisplay("Length", "Regression length", "Indicators")
self._multiplier = self.Param("Multiplier", 1.0).SetGreaterThanZero().SetDisplay("Multiplier", "Residual deviation multiplier for the bands", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._closes = deque()
self._prev_close = None
self._prev_line = None
self._prev_upper = None
self._prev_lower = None
def OnReseted(self):
super(costar_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(costar_strategy, self).OnStarted2(time)
self._reset_state()
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
length = self._length.Value
self._closes.append(close)
while len(self._closes) > length:
self._closes.popleft()
if len(self._closes) < length:
self._prev_close = close
return
line, deviation = self._calculate_regression(list(self._closes))
multiplier = float(self._multiplier.Value)
upper = line + deviation * multiplier
lower = line - deviation * multiplier
pc = self._prev_close
pl = self._prev_line
pu = self._prev_upper
plo = self._prev_lower
self._prev_close = close
self._prev_line = line
self._prev_upper = upper
self._prev_lower = lower
if pc is None or pl is None or pu is None or plo is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
cross_above_lower = pc <= plo and close > lower
cross_below_upper = pc >= pu and close < upper
if cross_above_lower and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif cross_below_upper and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
elif self.Position > 0 and pc <= pl and close > line:
self.SellMarket(self.Position)
elif self.Position < 0 and pc >= pl and close < line:
self.BuyMarket(-self.Position)
@staticmethod
def _calculate_regression(values):
n = len(values)
sum_x = 0.0
sum_y = 0.0
sum_xy = 0.0
sum_xx = 0.0
for i, y in enumerate(values):
sum_x += i
sum_y += y
sum_xy += i * y
sum_xx += i * i
denominator = n * sum_xx - sum_x * sum_x
slope = 0.0 if denominator == 0 else (n * sum_xy - sum_x * sum_y) / denominator
intercept = (sum_y - slope * sum_x) / n
sum_squares = 0.0
for i, y in enumerate(values):
residual = y - (intercept + slope * i)
sum_squares += residual * residual
line = intercept + slope * (n - 1)
return line, math.sqrt(sum_squares / n)
def CreateClone(self):
return costar_strategy()