設定可能なボイラープレート戦略
ボイラープレート設定可能戦略は、単純移動平均クロスオーバーまたはボリンジャースクイーズブレイクアウトの2つのモードを切り替えることができます。取引日とセッションフィルター、日付範囲、ニュースウィンドウ、ATRまたは静的リスク/リターンによるリスク管理機能を備えています。
詳細
- エントリー条件:
SmaCrossモードでは、高速SMAが低速SMAを上抜けするとロング、逆のクロスでショート。Squeezeモードでは、価格が狭いバンドの内側にある状態で外側のボリンジャーバンドをブレイクするとエントリー。
- ロング/ショート: ロング、ショート、または両方を任意の反転設定で設定可能。
- エグジット条件:
- ATRまたは静的パーセンテージに基づくストップロスとテイクプロフィット。
- 日次エグジット期間とニュースウィンドウがすべてのポジションをクローズ。
- ストップ: ドローダウン保護付きのトレードごとのストップロスとテイクプロフィット。
- デフォルト値:
Length= 20WideMultiplier= 1.5NarrowMultiplier= 2MaxLossPerc= 0.02AtrMultiplier= 1.5StaticRr= 2NewsWindow= 5MaxDrawdown= 0.1
- フィルター:
- カテゴリ: モジュラー
- 方向: ロングとショート
- インジケーター: SMA, Bollinger Bands, ATR
- ストップ: はい
- 複雑さ: 上級
- 時間軸: 任意
- 季節性: はい
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 高
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Boilerplate Configurable strategy.
/// In SMA cross mode a cross of SMA(FastLength) over SMA(Length) gives the signal; in squeeze mode a close beyond the
/// WideMultiplier Bollinger band that stays inside the NarrowMultiplier band does. Signals can be inverted and limited to one
/// side, and an opposite signal reverses the position (or only closes it when that side is disabled). Entries are allowed only
/// on selected days, inside the session and the date range, outside the daily exit period and the news window; the exit period
/// and the news window also close open positions. Each entry freezes a stop of AtrMultiplier ATRs or MaxLossPerc of the price
/// and a take profit StaticRr times that distance. Trading stops once the equity drawdown reaches MaxDrawdown.
/// </summary>
public class BoilerplateConfigurableStrategy : Strategy
{
private readonly StrategyParam<bool> _useSqueeze;
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _length;
private readonly StrategyParam<decimal> _wideMultiplier;
private readonly StrategyParam<decimal> _narrowMultiplier;
private readonly StrategyParam<bool> _allowLong;
private readonly StrategyParam<bool> _allowShort;
private readonly StrategyParam<bool> _invert;
private readonly StrategyParam<bool> _useAtrStops;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<decimal> _maxLossPerc;
private readonly StrategyParam<decimal> _staticRr;
private readonly StrategyParam<decimal> _maxDrawdown;
private readonly StrategyParam<bool> _tradeWeekends;
private readonly StrategyParam<int> _sessionStartHour;
private readonly StrategyParam<int> _sessionEndHour;
private readonly StrategyParam<DateTime> _startDate;
private readonly StrategyParam<DateTime> _endDate;
private readonly StrategyParam<bool> _useExitPeriod;
private readonly StrategyParam<int> _exitStartHour;
private readonly StrategyParam<int> _exitEndHour;
private readonly StrategyParam<bool> _useNewsFilter;
private readonly StrategyParam<int> _newsHour;
private readonly StrategyParam<int> _newsMinute;
private readonly StrategyParam<int> _newsWindow;
private readonly StrategyParam<DataType> _candleType;
private decimal? _prevFast;
private decimal? _prevSlow;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _initialEquity;
private decimal _peakEquity;
private bool _halted;
/// <summary>
/// Use the Bollinger squeeze mode instead of the SMA cross.
/// </summary>
public bool UseSqueeze
{
get => _useSqueeze.Value;
set => _useSqueeze.Value = value;
}
/// <summary>
/// Fast SMA period of the cross mode.
/// </summary>
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
/// <summary>
/// Slow SMA and Bollinger period.
/// </summary>
public int Length
{
get => _length.Value;
set => _length.Value = value;
}
/// <summary>
/// Deviation multiplier of the band price has to break.
/// </summary>
public decimal WideMultiplier
{
get => _wideMultiplier.Value;
set => _wideMultiplier.Value = value;
}
/// <summary>
/// Deviation multiplier of the band price has to stay inside.
/// </summary>
public decimal NarrowMultiplier
{
get => _narrowMultiplier.Value;
set => _narrowMultiplier.Value = value;
}
/// <summary>
/// Allow long entries.
/// </summary>
public bool AllowLong
{
get => _allowLong.Value;
set => _allowLong.Value = value;
}
/// <summary>
/// Allow short entries.
/// </summary>
public bool AllowShort
{
get => _allowShort.Value;
set => _allowShort.Value = value;
}
/// <summary>
/// Swap long and short signals.
/// </summary>
public bool Invert
{
get => _invert.Value;
set => _invert.Value = value;
}
/// <summary>
/// Size the stop with ATR instead of a fixed percent.
/// </summary>
public bool UseAtrStops
{
get => _useAtrStops.Value;
set => _useAtrStops.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrLength
{
get => _atrLength.Value;
set => _atrLength.Value = value;
}
/// <summary>
/// ATR multiple of the stop distance.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Stop distance as a fraction of the entry price.
/// </summary>
public decimal MaxLossPerc
{
get => _maxLossPerc.Value;
set => _maxLossPerc.Value = value;
}
/// <summary>
/// Take profit distance as a multiple of the stop distance.
/// </summary>
public decimal StaticRr
{
get => _staticRr.Value;
set => _staticRr.Value = value;
}
/// <summary>
/// Equity drawdown fraction that stops trading.
/// </summary>
public decimal MaxDrawdown
{
get => _maxDrawdown.Value;
set => _maxDrawdown.Value = value;
}
/// <summary>
/// Allow entries on Saturday and Sunday.
/// </summary>
public bool TradeWeekends
{
get => _tradeWeekends.Value;
set => _tradeWeekends.Value = value;
}
/// <summary>
/// Session start hour (UTC).
/// </summary>
public int SessionStartHour
{
get => _sessionStartHour.Value;
set => _sessionStartHour.Value = value;
}
/// <summary>
/// Session end hour (UTC, exclusive).
/// </summary>
public int SessionEndHour
{
get => _sessionEndHour.Value;
set => _sessionEndHour.Value = value;
}
/// <summary>
/// First date entries are allowed.
/// </summary>
public DateTime StartDate
{
get => _startDate.Value;
set => _startDate.Value = value;
}
/// <summary>
/// Last date entries are allowed.
/// </summary>
public DateTime EndDate
{
get => _endDate.Value;
set => _endDate.Value = value;
}
/// <summary>
/// Close positions and block entries in the daily exit period.
/// </summary>
public bool UseExitPeriod
{
get => _useExitPeriod.Value;
set => _useExitPeriod.Value = value;
}
/// <summary>
/// Exit period start hour (UTC).
/// </summary>
public int ExitStartHour
{
get => _exitStartHour.Value;
set => _exitStartHour.Value = value;
}
/// <summary>
/// Exit period end hour (UTC, exclusive).
/// </summary>
public int ExitEndHour
{
get => _exitEndHour.Value;
set => _exitEndHour.Value = value;
}
/// <summary>
/// Close positions and block entries around the daily news time.
/// </summary>
public bool UseNewsFilter
{
get => _useNewsFilter.Value;
set => _useNewsFilter.Value = value;
}
/// <summary>
/// News hour (UTC).
/// </summary>
public int NewsHour
{
get => _newsHour.Value;
set => _newsHour.Value = value;
}
/// <summary>
/// News minute.
/// </summary>
public int NewsMinute
{
get => _newsMinute.Value;
set => _newsMinute.Value = value;
}
/// <summary>
/// Minutes before and after the news time.
/// </summary>
public int NewsWindow
{
get => _newsWindow.Value;
set => _newsWindow.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public BoilerplateConfigurableStrategy()
{
_useSqueeze = Param(nameof(UseSqueeze), false)
.SetDisplay("Squeeze Mode", "Use the Bollinger squeeze mode instead of the SMA cross", "Mode");
_fastLength = Param(nameof(FastLength), 10)
.SetGreaterThanZero()
.SetDisplay("Fast Length", "Fast SMA period of the cross mode", "Indicators");
_length = Param(nameof(Length), 20)
.SetGreaterThanZero()
.SetDisplay("Length", "Slow SMA and Bollinger period", "Indicators");
_wideMultiplier = Param(nameof(WideMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("Wide Multiplier", "Deviation multiplier of the band price has to break", "Indicators");
_narrowMultiplier = Param(nameof(NarrowMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("Narrow Multiplier", "Deviation multiplier of the band price has to stay inside", "Indicators");
_allowLong = Param(nameof(AllowLong), true)
.SetDisplay("Allow Long", "Allow long entries", "Direction");
_allowShort = Param(nameof(AllowShort), true)
.SetDisplay("Allow Short", "Allow short entries", "Direction");
_invert = Param(nameof(Invert), false)
.SetDisplay("Invert", "Swap long and short signals", "Direction");
_useAtrStops = Param(nameof(UseAtrStops), true)
.SetDisplay("ATR Stops", "Size the stop with ATR instead of a fixed percent", "Risk");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiple of the stop distance", "Risk");
_maxLossPerc = Param(nameof(MaxLossPerc), 0.02m)
.SetGreaterThanZero()
.SetDisplay("Max Loss", "Stop distance as a fraction of the entry price", "Risk");
_staticRr = Param(nameof(StaticRr), 2m)
.SetGreaterThanZero()
.SetDisplay("Risk/Reward", "Take profit distance as a multiple of the stop distance", "Risk");
_maxDrawdown = Param(nameof(MaxDrawdown), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Max Drawdown", "Equity drawdown fraction that stops trading", "Risk");
_tradeWeekends = Param(nameof(TradeWeekends), true)
.SetDisplay("Trade Weekends", "Allow entries on Saturday and Sunday", "Filters");
_sessionStartHour = Param(nameof(SessionStartHour), 0)
.SetRange(0, 23)
.SetDisplay("Session Start", "Session start hour (UTC)", "Filters");
_sessionEndHour = Param(nameof(SessionEndHour), 24)
.SetRange(1, 24)
.SetDisplay("Session End", "Session end hour (UTC, exclusive)", "Filters");
_startDate = Param(nameof(StartDate), new DateTime(2000, 1, 1))
.SetDisplay("Start Date", "First date entries are allowed", "Filters");
_endDate = Param(nameof(EndDate), new DateTime(2100, 1, 1))
.SetDisplay("End Date", "Last date entries are allowed", "Filters");
_useExitPeriod = Param(nameof(UseExitPeriod), false)
.SetDisplay("Use Exit Period", "Close positions and block entries in the daily exit period", "Filters");
_exitStartHour = Param(nameof(ExitStartHour), 22)
.SetRange(0, 23)
.SetDisplay("Exit Start", "Exit period start hour (UTC)", "Filters");
_exitEndHour = Param(nameof(ExitEndHour), 24)
.SetRange(1, 24)
.SetDisplay("Exit End", "Exit period end hour (UTC, exclusive)", "Filters");
_useNewsFilter = Param(nameof(UseNewsFilter), false)
.SetDisplay("Use News Filter", "Close positions and block entries around the daily news time", "News");
_newsHour = Param(nameof(NewsHour), 12)
.SetRange(0, 23)
.SetDisplay("News Hour", "News hour (UTC)", "News");
_newsMinute = Param(nameof(NewsMinute), 30)
.SetRange(0, 59)
.SetDisplay("News Minute", "News minute", "News");
_newsWindow = Param(nameof(NewsWindow), 5)
.SetNotNegative()
.SetDisplay("News Window", "Minutes before and after the news time", "News");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_prevFast = null;
_prevSlow = null;
_stopPrice = null;
_takePrice = null;
_initialEquity = 0m;
_peakEquity = 0m;
_halted = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
_initialEquity = Portfolio?.BeginValue is decimal begin && begin > 0 ? begin : Portfolio?.CurrentValue ?? 0m;
_peakEquity = _initialEquity;
var fast = new SimpleMovingAverage { Length = FastLength };
var slow = new SimpleMovingAverage { Length = Length };
var deviation = new StandardDeviation { Length = Length };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(fast, slow, deviation, atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastValue, IIndicatorValue slowValue, IIndicatorValue deviationValue, IIndicatorValue atrValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!fastValue.IsFormed || !slowValue.IsFormed || !deviationValue.IsFormed || !atrValue.IsFormed)
return;
var fast = fastValue.GetValue<decimal>();
var slow = slowValue.GetValue<decimal>();
var prevFast = _prevFast;
var prevSlow = _prevSlow;
_prevFast = fast;
_prevSlow = slow;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var close = candle.ClosePrice;
// Stop and take profit frozen at entry.
if (Position > 0 && _stopPrice is decimal longStop && _takePrice is decimal longTake && (candle.LowPrice <= longStop || candle.HighPrice >= longTake))
{
ClosePosition();
return;
}
if (Position < 0 && _stopPrice is decimal shortStop && _takePrice is decimal shortTake && (candle.HighPrice >= shortStop || candle.LowPrice <= shortTake))
{
ClosePosition();
return;
}
if (_initialEquity > 0)
{
var equity = _initialEquity + PnL;
_peakEquity = Math.Max(_peakEquity, equity);
if (_peakEquity > 0 && (_peakEquity - equity) / _peakEquity >= MaxDrawdown)
_halted = true;
}
var time = candle.OpenTime;
if (_halted || InExitPeriod(time) || InNewsWindow(time))
{
ClosePosition();
return;
}
bool longSignal;
bool shortSignal;
if (UseSqueeze)
{
var sigma = deviationValue.GetValue<decimal>();
longSignal = close > slow + WideMultiplier * sigma && close < slow + NarrowMultiplier * sigma;
shortSignal = close < slow - WideMultiplier * sigma && close > slow - NarrowMultiplier * sigma;
}
else
{
longSignal = prevFast is decimal pf && prevSlow is decimal ps && pf <= ps && fast > slow;
shortSignal = prevFast is decimal pf2 && prevSlow is decimal ps2 && pf2 >= ps2 && fast < slow;
}
if (Invert)
(longSignal, shortSignal) = (shortSignal, longSignal);
var canEnter = InSession(time);
var stopDistance = UseAtrStops ? AtrMultiplier * atrValue.GetValue<decimal>() : MaxLossPerc * close;
if (longSignal && Position <= 0)
{
if (AllowLong && canEnter && stopDistance > 0)
{
BuyMarket(Volume + Math.Abs(Position));
_stopPrice = close - stopDistance;
_takePrice = close + StaticRr * stopDistance;
}
else if (Position < 0)
{
ClosePosition();
}
}
else if (shortSignal && Position >= 0)
{
if (AllowShort && canEnter && stopDistance > 0)
{
SellMarket(Volume + Math.Abs(Position));
_stopPrice = close + stopDistance;
_takePrice = close - StaticRr * stopDistance;
}
else if (Position > 0)
{
ClosePosition();
}
}
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
_stopPrice = null;
_takePrice = null;
}
private bool InSession(DateTime time)
{
if (!TradeWeekends && (time.DayOfWeek == DayOfWeek.Saturday || time.DayOfWeek == DayOfWeek.Sunday))
return false;
if (time.Date < StartDate.Date || time.Date > EndDate.Date)
return false;
return time.Hour >= SessionStartHour && time.Hour < SessionEndHour;
}
private bool InExitPeriod(DateTime time)
=> UseExitPeriod && time.Hour >= ExitStartHour && time.Hour < ExitEndHour;
private bool InNewsWindow(DateTime time)
{
if (!UseNewsFilter)
return false;
var news = time.Date.AddHours(NewsHour).AddMinutes(NewsMinute);
return Math.Abs((time - news).TotalMinutes) <= NewsWindow;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import TimeSpan, Decimal, DateTime, DayOfWeek
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import SimpleMovingAverage, StandardDeviation, AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
class boilerplate_configurable_strategy(Strategy):
"""
Boilerplate Configurable strategy.
In SMA cross mode a cross of SMA(FastLength) over SMA(Length) gives the signal; in squeeze mode a close beyond the
WideMultiplier Bollinger band that stays inside the NarrowMultiplier band does. Signals can be inverted and limited to one
side, and an opposite signal reverses the position (or only closes it when that side is disabled). Entries are allowed only
on selected days, inside the session and the date range, outside the daily exit period and the news window; the exit period
and the news window also close open positions. Each entry freezes a stop of AtrMultiplier ATRs or MaxLossPerc of the price
and a take profit StaticRr times that distance. Trading stops once the equity drawdown reaches MaxDrawdown.
"""
def __init__(self):
super(boilerplate_configurable_strategy, self).__init__()
self._use_squeeze = self.Param("UseSqueeze", False).SetDisplay("Squeeze Mode", "Use the Bollinger squeeze mode instead of the SMA cross", "Mode")
self._fast_length = self.Param("FastLength", 10).SetGreaterThanZero().SetDisplay("Fast Length", "Fast SMA period of the cross mode", "Indicators")
self._length = self.Param("Length", 20).SetGreaterThanZero().SetDisplay("Length", "Slow SMA and Bollinger period", "Indicators")
self._wide_multiplier = self.Param("WideMultiplier", 1.5).SetGreaterThanZero().SetDisplay("Wide Multiplier", "Deviation multiplier of the band price has to break", "Indicators")
self._narrow_multiplier = self.Param("NarrowMultiplier", 2.0).SetGreaterThanZero().SetDisplay("Narrow Multiplier", "Deviation multiplier of the band price has to stay inside", "Indicators")
self._allow_long = self.Param("AllowLong", True).SetDisplay("Allow Long", "Allow long entries", "Direction")
self._allow_short = self.Param("AllowShort", True).SetDisplay("Allow Short", "Allow short entries", "Direction")
self._invert = self.Param("Invert", False).SetDisplay("Invert", "Swap long and short signals", "Direction")
self._use_atr_stops = self.Param("UseAtrStops", True).SetDisplay("ATR Stops", "Size the stop with ATR instead of a fixed percent", "Risk")
self._atr_length = self.Param("AtrLength", 14).SetGreaterThanZero().SetDisplay("ATR Length", "ATR period", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5).SetGreaterThanZero().SetDisplay("ATR Multiplier", "ATR multiple of the stop distance", "Risk")
self._max_loss_perc = self.Param("MaxLossPerc", 0.02).SetGreaterThanZero().SetDisplay("Max Loss", "Stop distance as a fraction of the entry price", "Risk")
self._static_rr = self.Param("StaticRr", 2.0).SetGreaterThanZero().SetDisplay("Risk/Reward", "Take profit distance as a multiple of the stop distance", "Risk")
self._max_drawdown = self.Param("MaxDrawdown", 0.1).SetGreaterThanZero().SetDisplay("Max Drawdown", "Equity drawdown fraction that stops trading", "Risk")
self._trade_weekends = self.Param("TradeWeekends", True).SetDisplay("Trade Weekends", "Allow entries on Saturday and Sunday", "Filters")
self._session_start_hour = self.Param("SessionStartHour", 0).SetRange(0, 23).SetDisplay("Session Start", "Session start hour (UTC)", "Filters")
self._session_end_hour = self.Param("SessionEndHour", 24).SetRange(1, 24).SetDisplay("Session End", "Session end hour (UTC, exclusive)", "Filters")
self._start_date = self.Param("StartDate", DateTime(2000, 1, 1)).SetDisplay("Start Date", "First date entries are allowed", "Filters")
self._end_date = self.Param("EndDate", DateTime(2100, 1, 1)).SetDisplay("End Date", "Last date entries are allowed", "Filters")
self._use_exit_period = self.Param("UseExitPeriod", False).SetDisplay("Use Exit Period", "Close positions and block entries in the daily exit period", "Filters")
self._exit_start_hour = self.Param("ExitStartHour", 22).SetRange(0, 23).SetDisplay("Exit Start", "Exit period start hour (UTC)", "Filters")
self._exit_end_hour = self.Param("ExitEndHour", 24).SetRange(1, 24).SetDisplay("Exit End", "Exit period end hour (UTC, exclusive)", "Filters")
self._use_news_filter = self.Param("UseNewsFilter", False).SetDisplay("Use News Filter", "Close positions and block entries around the daily news time", "News")
self._news_hour = self.Param("NewsHour", 12).SetRange(0, 23).SetDisplay("News Hour", "News hour (UTC)", "News")
self._news_minute = self.Param("NewsMinute", 30).SetRange(0, 59).SetDisplay("News Minute", "News minute", "News")
self._news_window = self.Param("NewsWindow", 5).SetNotNegative().SetDisplay("News Window", "Minutes before and after the news time", "News")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._prev_fast = None
self._prev_slow = None
self._stop_price = None
self._take_price = None
self._initial_equity = Decimal(0)
self._peak_equity = Decimal(0)
self._halted = False
def OnReseted(self):
super(boilerplate_configurable_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(boilerplate_configurable_strategy, self).OnStarted2(time)
self._reset_state()
portfolio = self.Portfolio
if portfolio is not None:
begin = portfolio.BeginValue
if begin is not None and begin > 0:
self._initial_equity = begin
elif portfolio.CurrentValue is not None:
self._initial_equity = portfolio.CurrentValue
self._peak_equity = self._initial_equity
fast = SimpleMovingAverage()
fast.Length = self._fast_length.Value
slow = SimpleMovingAverage()
slow.Length = self._length.Value
deviation = StandardDeviation()
deviation.Length = self._length.Value
atr = AverageTrueRange()
atr.Length = self._atr_length.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(fast, slow, deviation, atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, fast)
self.DrawIndicator(area, slow)
self.DrawOwnTrades(area)
def _process_candle(self, candle, fast_value, slow_value, deviation_value, atr_value):
if candle.State != CandleStates.Finished:
return
if not fast_value.IsFormed or not slow_value.IsFormed or not deviation_value.IsFormed or not atr_value.IsFormed:
return
fast = fast_value.GetValue[Decimal](None)
slow = slow_value.GetValue[Decimal](None)
prev_fast = self._prev_fast
prev_slow = self._prev_slow
self._prev_fast = fast
self._prev_slow = slow
if not self.IsFormedAndOnlineAndAllowTrading():
return
close = candle.ClosePrice
# Stop and take profit frozen at entry.
if self.Position > 0 and self._stop_price is not None and self._take_price is not None and (candle.LowPrice <= self._stop_price or candle.HighPrice >= self._take_price):
self._close_position()
return
if self.Position < 0 and self._stop_price is not None and self._take_price is not None and (candle.HighPrice >= self._stop_price or candle.LowPrice <= self._take_price):
self._close_position()
return
if self._initial_equity > 0:
equity = self._initial_equity + self.PnL
if equity > self._peak_equity:
self._peak_equity = equity
if self._peak_equity > 0 and (self._peak_equity - equity) / self._peak_equity >= Decimal(self._max_drawdown.Value):
self._halted = True
time = candle.OpenTime
if self._halted or self._in_exit_period(time) or self._in_news_window(time):
self._close_position()
return
if self._use_squeeze.Value:
sigma = deviation_value.GetValue[Decimal](None)
wide = Decimal(self._wide_multiplier.Value) * sigma
narrow = Decimal(self._narrow_multiplier.Value) * sigma
long_signal = close > slow + wide and close < slow + narrow
short_signal = close < slow - wide and close > slow - narrow
else:
long_signal = prev_fast is not None and prev_slow is not None and prev_fast <= prev_slow and fast > slow
short_signal = prev_fast is not None and prev_slow is not None and prev_fast >= prev_slow and fast < slow
if self._invert.Value:
long_signal, short_signal = short_signal, long_signal
can_enter = self._in_session(time)
if self._use_atr_stops.Value:
stop_distance = Decimal(self._atr_multiplier.Value) * atr_value.GetValue[Decimal](None)
else:
stop_distance = Decimal(self._max_loss_perc.Value) * close
rr = Decimal(self._static_rr.Value)
if long_signal and self.Position <= 0:
if self._allow_long.Value and can_enter and stop_distance > 0:
self.BuyMarket(self.Volume + abs(self.Position))
self._stop_price = close - stop_distance
self._take_price = close + rr * stop_distance
elif self.Position < 0:
self._close_position()
elif short_signal and self.Position >= 0:
if self._allow_short.Value and can_enter and stop_distance > 0:
self.SellMarket(self.Volume + abs(self.Position))
self._stop_price = close + stop_distance
self._take_price = close - rr * stop_distance
elif self.Position > 0:
self._close_position()
def _close_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
self._stop_price = None
self._take_price = None
def _in_session(self, time):
if not self._trade_weekends.Value and (time.DayOfWeek == DayOfWeek.Saturday or time.DayOfWeek == DayOfWeek.Sunday):
return False
if time.Date < self._start_date.Value.Date or time.Date > self._end_date.Value.Date:
return False
return time.Hour >= self._session_start_hour.Value and time.Hour < self._session_end_hour.Value
def _in_exit_period(self, time):
return self._use_exit_period.Value and time.Hour >= self._exit_start_hour.Value and time.Hour < self._exit_end_hour.Value
def _in_news_window(self, time):
if not self._use_news_filter.Value:
return False
news = time.Date.AddHours(self._news_hour.Value).AddMinutes(self._news_minute.Value)
return abs((time - news).TotalMinutes) <= self._news_window.Value
def CreateClone(self):
return boilerplate_configurable_strategy()