Color Zerolag RSI OSMA 戦略
この戦略は、異なる期間の5つのRSI計算から構築された複合オシレーターを使用します。RSI値の加重和を2回平滑化してゼロラグOSMAラインを生成します。
仕組み
- 期間8、21、34、55、89の5つのRSI値を計算します。
- 各RSIをその重みで乗算して結果を合計します。
- 合計に2つの平滑化ステップを適用してOSMA値を得ます。
- OSMAが上向きに転換する場合(前の値が2本前より低く、現在の値が前の値を超える)、戦略はショートポジションをクローズしてオプションでロングを開きます。
- OSMAが下向きに転換する場合(前の値が2本前より高く、現在の値が前の値を下回る)、戦略はロングポジションをクローズしてオプションでショートを開きます。
パラメーター
- Smoothing 1, Smoothing 2 – 平滑化フェーズの長さ。
- Factor 1..5 – 各RSIコンポーネントの重み。
- RSI Period 1..5 – RSIインジケーターの期間。
- Allow Buy / Allow Sell – ロングまたはショートポジションの開設を有効化。
- Close Long / Close Short – 反対のシグナルで既存ポジションをクローズ。
- Candle Type – 処理するローソク足の時間軸(デフォルト4時間)。
注意事項
この戦略は確定したローソク足のみで動作します。ポジション保護は戦略開始時に自動的に開始されます。
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>Five independently formed Wilder RSIs and a two-stage zero-lag OSMA.</summary>
public class ColorZerolagRsiOsmaStrategy : Strategy
{
private sealed class RsiState
{
private readonly int _length;
private int _samples;
private decimal? _previous;
private decimal _sumGain, _sumLoss, _avgGain, _avgLoss;
public RsiState(int length) => _length = length;
// A strategy clone compares its reset indicator configuration structurally.
public override bool Equals(object other)
=> other is RsiState rhs && _length == rhs._length
&& _samples == rhs._samples && _previous == rhs._previous
&& _sumGain == rhs._sumGain && _sumLoss == rhs._sumLoss
&& _avgGain == rhs._avgGain && _avgLoss == rhs._avgLoss;
public override int GetHashCode()
=> HashCode.Combine(_length, _samples, _previous, _sumGain, _sumLoss, _avgGain, _avgLoss);
public decimal? Add(decimal close)
{
if (_previous is not decimal previous)
{
_previous = close;
_samples = 1;
return null;
}
var delta = close - previous;
_previous = close;
var gain = Math.Max(delta, 0m);
var loss = Math.Max(-delta, 0m);
if (_samples <= _length)
{
_sumGain += gain;
_sumLoss += loss;
_samples++;
if (_samples <= _length)
return null;
_avgGain = _sumGain / _length;
_avgLoss = _sumLoss / _length;
}
else
{
_avgGain = (_avgGain * (_length - 1) + gain) / _length;
_avgLoss = (_avgLoss * (_length - 1) + loss) / _length;
}
if (_avgLoss == 0m)
return _avgGain == 0m ? 50m : 100m;
return 100m - 100m / (1m + _avgGain / _avgLoss);
}
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _smoothing1, _smoothing2;
private readonly StrategyParam<int>[] _periods = new StrategyParam<int>[5];
private readonly StrategyParam<decimal>[] _factors = new StrategyParam<decimal>[5];
private readonly StrategyParam<bool> _allowBuy, _allowSell, _closeLong, _closeShort;
private readonly StrategyParam<decimal> _stopLoss, _takeProfit;
private RsiState[] _rsi;
private decimal? _slow, _osma, _previous, _beforePrevious;
private Order _pending;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int Smoothing1 { get => _smoothing1.Value; set => _smoothing1.Value = value; }
public int Smoothing2 { get => _smoothing2.Value; set => _smoothing2.Value = value; }
public int RsiPeriod1 { get => _periods[0].Value; set => _periods[0].Value = value; }
public int RsiPeriod2 { get => _periods[1].Value; set => _periods[1].Value = value; }
public int RsiPeriod3 { get => _periods[2].Value; set => _periods[2].Value = value; }
public int RsiPeriod4 { get => _periods[3].Value; set => _periods[3].Value = value; }
public int RsiPeriod5 { get => _periods[4].Value; set => _periods[4].Value = value; }
public decimal Factor1 { get => _factors[0].Value; set => _factors[0].Value = value; }
public decimal Factor2 { get => _factors[1].Value; set => _factors[1].Value = value; }
public decimal Factor3 { get => _factors[2].Value; set => _factors[2].Value = value; }
public decimal Factor4 { get => _factors[3].Value; set => _factors[3].Value = value; }
public decimal Factor5 { get => _factors[4].Value; set => _factors[4].Value = value; }
public bool AllowBuy { get => _allowBuy.Value; set => _allowBuy.Value = value; }
public bool AllowSell { get => _allowSell.Value; set => _allowSell.Value = value; }
public bool CloseLong { get => _closeLong.Value; set => _closeLong.Value = value; }
public bool CloseShort { get => _closeShort.Value; set => _closeShort.Value = value; }
public decimal StopLoss { get => _stopLoss.Value; set => _stopLoss.Value = value; }
public decimal TakeProfit { get => _takeProfit.Value; set => _takeProfit.Value = value; }
public ColorZerolagRsiOsmaStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Signal candle timeframe", "General");
_smoothing1 = Param(nameof(Smoothing1), 9).SetGreaterThanZero()
.SetDisplay("Smoothing 1", "Slow-trend EMA period", "Indicator");
_smoothing2 = Param(nameof(Smoothing2), 5).SetGreaterThanZero()
.SetDisplay("Smoothing 2", "OSMA-line EMA period", "Indicator");
var periods = new[] { 8, 21, 34, 55, 89 };
for (var i = 0; i < 5; i++)
{
_periods[i] = Param("RsiPeriod" + (i + 1), periods[i]).SetGreaterThanZero()
.SetDisplay("RSI period " + (i + 1), "Wilder RSI length", "Indicator");
_factors[i] = Param("Factor" + (i + 1), 1m)
.SetDisplay("RSI factor " + (i + 1), "Weighted component", "Indicator");
}
_allowBuy = Param(nameof(AllowBuy), true).SetDisplay("Allow Buy", "Allow long entries", "Trading");
_allowSell = Param(nameof(AllowSell), true).SetDisplay("Allow Sell", "Allow short entries", "Trading");
_closeLong = Param(nameof(CloseLong), true).SetDisplay("Close Long", "Allow long exits", "Trading");
_closeShort = Param(nameof(CloseShort), true).SetDisplay("Close Short", "Allow short exits", "Trading");
_stopLoss = Param(nameof(StopLoss), 0m).SetNotNegative()
.SetDisplay("Stop Loss", "Optional price-unit stop", "Risk");
_takeProfit = Param(nameof(TakeProfit), 0m).SetNotNegative()
.SetDisplay("Take Profit", "Optional price-unit target", "Risk");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
private void ResetHistory()
{
_rsi = new RsiState[5];
for (var i = 0; i < 5; i++)
_rsi[i] = new RsiState(_periods[i].Value);
_slow = _osma = _previous = _beforePrevious = null;
_pending = null;
}
protected override void OnReseted()
{
base.OnReseted();
ResetHistory();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetHistory();
StartProtection(
takeProfit: new Unit(TakeProfit, UnitTypes.Absolute),
stopLoss: new Unit(StopLoss, UnitTypes.Absolute));
SubscribeCandles(CandleType).Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var fast = 0m;
var formed = true;
for (var i = 0; i < 5; i++)
{
var value = _rsi[i].Add(candle.ClosePrice);
if (value is decimal rsi)
fast += rsi * _factors[i].Value;
else
formed = false;
}
if (!formed)
return;
var slow = _slow is decimal oldSlow
? oldSlow + 2m / (Smoothing1 + 1m) * (fast - oldSlow)
: fast;
var rawOsma = fast - slow;
var osma = _osma is decimal previousOsma
? previousOsma + 2m / (Smoothing2 + 1m) * (rawOsma - previousOsma)
: rawOsma;
var turnUp = _beforePrevious is decimal older && _previous is decimal previous
&& previous < older && osma > previous;
var turnDown = _beforePrevious is decimal olderDown && _previous is decimal previousDown
&& previousDown > olderDown && osma < previousDown;
_beforePrevious = _previous;
_previous = _osma = osma;
_slow = slow;
if (!IsFormedAndOnlineAndAllowTrading()
|| (_pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed)))
return;
if (turnUp)
{
if (Position < 0m && CloseShort)
_pending = BuyMarket(-Position + (AllowBuy ? Volume : 0m));
else if (Position == 0m && AllowBuy)
_pending = BuyMarket(Volume);
}
else if (turnDown)
{
if (Position > 0m && CloseLong)
_pending = SellMarket(Position + (AllowSell ? Volume : 0m));
else if (Position == 0m && AllowSell)
_pending = SellMarket(Volume);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderStates, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class _RsiState:
"""Wilder RSI, requiring period changes before the first usable value."""
def __init__(self, length):
self.length = length
self.samples = 0
self.previous = None
self.sum_gain = self.sum_loss = self.avg_gain = self.avg_loss = 0.0
def add(self, close):
if self.previous is None:
self.previous = close
self.samples = 1
return None
delta = close - self.previous
self.previous = close
gain, loss = max(delta, 0.0), max(-delta, 0.0)
if self.samples <= self.length:
self.sum_gain += gain
self.sum_loss += loss
self.samples += 1
if self.samples <= self.length:
return None
self.avg_gain = self.sum_gain / self.length
self.avg_loss = self.sum_loss / self.length
else:
self.avg_gain = (self.avg_gain * (self.length - 1) + gain) / self.length
self.avg_loss = (self.avg_loss * (self.length - 1) + loss) / self.length
if self.avg_loss == 0:
return 50.0 if self.avg_gain == 0 else 100.0
return 100.0 - 100.0 / (1.0 + self.avg_gain / self.avg_loss)
class color_zerolag_rsi_osma_strategy(Strategy):
"""Weighted 5-RSI composite, two EMA smoothing stages, OSMA slope reversals."""
def __init__(self):
super(color_zerolag_rsi_osma_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Signal candle timeframe", "General")
self._smoothing1 = self.Param("Smoothing1", 9).SetGreaterThanZero() \
.SetDisplay("Smoothing 1", "Slow-trend EMA period", "Indicator")
self._smoothing2 = self.Param("Smoothing2", 5).SetGreaterThanZero() \
.SetDisplay("Smoothing 2", "OSMA-line EMA period", "Indicator")
self._periods = []
self._factors = []
for index, period in enumerate((8, 21, 34, 55, 89), start=1):
self._periods.append(self.Param("RsiPeriod" + str(index), period).SetGreaterThanZero()
.SetDisplay("RSI Period " + str(index), "Wilder RSI length", "Indicator"))
self._factors.append(self.Param("Factor" + str(index), 1.0)
.SetDisplay("RSI Factor " + str(index), "Component weight", "Indicator"))
self._allow_buy = self.Param("AllowBuy", True).SetDisplay("Allow Buy", "Allow long entries", "Trading")
self._allow_sell = self.Param("AllowSell", True).SetDisplay("Allow Sell", "Allow short entries", "Trading")
self._close_long = self.Param("CloseLong", True).SetDisplay("Close Long", "Allow long exits", "Trading")
self._close_short = self.Param("CloseShort", True).SetDisplay("Close Short", "Allow short exits", "Trading")
self._stop_loss = self.Param("StopLoss", 0.0).SetNotNegative() \
.SetDisplay("Stop Loss", "Optional price-unit stop", "Risk")
self._take_profit = self.Param("TakeProfit", 0.0).SetNotNegative() \
.SetDisplay("Take Profit", "Optional price-unit target", "Risk")
self._reset_history()
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def _reset_history(self):
self._rsi = [_RsiState(int(p.Value)) for p in self._periods]
self._slow = self._osma = self._previous = self._before_previous = None
self._pending = None
def OnReseted(self):
super(color_zerolag_rsi_osma_strategy, self).OnReseted()
self._reset_history()
def OnStarted2(self, time):
super(color_zerolag_rsi_osma_strategy, self).OnStarted2(time)
self._reset_history()
self.StartProtection(takeProfit=Unit(self._take_profit.Value, UnitTypes.Absolute),
stopLoss=Unit(self._stop_loss.Value, UnitTypes.Absolute))
self.SubscribeCandles(self.CandleType).Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
vals = [indicator.add(float(candle.ClosePrice)) for indicator in self._rsi]
if any(value is None for value in vals):
return
fast = sum(value * float(param.Value) for value, param in zip(vals, self._factors))
slow = fast if self._slow is None else self._slow + 2.0 / (int(self._smoothing1.Value) + 1.0) * (fast - self._slow)
raw = fast - slow
osc = raw if self._osma is None else self._osma + 2.0 / (int(self._smoothing2.Value) + 1.0) * (raw - self._osma)
turn_up = self._before_previous is not None and self._previous < self._before_previous and osc > self._previous
turn_down = self._before_previous is not None and self._previous > self._before_previous and osc < self._previous
self._before_previous, self._previous, self._osma, self._slow = self._previous, osc, osc, slow
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed):
return
if turn_up:
if self.Position < 0 and self._close_short.Value:
self._pending = self.BuyMarket(-self.Position + (self.Volume if self._allow_buy.Value else 0))
elif self.Position == 0 and self._allow_buy.Value:
self._pending = self.BuyMarket(self.Volume)
elif turn_down:
if self.Position > 0 and self._close_long.Value:
self._pending = self.SellMarket(self.Position + (self.Volume if self._allow_sell.Value else 0))
elif self.Position == 0 and self._allow_sell.Value:
self._pending = self.SellMarket(self.Volume)
def CreateClone(self):
return color_zerolag_rsi_osma_strategy()