Bearish Wick Reversal
交易规则
本策略只做多,使用已完成的一分钟阴线下影线反转。空仓时,若 Close < Open 且 100 * (Low - Close) / Close <= Threshold,买入 Volume。默认 -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()