Estrategia de cuadrícula de cesta Ilan 1.4
Descripción general
Ilan 1.4 es un sistema de cuadrícula de promedio clásico. La estrategia convertida se suscribe a una única serie de velas y abre una posición inicial de mercado basada en la dirección de las dos últimas velas completadas: si el cierre más reciente está por debajo del más antiguo, la cesta comienza con una venta; de lo contrario, abre una compra. Cuando el precio se mueve contra la cesta activa en el Pip Step configurado, la estrategia opcionalmente agrega una nueva posición en la misma dirección y recalcula el precio de entrada promedio ponderado.
Todas las operaciones dentro de la cesta se ejecutan con órdenes de mercado. Cuando el precio de cierre alcanza el precio medio de entrada más la distancia Take Profit, se cierra toda la cesta. Un trailing stop, un stop loss fijo, un stop de emergencia basado en acciones y una salvaguardia de vida útil máxima reproducen los bloques de protección del experto original MetaTrader.
Reglas de trading
- Espere la siguiente vela terminada y evalúe los dos últimos cierres.
- Si no hay exposición, abra una cesta larga cuando el último cierre sea superior al anterior y una cesta corta en caso contrario.
- Mantenga un registro del último precio de llenado y del precio de entrada promedio ponderado de la canasta activa.
- Cuando Usar Agregar está habilitado y el precio se mueve contra la posición en puntos Pip Step, calcule el siguiente tamaño de lote y abra una operación de mercado adicional. Si Cerrar antes de agregar está habilitado, la cesta existente se cierra primero y se vuelve a abrir con el volumen escalado.
- Vuelva a calcular el precio de entrada promedio después de cada llenado. La canasta se liquida una vez que el precio toca el nivel promedio de obtención de ganancias o cuando se activa alguna de las reglas de riesgo.
- Una vez que se cierra una canasta, la lógica prepara inmediatamente una nueva señal utilizando los dos últimos cierres de velas.
Modos de administración de dinero
El parámetro Money Management reproduce el modificador MMType original:
- Solucionado: cada nuevo pedido utiliza el Volumen inicial configurado.
- Geométrico: las órdenes posteriores multiplican el volumen base por
LotExponent^n, donde n es igual al número actual de operaciones abiertas.
- RecoverLastLoss – después de una cesta perdedora, la siguiente posición utiliza el volumen de la última operación cerrada multiplicado por Exponente de lote; Las cestas rentables restablecen el volumen al valor base.
Los volúmenes comerciales se redondean según los Dígitos de volumen y el paso del volumen de seguridad. Cuando el redondeo produciría cero, se utiliza el volumen de entrada no redondeado.
Controles de riesgo
- Take Profit: cierra toda la cesta una vez que el precio alcanza el precio de entrada promedio ± puntos configurados.
- Stop Loss: cierra la cesta cuando el precio se mueve contra el precio de entrada promedio en la cantidad especificada de puntos.
- Usar Trailing Stop con Trail Start y Trail Stop: activa un nivel de seguimiento una vez que la cesta gana suficientes puntos; la compensación final sigue el precio para proteger las ganancias.
- Utilice Equity Stop con % de riesgo de capital: monitorea el valor de la cartera y cierra la canasta cuando la pérdida flotante excede el porcentaje elegido del pico de capital registrado.
- Usar tiempo de espera con Máximo de horas de apertura: cierra con fuerza la cesta cuando permanece abierta más tiempo que el número de horas permitido.
Parámetros
- Tipo de vela: período de tiempo utilizado para generar señales comerciales.
- Volumen inicial: tamaño del lote inicial para una canasta nueva.
- Dígitos de volumen: precisión utilizada al redondear los volúmenes calculados.
- Gestión de dinero – modo de cálculo de volumen (
Fixed, Geometric, RecoverLastLoss).
- Exponente de lote – multiplicador aplicado por los esquemas geométrico y de recuperación.
- Cerrar antes de agregar: cierre todas las operaciones abiertas antes de realizar la siguiente orden promedio.
- Usar Agregar: habilita o deshabilita los pedidos promedio por completo.
- Pip Step – movimiento adverso mínimo (en pasos de precio) antes de agregar una nueva operación.
- Take Profit: objetivo de beneficio a partir del precio de entrada promedio.
- Stop Loss: desviación adversa máxima permitida del precio de entrada promedio.
- Usar Trailing Stop / Trail Start / Trail Stop – configuración trailing-stop.
- Max Trades: número máximo de operaciones promedio permitidas dentro de una cesta.
- Utilice Equity Stop / Equity Risk %: parámetros de la protección contra pérdidas flotantes.
- Usar tiempo de espera / Horas máximas de apertura: control de vida útil de cada canasta.
Notas de conversión
- MetaTrader ayudantes de órdenes pendientes fueron reemplazados por órdenes de mercado directas porque la lógica de promedio siempre se ejecutaba inmediatamente en el código original.
- El bloque final ahora funciona en la cesta agregada en lugar de modificar cada pedido por separado; Las distancias de disparo coinciden con los valores predeterminados originales.
- El valor de la cartera se monitorea a través del objeto de cartera StockSharp para emular la rutina de parada de acciones del experto.
- Los promedios de posición y las estadísticas de la cesta se calculan dentro de la estrategia sin almacenar colecciones por operación, respetando las pautas de alto nivel API.
namespace StockSharp.Samples.Strategies;
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;
/// <summary>
/// Grid and martingale strategy converted from the MetaTrader Ilan 1.4 expert advisor.
/// The strategy opens an initial trade based on the last two candle closes and adds
/// averaging positions whenever price moves against the basket by the configured step.
/// </summary>
public class Ilan14GridStrategy : Strategy
{
private readonly StrategyParam<decimal> _initialVolume;
private readonly StrategyParam<int> _volumeDigits;
private readonly StrategyParam<MoneyManagementModes> _moneyManagementMode;
private readonly StrategyParam<bool> _useCloseBeforeAdding;
private readonly StrategyParam<bool> _useAdd;
private readonly StrategyParam<decimal> _lotExponent;
private readonly StrategyParam<decimal> _pipStep;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<bool> _useTrailingStop;
private readonly StrategyParam<decimal> _trailStart;
private readonly StrategyParam<decimal> _trailStop;
private readonly StrategyParam<int> _maxTrades;
private readonly StrategyParam<bool> _useEquityStop;
private readonly StrategyParam<decimal> _equityRiskPercent;
private readonly StrategyParam<bool> _useTimeOut;
private readonly StrategyParam<decimal> _maxTradeOpenHours;
private readonly StrategyParam<DataType> _candleType;
private int _tradeCount;
private decimal _averagePrice;
private decimal _totalVolume;
private decimal _lastEntryPrice;
private decimal _lastEntryVolume;
private decimal _equityPeak;
private decimal _lastClosedOrderVolume;
private bool _lastClosedWasLoss;
private decimal? _previousClose;
private decimal? _trailingStopLevel;
private DateTimeOffset? _basketExpiration;
private Sides? _basketDirection;
public enum MoneyManagementModes
{
Fixed,
Geometric,
RecoverLastLoss,
}
public Ilan14GridStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(8).TimeFrame())
.SetDisplay("Candle type", "Timeframe that feeds the strategy logic.", "General");
_initialVolume = Param(nameof(InitialVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Initial volume", "Base volume used for the first trade in a basket.", "Trading")
.SetOptimize(0.01m, 1m, 0.01m);
_volumeDigits = Param(nameof(VolumeDigits), 2)
.SetNotNegative()
.SetDisplay("Volume digits", "Number of decimal places used to round trade volume.", "Trading");
_moneyManagementMode = Param(nameof(MoneyManagementModes), MoneyManagementModes.Geometric)
.SetDisplay("Money management", "Volume calculation mode: fixed, geometric martingale or recover last loss.", "Trading");
_lotExponent = Param(nameof(LotExponent), 1.667m)
.SetGreaterThanZero()
.SetDisplay("Lot exponent", "Multiplier applied when calculating the next position size.", "Trading")
.SetOptimize(1.1m, 2m, 0.1m);
_useCloseBeforeAdding = Param(nameof(UseCloseBeforeAdding), false)
.SetDisplay("Close before adding", "Close the current basket before opening the next averaging order.", "Trading");
_useAdd = Param(nameof(UseAdd), true)
.SetDisplay("Allow averaging", "Enable opening of additional orders when price moves against the basket.", "Trading");
_pipStep = Param(nameof(PipStep), 30m)
.SetGreaterThanZero()
.SetDisplay("Pip step", "Distance in price steps that triggers a new averaging trade.", "Trading")
.SetOptimize(10m, 100m, 5m);
_takeProfit = Param(nameof(TakeProfit), 10m)
.SetGreaterThanZero()
.SetDisplay("Take profit", "Distance from the average price where the basket is closed in profit.", "Trading")
.SetOptimize(5m, 50m, 5m);
_stopLoss = Param(nameof(StopLoss), 500m)
.SetNotNegative()
.SetDisplay("Stop loss", "Maximum adverse distance from the average price before the basket is closed.", "Risk");
_useTrailingStop = Param(nameof(UseTrailingStop), false)
.SetDisplay("Use trailing stop", "Enable dynamic trailing of profits once the basket gains enough points.", "Risk");
_trailStart = Param(nameof(TrailStart), 10m)
.SetNotNegative()
.SetDisplay("Trail start", "Profit distance in points required before the trailing stop activates.", "Risk");
_trailStop = Param(nameof(TrailStop), 10m)
.SetNotNegative()
.SetDisplay("Trail distance", "Gap between current price and trailing stop when it is active.", "Risk");
_maxTrades = Param(nameof(MaxTrades), 10)
.SetGreaterThanZero()
.SetDisplay("Max trades", "Maximum number of averaging orders allowed in one basket.", "Trading")
.SetOptimize(1, 15, 1);
_useEquityStop = Param(nameof(UseEquityStop), false)
.SetDisplay("Use equity stop", "Close the basket if floating loss exceeds a share of the equity peak.", "Risk");
_equityRiskPercent = Param(nameof(EquityRiskPercent), 20m)
.SetNotNegative()
.SetDisplay("Equity risk %", "Percentage of the recorded equity peak tolerated as floating loss.", "Risk");
_useTimeOut = Param(nameof(UseTimeOut), false)
.SetDisplay("Use timeout", "Close all trades once the basket has been open for too long.", "Risk");
_maxTradeOpenHours = Param(nameof(MaxTradeOpenHours), 48m)
.SetNotNegative()
.SetDisplay("Max open hours", "Maximum lifetime of the basket before it is forcefully closed.", "Risk");
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
public int VolumeDigits
{
get => _volumeDigits.Value;
set => _volumeDigits.Value = value;
}
public MoneyManagementModes MoneyManagementMode
{
get => _moneyManagementMode.Value;
set => _moneyManagementMode.Value = value;
}
public bool UseCloseBeforeAdding
{
get => _useCloseBeforeAdding.Value;
set => _useCloseBeforeAdding.Value = value;
}
public bool UseAdd
{
get => _useAdd.Value;
set => _useAdd.Value = value;
}
public decimal LotExponent
{
get => _lotExponent.Value;
set => _lotExponent.Value = value;
}
public decimal PipStep
{
get => _pipStep.Value;
set => _pipStep.Value = value;
}
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
public bool UseTrailingStop
{
get => _useTrailingStop.Value;
set => _useTrailingStop.Value = value;
}
public decimal TrailStart
{
get => _trailStart.Value;
set => _trailStart.Value = value;
}
public decimal TrailStop
{
get => _trailStop.Value;
set => _trailStop.Value = value;
}
public int MaxTrades
{
get => _maxTrades.Value;
set => _maxTrades.Value = value;
}
public bool UseEquityStop
{
get => _useEquityStop.Value;
set => _useEquityStop.Value = value;
}
public decimal EquityRiskPercent
{
get => _equityRiskPercent.Value;
set => _equityRiskPercent.Value = value;
}
public bool UseTimeOut
{
get => _useTimeOut.Value;
set => _useTimeOut.Value = value;
}
public decimal MaxTradeOpenHours
{
get => _maxTradeOpenHours.Value;
set => _maxTradeOpenHours.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
_previousClose = null;
_lastClosedWasLoss = false;
_lastClosedOrderVolume = 0m;
_equityPeak = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
StartProtection(null, null);
ResetState();
_previousClose = null;
_lastClosedWasLoss = false;
var baseVolume = RoundVolume(InitialVolume);
if (baseVolume <= 0m)
baseVolume = InitialVolume;
_lastClosedOrderVolume = baseVolume;
Volume = baseVolume;
_equityPeak = Portfolio?.CurrentValue ?? Portfolio?.BeginValue ?? 0m;
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
var step = Security?.PriceStep ?? 1m;
if (step <= 0m)
step = 1m;
UpdateEquityPeak();
if (!IsFormedAndOnlineAndAllowTrading())
{
_previousClose = candle.ClosePrice;
return;
}
if (_tradeCount > 0)
{
if (UseTimeOut && _basketExpiration != null && candle.CloseTime >= _basketExpiration)
CloseBasket(price);
if (_tradeCount > 0 && UseEquityStop)
{
var floatingLoss = CalculateFloatingProfit(price);
var threshold = (EquityRiskPercent / 100m) * _equityPeak;
if (floatingLoss < 0m && Math.Abs(floatingLoss) > threshold && threshold > 0m)
CloseBasket(price);
}
if (_tradeCount > 0 && UseTrailingStop && TrailStop > 0m)
{
if (UpdateTrailingStop(price, step))
CloseBasket(price);
}
if (_tradeCount > 0 && StopLoss > 0m && _basketDirection != null)
{
var stopDistance = StopLoss * step;
if (_basketDirection == Sides.Buy && price <= _averagePrice - stopDistance)
CloseBasket(price);
else if (_basketDirection == Sides.Sell && price >= _averagePrice + stopDistance)
CloseBasket(price);
}
if (_tradeCount > 0 && TakeProfit > 0m && _basketDirection != null)
{
var targetDistance = TakeProfit * step;
if (_basketDirection == Sides.Buy && price >= _averagePrice + targetDistance)
CloseBasket(price);
else if (_basketDirection == Sides.Sell && price <= _averagePrice - targetDistance)
CloseBasket(price);
}
if (_tradeCount > 0 && UseAdd && _tradeCount <= MaxTrades && _basketDirection != null)
{
var trigger = PipStep * step;
if (trigger > 0m)
{
if (_basketDirection == Sides.Buy && _lastEntryPrice - price >= trigger)
TryAddPosition(price, candle.CloseTime, true);
else if (_basketDirection == Sides.Sell && price - _lastEntryPrice >= trigger)
TryAddPosition(price, candle.CloseTime, false);
}
}
}
if (_tradeCount == 0)
{
if (_previousClose is null)
{
_previousClose = candle.ClosePrice;
return;
}
var directionIsLong = _previousClose <= candle.ClosePrice;
var volume = CalculateTradeVolume();
if (volume > 0m)
OpenPosition(directionIsLong ? Sides.Buy : Sides.Sell, price, volume, candle.CloseTime);
}
_previousClose = candle.ClosePrice;
}
private void TryAddPosition(decimal price, DateTimeOffset time, bool isLong)
{
if (!UseAdd)
return;
if (UseCloseBeforeAdding)
{
var referenceVolume = _lastEntryVolume;
if (referenceVolume <= 0m)
referenceVolume = CalculateTradeVolume();
var nextVolume = RoundVolume(referenceVolume * LotExponent);
if (nextVolume <= 0m)
return;
CloseBasket(price);
OpenPosition(isLong ? Sides.Buy : Sides.Sell, price, nextVolume, time);
}
else
{
var volume = CalculateTradeVolume();
if (volume <= 0m)
return;
OpenPosition(isLong ? Sides.Buy : Sides.Sell, price, volume, time);
}
}
private void OpenPosition(Sides direction, decimal price, decimal volume, DateTimeOffset time)
{
var roundedVolume = RoundVolume(volume);
if (roundedVolume <= 0m)
return;
var isFirstTrade = _tradeCount == 0;
if (direction == Sides.Buy)
BuyMarket(roundedVolume);
else
SellMarket(roundedVolume);
if (isFirstTrade)
{
_basketDirection = direction;
_averagePrice = price;
_totalVolume = roundedVolume;
_tradeCount = 1;
}
else
{
var previousVolume = _totalVolume;
_totalVolume += roundedVolume;
_averagePrice = ((_averagePrice * previousVolume) + price * roundedVolume) / _totalVolume;
_tradeCount++;
}
_lastEntryPrice = price;
_lastEntryVolume = roundedVolume;
_trailingStopLevel = null;
if (isFirstTrade)
{
if (UseTimeOut && MaxTradeOpenHours > 0m)
_basketExpiration = time + TimeSpan.FromHours((double)MaxTradeOpenHours);
else
_basketExpiration = null;
}
}
private void CloseBasket(decimal price)
{
if (_tradeCount == 0)
return;
var volume = Math.Abs(Position);
if (volume > 0m)
{
if (_basketDirection == Sides.Buy)
{
if (Position > 0m)
SellMarket(Position);
}
else if (_basketDirection == Sides.Sell)
{
if (Position < 0m)
BuyMarket(-Position);
}
else
{
if (Position > 0m)
SellMarket(Position);
else if (Position < 0m)
BuyMarket(-Position);
}
}
if (_basketDirection != null && _totalVolume > 0m)
{
var diff = _basketDirection == Sides.Buy
? price - _averagePrice
: _averagePrice - price;
var profit = diff * _totalVolume;
_lastClosedWasLoss = profit < 0m;
_lastClosedOrderVolume = _lastEntryVolume > 0m ? _lastEntryVolume : InitialVolume;
}
ResetState();
}
private void ResetState()
{
_tradeCount = 0;
_averagePrice = 0m;
_totalVolume = 0m;
_lastEntryPrice = 0m;
_lastEntryVolume = 0m;
_trailingStopLevel = null;
_basketExpiration = null;
_basketDirection = null;
}
private void UpdateEquityPeak()
{
if (Portfolio == null)
return;
var current = Portfolio.CurrentValue ?? Portfolio.BeginValue ?? 0m;
if (_tradeCount == 0 || _equityPeak <= 0m)
_equityPeak = current;
else if (current > _equityPeak)
_equityPeak = current;
}
private decimal CalculateFloatingProfit(decimal price)
{
if (_tradeCount == 0 || _totalVolume <= 0m || _basketDirection == null)
return 0m;
return _basketDirection == Sides.Buy
? (price - _averagePrice) * _totalVolume
: (_averagePrice - price) * _totalVolume;
}
private bool UpdateTrailingStop(decimal price, decimal step)
{
if (_basketDirection == null || step <= 0m)
return false;
if (_basketDirection == Sides.Buy)
{
var profit = price - _averagePrice;
if (profit < TrailStart * step)
return false;
var candidate = price - TrailStop * step;
if (_trailingStopLevel is null || candidate > _trailingStopLevel)
_trailingStopLevel = candidate;
return _trailingStopLevel is not null && price <= _trailingStopLevel;
}
var shortProfit = _averagePrice - price;
if (shortProfit < TrailStart * step)
return false;
var shortCandidate = price + TrailStop * step;
if (_trailingStopLevel is null || shortCandidate < _trailingStopLevel)
_trailingStopLevel = shortCandidate;
return _trailingStopLevel is not null && price >= _trailingStopLevel;
}
private decimal CalculateTradeVolume()
{
decimal volume;
switch (MoneyManagementMode)
{
case MoneyManagementModes.Fixed:
volume = InitialVolume;
break;
case MoneyManagementModes.Geometric:
var power = _tradeCount;
volume = InitialVolume * (decimal)Math.Pow((double)LotExponent, power);
break;
case MoneyManagementModes.RecoverLastLoss:
volume = _lastClosedWasLoss
? (_lastClosedOrderVolume > 0m ? _lastClosedOrderVolume : InitialVolume) * LotExponent
: InitialVolume;
break;
default:
volume = InitialVolume;
break;
}
return RoundVolume(volume);
}
private decimal RoundVolume(decimal volume)
{
var abs = Math.Abs(volume);
if (abs <= 0m)
return 0m;
var rounded = VolumeDigits > 0
? Math.Round(abs, VolumeDigits, MidpointRounding.AwayFromZero)
: Math.Round(abs, MidpointRounding.AwayFromZero);
if (rounded <= 0m)
return 0m;
if (Security?.VolumeStep is decimal step && step > 0m)
{
var steps = Math.Round(rounded / step, MidpointRounding.AwayFromZero);
if (steps <= 0m)
steps = 1m;
rounded = steps * step;
}
return rounded;
}
}
import clr
import math
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
class ilan14_grid_strategy(Strategy):
"""
Grid martingale strategy. Opens on last two candle closes direction,
adds averaging positions when price moves against by pip step.
"""
def __init__(self):
super(ilan14_grid_strategy, self).__init__()
self._lot_exponent = self.Param("LotExponent", 1.667).SetDisplay("Lot Exponent", "Multiplier for averaging", "Trading")
self._pip_step = self.Param("PipStep", 30.0).SetDisplay("Pip Step", "Grid step in price steps", "Trading")
self._take_profit = self.Param("TakeProfit", 10.0).SetDisplay("Take Profit", "TP in price steps", "Trading")
self._max_trades = self.Param("MaxTrades", 10).SetDisplay("Max Trades", "Max averaging orders", "Trading")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(8))).SetDisplay("Candle Type", "Timeframe", "General")
self._trade_count = 0
self._avg_price = 0.0
self._total_vol = 0.0
self._last_entry = 0.0
self._direction = 0
self._prev_close = 0.0
@property
def candle_type(self):
return self._candle_type.Value
def OnReseted(self):
super(ilan14_grid_strategy, self).OnReseted()
self._trade_count = 0
self._avg_price = 0.0
self._total_vol = 0.0
self._last_entry = 0.0
self._direction = 0
self._prev_close = 0.0
def OnStarted2(self, time):
super(ilan14_grid_strategy, self).OnStarted2(time)
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0:
step = 1.0
if not self.IsFormedAndOnlineAndAllowTrading():
self._prev_close = price
return
if self._trade_count > 0:
tp_dist = self._take_profit.Value * step
if self._direction == 1 and price >= self._avg_price + tp_dist:
self.SellMarket()
self._reset_basket()
elif self._direction == -1 and price <= self._avg_price - tp_dist:
self.BuyMarket()
self._reset_basket()
if self._trade_count > 0 and self._trade_count < self._max_trades.Value:
trigger = self._pip_step.Value * step
if trigger > 0:
if self._direction == 1 and self._last_entry - price >= trigger:
self.BuyMarket()
prev_vol = self._total_vol
self._total_vol += 1
self._avg_price = (self._avg_price * prev_vol + price) / self._total_vol
self._last_entry = price
self._trade_count += 1
elif self._direction == -1 and price - self._last_entry >= trigger:
self.SellMarket()
prev_vol = self._total_vol
self._total_vol += 1
self._avg_price = (self._avg_price * prev_vol + price) / self._total_vol
self._last_entry = price
self._trade_count += 1
if self._trade_count == 0:
if self._prev_close == 0:
self._prev_close = price
return
is_long = self._prev_close <= price
if is_long:
self.BuyMarket()
self._direction = 1
else:
self.SellMarket()
self._direction = -1
self._avg_price = price
self._total_vol = 1
self._last_entry = price
self._trade_count = 1
self._prev_close = price
def _reset_basket(self):
self._trade_count = 0
self._avg_price = 0.0
self._total_vol = 0.0
self._last_entry = 0.0
self._direction = 0
def CreateClone(self):
return ilan14_grid_strategy()