高度な適応型グリッド戦略
高度な適応型グリッド戦略は、複数のテクニカルインジケーターを使用してトレンド方向を評価し、エントリーレベルの動的なグリッドを構築します。グリッドサイズはATRを通じてボラティリティに適応し、価格がトレンド方向のグリッドレベルに触れたときに注文が発注されます。リスク管理には固定ストップロス、テイクプロフィット、トレーリングストップ、時間ベースの決済、日次損失制限が含まれます。
詳細
- エントリー条件:
- トレンド相場:RSI確認とともに計算されたグリッドレベルに価格が到達する。
- 横ばい相場:RSIの過買い/過売りがグリッドエントリーをトリガーする。
- ロング/ショート: 両方。
- エグジット条件:
- ストップロス、テイクプロフィット、トレーリングストップ、トレンド反転、または時間ベースの決済。
- ストップ: 固定とトレーリング。
- デフォルト値:
BaseGridSize= 1MaxPositions= 5UseVolatilityGrid= TrueAtrLength= 14AtrMultiplier= 1.5RsiLength= 14RsiOverbought= 70RsiOversold= 30ShortMaLength= 20LongMaLength= 50SuperLongMaLength= 200MacdFastLength= 12MacdSlowLength= 26MacdSignalLength= 9StopLossPercent= 2TakeProfitPercent= 3UseTrailingStop= TrueTrailingStopPercent= 1MaxLossPerDay= 5TimeBasedExit= TrueMaxHoldingPeriod= 48
- フィルター:
- カテゴリ: グリッド / トレンド
- 方向: 両方
- インジケーター: ATR, SMA, MACD, RSI, Momentum
- ストップ: はい
- 複雑さ: 高
- 時間軸: 任意
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 高
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Advanced Adaptive Grid Trading Strategy.
/// Trend direction comes from three moving averages together with MACD, the distance between grid
/// levels adapts to volatility through ATR, and the open grid is guarded by stop-loss, take-profit,
/// trailing stop, a maximum holding period and a daily loss limit.
/// </summary>
public class AdvancedAdaptiveGridStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<decimal> _baseGridSize;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<bool> _useVolatilityGrid;
private readonly StrategyParam<int> _atrLength;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<int> _rsiLength;
private readonly StrategyParam<int> _rsiOverbought;
private readonly StrategyParam<int> _rsiOversold;
private readonly StrategyParam<int> _shortMaLength;
private readonly StrategyParam<int> _longMaLength;
private readonly StrategyParam<int> _superLongMaLength;
private readonly StrategyParam<int> _macdFastLength;
private readonly StrategyParam<int> _macdSlowLength;
private readonly StrategyParam<int> _macdSignalLength;
private readonly StrategyParam<decimal> _stopLossPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _trailingStopPercent;
private readonly StrategyParam<decimal> _maxLossPerDay;
private readonly StrategyParam<bool> _timeBasedExit;
private readonly StrategyParam<int> _maxHoldingPeriod;
private int _gridDirection;
private int _openLevels;
private decimal _averagePrice;
private decimal _lastLevelPrice;
private decimal _bestPrice;
private int _barsInPosition;
private DateTime _currentDay;
private decimal _dayStartPnL;
private decimal _dayStartValue;
private bool _dayLossReached;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public decimal BaseGridSize { get => _baseGridSize.Value; set => _baseGridSize.Value = value; }
public int MaxPositions { get => _maxPositions.Value; set => _maxPositions.Value = value; }
public bool UseVolatilityGrid { get => _useVolatilityGrid.Value; set => _useVolatilityGrid.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
public decimal AtrMultiplier { get => _atrMultiplier.Value; set => _atrMultiplier.Value = value; }
public int RsiLength { get => _rsiLength.Value; set => _rsiLength.Value = value; }
public int RsiOverbought { get => _rsiOverbought.Value; set => _rsiOverbought.Value = value; }
public int RsiOversold { get => _rsiOversold.Value; set => _rsiOversold.Value = value; }
public int ShortMaLength { get => _shortMaLength.Value; set => _shortMaLength.Value = value; }
public int LongMaLength { get => _longMaLength.Value; set => _longMaLength.Value = value; }
public int SuperLongMaLength { get => _superLongMaLength.Value; set => _superLongMaLength.Value = value; }
public int MacdFastLength { get => _macdFastLength.Value; set => _macdFastLength.Value = value; }
public int MacdSlowLength { get => _macdSlowLength.Value; set => _macdSlowLength.Value = value; }
public int MacdSignalLength { get => _macdSignalLength.Value; set => _macdSignalLength.Value = value; }
public decimal StopLossPercent { get => _stopLossPercent.Value; set => _stopLossPercent.Value = value; }
public decimal TakeProfitPercent { get => _takeProfitPercent.Value; set => _takeProfitPercent.Value = value; }
public bool UseTrailingStop { get => _useTrailingStop.Value; set => _useTrailingStop.Value = value; }
public decimal TrailingStopPercent { get => _trailingStopPercent.Value; set => _trailingStopPercent.Value = value; }
public decimal MaxLossPerDay { get => _maxLossPerDay.Value; set => _maxLossPerDay.Value = value; }
public bool TimeBasedExit { get => _timeBasedExit.Value; set => _timeBasedExit.Value = value; }
public int MaxHoldingPeriod { get => _maxHoldingPeriod.Value; set => _maxHoldingPeriod.Value = value; }
public AdvancedAdaptiveGridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
_baseGridSize = Param(nameof(BaseGridSize), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Grid Size", "Distance between grid levels in percent of price", "Grid");
_maxPositions = Param(nameof(MaxPositions), 5)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum number of grid levels open at the same time", "Grid");
_useVolatilityGrid = Param(nameof(UseVolatilityGrid), true)
.SetDisplay("Use Volatility Grid", "Size the grid step from ATR instead of Base Grid Size", "Grid");
_atrLength = Param(nameof(AtrLength), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Length", "ATR period for the volatility grid", "Grid");
_atrMultiplier = Param(nameof(AtrMultiplier), 1.5m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "ATR multiplier for the grid step", "Grid");
_rsiLength = Param(nameof(RsiLength), 14)
.SetGreaterThanZero()
.SetDisplay("RSI Length", "RSI period", "Indicators");
_rsiOverbought = Param(nameof(RsiOverbought), 70)
.SetDisplay("RSI Overbought", "Overbought level", "Indicators");
_rsiOversold = Param(nameof(RsiOversold), 30)
.SetDisplay("RSI Oversold", "Oversold level", "Indicators");
_shortMaLength = Param(nameof(ShortMaLength), 20)
.SetGreaterThanZero()
.SetDisplay("Short MA", "Short moving average length", "Trend");
_longMaLength = Param(nameof(LongMaLength), 50)
.SetGreaterThanZero()
.SetDisplay("Long MA", "Long moving average length", "Trend");
_superLongMaLength = Param(nameof(SuperLongMaLength), 200)
.SetGreaterThanZero()
.SetDisplay("Super Long MA", "Super long moving average length", "Trend");
_macdFastLength = Param(nameof(MacdFastLength), 12)
.SetGreaterThanZero()
.SetDisplay("MACD Fast", "MACD fast moving average length", "Trend");
_macdSlowLength = Param(nameof(MacdSlowLength), 26)
.SetGreaterThanZero()
.SetDisplay("MACD Slow", "MACD slow moving average length", "Trend");
_macdSignalLength = Param(nameof(MacdSignalLength), 9)
.SetGreaterThanZero()
.SetDisplay("MACD Signal", "MACD signal line length", "Trend");
_stopLossPercent = Param(nameof(StopLossPercent), 2m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss %", "Stop loss percentage from the average entry", "Risk");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 3m)
.SetGreaterThanZero()
.SetDisplay("Take Profit %", "Take profit percentage from the average entry", "Risk");
_useTrailingStop = Param(nameof(UseTrailingStop), true)
.SetDisplay("Use Trailing Stop", "Protect an open grid with a trailing stop", "Risk");
_trailingStopPercent = Param(nameof(TrailingStopPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Trailing Stop %", "Trailing stop distance from the best price", "Risk");
_maxLossPerDay = Param(nameof(MaxLossPerDay), 5m)
.SetGreaterThanZero()
.SetDisplay("Max Loss Per Day %", "Daily loss limit in percent of the account value", "Risk");
_timeBasedExit = Param(nameof(TimeBasedExit), true)
.SetDisplay("Time Based Exit", "Close the grid after Max Holding Period bars", "Risk");
_maxHoldingPeriod = Param(nameof(MaxHoldingPeriod), 48)
.SetGreaterThanZero()
.SetDisplay("Max Holding Period", "Maximum number of bars a grid stays open", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetGrid();
_currentDay = default;
_dayStartPnL = 0;
_dayStartValue = 0;
_dayLossReached = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var atr = new AverageTrueRange { Length = AtrLength };
var rsi = new RelativeStrengthIndex { Length = RsiLength };
var shortMa = new SimpleMovingAverage { Length = ShortMaLength };
var longMa = new SimpleMovingAverage { Length = LongMaLength };
var superLongMa = new SimpleMovingAverage { Length = SuperLongMaLength };
var macd = new MovingAverageConvergenceDivergenceSignal
{
Macd = { ShortMa = { Length = MacdFastLength }, LongMa = { Length = MacdSlowLength } },
SignalMa = { Length = MacdSignalLength }
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(atr, rsi, shortMa, longMa, superLongMa, macd, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, shortMa);
DrawIndicator(area, longMa);
DrawIndicator(area, superLongMa);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue atrValue, IIndicatorValue rsiValue, IIndicatorValue shortMaValue, IIndicatorValue longMaValue, IIndicatorValue superLongMaValue, IIndicatorValue macdValue)
{
if (candle.State != CandleStates.Finished)
return;
if (atrValue.IsEmpty || rsiValue.IsEmpty || shortMaValue.IsEmpty || longMaValue.IsEmpty || superLongMaValue.IsEmpty || macdValue.IsEmpty)
return;
var macdTyped = (MovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macdTyped.Macd is not decimal macd || macdTyped.Signal is not decimal macdSignal)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
var atr = atrValue.ToDecimal();
var rsi = rsiValue.ToDecimal();
var shortMa = shortMaValue.ToDecimal();
var longMa = longMaValue.ToDecimal();
var superLongMa = superLongMaValue.ToDecimal();
var price = candle.ClosePrice;
UpdateDailyLoss(candle.OpenTime.Date);
// ATR keeps the distance between levels proportional to current volatility, otherwise the
// levels sit a fixed percentage of price apart.
var gridStep = UseVolatilityGrid ? atr * AtrMultiplier : price * BaseGridSize / 100m;
if (gridStep <= 0)
return;
var trendUp = shortMa > longMa && price > superLongMa && macd > macdSignal;
var trendDown = shortMa < longMa && price < superLongMa && macd < macdSignal;
if (_gridDirection != 0)
{
_barsInPosition++;
_bestPrice = _gridDirection > 0
? Math.Max(_bestPrice, candle.HighPrice)
: Math.Min(_bestPrice, candle.LowPrice);
if (TryCloseGrid(price, trendUp, trendDown))
return;
}
if (_dayLossReached)
return;
if (_gridDirection == 0)
TryOpenGrid(price, rsi, trendUp, trendDown);
else
TryAddLevel(price, rsi, gridStep);
}
private void UpdateDailyLoss(DateTime day)
{
if (_currentDay != day)
{
_currentDay = day;
_dayStartPnL = PnL;
_dayStartValue = Portfolio?.CurrentValue ?? 0m;
_dayLossReached = false;
}
// Without a known account value there is nothing to take the percentage of.
if (_dayStartValue <= 0)
return;
if (PnL - _dayStartPnL <= -_dayStartValue * MaxLossPerDay / 100m)
_dayLossReached = true;
}
private bool TryCloseGrid(decimal price, bool trendUp, bool trendDown)
{
if (Position == 0)
return false;
var isLong = _gridDirection > 0;
var stopPrice = isLong
? _averagePrice * (1 - StopLossPercent / 100m)
: _averagePrice * (1 + StopLossPercent / 100m);
var takePrice = isLong
? _averagePrice * (1 + TakeProfitPercent / 100m)
: _averagePrice * (1 - TakeProfitPercent / 100m);
var exit = isLong
? price <= stopPrice || price >= takePrice
: price >= stopPrice || price <= takePrice;
// The trailing stop follows the best price reached by the grid and engages only once the
// grid is at least one trailing distance in profit.
if (!exit && UseTrailingStop)
{
exit = isLong
? _bestPrice >= _averagePrice * (1 + TrailingStopPercent / 100m) && price <= _bestPrice * (1 - TrailingStopPercent / 100m)
: _bestPrice <= _averagePrice * (1 - TrailingStopPercent / 100m) && price >= _bestPrice * (1 + TrailingStopPercent / 100m);
}
if (!exit && TimeBasedExit && _barsInPosition >= MaxHoldingPeriod)
exit = true;
// The trend turned against the whole grid.
if (!exit && (isLong ? trendDown : trendUp))
exit = true;
if (!exit && _dayLossReached)
exit = true;
if (!exit)
return false;
if (isLong)
SellMarket(Math.Abs(Position));
else
BuyMarket(Math.Abs(Position));
ResetGrid();
return true;
}
private void TryOpenGrid(decimal price, decimal rsi, bool trendUp, bool trendDown)
{
if (Position != 0)
return;
// In a trending market the first level follows the trend while RSI is not at the opposite
// extreme; in a sideways market the grid fades the RSI extremes instead.
var goLong = trendUp ? rsi < RsiOverbought : !trendDown && rsi < RsiOversold;
var goShort = trendDown ? rsi > RsiOversold : !trendUp && rsi > RsiOverbought;
if (goLong)
{
BuyMarket(Volume);
StartGrid(1, price);
}
else if (goShort)
{
SellMarket(Volume);
StartGrid(-1, price);
}
}
private void TryAddLevel(decimal price, decimal rsi, decimal gridStep)
{
if (_openLevels >= MaxPositions)
return;
// The next level is filled once price has travelled a full grid step against the grid.
if (_gridDirection > 0)
{
if (price > _lastLevelPrice - gridStep || rsi >= RsiOverbought)
return;
BuyMarket(Volume);
}
else
{
if (price < _lastLevelPrice + gridStep || rsi <= RsiOversold)
return;
SellMarket(Volume);
}
AddLevel(price);
}
private void StartGrid(int direction, decimal price)
{
_gridDirection = direction;
_openLevels = 1;
_averagePrice = price;
_lastLevelPrice = price;
_bestPrice = price;
_barsInPosition = 0;
}
private void AddLevel(decimal price)
{
// Every level has the same volume, so the average entry is the plain mean of the level prices.
_averagePrice = (_averagePrice * _openLevels + price) / (_openLevels + 1);
_openLevels++;
_lastLevelPrice = price;
}
private void ResetGrid()
{
_gridDirection = 0;
_openLevels = 0;
_averagePrice = 0;
_lastLevelPrice = 0;
_bestPrice = 0;
_barsInPosition = 0;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import (AverageTrueRange, IndicatorHelper,
MovingAverageConvergenceDivergenceSignal,
RelativeStrengthIndex, SimpleMovingAverage)
from StockSharp.Algo.Strategies import Strategy
class advanced_adaptive_grid_strategy(Strategy):
"""Advanced Adaptive Grid Trading Strategy.
Trend direction comes from three moving averages together with MACD, the distance between grid
levels adapts to volatility through ATR, and the open grid is guarded by stop-loss, take-profit,
trailing stop, a maximum holding period and a daily loss limit.
"""
def __init__(self):
super(advanced_adaptive_grid_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30))) \
.SetDisplay("Candle Type", "Type of candles", "General")
self._base_grid_size = self.Param("BaseGridSize", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Base Grid Size", "Distance between grid levels in percent of price", "Grid")
self._max_positions = self.Param("MaxPositions", 5) \
.SetGreaterThanZero() \
.SetDisplay("Max Positions", "Maximum number of grid levels open at the same time", "Grid")
self._use_volatility_grid = self.Param("UseVolatilityGrid", True) \
.SetDisplay("Use Volatility Grid", "Size the grid step from ATR instead of Base Grid Size", "Grid")
self._atr_length = self.Param("AtrLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("ATR Length", "ATR period for the volatility grid", "Grid")
self._atr_multiplier = self.Param("AtrMultiplier", 1.5) \
.SetGreaterThanZero() \
.SetDisplay("ATR Multiplier", "ATR multiplier for the grid step", "Grid")
self._rsi_length = self.Param("RsiLength", 14) \
.SetGreaterThanZero() \
.SetDisplay("RSI Length", "RSI period", "Indicators")
self._rsi_overbought = self.Param("RsiOverbought", 70) \
.SetDisplay("RSI Overbought", "Overbought level", "Indicators")
self._rsi_oversold = self.Param("RsiOversold", 30) \
.SetDisplay("RSI Oversold", "Oversold level", "Indicators")
self._short_ma_length = self.Param("ShortMaLength", 20) \
.SetGreaterThanZero() \
.SetDisplay("Short MA", "Short moving average length", "Trend")
self._long_ma_length = self.Param("LongMaLength", 50) \
.SetGreaterThanZero() \
.SetDisplay("Long MA", "Long moving average length", "Trend")
self._super_long_ma_length = self.Param("SuperLongMaLength", 200) \
.SetGreaterThanZero() \
.SetDisplay("Super Long MA", "Super long moving average length", "Trend")
self._macd_fast_length = self.Param("MacdFastLength", 12) \
.SetGreaterThanZero() \
.SetDisplay("MACD Fast", "MACD fast moving average length", "Trend")
self._macd_slow_length = self.Param("MacdSlowLength", 26) \
.SetGreaterThanZero() \
.SetDisplay("MACD Slow", "MACD slow moving average length", "Trend")
self._macd_signal_length = self.Param("MacdSignalLength", 9) \
.SetGreaterThanZero() \
.SetDisplay("MACD Signal", "MACD signal line length", "Trend")
self._stop_loss_percent = self.Param("StopLossPercent", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss %", "Stop loss percentage from the average entry", "Risk")
self._take_profit_percent = self.Param("TakeProfitPercent", 3.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit %", "Take profit percentage from the average entry", "Risk")
self._use_trailing_stop = self.Param("UseTrailingStop", True) \
.SetDisplay("Use Trailing Stop", "Protect an open grid with a trailing stop", "Risk")
self._trailing_stop_percent = self.Param("TrailingStopPercent", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Trailing Stop %", "Trailing stop distance from the best price", "Risk")
self._max_loss_per_day = self.Param("MaxLossPerDay", 5.0) \
.SetGreaterThanZero() \
.SetDisplay("Max Loss Per Day %", "Daily loss limit in percent of the account value", "Risk")
self._time_based_exit = self.Param("TimeBasedExit", True) \
.SetDisplay("Time Based Exit", "Close the grid after Max Holding Period bars", "Risk")
self._max_holding_period = self.Param("MaxHoldingPeriod", 48) \
.SetGreaterThanZero() \
.SetDisplay("Max Holding Period", "Maximum number of bars a grid stays open", "Risk")
self._grid_direction = 0
self._open_levels = 0
self._average_price = 0.0
self._last_level_price = 0.0
self._best_price = 0.0
self._bars_in_position = 0
self._current_day = None
self._day_start_pnl = 0.0
self._day_start_value = 0.0
self._day_loss_reached = False
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(advanced_adaptive_grid_strategy, self).OnReseted()
self._reset_grid()
self._current_day = None
self._day_start_pnl = 0.0
self._day_start_value = 0.0
self._day_loss_reached = False
def OnStarted2(self, time):
super(advanced_adaptive_grid_strategy, self).OnStarted2(time)
atr = AverageTrueRange()
atr.Length = int(self._atr_length.Value)
rsi = RelativeStrengthIndex()
rsi.Length = int(self._rsi_length.Value)
short_ma = SimpleMovingAverage()
short_ma.Length = int(self._short_ma_length.Value)
long_ma = SimpleMovingAverage()
long_ma.Length = int(self._long_ma_length.Value)
super_long_ma = SimpleMovingAverage()
super_long_ma.Length = int(self._super_long_ma_length.Value)
macd = MovingAverageConvergenceDivergenceSignal()
macd.Macd.ShortMa.Length = int(self._macd_fast_length.Value)
macd.Macd.LongMa.Length = int(self._macd_slow_length.Value)
macd.SignalMa.Length = int(self._macd_signal_length.Value)
subscription = self.SubscribeCandles(self.candle_type)
subscription.BindEx(atr, rsi, short_ma, long_ma, super_long_ma, macd, self._on_process).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, short_ma)
self.DrawIndicator(area, long_ma)
self.DrawIndicator(area, super_long_ma)
self.DrawOwnTrades(area)
def _on_process(self, candle, atr_val, rsi_val, short_ma_val, long_ma_val, super_long_ma_val, macd_val):
if candle.State != CandleStates.Finished:
return
if atr_val.IsEmpty or rsi_val.IsEmpty or short_ma_val.IsEmpty or long_ma_val.IsEmpty \
or super_long_ma_val.IsEmpty or macd_val.IsEmpty:
return
if macd_val.Macd is None or macd_val.Signal is None:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
macd = float(macd_val.Macd)
macd_signal = float(macd_val.Signal)
atr = float(IndicatorHelper.ToDecimal(atr_val))
rsi = float(IndicatorHelper.ToDecimal(rsi_val))
short_ma = float(IndicatorHelper.ToDecimal(short_ma_val))
long_ma = float(IndicatorHelper.ToDecimal(long_ma_val))
super_long_ma = float(IndicatorHelper.ToDecimal(super_long_ma_val))
price = float(candle.ClosePrice)
self._update_daily_loss(candle.OpenTime.Date)
# ATR keeps the distance between levels proportional to current volatility, otherwise the
# levels sit a fixed percentage of price apart.
if bool(self._use_volatility_grid.Value):
grid_step = atr * float(self._atr_multiplier.Value)
else:
grid_step = price * float(self._base_grid_size.Value) / 100.0
if grid_step <= 0:
return
trend_up = short_ma > long_ma and price > super_long_ma and macd > macd_signal
trend_down = short_ma < long_ma and price < super_long_ma and macd < macd_signal
if self._grid_direction != 0:
self._bars_in_position += 1
if self._grid_direction > 0:
self._best_price = max(self._best_price, float(candle.HighPrice))
else:
self._best_price = min(self._best_price, float(candle.LowPrice))
if self._try_close_grid(price, trend_up, trend_down):
return
if self._day_loss_reached:
return
if self._grid_direction == 0:
self._try_open_grid(price, rsi, trend_up, trend_down)
else:
self._try_add_level(price, rsi, grid_step)
def _update_daily_loss(self, day):
if self._current_day != day:
self._current_day = day
self._day_start_pnl = float(self.PnL)
account_value = self.Portfolio.CurrentValue if self.Portfolio is not None else None
self._day_start_value = float(account_value) if account_value is not None else 0.0
self._day_loss_reached = False
# Without a known account value there is nothing to take the percentage of.
if self._day_start_value <= 0:
return
limit = self._day_start_value * float(self._max_loss_per_day.Value) / 100.0
if float(self.PnL) - self._day_start_pnl <= -limit:
self._day_loss_reached = True
def _try_close_grid(self, price, trend_up, trend_down):
if self.Position == 0:
return False
is_long = self._grid_direction > 0
sl_pct = float(self._stop_loss_percent.Value)
tp_pct = float(self._take_profit_percent.Value)
trail_pct = float(self._trailing_stop_percent.Value)
if is_long:
stop_price = self._average_price * (1.0 - sl_pct / 100.0)
take_price = self._average_price * (1.0 + tp_pct / 100.0)
exit_now = price <= stop_price or price >= take_price
else:
stop_price = self._average_price * (1.0 + sl_pct / 100.0)
take_price = self._average_price * (1.0 - tp_pct / 100.0)
exit_now = price >= stop_price or price <= take_price
# The trailing stop follows the best price reached by the grid and engages only once the
# grid is at least one trailing distance in profit.
if not exit_now and bool(self._use_trailing_stop.Value):
if is_long:
exit_now = self._best_price >= self._average_price * (1.0 + trail_pct / 100.0) \
and price <= self._best_price * (1.0 - trail_pct / 100.0)
else:
exit_now = self._best_price <= self._average_price * (1.0 - trail_pct / 100.0) \
and price >= self._best_price * (1.0 + trail_pct / 100.0)
if not exit_now and bool(self._time_based_exit.Value) \
and self._bars_in_position >= int(self._max_holding_period.Value):
exit_now = True
# The trend turned against the whole grid.
if not exit_now and (trend_down if is_long else trend_up):
exit_now = True
if not exit_now and self._day_loss_reached:
exit_now = True
if not exit_now:
return False
if is_long:
self.SellMarket(Math.Abs(self.Position))
else:
self.BuyMarket(Math.Abs(self.Position))
self._reset_grid()
return True
def _try_open_grid(self, price, rsi, trend_up, trend_down):
if self.Position != 0:
return
overbought = float(self._rsi_overbought.Value)
oversold = float(self._rsi_oversold.Value)
# In a trending market the first level follows the trend while RSI is not at the opposite
# extreme; in a sideways market the grid fades the RSI extremes instead.
go_long = rsi < overbought if trend_up else (not trend_down and rsi < oversold)
go_short = rsi > oversold if trend_down else (not trend_up and rsi > overbought)
if go_long:
self.BuyMarket(self.Volume)
self._start_grid(1, price)
elif go_short:
self.SellMarket(self.Volume)
self._start_grid(-1, price)
def _try_add_level(self, price, rsi, grid_step):
if self._open_levels >= int(self._max_positions.Value):
return
overbought = float(self._rsi_overbought.Value)
oversold = float(self._rsi_oversold.Value)
# The next level is filled once price has travelled a full grid step against the grid.
if self._grid_direction > 0:
if price > self._last_level_price - grid_step or rsi >= overbought:
return
self.BuyMarket(self.Volume)
else:
if price < self._last_level_price + grid_step or rsi <= oversold:
return
self.SellMarket(self.Volume)
self._add_level(price)
def _start_grid(self, direction, price):
self._grid_direction = direction
self._open_levels = 1
self._average_price = price
self._last_level_price = price
self._best_price = price
self._bars_in_position = 0
def _add_level(self, price):
# Every level has the same volume, so the average entry is the plain mean of the level prices.
self._average_price = (self._average_price * self._open_levels + price) / (self._open_levels + 1)
self._open_levels += 1
self._last_level_price = price
def _reset_grid(self):
self._grid_direction = 0
self._open_levels = 0
self._average_price = 0.0
self._last_level_price = 0.0
self._best_price = 0.0
self._bars_in_position = 0
def CreateClone(self):
return advanced_adaptive_grid_strategy()