开盘时间双时段策略
概述
开盘时间双时段策略 通过预设的时间计划自动交易,可在同一交易日内管理两个独立的交易时段。每个时段都可以单独设置方向、风控参数以及可选的强制平仓窗口。该实现基于原始 MetaTrader 策略,使用 StockSharp 的高级 API、蜡烛数据和参数系统来完成配置与优化。
交易逻辑
- 时段平仓窗口。 每个时段都可以启用一个单独的平仓窗口。当蜡烛时间落入窗口(开始时间加上全局持续时间)时,对应时段的仓位会被强制关闭,同时重置内部状态。
- 移动止损维护。 当启用移动止损和步长后,策略会在每根收盘蜡烛上检查价格是否至少向有利方向移动了
TrailingStop + TrailingStep。满足条件时止损价向前推进TrailingStop,并要求推进距离至少达到步长以避免频繁更新。 - 止损/止盈监控。 每个时段拥有独立的止损和止盈距离(以点为单位)。每根收盘蜡烛都会用最高价/最低价与这些水平比较,一旦被触发立即关闭该时段。
- 按星期过滤。 只有在被允许的工作日内策略才会开仓;若当前蜡烛属于未启用的工作日,则不会产生新的交易。
- 开仓时间窗口。 每个时段有自己的开仓开始与结束时间,全局持续时间会延长窗口的结束边界。在窗口内且该时段尚未持仓时,会按配置的方向市价开仓。
- 仓位同步。 活动时段会贡献一个目标净仓位,策略调用
BuyMarket或SellMarket使实际净仓位与各时段的头寸之和一致。每个时段会保存各自的入场价、止损/止盈以及移动止损状态。
参数说明
- Close Window #1 / Close Window #2 – 是否启用每个时段的强制平仓窗口。
- Close Start #1 / Close Start #2 – 对应平仓窗口的起始时间(本地时区)。
- Trailing Stop / Trailing Step – 移动止损距离与步长(点)。只有两者均大于零时才会启用移动止损。
- Trade Monday … Trade Friday – 星期过滤开关,至少需要保留一个开启的交易日。
- Open Start #1 / Open End #1 / Open Start #2 / Open End #2 – 每个时段的开仓时间窗口,结束时间会加上全局持续时间。
- Window Duration – 添加到开仓与平仓窗口结束边界的额外时间。
- Direction #1 / Direction #2 – 每个时段的交易方向(
true为做多,false为做空)。 - Trade Volume – 每个时段的下单数量,默认与原策略相同,两段使用一致的手数。
- Stop Loss #1 / Take Profit #1 / Stop Loss #2 / Take Profit #2 – 各时段的止损与止盈距离(点),为零表示禁用该水平。
- Candle Type – 驱动策略的蜡烛类型,所有时间判断与风控检查都在蜡烛收盘时执行。
风控细节
- 点值通过证券的最小价格步长转换;若品种有三位或五位小数,则将步长乘以十以复现 MetaTrader 的点定义。
- 移动止损逻辑在两个时段之间共享,而止损/止盈值相互独立。
- 当止损、止盈或移动止损被触发后,该时段会立即重置,可在同一窗口内再次开仓。
限制与注意事项
- StockSharp 采用净头寸模式。如果两个时段设置为相反方向,净头寸会相互抵消,而无法同时保持对冲仓位。若需要真正的对冲,请使用支持对冲的投资组合。
- 策略基于所选蜡烛频率做出决策,较大的时间周期会比原始的逐笔实现响应更慢。
- 时间判断依赖终端的本地时间,请确保交易所时间与终端时间保持同步。
使用建议
- 选择与交易计划粒度匹配的蜡烛类型,例如一分钟蜡烛可以实现精细的时间控制。
- 结合星期过滤和平仓窗口可以避免在不希望的时段持仓过夜。
- 通过
StrategyParam参数执行优化,主要参数已启用SetCanOptimize,方便批量测试。
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>
/// Time based strategy that opens up to two independent sessions with individual direction and risk management.
/// Supports configurable opening windows, optional forced closing windows, pip based stops and trailing logic.
/// </summary>
public class OpenTimeTwoStrategy : Strategy
{
private readonly StrategyParam<int> _secondsInDay;
private readonly StrategyParam<bool> _useClosingWindowOne;
private readonly StrategyParam<TimeSpan> _closeWindowOneStart;
private readonly StrategyParam<bool> _useClosingWindowTwo;
private readonly StrategyParam<TimeSpan> _closeWindowTwoStart;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<bool> _tradeOnMonday;
private readonly StrategyParam<bool> _tradeOnTuesday;
private readonly StrategyParam<bool> _tradeOnWednesday;
private readonly StrategyParam<bool> _tradeOnThursday;
private readonly StrategyParam<bool> _tradeOnFriday;
private readonly StrategyParam<TimeSpan> _intervalOneOpenStart;
private readonly StrategyParam<TimeSpan> _intervalOneOpenEnd;
private readonly StrategyParam<TimeSpan> _intervalTwoOpenStart;
private readonly StrategyParam<TimeSpan> _intervalTwoOpenEnd;
private readonly StrategyParam<TimeSpan> _duration;
private readonly StrategyParam<bool> _intervalOneBuy;
private readonly StrategyParam<bool> _intervalTwoBuy;
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _stopLossOnePips;
private readonly StrategyParam<decimal> _takeProfitOnePips;
private readonly StrategyParam<decimal> _stopLossTwoPips;
private readonly StrategyParam<decimal> _takeProfitTwoPips;
private readonly StrategyParam<DataType> _candleType;
private readonly IntervalState _intervalOne = new();
private readonly IntervalState _intervalTwo = new();
private decimal _pipSize;
/// <summary>
/// Use closing window for the first interval.
/// </summary>
public bool UseClosingWindowOne
{
get => _useClosingWindowOne.Value;
set => _useClosingWindowOne.Value = value;
}
/// <summary>
/// Closing window start time for the first interval.
/// </summary>
public TimeSpan CloseWindowOneStart
{
get => _closeWindowOneStart.Value;
set => _closeWindowOneStart.Value = value;
}
/// <summary>
/// Use closing window for the second interval.
/// </summary>
public bool UseClosingWindowTwo
{
get => _useClosingWindowTwo.Value;
set => _useClosingWindowTwo.Value = value;
}
/// <summary>
/// Closing window start time for the second interval.
/// </summary>
public TimeSpan CloseWindowTwoStart
{
get => _closeWindowTwoStart.Value;
set => _closeWindowTwoStart.Value = value;
}
/// <summary>
/// Trailing stop distance in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Trailing step distance in pips.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Enable trading on Monday.
/// </summary>
public bool TradeOnMonday
{
get => _tradeOnMonday.Value;
set => _tradeOnMonday.Value = value;
}
/// <summary>
/// Enable trading on Tuesday.
/// </summary>
public bool TradeOnTuesday
{
get => _tradeOnTuesday.Value;
set => _tradeOnTuesday.Value = value;
}
/// <summary>
/// Enable trading on Wednesday.
/// </summary>
public bool TradeOnWednesday
{
get => _tradeOnWednesday.Value;
set => _tradeOnWednesday.Value = value;
}
/// <summary>
/// Enable trading on Thursday.
/// </summary>
public bool TradeOnThursday
{
get => _tradeOnThursday.Value;
set => _tradeOnThursday.Value = value;
}
/// <summary>
/// Enable trading on Friday.
/// </summary>
public bool TradeOnFriday
{
get => _tradeOnFriday.Value;
set => _tradeOnFriday.Value = value;
}
/// <summary>
/// Opening window start for the first interval.
/// </summary>
public TimeSpan IntervalOneOpenStart
{
get => _intervalOneOpenStart.Value;
set => _intervalOneOpenStart.Value = value;
}
/// <summary>
/// Opening window end for the first interval.
/// </summary>
public TimeSpan IntervalOneOpenEnd
{
get => _intervalOneOpenEnd.Value;
set => _intervalOneOpenEnd.Value = value;
}
/// <summary>
/// Opening window start for the second interval.
/// </summary>
public TimeSpan IntervalTwoOpenStart
{
get => _intervalTwoOpenStart.Value;
set => _intervalTwoOpenStart.Value = value;
}
/// <summary>
/// Opening window end for the second interval.
/// </summary>
public TimeSpan IntervalTwoOpenEnd
{
get => _intervalTwoOpenEnd.Value;
set => _intervalTwoOpenEnd.Value = value;
}
/// <summary>
/// Extra duration added to each opening and closing window.
/// </summary>
public TimeSpan Duration
{
get => _duration.Value;
set => _duration.Value = value;
}
/// <summary>
/// Total number of seconds considered a full trading day.
/// </summary>
public int SecondsInDay
{
get => _secondsInDay.Value;
set => _secondsInDay.Value = value;
}
/// <summary>
/// Trade direction for interval one (true for buy, false for sell).
/// </summary>
public bool IntervalOneBuy
{
get => _intervalOneBuy.Value;
set => _intervalOneBuy.Value = value;
}
/// <summary>
/// Trade direction for interval two (true for buy, false for sell).
/// </summary>
public bool IntervalTwoBuy
{
get => _intervalTwoBuy.Value;
set => _intervalTwoBuy.Value = value;
}
/// <summary>
/// Trade volume for each interval.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set => _tradeVolume.Value = value;
}
/// <summary>
/// Stop loss distance for interval one in pips.
/// </summary>
public decimal StopLossOnePips
{
get => _stopLossOnePips.Value;
set => _stopLossOnePips.Value = value;
}
/// <summary>
/// Take profit distance for interval one in pips.
/// </summary>
public decimal TakeProfitOnePips
{
get => _takeProfitOnePips.Value;
set => _takeProfitOnePips.Value = value;
}
/// <summary>
/// Stop loss distance for interval two in pips.
/// </summary>
public decimal StopLossTwoPips
{
get => _stopLossTwoPips.Value;
set => _stopLossTwoPips.Value = value;
}
/// <summary>
/// Take profit distance for interval two in pips.
/// </summary>
public decimal TakeProfitTwoPips
{
get => _takeProfitTwoPips.Value;
set => _takeProfitTwoPips.Value = value;
}
/// <summary>
/// Candle type used as a driver for the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the strategy.
/// </summary>
public OpenTimeTwoStrategy()
{
_useClosingWindowOne = Param(nameof(UseClosingWindowOne), true)
.SetDisplay("Close Window #1", "Enable closing window for interval #1", "Closing")
;
_closeWindowOneStart = Param(nameof(CloseWindowOneStart), new TimeSpan(19, 50, 0))
.SetDisplay("Close Start #1", "Start time for closing window #1", "Closing");
_useClosingWindowTwo = Param(nameof(UseClosingWindowTwo), true)
.SetDisplay("Close Window #2", "Enable closing window for interval #2", "Closing")
;
_closeWindowTwoStart = Param(nameof(CloseWindowTwoStart), new TimeSpan(23, 20, 0))
.SetDisplay("Close Start #2", "Start time for closing window #2", "Closing");
_trailingStopPips = Param(nameof(TrailingStopPips), 30m)
.SetDisplay("Trailing Stop", "Trailing stop distance in pips", "Risk")
.SetRange(0m, 500m)
;
_trailingStepPips = Param(nameof(TrailingStepPips), 3m)
.SetDisplay("Trailing Step", "Trailing step distance in pips", "Risk")
.SetRange(0m, 200m)
;
_tradeOnMonday = Param(nameof(TradeOnMonday), true)
.SetDisplay("Trade Monday", "Allow trading on Monday", "Schedule")
;
_tradeOnTuesday = Param(nameof(TradeOnTuesday), true)
.SetDisplay("Trade Tuesday", "Allow trading on Tuesday", "Schedule")
;
_tradeOnWednesday = Param(nameof(TradeOnWednesday), true)
.SetDisplay("Trade Wednesday", "Allow trading on Wednesday", "Schedule")
;
_tradeOnThursday = Param(nameof(TradeOnThursday), true)
.SetDisplay("Trade Thursday", "Allow trading on Thursday", "Schedule")
;
_tradeOnFriday = Param(nameof(TradeOnFriday), true)
.SetDisplay("Trade Friday", "Allow trading on Friday", "Schedule")
;
_intervalOneOpenStart = Param(nameof(IntervalOneOpenStart), new TimeSpan(9, 30, 0))
.SetDisplay("Open Start #1", "Opening window start for interval #1", "Opening");
_intervalOneOpenEnd = Param(nameof(IntervalOneOpenEnd), new TimeSpan(14, 0, 0))
.SetDisplay("Open End #1", "Opening window end for interval #1", "Opening");
_intervalTwoOpenStart = Param(nameof(IntervalTwoOpenStart), TimeSpan.Zero)
.SetDisplay("Open Start #2", "Opening window start for interval #2", "Opening");
_intervalTwoOpenEnd = Param(nameof(IntervalTwoOpenEnd), TimeSpan.Zero)
.SetDisplay("Open End #2", "Opening window end for interval #2", "Opening");
_duration = Param(nameof(Duration), TimeSpan.FromSeconds(30))
.SetDisplay("Window Duration", "Extra duration added to opening/closing windows", "Opening")
.SetRange(TimeSpan.Zero, TimeSpan.FromHours(1));
_secondsInDay = Param(nameof(SecondsInDay), 24 * 60 * 60)
.SetGreaterThanZero()
.SetDisplay("Seconds In Day", "Total number of seconds in a trading day", "Opening");
_intervalOneBuy = Param(nameof(IntervalOneBuy), true)
.SetDisplay("Direction #1", "Trade direction for interval #1 (Buy=true)", "Opening")
;
_intervalTwoBuy = Param(nameof(IntervalTwoBuy), true)
.SetDisplay("Direction #2", "Trade direction for interval #2 (Buy=true)", "Opening")
;
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetDisplay("Trade Volume", "Volume for each interval", "Risk")
.SetRange(0.01m, 100m)
;
_stopLossOnePips = Param(nameof(StopLossOnePips), 30m)
.SetDisplay("Stop Loss #1", "Stop loss for interval #1 (pips)", "Risk")
.SetRange(0m, 1000m)
;
_takeProfitOnePips = Param(nameof(TakeProfitOnePips), 90m)
.SetDisplay("Take Profit #1", "Take profit for interval #1 (pips)", "Risk")
.SetRange(0m, 2000m)
;
_stopLossTwoPips = Param(nameof(StopLossTwoPips), 10m)
.SetDisplay("Stop Loss #2", "Stop loss for interval #2 (pips)", "Risk")
.SetRange(0m, 1000m)
;
_takeProfitTwoPips = Param(nameof(TakeProfitTwoPips), 35m)
.SetDisplay("Take Profit #2", "Take profit for interval #2 (pips)", "Risk")
.SetRange(0m, 2000m)
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(2).TimeFrame())
.SetDisplay("Candle Type", "Base candle type driving decisions", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_pipSize = 0m;
ResetInterval(_intervalOne);
ResetInterval(_intervalTwo);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var decimals = Security?.Decimals ?? 0;
var adjust = decimals is 3 or 5 ? 10m : 1m;
var step = Security?.PriceStep ?? 1m;
_pipSize = step * adjust;
if (_pipSize <= 0m)
{
_pipSize = 1m;
}
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
{
return;
}
var localTime = candle.OpenTime.ToLocalTime();
var timeOfDay = localTime.TimeOfDay;
if (UseClosingWindowOne && IsWithinSimpleWindow(timeOfDay, CloseWindowOneStart, Duration))
{
ExitInterval(_intervalOne);
}
if (UseClosingWindowTwo && IsWithinSimpleWindow(timeOfDay, CloseWindowTwoStart, Duration))
{
ExitInterval(_intervalTwo);
}
if (TrailingStopPips > 0m && TrailingStepPips > 0m)
{
UpdateTrailingStops(candle);
}
CheckRiskControls(_intervalOne, candle);
CheckRiskControls(_intervalTwo, candle);
if (!IsTradingDay(localTime.DayOfWeek))
{
return;
}
var inFirstWindow = IsWithinOpeningWindow(timeOfDay, IntervalOneOpenStart, IntervalOneOpenEnd);
if (inFirstWindow)
{
TryOpenInterval(_intervalOne, IntervalOneBuy, StopLossOnePips, TakeProfitOnePips, candle.ClosePrice);
}
var inSecondWindow = IsWithinOpeningWindow(timeOfDay, IntervalTwoOpenStart, IntervalTwoOpenEnd);
if (inSecondWindow)
{
TryOpenInterval(_intervalTwo, IntervalTwoBuy, StopLossTwoPips, TakeProfitTwoPips, candle.ClosePrice);
}
}
private void TryOpenInterval(IntervalState state, bool isBuy, decimal stopLossPips, decimal takeProfitPips, decimal referencePrice)
{
if (state.IsActive)
{
return;
}
if (TradeVolume <= 0m)
{
return;
}
var direction = isBuy ? 1 : -1;
var stopDistance = stopLossPips > 0m ? stopLossPips * _pipSize : 0m;
var takeDistance = takeProfitPips > 0m ? takeProfitPips * _pipSize : 0m;
decimal? stopPrice = null;
decimal? takePrice = null;
if (direction > 0)
{
if (stopDistance > 0m)
{
stopPrice = referencePrice - stopDistance;
}
if (takeDistance > 0m)
{
takePrice = referencePrice + takeDistance;
}
}
else
{
if (stopDistance > 0m)
{
stopPrice = referencePrice + stopDistance;
}
if (takeDistance > 0m)
{
takePrice = referencePrice - takeDistance;
}
}
state.IsActive = true;
state.Direction = direction;
state.EntryPrice = referencePrice;
state.StopLossPrice = stopPrice;
state.TakeProfitPrice = takePrice;
state.TrailingStopPrice = null;
SyncPosition();
}
private void UpdateTrailingStops(ICandleMessage candle)
{
var trailingDistance = TrailingStopPips * _pipSize;
var stepDistance = TrailingStepPips * _pipSize;
if (trailingDistance <= 0m || stepDistance <= 0m)
{
return;
}
UpdateTrailingForInterval(_intervalOne, candle, trailingDistance, stepDistance);
UpdateTrailingForInterval(_intervalTwo, candle, trailingDistance, stepDistance);
}
private static void ResetInterval(IntervalState state)
{
state.IsActive = false;
state.Direction = 0;
state.EntryPrice = 0m;
state.StopLossPrice = null;
state.TakeProfitPrice = null;
state.TrailingStopPrice = null;
}
private void UpdateTrailingForInterval(IntervalState state, ICandleMessage candle, decimal trailingDistance, decimal stepDistance)
{
if (!state.IsActive)
{
return;
}
if (state.Direction > 0)
{
var profit = candle.ClosePrice - state.EntryPrice;
if (profit <= trailingDistance + stepDistance)
{
return;
}
var proposed = candle.ClosePrice - trailingDistance;
if (state.TrailingStopPrice is null || proposed - state.TrailingStopPrice.Value >= stepDistance)
{
state.TrailingStopPrice = state.TrailingStopPrice is null
? proposed
: Math.Max(state.TrailingStopPrice.Value, proposed);
}
}
else
{
var profit = state.EntryPrice - candle.ClosePrice;
if (profit <= trailingDistance + stepDistance)
{
return;
}
var proposed = candle.ClosePrice + trailingDistance;
if (state.TrailingStopPrice is null || state.TrailingStopPrice.Value - proposed >= stepDistance)
{
state.TrailingStopPrice = state.TrailingStopPrice is null
? proposed
: Math.Min(state.TrailingStopPrice.Value, proposed);
}
}
}
private void CheckRiskControls(IntervalState state, ICandleMessage candle)
{
if (!state.IsActive)
{
return;
}
if (state.Direction > 0)
{
if (state.StopLossPrice is decimal sl && candle.LowPrice <= sl)
{
ExitInterval(state);
return;
}
if (state.TrailingStopPrice is decimal trail && candle.LowPrice <= trail)
{
ExitInterval(state);
return;
}
if (state.TakeProfitPrice is decimal tp && candle.HighPrice >= tp)
{
ExitInterval(state);
}
}
else
{
if (state.StopLossPrice is decimal sl && candle.HighPrice >= sl)
{
ExitInterval(state);
return;
}
if (state.TrailingStopPrice is decimal trail && candle.HighPrice >= trail)
{
ExitInterval(state);
return;
}
if (state.TakeProfitPrice is decimal tp && candle.LowPrice <= tp)
{
ExitInterval(state);
}
}
}
private void ExitInterval(IntervalState state)
{
if (!state.IsActive)
{
return;
}
ResetInterval(state);
SyncPosition();
}
private void SyncPosition()
{
var target = GetTargetPosition();
var diff = target - Position;
if (diff == 0m)
{
return;
}
if (diff > 0m)
{
BuyMarket(diff);
}
else
{
SellMarket(-diff);
}
}
private new decimal GetTargetPosition()
{
var target = 0m;
if (_intervalOne.IsActive)
{
target += _intervalOne.Direction * TradeVolume;
}
if (_intervalTwo.IsActive)
{
target += _intervalTwo.Direction * TradeVolume;
}
return target;
}
private bool IsTradingDay(DayOfWeek day)
{
return day switch
{
DayOfWeek.Monday => TradeOnMonday,
DayOfWeek.Tuesday => TradeOnTuesday,
DayOfWeek.Wednesday => TradeOnWednesday,
DayOfWeek.Thursday => TradeOnThursday,
DayOfWeek.Friday => TradeOnFriday,
_ => true,
};
}
private bool IsWithinOpeningWindow(TimeSpan current, TimeSpan start, TimeSpan end)
{
var startSec = ToSeconds(start);
var endSec = ToSeconds(end);
var durationSec = ToSeconds(Duration);
var currentSec = ToSeconds(current);
if (endSec <= startSec)
{
return false;
}
var finalEnd = Math.Min(SecondsInDay, endSec + durationSec);
return currentSec >= startSec && currentSec < finalEnd;
}
private bool IsWithinSimpleWindow(TimeSpan current, TimeSpan start, TimeSpan length)
{
var startSec = ToSeconds(start);
var currentSec = ToSeconds(current);
var lengthSec = Math.Max(0, ToSeconds(length));
var endSec = startSec + lengthSec;
if (lengthSec == 0)
{
return currentSec == startSec;
}
if (endSec <= SecondsInDay)
{
return currentSec >= startSec && currentSec < endSec;
}
endSec -= SecondsInDay;
return currentSec >= startSec || currentSec < endSec;
}
private int ToSeconds(TimeSpan time)
{
var value = time.TotalSeconds;
if (value < 0)
{
return 0;
}
if (value > SecondsInDay)
{
return SecondsInDay;
}
return (int)Math.Floor(value);
}
private sealed class IntervalState : IEquatable<IntervalState>
{
public bool IsActive;
public int Direction;
public decimal EntryPrice;
public decimal? StopLossPrice;
public decimal? TakeProfitPrice;
public decimal? TrailingStopPrice;
public bool Equals(IntervalState other)
{
return other != null
&& IsActive == other.IsActive
&& Direction == other.Direction
&& EntryPrice == other.EntryPrice
&& StopLossPrice == other.StopLossPrice
&& TakeProfitPrice == other.TakeProfitPrice
&& TrailingStopPrice == other.TrailingStopPrice;
}
public override bool Equals(object obj)
{
return obj is IntervalState other && Equals(other);
}
public override int GetHashCode()
{
return HashCode.Combine(IsActive, Direction, EntryPrice, StopLossPrice, TakeProfitPrice, TrailingStopPrice);
}
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from datatype_extensions import *
class open_time_two_strategy(Strategy):
def __init__(self):
super(open_time_two_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(2))).SetDisplay("Candle Type", "Base candle type", "General")
self._trade_volume = self.Param("TradeVolume", 0.1).SetDisplay("Trade Volume", "Volume for each interval", "Risk")
self._interval_one_buy = self.Param("IntervalOneBuy", True).SetDisplay("Direction #1", "Buy for interval #1", "Opening")
self._interval_two_buy = self.Param("IntervalTwoBuy", True).SetDisplay("Direction #2", "Buy for interval #2", "Opening")
self._sl1_pips = self.Param("StopLossOnePips", 30.0).SetDisplay("Stop Loss #1", "Stop loss for interval #1 (pips)", "Risk")
self._tp1_pips = self.Param("TakeProfitOnePips", 90.0).SetDisplay("Take Profit #1", "Take profit for interval #1 (pips)", "Risk")
self._sl2_pips = self.Param("StopLossTwoPips", 10.0).SetDisplay("Stop Loss #2", "Stop loss for interval #2 (pips)", "Risk")
self._tp2_pips = self.Param("TakeProfitTwoPips", 35.0).SetDisplay("Take Profit #2", "Take profit for interval #2 (pips)", "Risk")
@property
def CandleType(self): return self._candle_type.Value
@CandleType.setter
def CandleType(self, value): self._candle_type.Value = value
def OnReseted(self):
super(open_time_two_strategy, self).OnReseted()
self._int1_active = False
self._int2_active = False
self._int1_entry = 0
self._int2_entry = 0
self._int1_stop = None
self._int1_take = None
self._int2_stop = None
self._int2_take = None
def OnStarted2(self, time):
super(open_time_two_strategy, self).OnStarted2(time)
self._int1_active = False
self._int2_active = False
step = 1.0
if self.Security is not None and self.Security.PriceStep is not None and self.Security.PriceStep > 0:
step = float(self.Security.PriceStep)
self._pip_size = step
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self.OnProcess).Start()
def OnProcess(self, candle):
if candle.State != CandleStates.Finished:
return
close = candle.ClosePrice
hour = candle.OpenTime.Hour
# Check SL/TP for interval 1
if self._int1_active:
direction = 1 if self._interval_one_buy.Value else -1
if direction > 0:
if self._int1_stop is not None and candle.LowPrice <= self._int1_stop:
self.SellMarket(self._trade_volume.Value)
self._int1_active = False
elif self._int1_take is not None and candle.HighPrice >= self._int1_take:
self.SellMarket(self._trade_volume.Value)
self._int1_active = False
else:
if self._int1_stop is not None and candle.HighPrice >= self._int1_stop:
self.BuyMarket(self._trade_volume.Value)
self._int1_active = False
elif self._int1_take is not None and candle.LowPrice <= self._int1_take:
self.BuyMarket(self._trade_volume.Value)
self._int1_active = False
# Check SL/TP for interval 2
if self._int2_active:
direction = 1 if self._interval_two_buy.Value else -1
if direction > 0:
if self._int2_stop is not None and candle.LowPrice <= self._int2_stop:
self.SellMarket(self._trade_volume.Value)
self._int2_active = False
elif self._int2_take is not None and candle.HighPrice >= self._int2_take:
self.SellMarket(self._trade_volume.Value)
self._int2_active = False
else:
if self._int2_stop is not None and candle.HighPrice >= self._int2_stop:
self.BuyMarket(self._trade_volume.Value)
self._int2_active = False
elif self._int2_take is not None and candle.LowPrice <= self._int2_take:
self.BuyMarket(self._trade_volume.Value)
self._int2_active = False
# Open interval 1 during morning hours
if not self._int1_active and 9 <= hour < 14:
self._open_interval(1, close)
# Open interval 2 during afternoon hours
if not self._int2_active and 14 <= hour < 20:
self._open_interval(2, close)
def _open_interval(self, interval_num, price):
if interval_num == 1:
is_buy = self._interval_one_buy.Value
sl_pips = self._sl1_pips.Value
tp_pips = self._tp1_pips.Value
if is_buy:
self.BuyMarket(self._trade_volume.Value)
self._int1_stop = price - sl_pips * self._pip_size if sl_pips > 0 else None
self._int1_take = price + tp_pips * self._pip_size if tp_pips > 0 else None
else:
self.SellMarket(self._trade_volume.Value)
self._int1_stop = price + sl_pips * self._pip_size if sl_pips > 0 else None
self._int1_take = price - tp_pips * self._pip_size if tp_pips > 0 else None
self._int1_entry = price
self._int1_active = True
else:
is_buy = self._interval_two_buy.Value
sl_pips = self._sl2_pips.Value
tp_pips = self._tp2_pips.Value
if is_buy:
self.BuyMarket(self._trade_volume.Value)
self._int2_stop = price - sl_pips * self._pip_size if sl_pips > 0 else None
self._int2_take = price + tp_pips * self._pip_size if tp_pips > 0 else None
else:
self.SellMarket(self._trade_volume.Value)
self._int2_stop = price + sl_pips * self._pip_size if sl_pips > 0 else None
self._int2_take = price - tp_pips * self._pip_size if tp_pips > 0 else None
self._int2_entry = price
self._int2_active = True
def CreateClone(self):
return open_time_two_strategy()