Martingale Bone Crusher Strategy
Overview
The Martingale Bone Crusher Strategy replicates the behaviour of the original MetaTrader expert advisor. The strategy trades in the direction of a fast/slow moving average comparison and applies a martingale money management model that increases the order size after a losing trade. A large set of risk-management tools is available, including fixed money targets, percentage targets, a configurable breakeven move, classic stop-loss/take-profit levels measured in price steps, and a profit-protection trailing stop measured in money.
Trading Logic
- Signal generation – two simple moving averages are calculated on the primary candle series. When the fast average is below the slow average, the strategy seeks long entries. When it is above, the strategy seeks short entries. No new trades are placed while there is an active position.
- Martingale sequencing – after each completed trade the next position size is updated. If the last trade closed with a loss the next volume is either multiplied or incremented (depending on settings). Winning trades reset the position size back to the initial value.
- Mode selection – two martingale variants are provided:
Martingale1: the next trade always follows the current moving average direction, even after a loss.Martingale2: after a loss the next trade is reversed relative to the direction that lost. This mirrors the behaviour of the original Expert Advisor’s second option.
- Risk controls – while a position is open the strategy continuously evaluates:
- classical stop-loss and take-profit levels expressed in price steps;
- an optional trailing stop that follows the extreme price with a fixed step distance;
- a breakeven move that shifts the exit level after the position moves in favour by a configurable distance;
- global money-based and percentage-based profit targets that close the position when the aggregated floating PnL exceeds the thresholds;
- an additional trailing stop in money that locks in accumulated profit once the floating gain reaches the activation level.
Parameters
| Parameter | Description |
|---|---|
UseTakeProfitMoney |
Enables a fixed money take-profit target. |
TakeProfitMoney |
Money amount that closes the trade when UseTakeProfitMoney is active. |
UseTakeProfitPercent |
Enables a profit target expressed as a percentage of the initial portfolio value. |
TakeProfitPercent |
Percentage used when UseTakeProfitPercent is enabled. |
EnableTrailing |
Enables the money-based trailing stop. |
TrailingTakeProfitMoney |
Floating profit required to arm the money trailing stop. |
TrailingStopMoney |
Allowed drawdown from the peak floating profit after the trailing stop is active. |
MartingaleModes |
Selects between Martingale1 and Martingale2 behaviour. |
UseMoveToBreakeven |
Enables the breakeven stop adjustment. |
MoveToBreakevenTrigger |
Price steps that the trade must move in favour before breakeven protection is activated. |
BreakevenOffset |
Distance added to the entry price when the breakeven stop is placed. |
Multiply |
Multiplier applied to the next volume after a loss when DoubleLotSize is true. |
InitialVolume |
Base order volume used for the first trade and after wins. |
DoubleLotSize |
Switches between multiplicative (true) and additive (false) martingale sizing. |
LotSizeIncrement |
Volume increment applied after a loss when DoubleLotSize is false. |
TrailingStopSteps |
Trailing-stop distance in price steps. |
StopLossSteps |
Classic stop-loss distance in price steps. |
TakeProfitSteps |
Classic take-profit distance in price steps. |
FastPeriod |
Period of the fast simple moving average. |
SlowPeriod |
Period of the slow simple moving average. |
CandleType |
Candle series used for all indicator calculations. |
Notes
- Position volume is aligned with the instrument’s volume step, minimum, and maximum limits.
- Floating profit calculations rely on the instrument’s
PriceStepandStepPrice. If they are zero the money-based protections are automatically skipped. - Only the C# implementation is supplied. The Python port is intentionally omitted per task requirements.
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>
/// Martingale strategy that increases position size after a loss and manages risk using money targets and trailing stops.
/// </summary>
public class MartingaleBoneCrusherStrategy : Strategy
{
private readonly StrategyParam<bool> _useTakeProfitMoney;
private readonly StrategyParam<decimal> _takeProfitMoney;
private readonly StrategyParam<bool> _useTakeProfitPercent;
private readonly StrategyParam<decimal> _takeProfitPercent;
private readonly StrategyParam<bool> _enableTrailing;
private readonly StrategyParam<decimal> _trailingTakeProfitMoney;
private readonly StrategyParam<decimal> _trailingStopMoney;
private readonly StrategyParam<MartingaleModes> _martingaleMode;
private readonly StrategyParam<bool> _useMoveToBreakeven;
private readonly StrategyParam<decimal> _moveToBreakevenTrigger;
private readonly StrategyParam<decimal> _breakevenOffset;
private readonly StrategyParam<decimal> _multiply;
private readonly StrategyParam<decimal> _initialVolume;
private readonly StrategyParam<bool> _doubleLotSize;
private readonly StrategyParam<decimal> _lotSizeIncrement;
private readonly StrategyParam<decimal> _trailingStopSteps;
private readonly StrategyParam<decimal> _stopLossSteps;
private readonly StrategyParam<decimal> _takeProfitSteps;
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<DataType> _candleType;
private SimpleMovingAverage _fastMa;
private SimpleMovingAverage _slowMa;
private decimal _averagePrice;
private decimal _positionVolume;
private decimal _currentVolume;
private decimal _lastOrderVolume;
private decimal _lastTradeResult;
private decimal _highestPrice;
private decimal _lowestPrice;
private decimal? _breakevenPrice;
private decimal _maxFloatingProfit;
private decimal _initialCapital;
private Sides? _lastPositionSide;
private Sides? _lastLosingSide;
/// <summary>
/// Initializes a new instance of <see cref="MartingaleBoneCrusherStrategy"/>.
/// </summary>
public MartingaleBoneCrusherStrategy()
{
_useTakeProfitMoney = Param(nameof(UseTakeProfitMoney), false)
.SetDisplay("Use Money TP", "Enable fixed money take profit", "Risk Management");
_takeProfitMoney = Param(nameof(TakeProfitMoney), 10m)
.SetGreaterThanZero()
.SetDisplay("Money TP", "Take profit in money", "Risk Management");
_useTakeProfitPercent = Param(nameof(UseTakeProfitPercent), false)
.SetDisplay("Use Percent TP", "Enable percentage take profit", "Risk Management");
_takeProfitPercent = Param(nameof(TakeProfitPercent), 10m)
.SetGreaterThanZero()
.SetDisplay("Percent TP", "Take profit percentage", "Risk Management");
_enableTrailing = Param(nameof(EnableTrailing), true)
.SetDisplay("Trailing Enabled", "Use money trailing stop", "Risk Management");
_trailingTakeProfitMoney = Param(nameof(TrailingTakeProfitMoney), 40m)
.SetGreaterThanZero()
.SetDisplay("Trailing Start", "Profit to activate trailing", "Risk Management");
_trailingStopMoney = Param(nameof(TrailingStopMoney), 10m)
.SetGreaterThanZero()
.SetDisplay("Trailing Step", "Allowed profit pullback", "Risk Management");
_martingaleMode = Param(nameof(MartingaleMode), MartingaleModes.Martingale2)
.SetDisplay("Mode", "Martingale logic variant", "General");
_useMoveToBreakeven = Param(nameof(UseMoveToBreakeven), true)
.SetDisplay("Use Breakeven", "Enable breakeven stop", "Risk Management");
_moveToBreakevenTrigger = Param(nameof(MoveToBreakevenTrigger), 10m)
.SetNotNegative()
.SetDisplay("Breakeven Trigger", "Steps to move stop", "Risk Management");
_breakevenOffset = Param(nameof(BreakevenOffset), 5m)
.SetNotNegative()
.SetDisplay("Breakeven Offset", "Offset from entry", "Risk Management");
_multiply = Param(nameof(Multiply), 2m)
.SetGreaterThanZero()
.SetDisplay("Multiply", "Lot multiplier after loss", "Position Sizing");
_initialVolume = Param(nameof(InitialVolume), 0.01m)
.SetGreaterThanZero()
.SetDisplay("Initial Volume", "Base order volume", "Position Sizing");
_doubleLotSize = Param(nameof(DoubleLotSize), false)
.SetDisplay("Double Volume", "Multiply volume after loss", "Position Sizing");
_lotSizeIncrement = Param(nameof(LotSizeIncrement), 0.01m)
.SetNotNegative()
.SetDisplay("Lot Increment", "Volume increment after loss", "Position Sizing");
_trailingStopSteps = Param(nameof(TrailingStopSteps), 30m)
.SetNotNegative()
.SetDisplay("Trailing Steps", "Trailing distance in steps", "Price Targets");
_stopLossSteps = Param(nameof(StopLossSteps), 5m)
.SetNotNegative()
.SetDisplay("Stop Steps", "Stop-loss distance in steps", "Price Targets");
_takeProfitSteps = Param(nameof(TakeProfitSteps), 5m)
.SetNotNegative()
.SetDisplay("Take Profit Steps", "Take-profit distance in steps", "Price Targets");
_fastPeriod = Param(nameof(FastPeriod), 2)
.SetGreaterThanZero()
.SetDisplay("Fast MA", "Fast moving average length", "Signals");
_slowPeriod = Param(nameof(SlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow MA", "Slow moving average length", "Signals");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Primary candle series", "General");
}
/// <summary>
/// Enable fixed take profit in money.
/// </summary>
public bool UseTakeProfitMoney
{
get => _useTakeProfitMoney.Value;
set => _useTakeProfitMoney.Value = value;
}
/// <summary>
/// Take profit amount in money.
/// </summary>
public decimal TakeProfitMoney
{
get => _takeProfitMoney.Value;
set => _takeProfitMoney.Value = value;
}
/// <summary>
/// Enable take profit measured in percent.
/// </summary>
public bool UseTakeProfitPercent
{
get => _useTakeProfitPercent.Value;
set => _useTakeProfitPercent.Value = value;
}
/// <summary>
/// Percentage profit target.
/// </summary>
public decimal TakeProfitPercent
{
get => _takeProfitPercent.Value;
set => _takeProfitPercent.Value = value;
}
/// <summary>
/// Enable trailing stop in money.
/// </summary>
public bool EnableTrailing
{
get => _enableTrailing.Value;
set => _enableTrailing.Value = value;
}
/// <summary>
/// Profit required to activate money trailing.
/// </summary>
public decimal TrailingTakeProfitMoney
{
get => _trailingTakeProfitMoney.Value;
set => _trailingTakeProfitMoney.Value = value;
}
/// <summary>
/// Allowed profit pullback while trailing.
/// </summary>
public decimal TrailingStopMoney
{
get => _trailingStopMoney.Value;
set => _trailingStopMoney.Value = value;
}
/// <summary>
/// Selected martingale mode.
/// </summary>
public MartingaleModes MartingaleMode
{
get => _martingaleMode.Value;
set => _martingaleMode.Value = value;
}
/// <summary>
/// Enable automatic move to breakeven.
/// </summary>
public bool UseMoveToBreakeven
{
get => _useMoveToBreakeven.Value;
set => _useMoveToBreakeven.Value = value;
}
/// <summary>
/// Distance in steps required to activate breakeven.
/// </summary>
public decimal MoveToBreakevenTrigger
{
get => _moveToBreakevenTrigger.Value;
set => _moveToBreakevenTrigger.Value = value;
}
/// <summary>
/// Offset added to entry price when moving stop to breakeven.
/// </summary>
public decimal BreakevenOffset
{
get => _breakevenOffset.Value;
set => _breakevenOffset.Value = value;
}
/// <summary>
/// Multiplier applied to volume after a loss.
/// </summary>
public decimal Multiply
{
get => _multiply.Value;
set => _multiply.Value = value;
}
/// <summary>
/// Base order volume.
/// </summary>
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
/// <summary>
/// Use multiplication instead of addition for martingale.
/// </summary>
public bool DoubleLotSize
{
get => _doubleLotSize.Value;
set => _doubleLotSize.Value = value;
}
/// <summary>
/// Additional volume added after a loss when doubling is disabled.
/// </summary>
public decimal LotSizeIncrement
{
get => _lotSizeIncrement.Value;
set => _lotSizeIncrement.Value = value;
}
/// <summary>
/// Trailing distance expressed in price steps.
/// </summary>
public decimal TrailingStopSteps
{
get => _trailingStopSteps.Value;
set => _trailingStopSteps.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in price steps.
/// </summary>
public decimal StopLossSteps
{
get => _stopLossSteps.Value;
set => _stopLossSteps.Value = value;
}
/// <summary>
/// Take-profit distance expressed in price steps.
/// </summary>
public decimal TakeProfitSteps
{
get => _takeProfitSteps.Value;
set => _takeProfitSteps.Value = value;
}
/// <summary>
/// Fast moving average period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Slow moving average period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Candle type used for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_fastMa = null;
_slowMa = null;
_averagePrice = 0m;
_positionVolume = 0m;
_currentVolume = AlignVolume(InitialVolume);
_lastOrderVolume = _currentVolume;
_lastTradeResult = 0m;
_highestPrice = 0m;
_lowestPrice = 0m;
_breakevenPrice = null;
_maxFloatingProfit = 0m;
_initialCapital = 0m;
_lastPositionSide = null;
_lastLosingSide = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = AlignVolume(InitialVolume);
_currentVolume = Volume;
_lastOrderVolume = Volume;
_initialCapital = Portfolio?.BeginValue ?? Portfolio?.CurrentValue ?? 0m;
_fastMa = new SimpleMovingAverage { Length = FastPeriod };
_slowMa = new SimpleMovingAverage { Length = SlowPeriod };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(_fastMa, _slowMa, ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fastMa.IsFormed || !_slowMa.IsFormed)
return;
if (Position != 0)
{
UpdateExtremes(candle);
if (TryApplyStopAndTake(candle))
return;
if (TryApplyBreakeven(candle.ClosePrice))
return;
if (TryApplyMoneyTargets(candle.ClosePrice))
return;
TryActivateBreakeven(candle.ClosePrice);
return;
}
var entrySide = DetermineEntrySide(fastValue, slowValue);
if (entrySide is null)
return;
var volume = AlignVolume(_currentVolume);
if (volume <= 0m)
return;
if (entrySide == Sides.Buy)
BuyMarket(volume);
else
SellMarket(volume);
_averagePrice = candle.ClosePrice;
_positionVolume = volume;
_lastOrderVolume = volume;
_lastPositionSide = entrySide;
_highestPrice = candle.ClosePrice;
_lowestPrice = candle.ClosePrice;
_breakevenPrice = null;
_maxFloatingProfit = 0m;
}
private Sides? DetermineEntrySide(decimal fastValue, decimal slowValue)
{
Sides? signal = null;
if (fastValue < slowValue)
signal = Sides.Buy;
else if (fastValue > slowValue)
signal = Sides.Sell;
if (_lastTradeResult < 0m)
{
if (MartingaleMode == MartingaleModes.Martingale2 && _lastLosingSide.HasValue)
return _lastLosingSide == Sides.Buy ? Sides.Sell : Sides.Buy;
return signal;
}
return signal;
}
private bool TryApplyStopAndTake(ICandleMessage candle)
{
if (_positionVolume <= 0m || !_lastPositionSide.HasValue)
return false;
var stopDistance = StepsToPrice(StopLossSteps);
var takeDistance = StepsToPrice(TakeProfitSteps);
var trailingDistance = StepsToPrice(TrailingStopSteps);
var closePrice = candle.ClosePrice;
if (_lastPositionSide == Sides.Buy)
{
if (stopDistance > 0m && candle.LowPrice <= _averagePrice - stopDistance)
{
ClosePosition(_averagePrice - stopDistance);
return true;
}
if (takeDistance > 0m && candle.HighPrice >= _averagePrice + takeDistance)
{
ClosePosition(_averagePrice + takeDistance);
return true;
}
if (TrailingStopSteps > 0m && trailingDistance > 0m && closePrice <= _highestPrice - trailingDistance)
{
ClosePosition(closePrice);
return true;
}
}
else
{
if (stopDistance > 0m && candle.HighPrice >= _averagePrice + stopDistance)
{
ClosePosition(_averagePrice + stopDistance);
return true;
}
if (takeDistance > 0m && candle.LowPrice <= _averagePrice - takeDistance)
{
ClosePosition(_averagePrice - takeDistance);
return true;
}
if (TrailingStopSteps > 0m && trailingDistance > 0m && closePrice >= _lowestPrice + trailingDistance)
{
ClosePosition(closePrice);
return true;
}
}
return false;
}
private bool TryApplyBreakeven(decimal closePrice)
{
if (!UseMoveToBreakeven || !_breakevenPrice.HasValue || !_lastPositionSide.HasValue)
return false;
if (_lastPositionSide == Sides.Buy && closePrice <= _breakevenPrice.Value)
{
ClosePosition(closePrice);
return true;
}
if (_lastPositionSide == Sides.Sell && closePrice >= _breakevenPrice.Value)
{
ClosePosition(closePrice);
return true;
}
return false;
}
private bool TryApplyMoneyTargets(decimal closePrice)
{
var profit = GetFloatingProfit(closePrice);
if (UseTakeProfitMoney && profit >= TakeProfitMoney)
{
ClosePosition(closePrice);
return true;
}
if (UseTakeProfitPercent && _initialCapital > 0m)
{
var target = _initialCapital * TakeProfitPercent / 100m;
if (profit >= target)
{
ClosePosition(closePrice);
return true;
}
}
if (EnableTrailing && profit > 0m)
{
if (profit >= TrailingTakeProfitMoney)
_maxFloatingProfit = Math.Max(_maxFloatingProfit, profit);
if (_maxFloatingProfit > 0m && _maxFloatingProfit - profit >= TrailingStopMoney)
{
ClosePosition(closePrice);
return true;
}
}
return false;
}
private void TryActivateBreakeven(decimal closePrice)
{
if (!UseMoveToBreakeven || _breakevenPrice.HasValue || !_lastPositionSide.HasValue)
return;
var trigger = StepsToPrice(MoveToBreakevenTrigger);
if (trigger <= 0m)
return;
var offset = StepsToPrice(BreakevenOffset);
if (_lastPositionSide == Sides.Buy)
{
if (closePrice >= _averagePrice + trigger)
_breakevenPrice = _averagePrice + offset;
}
else if (closePrice <= _averagePrice - trigger)
{
_breakevenPrice = _averagePrice - offset;
}
}
private decimal GetFloatingProfit(decimal currentPrice)
{
if (_positionVolume <= 0m || !_lastPositionSide.HasValue)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = Security?.PriceStep ?? 0m;
if (priceStep <= 0m || stepPrice <= 0m)
return 0m;
var direction = _lastPositionSide == Sides.Buy ? 1m : -1m;
var priceDiff = (currentPrice - _averagePrice) * direction;
var steps = priceDiff / priceStep;
return steps * stepPrice * _positionVolume;
}
private void ClosePosition(decimal exitPrice)
{
if (Position > 0m)
SellMarket(Position);
else if (Position < 0m)
BuyMarket(-Position);
ComputeTradeResult(exitPrice);
ResetPositionState();
UpdateNextVolume();
}
private void ComputeTradeResult(decimal exitPrice)
{
if (_positionVolume <= 0m || !_lastPositionSide.HasValue)
{
_lastTradeResult = 0m;
_lastLosingSide = null;
return;
}
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = Security?.PriceStep ?? 0m;
if (priceStep <= 0m || stepPrice <= 0m)
{
_lastTradeResult = 0m;
_lastLosingSide = null;
return;
}
var direction = _lastPositionSide == Sides.Buy ? 1m : -1m;
var priceDiff = (exitPrice - _averagePrice) * direction;
var steps = priceDiff / priceStep;
var pnl = steps * stepPrice * _positionVolume;
_lastTradeResult = pnl;
_lastLosingSide = pnl < 0m ? _lastPositionSide : null;
}
private void ResetPositionState()
{
_averagePrice = 0m;
_positionVolume = 0m;
_highestPrice = 0m;
_lowestPrice = 0m;
_breakevenPrice = null;
_maxFloatingProfit = 0m;
_lastPositionSide = null;
}
private void UpdateExtremes(ICandleMessage candle)
{
if (!_lastPositionSide.HasValue)
return;
if (_lastPositionSide == Sides.Buy)
{
if (candle.HighPrice > _highestPrice)
_highestPrice = candle.HighPrice;
}
else
{
if (_lowestPrice == 0m || candle.LowPrice < _lowestPrice)
_lowestPrice = candle.LowPrice;
}
}
private void UpdateNextVolume()
{
decimal nextVolume;
if (_lastTradeResult < 0m)
nextVolume = DoubleLotSize ? _lastOrderVolume * Multiply : _lastOrderVolume + LotSizeIncrement;
else
nextVolume = InitialVolume;
_currentVolume = AlignVolume(nextVolume);
_lastOrderVolume = _currentVolume;
}
private decimal StepsToPrice(decimal steps)
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 0m;
return steps * priceStep;
}
private decimal AlignVolume(decimal volume)
{
if (Security is null)
return volume;
var step = Security.VolumeStep ?? 0m;
if (step > 0m)
{
var ratio = Math.Round(volume / step, MidpointRounding.AwayFromZero);
if (ratio == 0m && volume > 0m)
ratio = 1m;
volume = ratio * step;
}
if (volume <= 0m)
volume = InitialVolume;
return volume;
}
/// <summary>
/// Supported martingale variants.
/// </summary>
public enum MartingaleModes
{
/// <summary>
/// Follow moving average direction after every loss.
/// </summary>
Martingale1,
/// <summary>
/// Reverse direction after a loss while using martingale sizing.
/// </summary>
Martingale2
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
from StockSharp.Messages import DataType
from System import TimeSpan, Math
class martingale_bone_crusher_strategy(Strategy):
def __init__(self):
super(martingale_bone_crusher_strategy, self).__init__()
self._initial_volume = self.Param("InitialVolume", 0.01)
self._multiply = self.Param("Multiply", 2.0)
self._double_lot_size = self.Param("DoubleLotSize", False)
self._lot_size_increment = self.Param("LotSizeIncrement", 0.01)
self._trailing_stop_steps = self.Param("TrailingStopSteps", 30.0)
self._stop_loss_steps = self.Param("StopLossSteps", 5.0)
self._take_profit_steps = self.Param("TakeProfitSteps", 5.0)
self._fast_period = self.Param("FastPeriod", 2)
self._slow_period = self.Param("SlowPeriod", 50)
self._enable_trailing = self.Param("EnableTrailing", True)
self._trailing_tp_money = self.Param("TrailingTakeProfitMoney", 40.0)
self._trailing_stop_money = self.Param("TrailingStopMoney", 10.0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._fast_ma = None
self._slow_ma = None
self._average_price = 0.0
self._position_volume = 0.0
self._current_volume = 0.01
self._last_order_volume = 0.01
self._last_trade_result = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._max_floating_profit = 0.0
self._last_position_side = None
self._last_losing_side = None
@property
def CandleType(self):
return self._candle_type.Value
@property
def InitialVolume(self):
return self._initial_volume.Value
def OnStarted2(self, time):
super(martingale_bone_crusher_strategy, self).OnStarted2(time)
self._current_volume = self.InitialVolume
self._last_order_volume = self.InitialVolume
self.Volume = self.InitialVolume
self._fast_ma = SimpleMovingAverage()
self._fast_ma.Length = self._fast_period.Value
self._slow_ma = SimpleMovingAverage()
self._slow_ma.Length = self._slow_period.Value
sub = self.SubscribeCandles(self.CandleType)
sub.Bind(self._fast_ma, self._slow_ma, self._process_candle).Start()
def _process_candle(self, candle, fast_val, slow_val):
fast_value = float(fast_val)
slow_value = float(slow_val)
if not self._fast_ma.IsFormed or not self._slow_ma.IsFormed:
return
if self.Position != 0:
self._update_extremes(candle)
if self._try_stop_take(candle):
return
if self._try_money_targets(float(candle.ClosePrice)):
return
return
entry_side = None
if fast_value < slow_value:
entry_side = "buy"
elif fast_value > slow_value:
entry_side = "sell"
if self._last_trade_result < 0 and self._last_losing_side is not None:
entry_side = "sell" if self._last_losing_side == "buy" else "buy"
if entry_side is None:
return
volume = self._current_volume
if volume <= 0:
return
if entry_side == "buy":
self.BuyMarket(volume)
else:
self.SellMarket(volume)
self._average_price = float(candle.ClosePrice)
self._position_volume = volume
self._last_order_volume = volume
self._last_position_side = entry_side
self._highest_price = float(candle.ClosePrice)
self._lowest_price = float(candle.ClosePrice)
self._max_floating_profit = 0.0
def _update_extremes(self, candle):
if self._last_position_side is None:
return
if self._last_position_side == "buy":
if float(candle.HighPrice) > self._highest_price:
self._highest_price = float(candle.HighPrice)
else:
if self._lowest_price == 0 or float(candle.LowPrice) < self._lowest_price:
self._lowest_price = float(candle.LowPrice)
def _steps_to_price(self, steps):
sec = self.Security
ps = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if ps <= 0:
return 0.0
return steps * ps
def _try_stop_take(self, candle):
if self._position_volume <= 0 or self._last_position_side is None:
return False
stop_dist = self._steps_to_price(self._stop_loss_steps.Value)
take_dist = self._steps_to_price(self._take_profit_steps.Value)
trail_dist = self._steps_to_price(self._trailing_stop_steps.Value)
close_price = float(candle.ClosePrice)
if self._last_position_side == "buy":
if stop_dist > 0 and float(candle.LowPrice) <= self._average_price - stop_dist:
self._close_position(self._average_price - stop_dist)
return True
if take_dist > 0 and float(candle.HighPrice) >= self._average_price + take_dist:
self._close_position(self._average_price + take_dist)
return True
if self._trailing_stop_steps.Value > 0 and trail_dist > 0 and close_price <= self._highest_price - trail_dist:
self._close_position(close_price)
return True
else:
if stop_dist > 0 and float(candle.HighPrice) >= self._average_price + stop_dist:
self._close_position(self._average_price + stop_dist)
return True
if take_dist > 0 and float(candle.LowPrice) <= self._average_price - take_dist:
self._close_position(self._average_price - take_dist)
return True
if self._trailing_stop_steps.Value > 0 and trail_dist > 0 and close_price >= self._lowest_price + trail_dist:
self._close_position(close_price)
return True
return False
def _try_money_targets(self, close_price):
profit = self._get_floating_profit(close_price)
if self._enable_trailing.Value and profit > 0:
if profit >= self._trailing_tp_money.Value:
self._max_floating_profit = max(self._max_floating_profit, profit)
if self._max_floating_profit > 0 and self._max_floating_profit - profit >= self._trailing_stop_money.Value:
self._close_position(close_price)
return True
return False
def _get_floating_profit(self, current_price):
if self._position_volume <= 0 or self._last_position_side is None:
return 0.0
sec = self.Security
ps = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if ps <= 0:
return 0.0
direction = 1.0 if self._last_position_side == "buy" else -1.0
diff = (current_price - self._average_price) * direction
steps = diff / ps
return steps * ps * self._position_volume
def _close_position(self, exit_price):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(abs(self.Position))
self._compute_trade_result(exit_price)
self._reset_position_state()
self._update_next_volume()
def _compute_trade_result(self, exit_price):
if self._position_volume <= 0 or self._last_position_side is None:
self._last_trade_result = 0.0
self._last_losing_side = None
return
sec = self.Security
ps = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if ps <= 0:
self._last_trade_result = 0.0
self._last_losing_side = None
return
direction = 1.0 if self._last_position_side == "buy" else -1.0
diff = (exit_price - self._average_price) * direction
steps = diff / ps
pnl = steps * ps * self._position_volume
self._last_trade_result = pnl
self._last_losing_side = self._last_position_side if pnl < 0 else None
def _reset_position_state(self):
self._average_price = 0.0
self._position_volume = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._max_floating_profit = 0.0
self._last_position_side = None
def _update_next_volume(self):
if self._last_trade_result < 0:
if self._double_lot_size.Value:
nv = self._last_order_volume * self._multiply.Value
else:
nv = self._last_order_volume + self._lot_size_increment.Value
else:
nv = self.InitialVolume
self._current_volume = nv
self._last_order_volume = nv
def OnReseted(self):
super(martingale_bone_crusher_strategy, self).OnReseted()
self._fast_ma = None
self._slow_ma = None
self._average_price = 0.0
self._position_volume = 0.0
self._current_volume = self.InitialVolume
self._last_order_volume = self.InitialVolume
self._last_trade_result = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._max_floating_profit = 0.0
self._last_position_side = None
self._last_losing_side = None
def CreateClone(self):
return martingale_bone_crusher_strategy()