線形クロス取引戦略
この戦略は出来高に基づいた価格の線形回帰を計算して予測価格を生成します。予測価格が加重移動平均線を上抜け、かつ MACD ラインがシグナルを上回って上昇しているときにロングポジションを建てます。MACD ラインがシグナルを下回って低下し、直近の安値が切り下がっているときにショートポジションを建てます。
詳細
- エントリー条件:
- ロング: 予測価格が WMA を上抜け、かつ MACD がシグナルを上回って上昇。
- ショート: MACD がシグナルを下回って低下し、安値が切り下がる。
- ロング/ショート: 両方向。
- エグジット条件: なし。逆方向のシグナルでポジションを反転。
- ストップ: いいえ。
- デフォルト値:
Length= 21.LinearLength= 9.CandleType= TimeSpan.FromMinutes(5).TimeFrame().
- フィルター:
- カテゴリ: トレンド
- 方向: 両方
- インジケーター: Linear Regression, WMA, MACD
- ストップ: いいえ
- 複雑さ: 中程度
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Linear Cross Trading strategy.
/// Regresses the close on volume over Length candles and predicts the price for the current volume. A long opens when the
/// predicted price crosses above its LinearLength WMA while MACD is above its signal and rising. A short opens when MACD is
/// below its signal and falling while the low is lower than the previous low. Opposite signals reverse the position.
/// </summary>
public class LinearCrossTradingStrategy : Strategy
{
private readonly StrategyParam<int> _length;
private readonly StrategyParam<int> _linearLength;
private readonly StrategyParam<DataType> _candleType;
private readonly Queue<(decimal close, decimal volume)> _window = new();
private WeightedMovingAverage _wma;
private decimal? _prevPredicted;
private decimal? _prevWma;
private decimal? _prevMacd;
private decimal? _prevLow;
/// <summary>
/// Number of candles in the price-on-volume regression.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// WMA period applied to the predicted price.
/// </summary>
public int LinearLength
{
get => _linearLength.Value;
set => _linearLength.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public LinearCrossTradingStrategy()
{
_length = Param(nameof(Length), 21)
.SetGreaterThanZero()
.SetDisplay("Length", "Number of candles in the price-on-volume regression", "Indicators");
_linearLength = Param(nameof(LinearLength), 9)
.SetGreaterThanZero()
.SetDisplay("Linear Length", "WMA period applied to the predicted price", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).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()
{
_window.Clear();
_prevPredicted = null;
_prevWma = null;
_prevMacd = null;
_prevLow = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_wma = new WeightedMovingAverage { Length = LinearLength };
var macd = new MovingAverageConvergenceDivergenceSignal();
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _wma);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, macd);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
var prevLow = _prevLow;
_prevLow = candle.LowPrice;
_window.Enqueue((candle.ClosePrice, candle.TotalVolume));
while (_window.Count > Length)
_window.Dequeue();
decimal? predicted = null;
decimal? wma = null;
if (_window.Count == Length)
{
predicted = Predict(candle.TotalVolume);
var wmaValue = _wma.Process(new DecimalIndicatorValue(_wma, predicted.Value, candle.OpenTime) { IsFinal = true });
if (_wma.IsFormed)
wma = wmaValue.GetValue<decimal>();
}
var prevPredicted = _prevPredicted;
var prevWma = _prevWma;
_prevPredicted = predicted;
_prevWma = wma;
if (!macdValue.IsFormed || macdValue is not IMovingAverageConvergenceDivergenceSignalValue { Macd: decimal macd, Signal: decimal signal })
return;
var prevMacd = _prevMacd;
_prevMacd = macd;
if (prevMacd is not decimal lastMacd || !IsFormedAndOnlineAndAllowTrading())
return;
var crossUp = predicted is decimal p && wma is decimal w && prevPredicted is decimal pp && prevWma is decimal pw && pp <= pw && p > w;
var macdUp = macd > signal && macd > lastMacd;
var macdDown = macd < signal && macd < lastMacd;
var lowerLow = prevLow is decimal pl && candle.LowPrice < pl;
if (crossUp && macdUp && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (macdDown && lowerLow && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
private decimal Predict(decimal volume)
{
// Least-squares fit close = a + b * volume over the window, evaluated at the current volume.
var n = _window.Count;
var meanVolume = _window.Sum(x => x.volume) / n;
var meanClose = _window.Sum(x => x.close) / n;
var covariance = 0m;
var variance = 0m;
foreach (var (close, vol) in _window)
{
var dv = vol - meanVolume;
covariance += dv * (close - meanClose);
variance += dv * dv;
}
var slope = variance == 0 ? 0 : covariance / variance;
return meanClose + slope * (volume - meanVolume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import WeightedMovingAverage, MovingAverageConvergenceDivergenceSignal
from StockSharp.Algo.Strategies import Strategy
from indicator_extensions import *
class linear_cross_trading_strategy(Strategy):
"""
Linear Cross Trading strategy.
Regresses the close on volume over Length candles and predicts the price for the current volume. A long opens when the
predicted price crosses above its LinearLength WMA while MACD is above its signal and rising. A short opens when MACD is
below its signal and falling while the low is lower than the previous low. Opposite signals reverse the position.
"""
def __init__(self):
super(linear_cross_trading_strategy, self).__init__()
self._length = self.Param("Length", 21).SetGreaterThanZero().SetDisplay("Length", "Number of candles in the price-on-volume regression", "Indicators")
self._linear_length = self.Param("LinearLength", 9).SetGreaterThanZero().SetDisplay("Linear Length", "WMA period applied to the predicted price", "Indicators")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._wma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._window = []
self._prev_predicted = None
self._prev_wma = None
self._prev_macd = None
self._prev_low = None
def OnReseted(self):
super(linear_cross_trading_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(linear_cross_trading_strategy, self).OnStarted2(time)
self._reset_state()
self._wma = WeightedMovingAverage()
self._wma.Length = self._linear_length.Value
macd = MovingAverageConvergenceDivergenceSignal()
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(macd, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._wma)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, macd)
def _process_candle(self, candle, macd_value):
if candle.State != CandleStates.Finished:
return
prev_low = self._prev_low
self._prev_low = candle.LowPrice
length = self._length.Value
self._window.append((candle.ClosePrice, candle.TotalVolume))
while len(self._window) > length:
self._window.pop(0)
predicted = None
wma = None
if len(self._window) == length:
predicted = self._predict(candle.TotalVolume)
wma_value = process_float(self._wma, predicted, candle.OpenTime, True)
if self._wma.IsFormed:
wma = wma_value.GetValue[Decimal](None)
prev_predicted = self._prev_predicted
prev_wma = self._prev_wma
self._prev_predicted = predicted
self._prev_wma = wma
if not macd_value.IsFormed or macd_value.Macd is None or macd_value.Signal is None:
return
macd = macd_value.Macd
signal = macd_value.Signal
prev_macd = self._prev_macd
self._prev_macd = macd
if prev_macd is None or not self.IsFormedAndOnlineAndAllowTrading():
return
cross_up = predicted is not None and wma is not None and prev_predicted is not None and prev_wma is not None \
and prev_predicted <= prev_wma and predicted > wma
macd_up = macd > signal and macd > prev_macd
macd_down = macd < signal and macd < prev_macd
lower_low = prev_low is not None and candle.LowPrice < prev_low
if cross_up and macd_up and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif macd_down and lower_low and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def _predict(self, volume):
# Least-squares fit close = a + b * volume over the window, evaluated at the current volume.
n = Decimal(len(self._window))
mean_volume = sum((v for _, v in self._window), Decimal(0)) / n
mean_close = sum((c for c, _ in self._window), Decimal(0)) / n
covariance = Decimal(0)
variance = Decimal(0)
for close, vol in self._window:
dv = vol - mean_volume
covariance += dv * (close - mean_close)
variance += dv * dv
slope = Decimal(0) if variance == Decimal(0) else covariance / variance
return mean_close + slope * (volume - mean_volume)
def CreateClone(self):
return linear_cross_trading_strategy()