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>
/// Conversion of the Brandy Expert Advisor from MetaTrader 5.
/// Combines two configurable moving averages to generate entries and manages positions with trailing exits.
/// </summary>
public class BrandyStrategy : Strategy
{
/// <summary>
/// Supported moving average smoothing methods.
/// </summary>
public enum MovingAverageMethods
{
/// <summary>
/// Simple moving average.
/// </summary>
Sma,
/// <summary>
/// Exponential moving average.
/// </summary>
Ema,
/// <summary>
/// Smoothed moving average.
/// </summary>
Smma,
/// <summary>
/// Linear weighted moving average.
/// </summary>
Lwma
}
/// <summary>
/// Price sources that can be fed into the moving averages.
/// </summary>
public enum AppliedPriceTypes
{
/// <summary>
/// Candle close price.
/// </summary>
Close,
/// <summary>
/// Candle open price.
/// </summary>
Open,
/// <summary>
/// Candle high price.
/// </summary>
High,
/// <summary>
/// Candle low price.
/// </summary>
Low,
/// <summary>
/// Median price of the candle (high + low) / 2.
/// </summary>
Median,
/// <summary>
/// Typical price (high + low + close) / 3.
/// </summary>
Typical,
/// <summary>
/// Weighted price (high + low + 2 * close) / 4.
/// </summary>
Weighted
}
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<int> _maClosePeriod;
private readonly StrategyParam<int> _maCloseShift;
private readonly StrategyParam<MovingAverageMethods> _maCloseMethod;
private readonly StrategyParam<AppliedPriceTypes> _maCloseAppliedPrice;
private readonly StrategyParam<int> _maCloseSignalBar;
private readonly StrategyParam<int> _maOpenPeriod;
private readonly StrategyParam<int> _maOpenShift;
private readonly StrategyParam<MovingAverageMethods> _maOpenMethod;
private readonly StrategyParam<AppliedPriceTypes> _maOpenAppliedPrice;
private readonly StrategyParam<int> _maOpenSignalBar;
private readonly StrategyParam<DataType> _candleType;
private DecimalLengthIndicator _maOpenIndicator;
private DecimalLengthIndicator _maCloseIndicator;
private decimal _pipSize;
private readonly List<decimal> _maOpenValues = [];
private readonly List<decimal> _maCloseValues = [];
private int _maxOpenQueueSize;
private int _maxCloseQueueSize;
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takePrice;
/// <summary>
/// Trading volume per order.
/// </summary>
public decimal TradeVolume
{
get => _tradeVolume.Value;
set
{
_tradeVolume.Value = value;
Volume = value;
}
}
/// <summary>
/// Stop loss distance expressed in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance expressed in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance expressed in pips.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Step that defines how far the price must move before the trailing stop is advanced.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Period of the moving average calculated on the close series.
/// </summary>
public int MaClosePeriod
{
get => _maClosePeriod.Value;
set => _maClosePeriod.Value = value;
}
/// <summary>
/// Displacement applied to the moving average calculated on closes.
/// </summary>
public int MaCloseShift
{
get => _maCloseShift.Value;
set => _maCloseShift.Value = value;
}
/// <summary>
/// Moving average smoothing method for the close series.
/// </summary>
public MovingAverageMethods MaCloseMethod
{
get => _maCloseMethod.Value;
set => _maCloseMethod.Value = value;
}
/// <summary>
/// Price source used by the close moving average.
/// </summary>
public AppliedPriceTypes MaCloseAppliedPrice
{
get => _maCloseAppliedPrice.Value;
set => _maCloseAppliedPrice.Value = value;
}
/// <summary>
/// Bar index used as a signal reference for the close moving average.
/// </summary>
public int MaCloseSignalBar
{
get => _maCloseSignalBar.Value;
set => _maCloseSignalBar.Value = value;
}
/// <summary>
/// Period of the moving average calculated on the open series.
/// </summary>
public int MaOpenPeriod
{
get => _maOpenPeriod.Value;
set => _maOpenPeriod.Value = value;
}
/// <summary>
/// Displacement applied to the moving average calculated on opens.
/// </summary>
public int MaOpenShift
{
get => _maOpenShift.Value;
set => _maOpenShift.Value = value;
}
/// <summary>
/// Moving average smoothing method for the open series.
/// </summary>
public MovingAverageMethods MaOpenMethod
{
get => _maOpenMethod.Value;
set => _maOpenMethod.Value = value;
}
/// <summary>
/// Price source used by the open moving average.
/// </summary>
public AppliedPriceTypes MaOpenAppliedPrice
{
get => _maOpenAppliedPrice.Value;
set => _maOpenAppliedPrice.Value = value;
}
/// <summary>
/// Bar index used as a signal reference for the open moving average.
/// </summary>
public int MaOpenSignalBar
{
get => _maOpenSignalBar.Value;
set => _maOpenSignalBar.Value = value;
}
/// <summary>
/// Candle type used to feed the indicators.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="BrandyStrategy"/> class.
/// </summary>
public BrandyStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Trade Volume", "Order size in lots", "General");
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetNotNegative()
.SetDisplay("Stop Loss (pips)", "Protective stop distance", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
.SetNotNegative()
.SetDisplay("Take Profit (pips)", "Profit target distance", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
.SetNotNegative()
.SetDisplay("Trailing Stop (pips)", "Distance for trailing stop", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetNotNegative()
.SetDisplay("Trailing Step (pips)", "Additional move required before trailing", "Risk");
_maClosePeriod = Param(nameof(MaClosePeriod), 20)
.SetGreaterThanZero()
.SetDisplay("MA Close Period", "Length of MA calculated on close", "Indicators");
_maCloseShift = Param(nameof(MaCloseShift), 0)
.SetNotNegative()
.SetDisplay("MA Close Shift", "Forward shift applied to close MA", "Indicators");
_maCloseMethod = Param(nameof(MaCloseMethod), MovingAverageMethods.Ema)
.SetDisplay("MA Close Method", "Smoothing method for close MA", "Indicators");
_maCloseAppliedPrice = Param(nameof(MaCloseAppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("MA Close Price", "Price source for close MA", "Indicators");
_maCloseSignalBar = Param(nameof(MaCloseSignalBar), 0)
.SetNotNegative()
.SetDisplay("MA Close Signal Bar", "Reference bar index for close MA", "Indicators");
_maOpenPeriod = Param(nameof(MaOpenPeriod), 70)
.SetGreaterThanZero()
.SetDisplay("MA Open Period", "Length of MA calculated on open", "Indicators");
_maOpenShift = Param(nameof(MaOpenShift), 0)
.SetNotNegative()
.SetDisplay("MA Open Shift", "Forward shift applied to open MA", "Indicators");
_maOpenMethod = Param(nameof(MaOpenMethod), MovingAverageMethods.Ema)
.SetDisplay("MA Open Method", "Smoothing method for open MA", "Indicators");
_maOpenAppliedPrice = Param(nameof(MaOpenAppliedPrice), AppliedPriceTypes.Close)
.SetDisplay("MA Open Price", "Price source for open MA", "Indicators");
_maOpenSignalBar = Param(nameof(MaOpenSignalBar), 0)
.SetNotNegative()
.SetDisplay("MA Open Signal Bar", "Reference bar index for open MA", "Indicators");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Time frame of input candles", "General");
Volume = _tradeVolume.Value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_maOpenIndicator = null;
_maCloseIndicator = null;
_maOpenValues.Clear();
_maCloseValues.Clear();
_pipSize = 0m;
_maxOpenQueueSize = 0;
_maxCloseQueueSize = 0;
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (TrailingStopPips > 0m && TrailingStepPips <= 0m)
throw new InvalidOperationException("Trailing step must be greater than zero when trailing stop is enabled.");
_maOpenIndicator = CreateMovingAverage(MaOpenMethod, MaOpenPeriod);
_maCloseIndicator = CreateMovingAverage(MaCloseMethod, MaClosePeriod);
UpdatePipSize();
UpdateQueueSizes();
Volume = _tradeVolume.Value;
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;
if (_maOpenIndicator == null || _maCloseIndicator == null)
return;
var openSource = GetAppliedPrice(candle, MaOpenAppliedPrice);
var closeSource = GetAppliedPrice(candle, MaCloseAppliedPrice);
var maOpenResult = _maOpenIndicator!.Process(new DecimalIndicatorValue(_maOpenIndicator, openSource, candle.OpenTime) { IsFinal = true });
var maCloseResult = _maCloseIndicator!.Process(new DecimalIndicatorValue(_maCloseIndicator, closeSource, candle.OpenTime) { IsFinal = true });
if (maOpenResult.IsEmpty || maCloseResult.IsEmpty || !_maOpenIndicator.IsFormed || !_maCloseIndicator.IsFormed)
return;
var maOpen = maOpenResult.ToDecimal();
var maClose = maCloseResult.ToDecimal();
EnqueueValue(_maOpenValues, maOpen, _maxOpenQueueSize);
EnqueueValue(_maCloseValues, maClose, _maxCloseQueueSize);
var maOpenPrev = GetQueueValue(_maOpenValues, 1 + MaOpenShift);
var maOpenSignal = GetQueueValue(_maOpenValues, MaOpenSignalBar + MaOpenShift);
var maClosePrev = GetQueueValue(_maCloseValues, 1 + MaCloseShift);
var maCloseSignal = GetQueueValue(_maCloseValues, MaCloseSignalBar + MaCloseShift);
if (maOpenPrev is null || maOpenSignal is null || maClosePrev is null || maCloseSignal is null)
return;
var longSignal = maOpenPrev > maOpenSignal && maClosePrev > maCloseSignal;
var shortSignal = maOpenPrev < maOpenSignal && maClosePrev < maCloseSignal;
if (Position == 0)
{
if (longSignal)
{
OpenLong(candle.ClosePrice);
}
else if (shortSignal)
{
OpenShort(candle.ClosePrice);
}
}
else
{
ManageOpenPosition(candle, maOpenPrev.Value, maOpenSignal.Value);
}
}
private void OpenLong(decimal price)
{
var volume = Volume;
if (volume <= 0m)
return;
_entryPrice = price;
_stopPrice = StopLossPips > 0m ? price - StopLossPips * _pipSize : null;
_takePrice = TakeProfitPips > 0m ? price + TakeProfitPips * _pipSize : null;
BuyMarket(volume);
}
private void OpenShort(decimal price)
{
var volume = Volume;
if (volume <= 0m)
return;
_entryPrice = price;
_stopPrice = StopLossPips > 0m ? price + StopLossPips * _pipSize : null;
_takePrice = TakeProfitPips > 0m ? price - TakeProfitPips * _pipSize : null;
SellMarket(volume);
}
private void ManageOpenPosition(ICandleMessage candle, decimal maOpenPrev, decimal maOpenSignal)
{
var position = Position;
if (position > 0)
{
if (maOpenPrev < maOpenSignal)
{
SellMarket(position);
ResetPositionState();
return;
}
UpdateTrailingForLong(candle);
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(position);
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(position);
ResetPositionState();
}
}
else if (position < 0)
{
if (maOpenPrev > maOpenSignal)
{
BuyMarket(-position);
ResetPositionState();
return;
}
UpdateTrailingForShort(candle);
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(-position);
ResetPositionState();
return;
}
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(-position);
ResetPositionState();
}
}
else
{
ResetPositionState();
}
}
private void UpdateTrailingForLong(ICandleMessage candle)
{
if (TrailingStopPips <= 0m || TrailingStepPips <= 0m || _entryPrice is null)
return;
var trailingStop = TrailingStopPips * _pipSize;
var trailingStep = TrailingStepPips * _pipSize;
if (trailingStop <= 0m)
return;
var currentPrice = candle.ClosePrice;
var entryPrice = _entryPrice.Value;
if (currentPrice - entryPrice <= trailingStop + trailingStep)
return;
var threshold = currentPrice - (trailingStop + trailingStep);
if (_stopPrice.HasValue && _stopPrice.Value >= threshold)
return;
var newStop = currentPrice - trailingStop;
if (!_stopPrice.HasValue || newStop > _stopPrice.Value)
_stopPrice = newStop;
}
private void UpdateTrailingForShort(ICandleMessage candle)
{
if (TrailingStopPips <= 0m || TrailingStepPips <= 0m || _entryPrice is null)
return;
var trailingStop = TrailingStopPips * _pipSize;
var trailingStep = TrailingStepPips * _pipSize;
if (trailingStop <= 0m)
return;
var currentPrice = candle.ClosePrice;
var entryPrice = _entryPrice.Value;
if (entryPrice - currentPrice <= trailingStop + trailingStep)
return;
var threshold = currentPrice + trailingStop + trailingStep;
if (_stopPrice.HasValue && _stopPrice.Value <= threshold)
return;
var newStop = currentPrice + trailingStop;
if (!_stopPrice.HasValue || newStop < _stopPrice.Value)
_stopPrice = newStop;
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takePrice = null;
}
private void UpdatePipSize()
{
var step = Security?.PriceStep ?? 1m;
_pipSize = step;
}
private void UpdateQueueSizes()
{
var shiftOpen = Math.Max(0, MaOpenShift);
var shiftClose = Math.Max(0, MaCloseShift);
var openDepth = Math.Max(Math.Max(1 + shiftOpen, MaOpenSignalBar + shiftOpen), MaCloseSignalBar + shiftOpen);
var closeDepth = Math.Max(1 + shiftClose, 1);
_maxOpenQueueSize = Math.Max(2, openDepth + 2);
_maxCloseQueueSize = Math.Max(2, closeDepth + 2);
}
private static void EnqueueValue(List<decimal> queue, decimal value, int maxSize)
{
queue.Add(value);
while (queue.Count > maxSize)
queue.RemoveAt(0);
}
private static decimal? GetQueueValue(List<decimal> queue, int indexFromCurrent)
{
if (indexFromCurrent < 0)
return null;
if (queue.Count <= indexFromCurrent)
return null;
var targetIndex = queue.Count - 1 - indexFromCurrent;
return targetIndex >= 0 && targetIndex < queue.Count
? queue[targetIndex]
: null;
}
private static DecimalLengthIndicator CreateMovingAverage(MovingAverageMethods method, int length)
{
return method switch
{
MovingAverageMethods.Sma => new SimpleMovingAverage { Length = length },
MovingAverageMethods.Ema => new ExponentialMovingAverage { Length = length },
MovingAverageMethods.Smma => new SmoothedMovingAverage { Length = length },
MovingAverageMethods.Lwma => new WeightedMovingAverage { Length = length },
_ => new ExponentialMovingAverage { Length = length }
};
}
private static decimal GetAppliedPrice(ICandleMessage candle, AppliedPriceTypes priceType)
{
return priceType switch
{
AppliedPriceTypes.Close => candle.ClosePrice,
AppliedPriceTypes.Open => candle.OpenPrice,
AppliedPriceTypes.High => candle.HighPrice,
AppliedPriceTypes.Low => candle.LowPrice,
AppliedPriceTypes.Median => (candle.HighPrice + candle.LowPrice) / 2m,
AppliedPriceTypes.Typical => (candle.HighPrice + candle.LowPrice + candle.ClosePrice) / 3m,
AppliedPriceTypes.Weighted => (candle.HighPrice + candle.LowPrice + candle.ClosePrice + candle.ClosePrice) / 4m,
_ => candle.ClosePrice
};
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType, CandleStates
from System import TimeSpan, Math, Decimal
from indicator_extensions import *
class brandy_strategy(Strategy):
def __init__(self):
super(brandy_strategy, self).__init__()
self._stop_loss_pips = self.Param("StopLossPips", 50.0)
self._take_profit_pips = self.Param("TakeProfitPips", 150.0)
self._trailing_stop_pips = self.Param("TrailingStopPips", 5.0)
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0)
self._ma_close_period = self.Param("MaClosePeriod", 20)
self._ma_close_shift = self.Param("MaCloseShift", 0)
self._ma_close_signal_bar = self.Param("MaCloseSignalBar", 0)
self._ma_open_period = self.Param("MaOpenPeriod", 70)
self._ma_open_shift = self.Param("MaOpenShift", 0)
self._ma_open_signal_bar = self.Param("MaOpenSignalBar", 0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._ma_open_indicator = None
self._ma_close_indicator = None
self._pip_size = 1.0
self._ma_open_values = []
self._ma_close_values = []
self._max_open_queue_size = 2
self._max_close_queue_size = 2
self._entry_price = None
self._stop_price = None
self._take_price = None
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(brandy_strategy, self).OnStarted2(time)
self._ma_open_indicator = ExponentialMovingAverage()
self._ma_open_indicator.Length = self._ma_open_period.Value
self._ma_close_indicator = ExponentialMovingAverage()
self._ma_close_indicator.Length = self._ma_close_period.Value
step = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 1.0
self._pip_size = step if step > 0 else 1.0
shift_open = max(0, self._ma_open_shift.Value)
shift_close = max(0, self._ma_close_shift.Value)
open_depth = max(max(1 + shift_open, self._ma_open_signal_bar.Value + shift_open),
self._ma_close_signal_bar.Value + shift_open)
close_depth = max(1 + shift_close, 1)
self._max_open_queue_size = max(2, open_depth + 2)
self._max_close_queue_size = max(2, close_depth + 2)
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
if self._ma_open_indicator is None or self._ma_close_indicator is None:
return
open_source = float(candle.ClosePrice)
close_source = float(candle.ClosePrice)
ma_open_result = process_float(self._ma_open_indicator, Decimal(float(open_source)), candle.OpenTime, True)
ma_close_result = process_float(self._ma_close_indicator, Decimal(float(close_source)), candle.OpenTime, True)
if (ma_open_result.IsEmpty or ma_close_result.IsEmpty or
not self._ma_open_indicator.IsFormed or not self._ma_close_indicator.IsFormed):
return
ma_open = float(ma_open_result.Value)
ma_close = float(ma_close_result.Value)
self._enqueue_value(self._ma_open_values, ma_open, self._max_open_queue_size)
self._enqueue_value(self._ma_close_values, ma_close, self._max_close_queue_size)
ma_open_prev = self._get_queue_value(self._ma_open_values, 1 + self._ma_open_shift.Value)
ma_open_signal = self._get_queue_value(self._ma_open_values, self._ma_open_signal_bar.Value + self._ma_open_shift.Value)
ma_close_prev = self._get_queue_value(self._ma_close_values, 1 + self._ma_close_shift.Value)
ma_close_signal = self._get_queue_value(self._ma_close_values, self._ma_close_signal_bar.Value + self._ma_close_shift.Value)
if ma_open_prev is None or ma_open_signal is None or ma_close_prev is None or ma_close_signal is None:
return
long_signal = ma_open_prev > ma_open_signal and ma_close_prev > ma_close_signal
short_signal = ma_open_prev < ma_open_signal and ma_close_prev < ma_close_signal
if self.Position == 0:
if long_signal:
self._open_long(float(candle.ClosePrice))
elif short_signal:
self._open_short(float(candle.ClosePrice))
else:
self._manage_open_position(candle, ma_open_prev, ma_open_signal)
def _open_long(self, price):
self._entry_price = price
self._stop_price = price - self._stop_loss_pips.Value * self._pip_size if self._stop_loss_pips.Value > 0 else None
self._take_price = price + self._take_profit_pips.Value * self._pip_size if self._take_profit_pips.Value > 0 else None
self.BuyMarket()
def _open_short(self, price):
self._entry_price = price
self._stop_price = price + self._stop_loss_pips.Value * self._pip_size if self._stop_loss_pips.Value > 0 else None
self._take_price = price - self._take_profit_pips.Value * self._pip_size if self._take_profit_pips.Value > 0 else None
self.SellMarket()
def _manage_open_position(self, candle, ma_open_prev, ma_open_signal):
if self.Position > 0:
if ma_open_prev < ma_open_signal:
self.SellMarket(self.Position)
self._reset_position_state()
return
self._update_trailing_for_long(candle)
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(self.Position)
self._reset_position_state()
return
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(self.Position)
self._reset_position_state()
elif self.Position < 0:
if ma_open_prev > ma_open_signal:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
return
self._update_trailing_for_short(candle)
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
return
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(abs(self.Position))
self._reset_position_state()
else:
self._reset_position_state()
def _update_trailing_for_long(self, candle):
if self._trailing_stop_pips.Value <= 0 or self._trailing_step_pips.Value <= 0 or self._entry_price is None:
return
trailing_stop = self._trailing_stop_pips.Value * self._pip_size
trailing_step = self._trailing_step_pips.Value * self._pip_size
current_price = float(candle.ClosePrice)
if current_price - self._entry_price <= trailing_stop + trailing_step:
return
threshold = current_price - (trailing_stop + trailing_step)
if self._stop_price is not None and self._stop_price >= threshold:
return
new_stop = current_price - trailing_stop
if self._stop_price is None or new_stop > self._stop_price:
self._stop_price = new_stop
def _update_trailing_for_short(self, candle):
if self._trailing_stop_pips.Value <= 0 or self._trailing_step_pips.Value <= 0 or self._entry_price is None:
return
trailing_stop = self._trailing_stop_pips.Value * self._pip_size
trailing_step = self._trailing_step_pips.Value * self._pip_size
current_price = float(candle.ClosePrice)
if self._entry_price - current_price <= trailing_stop + trailing_step:
return
threshold = current_price + trailing_stop + trailing_step
if self._stop_price is not None and self._stop_price <= threshold:
return
new_stop = current_price + trailing_stop
if self._stop_price is None or new_stop < self._stop_price:
self._stop_price = new_stop
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_price = None
def _enqueue_value(self, queue, value, max_size):
queue.append(value)
while len(queue) > max_size:
queue.pop(0)
def _get_queue_value(self, queue, index_from_current):
if index_from_current < 0:
return None
if len(queue) <= index_from_current:
return None
target = len(queue) - 1 - index_from_current
if target >= 0 and target < len(queue):
return queue[target]
return None
def OnReseted(self):
super(brandy_strategy, self).OnReseted()
self._ma_open_indicator = None
self._ma_close_indicator = None
self._pip_size = 1.0
self._ma_open_values = []
self._ma_close_values = []
self._entry_price = None
self._stop_price = None
self._take_price = None
def CreateClone(self):
return brandy_strategy()