十字線リバーサル戦略
Doji(十字線)ローソク足は、買い手と売り手の一時的な均衡を反映します。強い方向性のある動きの後にDojiが現れると、モメンタムが薄れるにつれてリバーサルの前兆となることがあります。この戦略は、真のDojiが形成されたかどうかを判断するために、ローソク足の実体をそのレンジと比較して測定します。
テストでは年平均リターンが約103%であることが示されています。株式市場で最も良いパフォーマンスを発揮します。
Dojiが検出されると、前のローソク足が上昇トレンドまたは下降トレンドを確認するためにチェックされます。下落後のDojiはロングエントリーを、上昇後のDojiはショートポジションを開くことができます。ストップはエントリーから一定のパーセント距離に設定され、価格がDojiの高値または安値を突き抜けると決済されます。
この手法はDojiからの最初の反応を捉えることを目的としており、素早いリバーサルが頻繁に展開されるイントラデイチャートに最も適しています。
詳細
- エントリー条件: 方向性のある動きの後のDojiローソク足。
- ロング/ショート: 両方。
- エグジット条件: 価格がDojiの高値/安値を超えるか、ストップロス。
- ストップ: はい、パーセントベース。
- デフォルト値:
CandleType= 5 minuteDojiThreshold= 0.1StopLossPercent= 1
- フィルター:
- カテゴリ: パターン
- 方向: 両方
- インジケーター: Candlestick
- ストップ: はい
- 複雑さ: 中級
- 時間軸: イントラデイ
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Enters against the preceding two-close move on a finished doji.
/// Exits at the opposite doji extreme or native actual-fill percent protection.
/// </summary>
public class DojiReversalStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _dojiThreshold;
private readonly StrategyParam<decimal> _stopLossPercent;
private ICandleMessage _older;
private ICandleMessage _previous;
private decimal? _targetHigh;
private decimal? _targetLow;
private Order _pendingOrder;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal DojiThreshold { get => _dojiThreshold.Value; set => _dojiThreshold.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public DojiReversalStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Doji and prior-close timeframe", "General");
_dojiThreshold = Param(nameof(DojiThreshold), 0.1m).SetRange(0m, 1m)
.SetDisplay("Doji Threshold", "Strict upper bound for absolute body divided by high-low range", "Pattern");
_stopLossPercent = Param(nameof(StopLossPercent), 1m).SetNotNegative()
.SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it", "Protection");
OrderRegistering += order =>
{
_pendingOrder = order;
if (Position > 0m && order.Side == Sides.Sell ||
Position < 0m && order.Side == Sides.Buy)
_targetHigh = _targetLow = null;
};
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType), (Security, DataType.Level1)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_older = _previous = null;
_targetHigh = _targetLow = null;
_pendingOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
StartProtection(new Unit(), new Unit(StopLossPercent, UnitTypes.Percent), useMarketOrders: true, isLocalStop: true);
foreach (var field in new[] { Level1Fields.BestBidPrice, Level1Fields.BestAskPrice })
{
var quotes = new Subscription(DataType.Level1, Security);
quotes.MarketData.BuildField = field;
SubscribeLevel1(quotes).Bind(ProcessQuote).Start();
}
var candles = SubscribeCandles(CandleType);
candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, candles);
DrawOwnTrades(area);
}
}
private bool Pending => _pendingOrder is not null &&
_pendingOrder.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessQuote(Level1ChangeMessage message)
{
if (Pending)
return;
if (Position > 0m && _targetHigh is decimal high &&
message.TryGetDecimal(Level1Fields.BestBidPrice) is decimal bid &&
bid > high)
SellMarket(Position);
else if (Position < 0m && _targetLow is decimal low &&
message.TryGetDecimal(Level1Fields.BestAskPrice) is decimal ask &&
ask > 0m && ask < low)
BuyMarket(Math.Abs(Position));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (!Pending && Position > 0m && _targetHigh is decimal high && candle.HighPrice > high)
SellMarket(Position);
else if (!Pending && Position < 0m && _targetLow is decimal low && candle.LowPrice < low)
BuyMarket(Math.Abs(Position));
else if (!Pending && Position == 0m && _older is not null && _previous is not null &&
IsDoji(candle) && IsFormedAndOnlineAndAllowTrading())
{
if (_previous.ClosePrice < _older.ClosePrice)
{
_targetHigh = candle.HighPrice;
_targetLow = null;
BuyMarket(Volume);
}
else if (_previous.ClosePrice > _older.ClosePrice)
{
_targetLow = candle.LowPrice;
_targetHigh = null;
SellMarket(Volume);
}
}
_older = _previous;
_previous = candle;
}
private bool IsDoji(ICandleMessage candle)
{
var range = candle.HighPrice - candle.LowPrice;
return range > 0m && Math.Abs(candle.ClosePrice - candle.OpenPrice) / range < DojiThreshold;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes, OrderStates, Level1Fields, Sides
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
class doji_reversal_strategy(Strategy):
"""Reverse the prior two-close move on a doji; exit at its far extreme or percent protection."""
def __init__(self):
super(doji_reversal_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Doji and prior-close timeframe", "General")
self._doji_threshold = self.Param("DojiThreshold", 0.1).SetRange(0.0, 1.0).SetDisplay("Doji Threshold", "Strict upper bound for absolute body divided by high-low range", "Pattern")
self._stop_loss_percent = self.Param("StopLossPercent", 1.0).SetNotNegative().SetDisplay("Stop Loss (%)", "Actual-fill percent stop; zero disables it", "Protection")
self._clear_state()
self.OrderRegistering += self._track_pending
@property
def candle_type(self):
return self._candle_type.Value
def GetWorkingSecurities(self):
return [(self.Security, self.candle_type), (self.Security, DataType.Level1)]
def _track_pending(self, order):
self._pending_order = order
if ((self.Position > 0 and order.Side == Sides.Sell) or
(self.Position < 0 and order.Side == Sides.Buy)):
self._target_high = None
self._target_low = None
def _clear_state(self):
self._older = None
self._previous = None
self._target_high = None
self._target_low = None
self._pending_order = None
def OnReseted(self):
super(doji_reversal_strategy, self).OnReseted()
self._clear_state()
def OnStarted2(self, time):
super(doji_reversal_strategy, self).OnStarted2(time)
self._clear_state()
self.StartProtection(Unit(), Unit(Decimal(self._stop_loss_percent.Value), UnitTypes.Percent), useMarketOrders=True, isLocalStop=True)
for field in (Level1Fields.BestBidPrice, Level1Fields.BestAskPrice):
quotes = Subscription(DataType.Level1, self.Security)
quotes.MarketData.BuildField = field
self.SubscribeLevel1(quotes).Bind(self._process_quote).Start()
candles = self.SubscribeCandles(self.candle_type)
candles.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
self.DrawOwnTrades(area)
def _pending(self):
return self._pending_order is not None and self._pending_order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_quote(self, message):
if self._pending():
return
if self.Position > 0 and self._target_high is not None and message.Changes.ContainsKey(Level1Fields.BestBidPrice):
bid = message.Changes[Level1Fields.BestBidPrice]
if bid > self._target_high:
self.SellMarket(self.Position)
elif self.Position < 0 and self._target_low is not None and message.Changes.ContainsKey(Level1Fields.BestAskPrice):
ask = message.Changes[Level1Fields.BestAskPrice]
if ask > 0 and ask < self._target_low:
self.BuyMarket(Math.Abs(self.Position))
def _is_doji(self, candle):
candle_range = candle.HighPrice - candle.LowPrice
return (candle_range > 0 and
Math.Abs(candle.ClosePrice - candle.OpenPrice) / candle_range < Decimal(self._doji_threshold.Value))
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if not self._pending() and self.Position > 0 and self._target_high is not None and candle.HighPrice > self._target_high:
self.SellMarket(self.Position)
elif not self._pending() and self.Position < 0 and self._target_low is not None and candle.LowPrice < self._target_low:
self.BuyMarket(Math.Abs(self.Position))
elif (not self._pending() and self.Position == 0 and self._older is not None and
self._previous is not None and self._is_doji(candle) and self.IsFormedAndOnlineAndAllowTrading()):
if self._previous.ClosePrice < self._older.ClosePrice:
self._target_high = candle.HighPrice
self._target_low = None
self.BuyMarket(self.Volume)
elif self._previous.ClosePrice > self._older.ClosePrice:
self._target_low = candle.LowPrice
self._target_high = None
self.SellMarket(self.Volume)
self._older = self._previous
self._previous = candle
def CreateClone(self):
return doji_reversal_strategy()