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;
using StockSharp.Algo;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// OzFx strategy converted from MetaTrader 5 to the StockSharp high-level API.
/// Stacks multiple entries when the Acceleration/Deceleration oscillator and stochastic agree.
/// Implements layered targets and dynamic protection to mimic the expert advisor behaviour.
/// </summary>
public class OzFxAcceleratorStochasticStrategy : Strategy
{
private readonly StrategyParam<int> _maxLayers;
private readonly StrategyParam<decimal> _orderVolume;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _trailingStopPips;
private readonly StrategyParam<decimal> _trailingStepPips;
private readonly StrategyParam<int> _kPeriod;
private readonly StrategyParam<int> _dPeriod;
private readonly StrategyParam<int> _smoothingPeriod;
private readonly StrategyParam<decimal> _stochasticLevel;
private readonly StrategyParam<DataType> _candleType;
private AwesomeOscillator _ao = null!;
private SimpleMovingAverage _aoSma = null!;
private StochasticOscillator _stochastic = null!;
private decimal? _lastAc;
private bool _lastExitWasTakeProfit;
private decimal _pipSize;
private bool _pipInitialized;
private readonly List<EntryInfo> _longEntries = new();
private readonly List<EntryInfo> _shortEntries = new();
/// <summary>
/// Defines exit origin to replicate modok flag logic.
/// </summary>
private enum ExitReasons
{
Manual,
TakeProfit,
StopLoss,
}
/// <summary>
/// Stores layered entry metadata (volume, price, protective levels).
/// </summary>
private sealed class EntryInfo
{
public decimal Volume;
public decimal EntryPrice;
public decimal? StopPrice;
public decimal? TakeProfitPrice;
public int Layer;
}
/// <summary>
/// Maximum number of layered positions.
/// </summary>
public int MaxLayers
{
get => _maxLayers.Value;
set => _maxLayers.Value = value;
}
/// <summary>
/// Order volume for each layer.
/// </summary>
public decimal OrderVolume
{
get => _orderVolume.Value;
set => _orderVolume.Value = value;
}
/// <summary>
/// Stop loss distance measured in pips.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Base take profit distance per layer measured in pips.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Trailing stop distance in pips. Zero disables trailing mode.
/// </summary>
public decimal TrailingStopPips
{
get => _trailingStopPips.Value;
set => _trailingStopPips.Value = value;
}
/// <summary>
/// Additional distance in pips before the trailing stop is advanced.
/// </summary>
public decimal TrailingStepPips
{
get => _trailingStepPips.Value;
set => _trailingStepPips.Value = value;
}
/// <summary>
/// Main stochastic lookback period.
/// </summary>
public int KPeriod
{
get => _kPeriod.Value;
set => _kPeriod.Value = value;
}
/// <summary>
/// %D smoothing length.
/// </summary>
public int DPeriod
{
get => _dPeriod.Value;
set => _dPeriod.Value = value;
}
/// <summary>
/// Final smoothing applied to %K.
/// </summary>
public int SmoothingPeriod
{
get => _smoothingPeriod.Value;
set => _smoothingPeriod.Value = value;
}
/// <summary>
/// Stochastic threshold separating bullish and bearish regimes.
/// </summary>
public decimal StochasticLevel
{
get => _stochasticLevel.Value;
set => _stochasticLevel.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes <see cref="OzFxAcceleratorStochasticStrategy"/>.
/// </summary>
public OzFxAcceleratorStochasticStrategy()
{
_maxLayers = Param(nameof(MaxLayers), 1)
.SetGreaterThanZero()
.SetDisplay("Max Layers", "Maximum number of layered positions", "Risk");
_orderVolume = Param(nameof(OrderVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Volume", "Order volume for each layer", "Trading");
_stopLossPips = Param(nameof(StopLossPips), 10m)
.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 5m)
.SetDisplay("Take Profit (pips)", "Base take profit increment in pips", "Risk");
_trailingStopPips = Param(nameof(TrailingStopPips), 5m)
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk");
_trailingStepPips = Param(nameof(TrailingStepPips), 5m)
.SetDisplay("Trailing Step (pips)", "Extra move required before advancing the trailing stop", "Risk");
_kPeriod = Param(nameof(KPeriod), 5)
.SetGreaterThanZero()
.SetDisplay("%K Period", "Stochastic lookback window", "Stochastic");
_dPeriod = Param(nameof(DPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("%D Period", "Smoothing length for %D", "Stochastic");
_smoothingPeriod = Param(nameof(SmoothingPeriod), 3)
.SetGreaterThanZero()
.SetDisplay("Slowing", "Final smoothing for %K", "Stochastic");
_stochasticLevel = Param(nameof(StochasticLevel), 50m)
.SetDisplay("Stochastic Level", "Threshold used to trigger signals", "Stochastic");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longEntries.Clear();
_shortEntries.Clear();
_lastAc = null;
_lastExitWasTakeProfit = false;
_pipInitialized = false;
_pipSize = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_ao = new AwesomeOscillator
{
ShortMa = { Length = 5 },
LongMa = { Length = 34 },
};
_aoSma = new SMA
{
Length = 5,
};
_stochastic = new StochasticOscillator
{
K = { Length = KPeriod },
D = { Length = DPeriod },
};
var subscription = SubscribeCandles(CandleType);
subscription
.BindEx(_ao, _stochastic, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _ao);
DrawIndicator(area, _stochastic);
DrawOwnTrades(area);
}
}
/// <summary>
/// Processes finished candles, updates indicators, and manages entries/exits.
/// </summary>
private void ProcessCandle(ICandleMessage candle, IIndicatorValue aoValue, IIndicatorValue stochValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!aoValue.IsFinal || !stochValue.IsFinal)
return;
var stoch = (StochasticOscillatorValue)stochValue;
if (stoch.K is not decimal stochK)
return;
var ao = aoValue.GetValue<decimal>();
var aoSmaValue = _aoSma.Process(new DecimalIndicatorValue(_aoSma, ao, candle.ServerTime) { IsFinal = true });
if (!_aoSma.IsFormed)
return;
var ac = ao - aoSmaValue.GetValue<decimal>();
var prevAcNullable = _lastAc;
if (prevAcNullable is not decimal prevAc)
{
_lastAc = ac;
return;
}
// indicators checked via BindEx
if (!_ao.IsFormed || !_stochastic.IsFormed)
{
_lastAc = ac;
return;
}
var pip = GetPipSize();
var stopDistance = StopLossPips > 0m ? StopLossPips * pip : 0m;
var takeDistance = TakeProfitPips > 0m ? TakeProfitPips * pip : 0m;
var trailingStopDistance = TrailingStopPips > 0m ? TrailingStopPips * pip : 0m;
var trailingStepDistance = TrailingStepPips > 0m ? TrailingStepPips * pip : 0m;
var useTrailing = TrailingStopPips > 0m;
TryEnterLong(candle, stochK, ac, prevAc, stopDistance, takeDistance);
TryEnterShort(candle, stochK, ac, prevAc, stopDistance, takeDistance);
ManageLongPositions(candle, stochK, ac, prevAc, trailingStopDistance, trailingStepDistance, useTrailing);
ManageShortPositions(candle, stochK, ac, prevAc, trailingStopDistance, trailingStepDistance, useTrailing);
_lastAc = ac;
}
/// <summary>
/// Opens up to five long layers when momentum turns bullish.
/// </summary>
private void TryEnterLong(ICandleMessage candle, decimal stochK, decimal currentAc, decimal previousAc, decimal stopDistance, decimal takeDistance)
{
if (_longEntries.Count != 0 || _shortEntries.Count != 0)
return;
if (!(stochK > StochasticLevel && currentAc > previousAc))
return;
var volume = OrderVolume;
if (volume <= 0m)
return;
var entryPrice = candle.ClosePrice;
// First layer mirrors the expert advisor: no stop or target until trailing engages.
BuyMarket();
_longEntries.Add(new EntryInfo
{
Volume = volume,
EntryPrice = entryPrice,
StopPrice = null,
TakeProfitPrice = null,
Layer = 0,
});
for (var i = 1; i < MaxLayers; i++)
{
BuyMarket();
var stopPrice = stopDistance > 0m ? entryPrice - stopDistance : (decimal?)null;
var takePrice = takeDistance > 0m ? entryPrice + takeDistance * i : (decimal?)null;
_longEntries.Add(new EntryInfo
{
Volume = volume,
EntryPrice = entryPrice,
StopPrice = stopPrice,
TakeProfitPrice = takePrice,
Layer = i,
});
}
}
/// <summary>
/// Opens up to five short layers when momentum turns bearish.
/// </summary>
private void TryEnterShort(ICandleMessage candle, decimal stochK, decimal currentAc, decimal previousAc, decimal stopDistance, decimal takeDistance)
{
if (_shortEntries.Count != 0 || _longEntries.Count != 0)
return;
if (!(stochK < StochasticLevel && currentAc < previousAc))
return;
var volume = OrderVolume;
if (volume <= 0m)
return;
var entryPrice = candle.ClosePrice;
SellMarket();
_shortEntries.Add(new EntryInfo
{
Volume = volume,
EntryPrice = entryPrice,
StopPrice = null,
TakeProfitPrice = null,
Layer = 0,
});
for (var i = 1; i < MaxLayers; i++)
{
SellMarket();
var stopPrice = stopDistance > 0m ? entryPrice + stopDistance : (decimal?)null;
var takePrice = takeDistance > 0m ? entryPrice - takeDistance * i : (decimal?)null;
_shortEntries.Add(new EntryInfo
{
Volume = volume,
EntryPrice = entryPrice,
StopPrice = stopPrice,
TakeProfitPrice = takePrice,
Layer = i,
});
}
}
/// <summary>
/// Manages open long layers including trailing logic and staged targets.
/// </summary>
private void ManageLongPositions(ICandleMessage candle, decimal stochK, decimal currentAc, decimal previousAc, decimal trailingStopDistance, decimal trailingStepDistance, bool useTrailing)
{
if (_longEntries.Count == 0)
return;
if (Position <= 0m)
{
_longEntries.Clear();
return;
}
var closePrice = candle.ClosePrice;
var highPrice = candle.HighPrice;
var lowPrice = candle.LowPrice;
var exitSignal = stochK < 50m && currentAc < previousAc;
if (useTrailing)
{
if (exitSignal)
{
CloseAllLong(ExitReasons.Manual);
return;
}
if (trailingStopDistance > 0m)
{
for (var i = 0; i < _longEntries.Count; i++)
{
var entry = _longEntries[i];
var profit = closePrice - entry.EntryPrice;
if (profit > trailingStopDistance + trailingStepDistance)
{
var newStop = closePrice - trailingStopDistance;
if (entry.StopPrice is not decimal existing || newStop > existing)
entry.StopPrice = newStop;
}
}
}
}
else if (_lastExitWasTakeProfit)
{
if (exitSignal)
{
CloseAllLong(ExitReasons.Manual);
return;
}
for (var i = 0; i < _longEntries.Count; i++)
{
var entry = _longEntries[i];
if (entry.StopPrice is null && closePrice > entry.EntryPrice)
entry.StopPrice = entry.EntryPrice;
}
}
for (var i = 0; i < _longEntries.Count; i++)
{
var entry = _longEntries[i];
if (entry.StopPrice is decimal stopPrice && lowPrice <= stopPrice)
{
CloseAllLong(ExitReasons.StopLoss);
return;
}
}
var anyTakeProfit = false;
for (var i = _longEntries.Count - 1; i >= 0; i--)
{
var entry = _longEntries[i];
if (entry.TakeProfitPrice is decimal takePrice && highPrice >= takePrice)
{
SellMarket();
_longEntries.RemoveAt(i);
anyTakeProfit = true;
}
}
if (anyTakeProfit)
_lastExitWasTakeProfit = true;
}
/// <summary>
/// Manages open short layers including trailing logic and staged targets.
/// </summary>
private void ManageShortPositions(ICandleMessage candle, decimal stochK, decimal currentAc, decimal previousAc, decimal trailingStopDistance, decimal trailingStepDistance, bool useTrailing)
{
if (_shortEntries.Count == 0)
return;
if (Position >= 0m)
{
_shortEntries.Clear();
return;
}
var closePrice = candle.ClosePrice;
var highPrice = candle.HighPrice;
var lowPrice = candle.LowPrice;
var exitSignal = stochK > 50m && currentAc > previousAc;
if (useTrailing)
{
if (exitSignal)
{
CloseAllShort(ExitReasons.Manual);
return;
}
if (trailingStopDistance > 0m)
{
for (var i = 0; i < _shortEntries.Count; i++)
{
var entry = _shortEntries[i];
var profit = entry.EntryPrice - closePrice;
if (profit > trailingStopDistance + trailingStepDistance)
{
var newStop = closePrice + trailingStopDistance;
if (entry.StopPrice is not decimal existing || newStop < existing)
entry.StopPrice = newStop;
}
}
}
}
else if (_lastExitWasTakeProfit)
{
if (exitSignal)
{
CloseAllShort(ExitReasons.Manual);
return;
}
for (var i = 0; i < _shortEntries.Count; i++)
{
var entry = _shortEntries[i];
if (entry.StopPrice is null && closePrice < entry.EntryPrice)
entry.StopPrice = entry.EntryPrice;
}
}
for (var i = 0; i < _shortEntries.Count; i++)
{
var entry = _shortEntries[i];
if (entry.StopPrice is decimal stopPrice && highPrice >= stopPrice)
{
CloseAllShort(ExitReasons.StopLoss);
return;
}
}
var anyTakeProfit = false;
for (var i = _shortEntries.Count - 1; i >= 0; i--)
{
var entry = _shortEntries[i];
if (entry.TakeProfitPrice is decimal takePrice && lowPrice <= takePrice)
{
BuyMarket();
_shortEntries.RemoveAt(i);
anyTakeProfit = true;
}
}
if (anyTakeProfit)
_lastExitWasTakeProfit = true;
}
/// <summary>
/// Closes all long layers and updates the modok-like flag.
/// </summary>
private void CloseAllLong(ExitReasons reason)
{
var volume = 0m;
for (var i = 0; i < _longEntries.Count; i++)
volume += _longEntries[i].Volume;
if (volume > 0m && Position > 0m)
SellMarket();
_longEntries.Clear();
if (reason == ExitReasons.TakeProfit)
_lastExitWasTakeProfit = true;
else if (reason == ExitReasons.StopLoss)
_lastExitWasTakeProfit = false;
}
/// <summary>
/// Closes all short layers and updates the modok-like flag.
/// </summary>
private void CloseAllShort(ExitReasons reason)
{
var volume = 0m;
for (var i = 0; i < _shortEntries.Count; i++)
volume += _shortEntries[i].Volume;
if (volume > 0m && Position < 0m)
BuyMarket();
_shortEntries.Clear();
if (reason == ExitReasons.TakeProfit)
_lastExitWasTakeProfit = true;
else if (reason == ExitReasons.StopLoss)
_lastExitWasTakeProfit = false;
}
/// <summary>
/// Calculates pip value based on the security tick size and decimal digits.
/// </summary>
private decimal GetPipSize()
{
if (_pipInitialized)
return _pipSize;
var security = Security;
var step = security?.PriceStep ?? 0m;
if (step <= 0m)
step = 0.0001m;
var decimals = security?.Decimals ?? 0;
var adjust = decimals == 3 || decimals == 5 ? 10m : 1m;
_pipSize = step * adjust;
if (_pipSize <= 0m)
_pipSize = step;
if (_pipSize <= 0m)
_pipSize = 0.0001m;
_pipInitialized = true;
return _pipSize;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from System.Collections.Generic import List
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Algo.Indicators import AwesomeOscillator, StochasticOscillator
# Exit reason constants
EXIT_MANUAL = 0
EXIT_TAKE_PROFIT = 1
EXIT_STOP_LOSS = 2
class oz_fx_accelerator_stochastic_strategy(Strategy):
"""OzFx strategy: stacks layered entries when AC oscillator and stochastic agree."""
def __init__(self):
super(oz_fx_accelerator_stochastic_strategy, self).__init__()
self._max_layers = self.Param("MaxLayers", 1) \
.SetGreaterThanZero() \
.SetDisplay("Max Layers", "Maximum number of layered positions", "Risk")
self._order_volume = self.Param("OrderVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Volume", "Order volume for each layer", "Trading")
self._stop_loss_pips = self.Param("StopLossPips", 10.0) \
.SetDisplay("Stop Loss (pips)", "Protective stop distance in pips", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 5.0) \
.SetDisplay("Take Profit (pips)", "Base take profit increment in pips", "Risk")
self._trailing_stop_pips = self.Param("TrailingStopPips", 5.0) \
.SetDisplay("Trailing Stop (pips)", "Trailing stop distance in pips", "Risk")
self._trailing_step_pips = self.Param("TrailingStepPips", 5.0) \
.SetDisplay("Trailing Step (pips)", "Extra move required before advancing trailing stop", "Risk")
self._k_period = self.Param("KPeriod", 5) \
.SetGreaterThanZero() \
.SetDisplay("%K Period", "Stochastic lookback window", "Stochastic")
self._d_period = self.Param("DPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("%D Period", "Smoothing length for %D", "Stochastic")
self._smoothing_period = self.Param("SmoothingPeriod", 3) \
.SetGreaterThanZero() \
.SetDisplay("Slowing", "Final smoothing for %K", "Stochastic")
self._stochastic_level = self.Param("StochasticLevel", 50.0) \
.SetDisplay("Stochastic Level", "Threshold used to trigger signals", "Stochastic")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Primary candle series", "General")
self._last_ac = None
self._last_exit_was_tp = False
self._pip_size = 0.0
self._pip_initialized = False
self._long_entries = []
self._short_entries = []
self._ao_buffer = []
self._ao_sma_length = 5
@property
def MaxLayers(self):
return int(self._max_layers.Value)
@property
def OrderVolume(self):
return float(self._order_volume.Value)
@property
def StopLossPips(self):
return float(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def TrailingStopPips(self):
return float(self._trailing_stop_pips.Value)
@property
def TrailingStepPips(self):
return float(self._trailing_step_pips.Value)
@property
def KPeriod(self):
return int(self._k_period.Value)
@property
def DPeriod(self):
return int(self._d_period.Value)
@property
def SmoothingPeriod(self):
return int(self._smoothing_period.Value)
@property
def StochasticLevel(self):
return float(self._stochastic_level.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _get_pip_size(self):
if self._pip_initialized:
return self._pip_size
sec = self.Security
step = 0.0001
if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0:
step = float(sec.PriceStep)
decimals = 0
if sec is not None and sec.Decimals is not None:
decimals = int(sec.Decimals)
adjust = 10.0 if decimals == 3 or decimals == 5 else 1.0
self._pip_size = step * adjust
if self._pip_size <= 0:
self._pip_size = step
if self._pip_size <= 0:
self._pip_size = 0.0001
self._pip_initialized = True
return self._pip_size
def OnStarted2(self, time):
super(oz_fx_accelerator_stochastic_strategy, self).OnStarted2(time)
self._last_ac = None
self._last_exit_was_tp = False
self._pip_initialized = False
self._pip_size = 0.0
self._long_entries = []
self._short_entries = []
self._ao_buffer = []
self._ao = AwesomeOscillator()
self._ao.ShortMa.Length = 5
self._ao.LongMa.Length = 34
self._stoch = StochasticOscillator()
self._stoch.K.Length = self.KPeriod
self._stoch.D.Length = self.DPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.BindEx(self._ao, self._stoch, self.ProcessCandle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._ao)
self.DrawIndicator(area, self._stoch)
self.DrawOwnTrades(area)
def ProcessCandle(self, candle, ao_value, stoch_value):
if candle.State != CandleStates.Finished:
return
if not ao_value.IsFinal or not stoch_value.IsFinal:
return
# Get stochastic K value
stoch_k_raw = stoch_value.K if hasattr(stoch_value, 'K') else None
if stoch_k_raw is None:
return
stoch_k = float(stoch_k_raw)
ao_val = float(ao_value)
# Compute AC = AO - SMA(AO, 5) manually
self._ao_buffer.append(ao_val)
if len(self._ao_buffer) > self._ao_sma_length:
self._ao_buffer = self._ao_buffer[-self._ao_sma_length:]
if len(self._ao_buffer) < self._ao_sma_length:
return
ao_sma = sum(self._ao_buffer) / self._ao_sma_length
ac = ao_val - ao_sma
prev_ac = self._last_ac
if prev_ac is None:
self._last_ac = ac
return
if not self._ao.IsFormed or not self._stoch.IsFormed:
self._last_ac = ac
return
pip = self._get_pip_size()
stop_dist = self.StopLossPips * pip if self.StopLossPips > 0 else 0.0
take_dist = self.TakeProfitPips * pip if self.TakeProfitPips > 0 else 0.0
trail_dist = self.TrailingStopPips * pip if self.TrailingStopPips > 0 else 0.0
trail_step = self.TrailingStepPips * pip if self.TrailingStepPips > 0 else 0.0
use_trailing = self.TrailingStopPips > 0
self._try_enter_long(candle, stoch_k, ac, prev_ac, stop_dist, take_dist)
self._try_enter_short(candle, stoch_k, ac, prev_ac, stop_dist, take_dist)
self._manage_longs(candle, stoch_k, ac, prev_ac, trail_dist, trail_step, use_trailing)
self._manage_shorts(candle, stoch_k, ac, prev_ac, trail_dist, trail_step, use_trailing)
self._last_ac = ac
def _try_enter_long(self, candle, stoch_k, ac, prev_ac, stop_dist, take_dist):
if len(self._long_entries) != 0 or len(self._short_entries) != 0:
return
if not (stoch_k > self.StochasticLevel and ac > prev_ac):
return
volume = self.OrderVolume
if volume <= 0:
return
entry_price = float(candle.ClosePrice)
self.BuyMarket()
self._long_entries.append({"volume": volume, "entry": entry_price, "stop": None, "take": None, "layer": 0})
for i in range(1, self.MaxLayers):
self.BuyMarket()
sp = entry_price - stop_dist if stop_dist > 0 else None
tp = entry_price + take_dist * i if take_dist > 0 else None
self._long_entries.append({"volume": volume, "entry": entry_price, "stop": sp, "take": tp, "layer": i})
def _try_enter_short(self, candle, stoch_k, ac, prev_ac, stop_dist, take_dist):
if len(self._short_entries) != 0 or len(self._long_entries) != 0:
return
if not (stoch_k < self.StochasticLevel and ac < prev_ac):
return
volume = self.OrderVolume
if volume <= 0:
return
entry_price = float(candle.ClosePrice)
self.SellMarket()
self._short_entries.append({"volume": volume, "entry": entry_price, "stop": None, "take": None, "layer": 0})
for i in range(1, self.MaxLayers):
self.SellMarket()
sp = entry_price + stop_dist if stop_dist > 0 else None
tp = entry_price - take_dist * i if take_dist > 0 else None
self._short_entries.append({"volume": volume, "entry": entry_price, "stop": sp, "take": tp, "layer": i})
def _manage_longs(self, candle, stoch_k, ac, prev_ac, trail_dist, trail_step, use_trailing):
if len(self._long_entries) == 0:
return
if self.Position <= 0:
self._long_entries = []
return
close_price = float(candle.ClosePrice)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
exit_signal = stoch_k < 50.0 and ac < prev_ac
if use_trailing:
if exit_signal:
self._close_all_long(EXIT_MANUAL)
return
if trail_dist > 0:
for e in self._long_entries:
profit = close_price - e["entry"]
if profit > trail_dist + trail_step:
new_stop = close_price - trail_dist
if e["stop"] is None or new_stop > e["stop"]:
e["stop"] = new_stop
elif self._last_exit_was_tp:
if exit_signal:
self._close_all_long(EXIT_MANUAL)
return
for e in self._long_entries:
if e["stop"] is None and close_price > e["entry"]:
e["stop"] = e["entry"]
for e in self._long_entries:
if e["stop"] is not None and low_price <= e["stop"]:
self._close_all_long(EXIT_STOP_LOSS)
return
any_tp = False
i = len(self._long_entries) - 1
while i >= 0:
e = self._long_entries[i]
if e["take"] is not None and high_price >= e["take"]:
self.SellMarket()
self._long_entries.pop(i)
any_tp = True
i -= 1
if any_tp:
self._last_exit_was_tp = True
def _manage_shorts(self, candle, stoch_k, ac, prev_ac, trail_dist, trail_step, use_trailing):
if len(self._short_entries) == 0:
return
if self.Position >= 0:
self._short_entries = []
return
close_price = float(candle.ClosePrice)
high_price = float(candle.HighPrice)
low_price = float(candle.LowPrice)
exit_signal = stoch_k > 50.0 and ac > prev_ac
if use_trailing:
if exit_signal:
self._close_all_short(EXIT_MANUAL)
return
if trail_dist > 0:
for e in self._short_entries:
profit = e["entry"] - close_price
if profit > trail_dist + trail_step:
new_stop = close_price + trail_dist
if e["stop"] is None or new_stop < e["stop"]:
e["stop"] = new_stop
elif self._last_exit_was_tp:
if exit_signal:
self._close_all_short(EXIT_MANUAL)
return
for e in self._short_entries:
if e["stop"] is None and close_price < e["entry"]:
e["stop"] = e["entry"]
for e in self._short_entries:
if e["stop"] is not None and high_price >= e["stop"]:
self._close_all_short(EXIT_STOP_LOSS)
return
any_tp = False
i = len(self._short_entries) - 1
while i >= 0:
e = self._short_entries[i]
if e["take"] is not None and low_price <= e["take"]:
self.BuyMarket()
self._short_entries.pop(i)
any_tp = True
i -= 1
if any_tp:
self._last_exit_was_tp = True
def _close_all_long(self, reason):
volume = 0.0
for e in self._long_entries:
volume += e["volume"]
if volume > 0 and self.Position > 0:
self.SellMarket()
self._long_entries = []
if reason == EXIT_TAKE_PROFIT:
self._last_exit_was_tp = True
elif reason == EXIT_STOP_LOSS:
self._last_exit_was_tp = False
def _close_all_short(self, reason):
volume = 0.0
for e in self._short_entries:
volume += e["volume"]
if volume > 0 and self.Position < 0:
self.BuyMarket()
self._short_entries = []
if reason == EXIT_TAKE_PROFIT:
self._last_exit_was_tp = True
elif reason == EXIT_STOP_LOSS:
self._last_exit_was_tp = False
def OnReseted(self):
super(oz_fx_accelerator_stochastic_strategy, self).OnReseted()
self._last_ac = None
self._last_exit_was_tp = False
self._pip_initialized = False
self._pip_size = 0.0
self._long_entries = []
self._short_entries = []
self._ao_buffer = []
def CreateClone(self):
return oz_fx_accelerator_stochastic_strategy()