オプション戦略 V1.3
RSI、ATRベースのストップとテイクプロフィット、および出来高フィルターを備えたEMAクロスオーバー戦略です。オープニングレンジのブレイクアウトをオプションで要求でき、ニューヨーク時間15:55にポジションをクローズします。事前定義されたセッションおよびユーザー指定の取引禁止インターバル中は取引がブロックされます。
詳細
- エントリー条件:
- ロング: 短期EMAが長期EMAを上抜け、RSI ≥
RsiLongThreshold、出来高 ≥ 出来高SMA、オプションで終値 > オープニングレンジ高値。 - ショート: 短期EMAが長期EMAを下抜け、RSI ≤
RsiShortThreshold、出来高 ≥ 出来高SMA、オプションで終値 < オープニングレンジ安値。
- ロング: 短期EMAが長期EMAを上抜け、RSI ≥
- ロング/ショート: 両方。
- エグジット条件:
- ATRベースのストップロスとテイクプロフィット。
- 逆方向のEMAクロス。
- NY時間15:55に自動クローズ。
- ストップ: はい。
- デフォルト値:
EmaShortLength = 8EmaLongLength = 28RsiLength = 12AtrLength = 14SlMultiplier = 1.4TpSlRatio = 4VolumeMaLength = 20
- フィルター:
- カテゴリ: トレンドフォロー
- 方向: 設定可能
- インジケーター: EMA, RSI, ATR, SMA
- ストップ: はい
- 時間軸: イントラデイ
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>
/// Options strategy V1.3.
/// A long opens when the short EMA crosses above the long EMA with RSI at or above RsiLongThreshold and candle volume at or above its
/// SMA; a short on the opposite cross with RSI at or below RsiShortThreshold. Optionally the close must also be beyond the New York
/// opening range. The stop is SlMultiplier times ATR from the entry and the target TpSlRatio times the stop distance. An opposite
/// cross closes (or reverses) the position, an optional no-trade window blocks entries and everything is flat at 15:55 New York time.
/// </summary>
public class OptionsV13Strategy : Strategy
{
private readonly StrategyParam<int> _emaShortLength;
private readonly StrategyParam<int> _emaLongLength;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<decimal> _rsiLongThreshold;
private readonly StrategyParam<decimal> _rsiShortThreshold;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _slMultiplier;
private readonly StrategyParam<decimal> _tpSlRatio;
private readonly StrategyParam<int> _volumeMaLength;
private readonly StrategyParam<bool> _useOpeningRange;
private readonly StrategyParam<TimeSpan> _openingRangeStart;
private readonly StrategyParam<TimeSpan> _openingRangeEnd;
private readonly StrategyParam<TimeSpan> _closeTime;
private readonly StrategyParam<bool> _useNoTradeWindow;
private readonly StrategyParam<TimeSpan> _noTradeStart;
private readonly StrategyParam<TimeSpan> _noTradeEnd;
private readonly StrategyParam<DataType> _candleType;
private static readonly TimeZoneInfo _newYork = TimeZoneInfo.FindSystemTimeZoneById("America/New_York");
private SimpleMovingAverage _volumeMa;
private decimal? _prevShort;
private decimal? _prevLong;
private DateTime _orDay;
private decimal? _orHigh;
private decimal? _orLow;
private decimal _stopPrice;
private decimal _takePrice;
/// <summary>
/// Short EMA length.
/// </summary>
public int EmaShortLength
{
get => _emaShortLength.Value;
set => _emaShortLength.Value = value;
}
/// <summary>
/// Long EMA length.
/// </summary>
public int EmaLongLength
{
get => _emaLongLength.Value;
set => _emaLongLength.Value = value;
}
/// <summary>
/// RSI length.
/// </summary>
public int RsiLength
{
get => _rsiLength.Value;
set => _rsiLength.Value = value;
}
/// <summary>
/// Minimum RSI for a long entry.
/// </summary>
public decimal RsiLongThreshold
{
get => _rsiLongThreshold.Value;
set => _rsiLongThreshold.Value = value;
}
/// <summary>
/// Maximum RSI for a short entry.
/// </summary>
public decimal RsiShortThreshold
{
get => _rsiShortThreshold.Value;
set => _rsiShortThreshold.Value = value;
}
/// <summary>
/// ATR length.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// Stop distance in ATR multiples.
/// </summary>
public decimal SlMultiplier
{
get => _slMultiplier.Value;
set => _slMultiplier.Value = value;
}
/// <summary>
/// Target distance in multiples of the stop distance.
/// </summary>
public decimal TpSlRatio
{
get => _tpSlRatio.Value;
set => _tpSlRatio.Value = value;
}
/// <summary>
/// Length of the volume SMA.
/// </summary>
public int VolumeMaLength
{
get => _volumeMaLength.Value;
set => _volumeMaLength.Value = value;
}
/// <summary>
/// Require a close beyond the opening range.
/// </summary>
public bool UseOpeningRange
{
get => _useOpeningRange.Value;
set => _useOpeningRange.Value = value;
}
/// <summary>
/// Opening range start (New York time).
/// </summary>
public TimeSpan OpeningRangeStart
{
get => _openingRangeStart.Value;
set => _openingRangeStart.Value = value;
}
/// <summary>
/// Opening range end (New York time).
/// </summary>
public TimeSpan OpeningRangeEnd
{
get => _openingRangeEnd.Value;
set => _openingRangeEnd.Value = value;
}
/// <summary>
/// Time when positions are closed (New York time).
/// </summary>
public TimeSpan CloseTime
{
get => _closeTime.Value;
set => _closeTime.Value = value;
}
/// <summary>
/// Block entries inside the no-trade window.
/// </summary>
public bool UseNoTradeWindow
{
get => _useNoTradeWindow.Value;
set => _useNoTradeWindow.Value = value;
}
/// <summary>
/// No-trade window start (New York time).
/// </summary>
public TimeSpan NoTradeStart
{
get => _noTradeStart.Value;
set => _noTradeStart.Value = value;
}
/// <summary>
/// No-trade window end (New York time).
/// </summary>
public TimeSpan NoTradeEnd
{
get => _noTradeEnd.Value;
set => _noTradeEnd.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public OptionsV13Strategy()
{
_emaShortLength = Param(nameof(EmaShortLength), 8)
.SetGreaterThanZero()
.SetDisplay("EMA Short", "Short EMA length", "Indicators");
_emaLongLength = Param(nameof(EmaLongLength), 28)
.SetGreaterThanZero()
.SetDisplay("EMA Long", "Long EMA length", "Indicators");
_rsiLength = Param(nameof(RsiLength), 12)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI length", "Indicators");
_rsiLongThreshold = Param(nameof(RsiLongThreshold), 50m)
.SetDisplay("RSI Long", "Minimum RSI for a long entry", "Indicators");
_rsiShortThreshold = Param(nameof(RsiShortThreshold), 50m)
.SetDisplay("RSI Short", "Maximum RSI for a short entry", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR length", "Risk");
_slMultiplier = Param(nameof(SlMultiplier), 1.4m)
.SetGreaterThanZero()
.SetDisplay("SL Multiplier", "Stop distance in ATR multiples", "Risk");
_tpSlRatio = Param(nameof(TpSlRatio), 4m)
.SetGreaterThanZero()
.SetDisplay("TP/SL Ratio", "Target distance in multiples of the stop distance", "Risk");
_volumeMaLength = Param(nameof(VolumeMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Volume MA Length", "Length of the volume SMA", "Indicators");
_useOpeningRange = Param(nameof(UseOpeningRange), false)
.SetDisplay("Use Opening Range", "Require a close beyond the opening range", "Session");
_openingRangeStart = Param(nameof(OpeningRangeStart), new TimeSpan(9, 30, 0))
.SetDisplay("OR Start", "Opening range start (New York time)", "Session");
_openingRangeEnd = Param(nameof(OpeningRangeEnd), new TimeSpan(10, 0, 0))
.SetDisplay("OR End", "Opening range end (New York time)", "Session");
_closeTime = Param(nameof(CloseTime), new TimeSpan(15, 55, 0))
.SetDisplay("Close Time", "Time when positions are closed (New York time)", "Session");
_useNoTradeWindow = Param(nameof(UseNoTradeWindow), false)
.SetDisplay("Use No-Trade Window", "Block entries inside the no-trade window", "Session");
_noTradeStart = Param(nameof(NoTradeStart), new TimeSpan(12, 0, 0))
.SetDisplay("No-Trade Start", "No-trade window start (New York time)", "Session");
_noTradeEnd = Param(nameof(NoTradeEnd), new TimeSpan(13, 0, 0))
.SetDisplay("No-Trade End", "No-trade window end (New York time)", "Session");
_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()
{
_prevShort = null;
_prevLong = null;
_orDay = default;
_orHigh = null;
_orLow = null;
_stopPrice = 0m;
_takePrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var emaShort = new ExponentialMovingAverage { Length = EmaShortLength };
var emaLong = new ExponentialMovingAverage { Length = EmaLongLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var atr = new AverageTrueRange { Length = AtrLength };
_volumeMa = new SimpleMovingAverage { Length = VolumeMaLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(emaShort, emaLong, rsi, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, emaShort);
DrawIndicator(area, emaLong);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaShort, decimal emaLong, decimal rsi, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
var volumeValue = _volumeMa.Process(new DecimalIndicatorValue(_volumeMa, candle.TotalVolume, candle.OpenTime) { IsFinal = true });
decimal? volumeMa = _volumeMa.IsFormed ? volumeValue.GetValue<decimal>() : null;
var nyTime = TimeZoneInfo.ConvertTimeFromUtc(DateTime.SpecifyKind(candle.OpenTime.ToUniversalTime(), DateTimeKind.Unspecified), _newYork);
var nyDay = nyTime.Date;
var tod = nyTime.TimeOfDay;
if (nyDay != _orDay)
{
_orDay = nyDay;
_orHigh = null;
_orLow = null;
}
var inOpeningRange = tod >= OpeningRangeStart && tod < OpeningRangeEnd;
if (inOpeningRange)
{
_orHigh = _orHigh is decimal h ? Math.Max(h, candle.HighPrice) : candle.HighPrice;
_orLow = _orLow is decimal l ? Math.Min(l, candle.LowPrice) : candle.LowPrice;
}
var prevShort = _prevShort;
var prevLong = _prevLong;
_prevShort = emaShort;
_prevLong = emaLong;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (tod >= CloseTime)
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
return;
}
if (Position > 0 && (candle.LowPrice <= _stopPrice || candle.HighPrice >= _takePrice))
{
SellMarket(Position);
return;
}
if (Position < 0 && (candle.HighPrice >= _stopPrice || candle.LowPrice <= _takePrice))
{
BuyMarket(-Position);
return;
}
if (prevShort is not decimal ps || prevLong is not decimal pl)
return;
var crossUp = ps <= pl && emaShort > emaLong;
var crossDown = ps >= pl && emaShort < emaLong;
if (!crossUp && !crossDown)
return;
var close = candle.ClosePrice;
var volumeOk = volumeMa is decimal vma && candle.TotalVolume >= vma;
var blocked = (UseNoTradeWindow && tod >= NoTradeStart && tod < NoTradeEnd)
|| (UseOpeningRange && (inOpeningRange || tod < OpeningRangeStart));
var stopDistance = atr * SlMultiplier;
if (crossUp)
{
var orOk = !UseOpeningRange || (_orHigh is decimal orHigh && close > orHigh);
if (Position <= 0 && !blocked && rsi >= RsiLongThreshold && volumeOk && orOk && stopDistance > 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - stopDistance;
_takePrice = close + stopDistance * TpSlRatio;
}
else if (Position < 0)
{
BuyMarket(-Position);
}
}
else
{
var orOk = !UseOpeningRange || (_orLow is decimal orLow && close < orLow);
if (Position >= 0 && !blocked && rsi <= RsiShortThreshold && volumeOk && orOk && stopDistance > 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + stopDistance;
_takePrice = close - stopDistance * TpSlRatio;
}
else if (Position > 0)
{
SellMarket(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, DateTime, DateTimeKind, TimeZoneInfo
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import ExponentialMovingAverage, RelativeStrengthIndex, AverageTrueRange, SimpleMovingAverage, DecimalIndicatorValue
from StockSharp.Algo.Strategies import Strategy
class options_v13_strategy(Strategy):
"""
Options strategy V1.3.
A long opens when the short EMA crosses above the long EMA with RSI at or above RsiLongThreshold and candle volume at or above its
SMA; a short on the opposite cross with RSI at or below RsiShortThreshold. Optionally the close must also be beyond the New York
opening range. The stop is SlMultiplier times ATR from the entry and the target TpSlRatio times the stop distance. An opposite
cross closes (or reverses) the position, an optional no-trade window blocks entries and everything is flat at 15:55 New York time.
"""
def __init__(self):
super(options_v13_strategy, self).__init__()
self._ema_short_length = self.Param("EmaShortLength", 8).SetGreaterThanZero().SetDisplay("EMA Short", "Short EMA length", "Indicators")
self._ema_long_length = self.Param("EmaLongLength", 28).SetGreaterThanZero().SetDisplay("EMA Long", "Long EMA length", "Indicators")
self._rsi_length = self.Param("RsiLength", 12).SetGreaterThanZero().SetDisplay("RSI Length", "RSI length", "Indicators")
self._rsi_long_threshold = self.Param("RsiLongThreshold", 50.0).SetDisplay("RSI Long", "Minimum RSI for a long entry", "Indicators")
self._rsi_short_threshold = self.Param("RsiShortThreshold", 50.0).SetDisplay("RSI Short", "Maximum RSI for a short entry", "Indicators")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR length", "Risk")
self._sl_multiplier = self.Param("SlMultiplier", 1.4).SetGreaterThanZero().SetDisplay("SL Multiplier", "Stop distance in ATR multiples", "Risk")
self._tp_sl_ratio = self.Param("TpSlRatio", 4.0).SetGreaterThanZero().SetDisplay("TP/SL Ratio", "Target distance in multiples of the stop distance", "Risk")
self._volume_ma_length = self.Param("VolumeMaLength", 20).SetGreaterThanZero().SetDisplay("Volume MA Length", "Length of the volume SMA", "Indicators")
self._use_opening_range = self.Param("UseOpeningRange", False).SetDisplay("Use Opening Range", "Require a close beyond the opening range", "Session")
self._opening_range_start = self.Param("OpeningRangeStart", TimeSpan(9, 30, 0)).SetDisplay("OR Start", "Opening range start (New York time)", "Session")
self._opening_range_end = self.Param("OpeningRangeEnd", TimeSpan(10, 0, 0)).SetDisplay("OR End", "Opening range end (New York time)", "Session")
self._close_time = self.Param("CloseTime", TimeSpan(15, 55, 0)).SetDisplay("Close Time", "Time when positions are closed (New York time)", "Session")
self._use_no_trade_window = self.Param("UseNoTradeWindow", False).SetDisplay("Use No-Trade Window", "Block entries inside the no-trade window", "Session")
self._no_trade_start = self.Param("NoTradeStart", TimeSpan(12, 0, 0)).SetDisplay("No-Trade Start", "No-trade window start (New York time)", "Session")
self._no_trade_end = self.Param("NoTradeEnd", TimeSpan(13, 0, 0)).SetDisplay("No-Trade End", "No-trade window end (New York time)", "Session")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._new_york = TimeZoneInfo.FindSystemTimeZoneById("America/New_York")
self._volume_ma = None
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_short = None
self._prev_long = None
self._or_day = None
self._or_high = None
self._or_low = None
self._stop_price = Decimal(0)
self._take_price = Decimal(0)
def OnReseted(self):
super(options_v13_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(options_v13_strategy, self).OnStarted2(time)
self._reset_state()
ema_short = ExponentialMovingAverage()
ema_short.Length = self._ema_short_length.Value
ema_long = ExponentialMovingAverage()
ema_long.Length = self._ema_long_length.Value
rsi = RelativeStrengthIndex()
rsi.Length = self._rsi_length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
self._volume_ma = SimpleMovingAverage()
self._volume_ma.Length = self._volume_ma_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(ema_short, ema_long, rsi, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, ema_short)
self.DrawIndicator(area, ema_long)
self.DrawOwnTrades(area)
def _process_candle(self, candle, ema_short, ema_long, rsi, atr):
if candle.State != CandleStates.Finished:
return
volume_input = DecimalIndicatorValue(self._volume_ma, candle.TotalVolume, candle.OpenTime)
volume_input.IsFinal = True
volume_value = self._volume_ma.Process(volume_input)
volume_ma = volume_value.GetValue[Decimal](None) if self._volume_ma.IsFormed else None
utc = DateTime.SpecifyKind(candle.OpenTime.ToUniversalTime(), DateTimeKind.Unspecified)
ny_time = TimeZoneInfo.ConvertTimeFromUtc(utc, self._new_york)
ny_day = ny_time.Date
tod = ny_time.TimeOfDay.Ticks
if self._or_day is None or ny_day != self._or_day:
self._or_day = ny_day
self._or_high = None
self._or_low = None
or_start = self._opening_range_start.Value.Ticks
or_end = self._opening_range_end.Value.Ticks
in_opening_range = tod >= or_start and tod < or_end
if in_opening_range:
self._or_high = candle.HighPrice if self._or_high is None else max(self._or_high, candle.HighPrice)
self._or_low = candle.LowPrice if self._or_low is None else min(self._or_low, candle.LowPrice)
prev_short = self._prev_short
prev_long = self._prev_long
self._prev_short = ema_short
self._prev_long = ema_long
if not self.IsFormedAndOnlineAndAllowTrading():
return
if tod >= self._close_time.Value.Ticks:
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
return
if self.Position > 0 and (candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price):
self.SellMarket(self.Position)
return
if self.Position < 0 and (candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price):
self.BuyMarket(-self.Position)
return
if prev_short is None or prev_long is None:
return
cross_up = prev_short <= prev_long and ema_short > ema_long
cross_down = prev_short >= prev_long and ema_short < ema_long
if not cross_up and not cross_down:
return
close = candle.ClosePrice
volume_ok = volume_ma is not None and candle.TotalVolume >= volume_ma
use_or = self._use_opening_range.Value
blocked = (self._use_no_trade_window.Value and tod >= self._no_trade_start.Value.Ticks and tod < self._no_trade_end.Value.Ticks) \
or (use_or and (in_opening_range or tod < or_start))
stop_distance = atr * Decimal(self._sl_multiplier.Value)
tp_ratio = Decimal(self._tp_sl_ratio.Value)
if cross_up:
or_ok = not use_or or (self._or_high is not None and close > self._or_high)
if self.Position <= 0 and not blocked and rsi >= Decimal(self._rsi_long_threshold.Value) and volume_ok and or_ok and stop_distance > 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance
self._take_price = close + stop_distance * tp_ratio
elif self.Position < 0:
self.BuyMarket(-self.Position)
else:
or_ok = not use_or or (self._or_low is not None and close < self._or_low)
if self.Position >= 0 and not blocked and rsi <= Decimal(self._rsi_short_threshold.Value) and volume_ok and or_ok and stop_distance > 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance
self._take_price = close - stop_distance * tp_ratio
elif self.Position > 0:
self.SellMarket(self.Position)
def CreateClone(self):
return options_v13_strategy()