Bull vs Medved 时间窗口策略
概述
Bull vs Medved 时间窗口策略是 MetaTrader 4 专家顾问 Bull_vs_Medved.mq4 的 StockSharp 版本。系统在一天内的六个固定 五分钟交易窗口中挂出限价订单,尝试捕捉强势趋势后的回调。移植版本保留了“每个时间窗只交易一次”的限制, 会清理超时未成交的挂单,并用信号蜡烛的实体长度计算动态止损和止盈距离。
交易逻辑
- 订阅
CandleType指定的蜡烛序列,并只处理收盘完成的蜡烛。 - 保存最近两根收盘蜡烛,使当前蜡烛 (
shift1)、上一根 (shift2) 以及再往前一根 (shift3) 对应 MetaTrader 中的Close[1..3]。 - 在每个交易窗口内(从
StartTime0..5开始,持续EntryWindowMinutes分钟)依次检查以下形态:- Bull:
shift3收于shift2开盘价之上,shift2的实体不少于 10 个点,shift1的实体不少于CandleSizePoints点;若IsBadBull为假(没有连续三根大阳线),则下达买入限价单。 - Cool Bull:
shift2是至少 20 点的回调并收于shift1开盘价之下,而shift1收在shift2开盘价之上, 且实体不小于阈值的 40%;此时同样挂买入限价单。 - Bear:
shift1为实体大于等于CandleSizePoints点的阴线,则挂卖出限价单。
- Bull:
- 买入限价价差为
ask - BuyIndentPoints * PriceStep,卖出限价价差为bid + SellIndentPoints * PriceStep。 如果当前窗口内已有挂单或持仓,新的信号会被忽略。 - 止损与止盈由策略内部追踪。挂单成交后,
shift1的实体乘以StopLossMultiplier与TakeProfitMultiplier, 按PriceStep归一化后保存为保护价格。 - 每根蜡烛收盘时判断最高价/最低价是否触及保护价。若触发,策略用市价单平掉净头寸并清空保护标记。
- 超过 230 分钟仍未成交的挂单会被取消,以贴合原始 EA;离开交易窗口时
_orderPlacedInWindow会被复位。
参数
| 名称 | 类型 | 默认值 | 说明 |
|---|---|---|---|
OrderVolume |
decimal |
0.1 |
每张限价订单的交易量。 |
CandleSizePoints |
decimal |
75 |
信号蜡烛实体的最小长度(按经纪商报价点)。 |
StopLossMultiplier |
decimal |
0.8 |
乘以蜡烛实体后得到止损距离。 |
TakeProfitMultiplier |
decimal |
0.8 |
乘以蜡烛实体后得到止盈距离。 |
BuyIndentPoints |
decimal |
16 |
买入限价单相对于卖价的下移点数。 |
SellIndentPoints |
decimal |
20 |
卖出限价单相对于买价的上移点数。 |
EntryWindowMinutes |
int |
5 |
每个交易窗口的持续时间。 |
CandleType |
DataType |
5 分钟蜡烛 | 策略使用的主时间框。 |
StartTime0..5 |
TimeSpan |
00:05, 04:05, 08:05, 12:05, 16:05, 20:05 |
六个交易窗口的起始时间。 |
与原版 EA 的差异
- 原 EA 在下单时直接附带止损和止盈。本移植通过内部保存价格并在触发时用市价单平仓来模拟该行为。
- 所有阈值均基于
Security.PriceStep,因此无需额外参数即可适配四位或五位报价的外汇品种。 - 止损和止盈只在蜡烛收盘时检查,而 MetaTrader 服务器上的止损可能在蜡烛内部触发。
- 移植版本移除了声音提示和订单评论,取而代之的是 StockSharp 的日志信息。
使用建议
- 该策略面向采用分数点定价的外汇产品。运行前请确认
PriceStep与预期的点值一致,以免过滤条件失真。 - 由于止损/止盈属于“隐藏”逻辑,建议在独立环境运行,或配合券商侧风控以防连接中断。
- 若经纪商交易时段不同,可调整
StartTime参数,或把时间设置到交易日之外以禁用某个窗口。 - 将策略加载到图表上有助于可视化挂单,并验证每个窗口最多只出现一次入场机会。
using System;
using System.Linq;
using System.Collections.Generic;
using Ecng.Common;
using Ecng.Collections;
using Ecng.Serialization;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Bull vs Medved strategy converted from MetaTrader 4.
/// Enters market orders during predefined intraday windows when multi-candle patterns appear.
/// Exits on candle-based stop-loss / take-profit levels.
/// </summary>
public class BullVsMedvedWindowStrategy : Strategy
{
private readonly StrategyParam<decimal> _candleSizePoints;
private readonly StrategyParam<decimal> _stopLossMultiplier;
private readonly StrategyParam<decimal> _takeProfitMultiplier;
private readonly StrategyParam<int> _entryWindowMinutes;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<TimeSpan> _startTime0;
private readonly StrategyParam<TimeSpan> _startTime1;
private readonly StrategyParam<TimeSpan> _startTime2;
private readonly StrategyParam<TimeSpan> _startTime3;
private readonly StrategyParam<TimeSpan> _startTime4;
private readonly StrategyParam<TimeSpan> _startTime5;
private decimal _pointValue;
private decimal _candleSizeThreshold;
private decimal _bodyMinSize;
private decimal _pullbackSize;
private ICandleMessage _previousCandle1;
private ICandleMessage _previousCandle2;
private TimeSpan[] _entryTimes = Array.Empty<TimeSpan>();
private TimeSpan _entryWindow = TimeSpan.Zero;
private bool _orderPlacedInWindow;
private decimal _entryPrice;
private decimal? _longStopPrice;
private decimal? _longTakePrice;
private decimal? _shortStopPrice;
private decimal? _shortTakePrice;
private bool _exitRequested;
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public BullVsMedvedWindowStrategy()
{
_candleSizePoints = Param(nameof(CandleSizePoints), 75m)
.SetDisplay("Body Size (points)", "Minimum body size for the latest candle", "Filters")
.SetGreaterThanZero();
_stopLossMultiplier = Param(nameof(StopLossMultiplier), 0.8m)
.SetDisplay("Stop Multiplier", "Coefficient applied to the candle body for stop-loss", "Risk")
.SetGreaterThanZero();
_takeProfitMultiplier = Param(nameof(TakeProfitMultiplier), 0.8m)
.SetDisplay("Take Profit Multiplier", "Coefficient applied to the candle body for take-profit", "Risk")
.SetGreaterThanZero();
_entryWindowMinutes = Param(nameof(EntryWindowMinutes), 10)
.SetDisplay("Entry Window", "Duration of each trading window in minutes", "Timing")
.SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for pattern detection", "Data");
_startTime0 = Param(nameof(StartTime0), new TimeSpan(0, 5, 0))
.SetDisplay("Start Time #1", "First trading window start", "Timing");
_startTime1 = Param(nameof(StartTime1), new TimeSpan(4, 5, 0))
.SetDisplay("Start Time #2", "Second trading window start", "Timing");
_startTime2 = Param(nameof(StartTime2), new TimeSpan(8, 5, 0))
.SetDisplay("Start Time #3", "Third trading window start", "Timing");
_startTime3 = Param(nameof(StartTime3), new TimeSpan(12, 5, 0))
.SetDisplay("Start Time #4", "Fourth trading window start", "Timing");
_startTime4 = Param(nameof(StartTime4), new TimeSpan(16, 5, 0))
.SetDisplay("Start Time #5", "Fifth trading window start", "Timing");
_startTime5 = Param(nameof(StartTime5), new TimeSpan(20, 5, 0))
.SetDisplay("Start Time #6", "Sixth trading window start", "Timing");
}
/// <summary>
/// Minimum bullish or bearish body size in broker points.
/// </summary>
public decimal CandleSizePoints
{
get => _candleSizePoints.Value;
set => _candleSizePoints.Value = value;
}
/// <summary>
/// Multiplier applied to the signal candle body to calculate the stop-loss distance.
/// </summary>
public decimal StopLossMultiplier
{
get => _stopLossMultiplier.Value;
set => _stopLossMultiplier.Value = value;
}
/// <summary>
/// Multiplier applied to the signal candle body to calculate the take-profit distance.
/// </summary>
public decimal TakeProfitMultiplier
{
get => _takeProfitMultiplier.Value;
set => _takeProfitMultiplier.Value = value;
}
/// <summary>
/// Duration of each trading window in minutes.
/// </summary>
public int EntryWindowMinutes
{
get => _entryWindowMinutes.Value;
set => _entryWindowMinutes.Value = value;
}
/// <summary>
/// Candle type used to evaluate price patterns.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// First trading window start time.
/// </summary>
public TimeSpan StartTime0
{
get => _startTime0.Value;
set => _startTime0.Value = value;
}
/// <summary>
/// Second trading window start time.
/// </summary>
public TimeSpan StartTime1
{
get => _startTime1.Value;
set => _startTime1.Value = value;
}
/// <summary>
/// Third trading window start time.
/// </summary>
public TimeSpan StartTime2
{
get => _startTime2.Value;
set => _startTime2.Value = value;
}
/// <summary>
/// Fourth trading window start time.
/// </summary>
public TimeSpan StartTime3
{
get => _startTime3.Value;
set => _startTime3.Value = value;
}
/// <summary>
/// Fifth trading window start time.
/// </summary>
public TimeSpan StartTime4
{
get => _startTime4.Value;
set => _startTime4.Value = value;
}
/// <summary>
/// Sixth trading window start time.
/// </summary>
public TimeSpan StartTime5
{
get => _startTime5.Value;
set => _startTime5.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pointValue = 0m;
_candleSizeThreshold = 0m;
_bodyMinSize = 0m;
_pullbackSize = 0m;
_entryWindow = TimeSpan.Zero;
_previousCandle1 = null;
_previousCandle2 = null;
_entryTimes = Array.Empty<TimeSpan>();
_orderPlacedInWindow = false;
_entryPrice = 0m;
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
_exitRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_pointValue = Security?.PriceStep ?? 1m;
_candleSizeThreshold = CandleSizePoints * _pointValue;
_bodyMinSize = 10m * _pointValue;
_pullbackSize = 20m * _pointValue;
_entryWindow = TimeSpan.FromMinutes(EntryWindowMinutes);
_entryTimes = new[]
{
StartTime0,
StartTime1,
StartTime2,
StartTime3,
StartTime4,
StartTime5,
};
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (HandlePositionExits(candle))
{
ShiftHistory(candle);
return;
}
var inWindow = IsWithinEntryWindow(candle.CloseTime);
if (!inWindow)
{
_orderPlacedInWindow = false;
ShiftHistory(candle);
return;
}
if (_orderPlacedInWindow || Position != 0m)
{
ShiftHistory(candle);
return;
}
if (_previousCandle1 is null || _previousCandle2 is null)
{
ShiftHistory(candle);
return;
}
var shift1 = candle;
var shift2 = _previousCandle1;
var shift3 = _previousCandle2;
var placedOrder = false;
var isBull = IsBull(shift3, shift2, shift1);
var isBadBull = IsBadBull(shift3, shift2, shift1);
var isCoolBull = IsCoolBull(shift2, shift1);
var isBear = IsBear(shift1);
if (isBull && !isBadBull)
placedOrder = TryBuyMarket(shift1);
else if (isCoolBull)
placedOrder = TryBuyMarket(shift1);
else if (isBear)
placedOrder = TrySellMarket(shift1);
if (placedOrder)
_orderPlacedInWindow = true;
ShiftHistory(candle);
}
private bool HandlePositionExits(ICandleMessage candle)
{
if (Position > 0m)
{
if (!_exitRequested && _longStopPrice is decimal stop && candle.LowPrice <= stop)
{
_exitRequested = true;
SellMarket();
ResetProtectionLevels();
return true;
}
if (!_exitRequested && _longTakePrice is decimal take && candle.HighPrice >= take)
{
_exitRequested = true;
SellMarket();
ResetProtectionLevels();
return true;
}
}
else if (Position < 0m)
{
if (!_exitRequested && _shortStopPrice is decimal stop && candle.HighPrice >= stop)
{
_exitRequested = true;
BuyMarket();
ResetProtectionLevels();
return true;
}
if (!_exitRequested && _shortTakePrice is decimal take && candle.LowPrice <= take)
{
_exitRequested = true;
BuyMarket();
ResetProtectionLevels();
return true;
}
}
return false;
}
private bool TryBuyMarket(ICandleMessage referenceCandle)
{
var body = (referenceCandle.ClosePrice - referenceCandle.OpenPrice).Abs();
var stopDistance = RoundToPoint(body * StopLossMultiplier);
var takeDistance = RoundToPoint(body * TakeProfitMultiplier);
var price = referenceCandle.ClosePrice;
BuyMarket();
_entryPrice = price;
_longStopPrice = stopDistance > 0m ? NormalizePrice(price - stopDistance) : null;
_longTakePrice = takeDistance > 0m ? NormalizePrice(price + takeDistance) : null;
_shortStopPrice = null;
_shortTakePrice = null;
_exitRequested = false;
return true;
}
private bool TrySellMarket(ICandleMessage referenceCandle)
{
var body = (referenceCandle.ClosePrice - referenceCandle.OpenPrice).Abs();
var stopDistance = RoundToPoint(body * StopLossMultiplier);
var takeDistance = RoundToPoint(body * TakeProfitMultiplier);
var price = referenceCandle.ClosePrice;
SellMarket();
_entryPrice = price;
_shortStopPrice = stopDistance > 0m ? NormalizePrice(price + stopDistance) : null;
_shortTakePrice = takeDistance > 0m ? NormalizePrice(price - takeDistance) : null;
_longStopPrice = null;
_longTakePrice = null;
_exitRequested = false;
return true;
}
private bool IsWithinEntryWindow(DateTimeOffset time)
{
if (_entryWindow <= TimeSpan.Zero)
return false;
var tod = time.TimeOfDay;
for (var i = 0; i < _entryTimes.Length; i++)
{
var start = _entryTimes[i];
var end = start + _entryWindow;
if (tod >= start && tod <= end)
return true;
}
return false;
}
private void ShiftHistory(ICandleMessage candle)
{
_previousCandle2 = _previousCandle1;
_previousCandle1 = candle;
}
private bool IsBull(ICandleMessage shift3, ICandleMessage shift2, ICandleMessage shift1)
{
return shift3.ClosePrice > shift2.OpenPrice &&
(shift2.ClosePrice - shift2.OpenPrice) >= _bodyMinSize &&
(shift1.ClosePrice - shift1.OpenPrice) >= _candleSizeThreshold;
}
private bool IsBadBull(ICandleMessage shift3, ICandleMessage shift2, ICandleMessage shift1)
{
return (shift3.ClosePrice - shift3.OpenPrice) >= _bodyMinSize &&
(shift2.ClosePrice - shift2.OpenPrice) >= _bodyMinSize &&
(shift1.ClosePrice - shift1.OpenPrice) >= _candleSizeThreshold;
}
private bool IsCoolBull(ICandleMessage shift2, ICandleMessage shift1)
{
return (shift2.OpenPrice - shift2.ClosePrice) >= _pullbackSize &&
shift2.ClosePrice <= shift1.OpenPrice &&
shift1.ClosePrice > shift2.OpenPrice &&
(shift1.ClosePrice - shift1.OpenPrice) >= 0.4m * _candleSizeThreshold;
}
private bool IsBear(ICandleMessage shift1)
{
return (shift1.OpenPrice - shift1.ClosePrice) >= _candleSizeThreshold;
}
private decimal NormalizePrice(decimal price)
{
if (_pointValue <= 0m)
return price;
var steps = price / _pointValue;
var roundedSteps = decimal.Round(steps, MidpointRounding.AwayFromZero);
return roundedSteps * _pointValue;
}
private decimal RoundToPoint(decimal value)
{
if (_pointValue <= 0m)
return value;
var steps = value / _pointValue;
var roundedSteps = decimal.Round(steps, MidpointRounding.AwayFromZero);
return roundedSteps * _pointValue;
}
private void ResetProtectionLevels()
{
_longStopPrice = null;
_longTakePrice = null;
_shortStopPrice = null;
_shortTakePrice = null;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math, Array, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
class bull_vs_medved_window_strategy(Strategy):
def __init__(self):
super(bull_vs_medved_window_strategy, self).__init__()
self._candle_size_points = self.Param("CandleSizePoints", Decimal(75)) \
.SetDisplay("Body Size (points)", "Minimum body size for the latest candle", "Filters")
self._stop_loss_multiplier = self.Param("StopLossMultiplier", Decimal(0.8)) \
.SetDisplay("Stop Multiplier", "Coefficient applied to the candle body for stop-loss", "Risk")
self._take_profit_multiplier = self.Param("TakeProfitMultiplier", Decimal(0.8)) \
.SetDisplay("Take Profit Multiplier", "Coefficient applied to the candle body for take-profit", "Risk")
self._entry_window_minutes = self.Param("EntryWindowMinutes", 10) \
.SetDisplay("Entry Window", "Duration of each trading window in minutes", "Timing")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Primary timeframe for pattern detection", "Data")
self._start_time0 = self.Param("StartTime0", TimeSpan(0, 5, 0)) \
.SetDisplay("Start Time #1", "First trading window start", "Timing")
self._start_time1 = self.Param("StartTime1", TimeSpan(4, 5, 0)) \
.SetDisplay("Start Time #2", "Second trading window start", "Timing")
self._start_time2 = self.Param("StartTime2", TimeSpan(8, 5, 0)) \
.SetDisplay("Start Time #3", "Third trading window start", "Timing")
self._start_time3 = self.Param("StartTime3", TimeSpan(12, 5, 0)) \
.SetDisplay("Start Time #4", "Fourth trading window start", "Timing")
self._start_time4 = self.Param("StartTime4", TimeSpan(16, 5, 0)) \
.SetDisplay("Start Time #5", "Fifth trading window start", "Timing")
self._start_time5 = self.Param("StartTime5", TimeSpan(20, 5, 0)) \
.SetDisplay("Start Time #6", "Sixth trading window start", "Timing")
self._point_value = Decimal(0)
self._candle_size_threshold = Decimal(0)
self._body_min_size = Decimal(0)
self._pullback_size = Decimal(0)
self._entry_window = TimeSpan.Zero
self._prev_candle1 = None
self._prev_candle2 = None
self._entry_times = []
self._order_placed_in_window = False
self._entry_price = Decimal(0)
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._exit_requested = False
@property
def CandleSizePoints(self):
return self._candle_size_points.Value
@property
def StopLossMultiplier(self):
return self._stop_loss_multiplier.Value
@property
def TakeProfitMultiplier(self):
return self._take_profit_multiplier.Value
@property
def EntryWindowMinutes(self):
return self._entry_window_minutes.Value
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(bull_vs_medved_window_strategy, self).OnStarted2(time)
ps = self.Security.PriceStep if self.Security is not None else None
self._point_value = Decimal(ps) if ps is not None else Decimal(1)
if self._point_value <= Decimal(0):
self._point_value = Decimal(1)
self._candle_size_threshold = Decimal(self.CandleSizePoints) * self._point_value
self._body_min_size = Decimal(10) * self._point_value
self._pullback_size = Decimal(20) * self._point_value
self._entry_window = TimeSpan.FromMinutes(float(self.EntryWindowMinutes))
self._entry_times = [
self._start_time0.Value,
self._start_time1.Value,
self._start_time2.Value,
self._start_time3.Value,
self._start_time4.Value,
self._start_time5.Value,
]
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._handle_position_exits(candle):
self._shift_history(candle)
return
in_window = self._is_within_entry_window(candle.CloseTime)
if not in_window:
self._order_placed_in_window = False
self._shift_history(candle)
return
if self._order_placed_in_window or self.Position != Decimal(0):
self._shift_history(candle)
return
if self._prev_candle1 is None or self._prev_candle2 is None:
self._shift_history(candle)
return
shift1 = candle
shift2 = self._prev_candle1
shift3 = self._prev_candle2
placed_order = False
is_bull = self._check_bull(shift3, shift2, shift1)
is_bad_bull = self._check_bad_bull(shift3, shift2, shift1)
is_cool_bull = self._check_cool_bull(shift2, shift1)
is_bear = self._check_bear(shift1)
if is_bull and not is_bad_bull:
placed_order = self._try_buy_market(shift1)
elif is_cool_bull:
placed_order = self._try_buy_market(shift1)
elif is_bear:
placed_order = self._try_sell_market(shift1)
if placed_order:
self._order_placed_in_window = True
self._shift_history(candle)
def _handle_position_exits(self, candle):
if self.Position > Decimal(0):
if not self._exit_requested and self._long_stop is not None and candle.LowPrice <= self._long_stop:
self._exit_requested = True
self.SellMarket()
self._reset_protection()
return True
if not self._exit_requested and self._long_take is not None and candle.HighPrice >= self._long_take:
self._exit_requested = True
self.SellMarket()
self._reset_protection()
return True
elif self.Position < Decimal(0):
if not self._exit_requested and self._short_stop is not None and candle.HighPrice >= self._short_stop:
self._exit_requested = True
self.BuyMarket()
self._reset_protection()
return True
if not self._exit_requested and self._short_take is not None and candle.LowPrice <= self._short_take:
self._exit_requested = True
self.BuyMarket()
self._reset_protection()
return True
return False
def _try_buy_market(self, reference_candle):
body = Math.Abs(reference_candle.ClosePrice - reference_candle.OpenPrice)
stop_distance = self._round_to_point(body * self.StopLossMultiplier)
take_distance = self._round_to_point(body * self.TakeProfitMultiplier)
price = reference_candle.ClosePrice
self.BuyMarket()
self._entry_price = price
self._long_stop = self._normalize_price(price - stop_distance) if stop_distance > Decimal(0) else None
self._long_take = self._normalize_price(price + take_distance) if take_distance > Decimal(0) else None
self._short_stop = None
self._short_take = None
self._exit_requested = False
return True
def _try_sell_market(self, reference_candle):
body = Math.Abs(reference_candle.ClosePrice - reference_candle.OpenPrice)
stop_distance = self._round_to_point(body * self.StopLossMultiplier)
take_distance = self._round_to_point(body * self.TakeProfitMultiplier)
price = reference_candle.ClosePrice
self.SellMarket()
self._entry_price = price
self._short_stop = self._normalize_price(price + stop_distance) if stop_distance > Decimal(0) else None
self._short_take = self._normalize_price(price - take_distance) if take_distance > Decimal(0) else None
self._long_stop = None
self._long_take = None
self._exit_requested = False
return True
def _is_within_entry_window(self, time):
if self._entry_window <= TimeSpan.Zero:
return False
tod = time.TimeOfDay
for start in self._entry_times:
end = start.Add(self._entry_window)
if tod >= start and tod <= end:
return True
return False
def _check_bull(self, s3, s2, s1):
return (s3.ClosePrice > s2.OpenPrice and
(s2.ClosePrice - s2.OpenPrice) >= self._body_min_size and
(s1.ClosePrice - s1.OpenPrice) >= self._candle_size_threshold)
def _check_bad_bull(self, s3, s2, s1):
return ((s3.ClosePrice - s3.OpenPrice) >= self._body_min_size and
(s2.ClosePrice - s2.OpenPrice) >= self._body_min_size and
(s1.ClosePrice - s1.OpenPrice) >= self._candle_size_threshold)
def _check_cool_bull(self, s2, s1):
return ((s2.OpenPrice - s2.ClosePrice) >= self._pullback_size and
s2.ClosePrice <= s1.OpenPrice and
s1.ClosePrice > s2.OpenPrice and
(s1.ClosePrice - s1.OpenPrice) >= Decimal(0.4) * self._candle_size_threshold)
def _check_bear(self, s1):
return (s1.OpenPrice - s1.ClosePrice) >= self._candle_size_threshold
def _normalize_price(self, price):
if self._point_value <= Decimal(0):
return price
steps = price / self._point_value
rounded_steps = Decimal.Round(steps, 0)
return rounded_steps * self._point_value
def _round_to_point(self, value):
if self._point_value <= Decimal(0):
return value
steps = value / self._point_value
rounded_steps = Decimal.Round(steps, 0)
return rounded_steps * self._point_value
def _shift_history(self, candle):
self._prev_candle2 = self._prev_candle1
self._prev_candle1 = candle
def _reset_protection(self):
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
def OnReseted(self):
super(bull_vs_medved_window_strategy, self).OnReseted()
self._point_value = Decimal(0)
self._candle_size_threshold = Decimal(0)
self._body_min_size = Decimal(0)
self._pullback_size = Decimal(0)
self._entry_window = TimeSpan.Zero
self._prev_candle1 = None
self._prev_candle2 = None
self._entry_times = []
self._order_placed_in_window = False
self._entry_price = Decimal(0)
self._long_stop = None
self._long_take = None
self._short_stop = None
self._short_take = None
self._exit_requested = False
def CreateClone(self):
return bull_vs_medved_window_strategy()