Martingale Bone Crusher Strategie
Überblick
Die Martingale Bone Crusher Strategie repliziert das Verhalten des ursprünglichen MetaTrader Expert Advisors. Die Strategie handelt in Richtung eines Schnell/Langsam-Vergleichs gleitender Durchschnitte und wendet ein Martingale-Geldmanagement-Modell an, das die Ordergröße nach einem Verlusthandel erhöht. Eine große Auswahl an Risikomanagement-Tools ist verfügbar, darunter feste Geldziele, Prozentsatzziele, ein konfigurierbarer Breakeven-Zug, klassische Stop-Loss/Take-Profit-Niveaus gemessen in Preisschritten und ein gewinnschützender Trailing Stop gemessen in Geld.
Handelslogik
- Signalerzeugung – zwei einfache gleitende Durchschnitte werden auf der primären Kerzenreihe berechnet. Wenn der schnelle Durchschnitt unter dem langsamen liegt, sucht die Strategie nach Long-Einstiegen. Wenn er darüber liegt, sucht sie nach Short-Einstiegen. Neue Trades werden nicht platziert, während eine aktive Position besteht.
- Martingale-Sequenzierung – nach jedem abgeschlossenen Trade wird die nächste Positionsgröße aktualisiert. Wenn der letzte Trade mit einem Verlust schloss, wird das nächste Volumen entweder multipliziert oder erhöht (je nach Einstellungen). Gewinnende Trades setzen die Positionsgröße auf den Anfangswert zurück.
- Modusauswahl – zwei Martingale-Varianten werden bereitgestellt:
Martingale1: Der nächste Trade folgt immer der aktuellen Richtung des gleitenden Durchschnitts, auch nach einem Verlust.Martingale2: Nach einem Verlust wird der nächste Trade relativ zur verlierenden Richtung umgekehrt. Dies spiegelt das Verhalten der zweiten Option des ursprünglichen Expert Advisors wider.
- Risikokontrollen – während eine Position offen ist, wertet die Strategie kontinuierlich aus:
- klassische Stop-Loss- und Take-Profit-Niveaus ausgedrückt in Preisschritten;
- einen optionalen Trailing Stop, der dem Extrempreis mit einem festen Schrittabstand folgt;
- einen Breakeven-Zug, der das Ausstiegsniveau verschiebt, nachdem sich die Position um eine konfigurierbare Distanz zu Gunsten bewegt;
- globale geld- und prozentbasierte Gewinnziele, die die Position schließen, wenn der aggregierte schwebende PnL die Schwellenwerte überschreitet;
- einen zusätzlichen Trailing Stop in Geld, der angesammelten Gewinn sichert, sobald der schwebende Gewinn das Aktivierungsniveau erreicht.
Parameter
| Parameter | Beschreibung |
|---|---|
UseTakeProfitMoney |
Aktiviert ein festes Geld-Take-Profit-Ziel. |
TakeProfitMoney |
Geldbetrag, der den Trade schließt, wenn UseTakeProfitMoney aktiv ist. |
UseTakeProfitPercent |
Aktiviert ein Gewinnziel ausgedrückt als Prozentsatz des anfänglichen Portfoliowerts. |
TakeProfitPercent |
Prozentsatz, der verwendet wird, wenn UseTakeProfitPercent aktiviert ist. |
EnableTrailing |
Aktiviert den geldbasierten Trailing Stop. |
TrailingTakeProfitMoney |
Schwebender Gewinn, der erforderlich ist, um den Geld-Trailing-Stop zu aktivieren. |
TrailingStopMoney |
Erlaubter Drawdown vom Spitzen-Schwebgewinn, nachdem der Trailing Stop aktiv ist. |
MartingaleModes |
Wählt zwischen Martingale1 und Martingale2 Verhalten. |
UseMoveToBreakeven |
Aktiviert die Breakeven-Stop-Anpassung. |
MoveToBreakevenTrigger |
Preisschritte, um die sich der Trade zu Gunsten bewegen muss, bevor der Breakeven-Schutz aktiviert wird. |
BreakevenOffset |
Abstand, der zum Einstiegspreis hinzugefügt wird, wenn der Breakeven-Stop platziert wird. |
Multiply |
Multiplikator, der auf das nächste Volumen nach einem Verlust angewendet wird, wenn DoubleLotSize true ist. |
InitialVolume |
Basis-Ordervolumen für den ersten Trade und nach Gewinnen. |
DoubleLotSize |
Wechselt zwischen multiplikativem (true) und additivem (false) Martingale-Sizing. |
LotSizeIncrement |
Volumenerhöhung nach einem Verlust, wenn DoubleLotSize false ist. |
TrailingStopSteps |
Trailing-Stop-Abstand in Preisschritten. |
StopLossSteps |
Klassischer Stop-Loss-Abstand in Preisschritten. |
TakeProfitSteps |
Klassischer Take-Profit-Abstand in Preisschritten. |
FastPeriod |
Periode des schnellen einfachen gleitenden Durchschnitts. |
SlowPeriod |
Periode des langsamen einfachen gleitenden Durchschnitts. |
CandleType |
Kerzenreihe, die für alle Indikatorberechnungen verwendet wird. |
Hinweise
- Das Positionsvolumen wird am Volumen-Step des Instruments sowie den minimalen und maximalen Limits ausgerichtet.
- Die Berechnungen des schwebenden Gewinns hängen vom
PriceStepundStepPricedes Instruments ab. Wenn sie null sind, werden die geldbasierten Schutzmaßnahmen automatisch übersprungen. - Nur die C#-Implementierung wird bereitgestellt. Der Python-Port wird gemäß den Aufgabenanforderungen absichtlich weggelassen.
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()