MCOTs Intuition戦略
RSIモメンタムとその標準偏差の関係に基づく戦略。上昇モメンタムが強いが衰退しているときに買い、反対の条件で売ります。固定の利益目標とストップロスをティック単位で設定します。
詳細
- エントリー条件:
- ロング: momentum > stdDev * multiplier かつ momentum < previousMomentum * exhaustionMultiplier
- ショート: momentum < -stdDev * multiplier かつ momentum > previousMomentum * exhaustionMultiplier
- ロング/ショート: 両方
- エグジット条件:
- ティック単位の固定利益目標とストップロス
- ストップ: はい
- デフォルト値:
RsiPeriod= 14StdDevMultiplier= 1mExhaustionMultiplier= 1mProfitTargetTicks= 40StopLossTicks= 160CandleType= TimeSpan.FromMinutes(5).TimeFrame()
- フィルター:
- カテゴリ: リバーサル
- 方向: 両方
- インジケーター: RSI, StandardDeviation
- ストップ: はい
- 複雑さ: 基本
- 時間軸: 中期
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// MCOTs Intuition strategy.
/// Momentum is the change of RSI from the previous candle and its standard deviation is measured over RsiPeriod values.
/// A long opens when momentum exceeds the deviation times StdDevMultiplier while staying below the previous momentum times
/// ExhaustionMultiplier (strong but fading); a short opens on the mirrored condition. Exits are a fixed profit target and
/// stop loss in ticks.
/// </summary>
public class McotsIntuitionStrategy : Strategy
{
private readonly StrategyParam<int> _rsiPeriod;
private readonly StrategyParam<decimal> _stdDevMultiplier;
private readonly StrategyParam<decimal> _exhaustionMultiplier;
private readonly StrategyParam<int> _profitTargetTicks;
private readonly StrategyParam<int> _stopLossTicks;
private readonly StrategyParam<DataType> _candleType;
private StandardDeviation _stdDev;
private decimal? _prevRsi;
private decimal? _prevMomentum;
/// <summary>
/// RSI period, also the window of the momentum standard deviation.
/// </summary>
public int RsiPeriod
{
get => _rsiPeriod.Value;
set => _rsiPeriod.Value = value;
}
/// <summary>
/// Multiplier of the momentum standard deviation.
/// </summary>
public decimal StdDevMultiplier
{
get => _stdDevMultiplier.Value;
set => _stdDevMultiplier.Value = value;
}
/// <summary>
/// Multiplier of the previous momentum that marks exhaustion.
/// </summary>
public decimal ExhaustionMultiplier
{
get => _exhaustionMultiplier.Value;
set => _exhaustionMultiplier.Value = value;
}
/// <summary>
/// Profit target in ticks.
/// </summary>
public int ProfitTargetTicks
{
get => _profitTargetTicks.Value;
set => _profitTargetTicks.Value = value;
}
/// <summary>
/// Stop loss in ticks.
/// </summary>
public int StopLossTicks
{
get => _stopLossTicks.Value;
set => _stopLossTicks.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public McotsIntuitionStrategy()
{
_rsiPeriod = Param(nameof(RsiPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Period", "RSI period and deviation window", "Indicators");
_stdDevMultiplier = Param(nameof(StdDevMultiplier), 1m)
.SetNotNegative()
.SetDisplay("StdDev Multiplier", "Multiplier of the momentum standard deviation", "Indicators");
_exhaustionMultiplier = Param(nameof(ExhaustionMultiplier), 1m)
.SetNotNegative()
.SetDisplay("Exhaustion Multiplier", "Multiplier of the previous momentum", "Indicators");
_profitTargetTicks = Param(nameof(ProfitTargetTicks), 40)
.SetNotNegative()
.SetDisplay("Profit Target Ticks", "Profit target in ticks", "Risk");
_stopLossTicks = Param(nameof(StopLossTicks), 160)
.SetNotNegative()
.SetDisplay("Stop Loss Ticks", "Stop loss in ticks", "Risk");
_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();
_stdDev = null;
_prevRsi = null;
_prevMomentum = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevRsi = null;
_prevMomentum = null;
var rsi = new RelativeStrengthIndex { Length = RsiPeriod };
_stdDev = new StandardDeviation { Length = RsiPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(rsi, ProcessCandle)
.Start();
var step = Security?.PriceStep ?? 1m;
StartProtection(
ProfitTargetTicks > 0 ? new Unit(ProfitTargetTicks * step, UnitTypes.Absolute) : new Unit(),
StopLossTicks > 0 ? new Unit(StopLossTicks * step, UnitTypes.Absolute) : new Unit(),
useMarketOrders: true, isLocalStop: true);
// The target and stop have to see prices between candles, not only at their close.
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ObserveProtectionQuote).Start();
}
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
var oscillators = CreateChartArea();
if (oscillators != null)
DrawIndicator(oscillators, rsi);
}
}
private void ObserveProtectionQuote(Level1ChangeMessage quote)
{
// The high-level handler activates native protection before this callback, also between signal bars.
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue rsiValue)
{
if (candle.State != CandleStates.Finished || !rsiValue.IsFormed)
return;
var rsi = rsiValue.GetValue<decimal>();
if (_prevRsi is not decimal prevRsi)
{
_prevRsi = rsi;
return;
}
_prevRsi = rsi;
var momentum = rsi - prevRsi;
var stdDevValue = _stdDev.Process(new DecimalIndicatorValue(_stdDev, momentum, candle.OpenTime) { IsFinal = true });
var prevMomentum = _prevMomentum;
_prevMomentum = momentum;
if (!_stdDev.IsFormed || prevMomentum is not decimal previous)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var band = stdDevValue.GetValue<decimal>() * StdDevMultiplier;
var exhaustion = previous * ExhaustionMultiplier;
if (momentum > band && momentum < exhaustion && Position <= 0)
BuyMarket(Volume + Math.Abs(Position));
else if (momentum < -band && momentum > exhaustion && Position >= 0)
SellMarket(Volume + Math.Abs(Position));
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, Level1Fields
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Indicators import RelativeStrengthIndex, StandardDeviation, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class mcots_intuition_strategy(Strategy):
"""
MCOTs Intuition strategy.
Momentum is the change of RSI from the previous candle and its standard deviation is measured over RsiPeriod values.
A long opens when momentum exceeds the deviation times StdDevMultiplier while staying below the previous momentum times
ExhaustionMultiplier (strong but fading); a short opens on the mirrored condition. Exits are a fixed profit target and
stop loss in ticks.
"""
def __init__(self):
super(mcots_intuition_strategy, self).__init__()
self._rsi_period = self.Param("RsiPeriod", 14).SetGreaterThanZero().SetDisplay("RSI Period", "RSI period and deviation window", "Indicators")
self._std_dev_multiplier = self.Param("StdDevMultiplier", 1.0).SetNotNegative().SetDisplay("StdDev Multiplier", "Multiplier of the momentum standard deviation", "Indicators")
self._exhaustion_multiplier = self.Param("ExhaustionMultiplier", 1.0).SetNotNegative().SetDisplay("Exhaustion Multiplier", "Multiplier of the previous momentum", "Indicators")
self._profit_target_ticks = self.Param("ProfitTargetTicks", 40).SetNotNegative().SetDisplay("Profit Target Ticks", "Profit target in ticks", "Risk")
self._stop_loss_ticks = self.Param("StopLossTicks", 160).SetNotNegative().SetDisplay("Stop Loss Ticks", "Stop loss in ticks", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._std_dev = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_rsi = None
self._prev_momentum = None
def OnReseted(self):
super(mcots_intuition_strategy, self).OnReseted()
self._std_dev = None
self._reset_state()
def OnStarted2(self, time):
super(mcots_intuition_strategy, self).OnStarted2(time)
self._reset_state()
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_period.Value
self._std_dev = StandardDeviation()
self._std_dev.Length = self._rsi_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(rsi, self._process_candle).Start()
step = self.Security.PriceStep if self.Security is not None and self.Security.PriceStep is not None else Decimal(1)
take_ticks = self._profit_target_ticks.Value
stop_ticks = self._stop_loss_ticks.Value
take = Unit(Decimal(take_ticks) * step, UnitTypes.Absolute) if take_ticks > 0 else Unit()
stop = Unit(Decimal(stop_ticks) * step, UnitTypes.Absolute) if stop_ticks > 0 else Unit()
self.StartProtection(take, stop, useMarketOrders=True, isLocalStop=True)
# The target and stop have to see prices between candles, not only at their close.
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._observe_protection_quote).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
oscillators = self.CreateChartArea()
if oscillators is not None:
self.DrawIndicator(oscillators, rsi)
def _observe_protection_quote(self, quote):
# The high-level handler activates native protection before the callback, including between bars.
pass
def _process_candle(self, candle, rsi_value):
if candle.State != CandleStates.Finished or not rsi_value.IsFormed:
return
rsi = rsi_value.GetValue[Decimal](None)
if self._prev_rsi is None:
self._prev_rsi = rsi
return
momentum = rsi - self._prev_rsi
self._prev_rsi = rsi
indicator_input = DecimalIndicatorValue(self._std_dev, momentum, candle.OpenTime)
indicator_input.IsFinal = True
std_dev_value = self._std_dev.Process(indicator_input)
prev_momentum = self._prev_momentum
self._prev_momentum = momentum
if not self._std_dev.IsFormed or prev_momentum is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
band = std_dev_value.GetValue[Decimal](None) * Decimal(self._std_dev_multiplier.Value)
exhaustion = prev_momentum * Decimal(self._exhaustion_multiplier.Value)
if momentum > band and momentum < exhaustion and self.Position <= 0:
self.BuyMarket(self.Volume + abs(self.Position))
elif momentum < Decimal.Negate(band) and momentum > exhaustion and self.Position >= 0:
self.SellMarket(self.Volume + abs(self.Position))
def CreateClone(self):
return mcots_intuition_strategy()