Bearish Wick Reversal
取引ルール
確定した 1 分足の下ひげ反転で買いだけを行います。建玉がなければ、Close < Open かつ 100 * (Low - Close) / Close <= Threshold のとき Volume を買います。既定値 -1 は、終値の 1% 以上の下ひげを要求します。
足の UTC 開始時刻が両端を含む StartTime–EndTime 内であることが新規条件です。UseEmaFilter 有効時は EMA(EmaPeriod) の形成と Close > EMA も必要です。無効時は EMA の準備完了を待ちません。
確定終値が直前の確定足の高値を上回れば、実際の買い建玉を全量決済します。新規の期間制限と EMA は決済を妨げません。売り建て、隠れた待機本数、自動利確・損切りはありません。未完了の注文がある間は重複発注しません。
パラメーター
| パラメーター | 既定値 |
|---|---|
Threshold |
-1 |
UseEmaFilter |
false |
EmaPeriod |
200 |
CandleType |
1m |
StartTime |
2014-01-01T00:00:00Z |
EndTime |
2099-01-01T00:00:00Z |
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>Long-only bearish lower-wick reversal with independent previous-high exits.</summary>
public class BearishWickReversalStrategy : Strategy
{
private readonly StrategyParam<decimal> _threshold;
private readonly StrategyParam<bool> _useEmaFilter;
private readonly StrategyParam<int> _emaPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<DateTime> _startTime;
private readonly StrategyParam<DateTime> _endTime;
private EMA _ema;
private decimal _previousHigh;
private Order _pending;
private DateTime _lastTime;
/// <summary>Negative lower wick percentage threshold.</summary>
public decimal Threshold
{
get => _threshold.Value;
set => _threshold.Value = value;
}
/// <summary>Require close above the formed EMA.</summary>
public bool UseEmaFilter
{
get => _useEmaFilter.Value;
set => _useEmaFilter.Value = value;
}
/// <summary>Optional EMA length.</summary>
public int EmaPeriod
{
get => _emaPeriod.Value;
set => _emaPeriod.Value = value;
}
/// <summary>Finished signal candle interval.</summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>First eligible UTC entry time.</summary>
public DateTime StartTime
{
get => _startTime.Value;
set => _startTime.Value = value;
}
/// <summary>Last eligible UTC entry time.</summary>
public DateTime EndTime
{
get => _endTime.Value;
set => _endTime.Value = value;
}
/// <summary>Initializes the published wick, optional trend and UTC entry-window parameters.</summary>
public BearishWickReversalStrategy()
{
_threshold = Param(nameof(Threshold), -1m).SetRange(-100m, 0m)
.SetDisplay("Threshold", "Negative lower wick percentage threshold", "Parameters");
_useEmaFilter = Param(nameof(UseEmaFilter), false)
.SetDisplay("UseEmaFilter", "Require close above the formed EMA", "Parameters");
_emaPeriod = Param(nameof(EmaPeriod), 200).SetGreaterThanZero()
.SetDisplay("EmaPeriod", "Optional EMA length", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("CandleType", "Finished signal candle interval", "Parameters");
_startTime = Param(nameof(StartTime), new DateTime(2014, 1, 1, 0, 0, 0, DateTimeKind.Utc))
.SetDisplay("StartTime", "First eligible UTC entry time", "Parameters");
_endTime = Param(nameof(EndTime), new DateTime(2099, 1, 1, 0, 0, 0, DateTimeKind.Utc))
.SetDisplay("EndTime", "Last eligible UTC entry time", "Parameters");
OrderRegistering += order => _pending = order;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_ema = null;
_previousHigh = 0m;
_pending = null;
_lastTime = default;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
if (StartTime.Kind != DateTimeKind.Utc || EndTime.Kind != DateTimeKind.Utc || StartTime > EndTime)
throw new InvalidOperationException("StartTime and EndTime must form an ordered UTC entry window.");
base.OnStarted2(time);
var candles = SubscribeCandles(CandleType);
if (UseEmaFilter)
{
_ema = new EMA { Length = EmaPeriod };
candles.Bind(_ema, ProcessFilteredCandle).Start();
}
else candles.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area is not null)
{
DrawCandles(area, candles);
if (_ema is not null) DrawIndicator(area, _ema);
DrawOwnTrades(area);
}
}
private void ProcessFilteredCandle(ICandleMessage candle, decimal average) => ProcessCandle(candle);
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished || candle.ClosePrice <= 0m || candle.OpenTime <= _lastTime)
return;
var previousHigh = _previousHigh;
_previousHigh = candle.HighPrice;
_lastTime = candle.OpenTime;
if (!IsFormedAndOnlineAndAllowTrading() || _pending is not null && _pending.State is not (OrderStates.Done or OrderStates.Failed))
return;
if (Position > 0m)
{
if (previousHigh > 0m && candle.ClosePrice > previousHigh)
SellMarket(Position);
return;
}
if (Position != 0m || candle.OpenTime < StartTime || candle.OpenTime > EndTime
|| candle.ClosePrice >= candle.OpenPrice || 100m * (candle.LowPrice - candle.ClosePrice) / candle.ClosePrice > Threshold)
return;
if (_ema is not null && (!_ema.IsFormed || candle.ClosePrice <= _ema.GetCurrentValue()))
return;
BuyMarket(Volume);
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
import math
from collections import deque
from System import TimeSpan, DateTime, DateTimeKind, Decimal, Nullable
from StockSharp.Messages import DataType, CandleStates, Sides, OrderStates, Unit, UnitTypes
from StockSharp.BusinessEntities import Subscription
from StockSharp.Algo.Strategies import Strategy
from System import Array
from StockSharp.Algo.Indicators import EMA, IIndicator
class bearish_wick_reversal_strategy(Strategy):
def __init__(self):
super(bearish_wick_reversal_strategy, self).__init__()
self._threshold = self.Param("Threshold", -1.0).SetRange(-100.0, 0.0).SetDisplay(
"Threshold", "Negative lower wick percentage threshold", "Parameters")
self._use_ema_filter = self.Param("UseEmaFilter", False).SetDisplay(
"UseEmaFilter", "Require close above the formed EMA", "Parameters")
self._ema_period = self.Param("EmaPeriod", 200).SetGreaterThanZero().SetDisplay(
"EmaPeriod", "Optional EMA length", "Parameters")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay(
"CandleType", "Finished signal candle interval", "Parameters")
self._start_time = self.Param("StartTime", DateTime(2014, 1, 1, 0, 0, 0, DateTimeKind.Utc)).SetDisplay(
"StartTime", "First eligible UTC entry time", "Parameters")
self._end_time = self.Param("EndTime", DateTime(2099, 1, 1, 0, 0, 0, DateTimeKind.Utc)).SetDisplay(
"EndTime", "Last eligible UTC entry time", "Parameters")
self._reset_state()
self.OrderRegistering += self._remember_order
@property
def Threshold(self):
return self._threshold.Value
@Threshold.setter
def Threshold(self, value):
self._threshold.Value = value
@property
def UseEmaFilter(self):
return self._use_ema_filter.Value
@UseEmaFilter.setter
def UseEmaFilter(self, value):
self._use_ema_filter.Value = value
@property
def EmaPeriod(self):
return self._ema_period.Value
@EmaPeriod.setter
def EmaPeriod(self, value):
self._ema_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def StartTime(self):
return self._start_time.Value
@StartTime.setter
def StartTime(self, value):
self._start_time.Value = value
@property
def EndTime(self):
return self._end_time.Value
@EndTime.setter
def EndTime(self, value):
self._end_time.Value = value
def _reset_state(self):
self._ema = None
self._previous_high = Decimal(0)
self._pending = None
self._last_time = DateTime.MinValue
def _remember_order(self, order):
self._pending = order
def OnReseted(self):
super(bearish_wick_reversal_strategy, self).OnReseted()
self._reset_state()
def GetWorkingSecurities(self):
return [(self.Security, self.CandleType)]
def OnStarted2(self, time):
if self.StartTime.Kind != DateTimeKind.Utc or self.EndTime.Kind != DateTimeKind.Utc or self.StartTime > self.EndTime:
raise ValueError("StartTime and EndTime must form an ordered UTC entry window.")
super(bearish_wick_reversal_strategy, self).OnStarted2(time)
candles = self.SubscribeCandles(self.CandleType)
if self.UseEmaFilter:
self._ema = EMA()
self._ema.Length = self.EmaPeriod
candles.BindEx(Array[IIndicator]([self._ema]), self._process_filtered).Start()
else:
candles.Bind(self._process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, candles)
if self._ema is not None:
self.DrawIndicator(area, self._ema)
self.DrawOwnTrades(area)
def _process_filtered(self, candle, values):
if values[0].IsFormed:
self._process(candle, values[0].GetValue[Decimal](None))
def _process(self, candle, average=None):
if candle.State != CandleStates.Finished or candle.ClosePrice <= 0 or candle.OpenTime <= self._last_time:
return
previous_high = self._previous_high
self._previous_high = candle.HighPrice
self._last_time = candle.OpenTime
if not self.IsFormedAndOnlineAndAllowTrading() or (self._pending is not None and self._pending.State not in (OrderStates.Done, OrderStates.Failed)):
return
if self.Position > 0:
if previous_high > 0 and candle.ClosePrice > previous_high:
self.SellMarket(self.Position)
return
if self.Position != 0 or candle.OpenTime < self.StartTime or candle.OpenTime > self.EndTime:
return
if candle.ClosePrice >= candle.OpenPrice or 100.0 * (float(candle.LowPrice) - float(candle.ClosePrice)) / float(candle.ClosePrice) > float(self.Threshold):
return
if self._ema is not None and (not self._ema.IsFormed or average is None or candle.ClosePrice <= average):
return
self.BuyMarket(self.Volume)
def CreateClone(self):
return bearish_wick_reversal_strategy()