COSTAR戦略
COSTAR戦略は終値の線形回帰を構築し、残差の標準偏差を計測します。偏差にファクターを乗じた値を加減することで上限バンドと下限バンドを形成します。取引は極端な偏差に逆張りして行われ、価格が回帰線に戻ったときにイグジットします。
詳細
- エントリー条件:
- ロング: 価格が下限バンドを上抜け。
- ショート: 価格が上限バンドを下抜け。
- ロング/ショート: 両方。
- エグジット条件:
- 価格が回帰線を再度クロス。
- ストップ: なし。
- デフォルト値:
Length= 100Multiplier= 1
- フィルター:
- カテゴリ: 平均回帰
- 方向: 両方
- インジケーター: Linear Regression, Standard Deviation
- ストップ: いいえ
- 複雑さ: 低
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
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()