Estratégia Martingale Bone Crusher
Visão geral
A Estratégia Martingale Bone Crusher replica o comportamento do expert advisor original do MetaTrader. A estratégia opera na direção de uma comparação de médias móveis rápida/lenta e aplica um modelo de gestão de dinheiro martingale que aumenta o tamanho da ordem após uma operação perdedora. Um grande conjunto de ferramentas de gestão de risco está disponível, incluindo alvos de dinheiro fixo, alvos de porcentagem, um movimento de breakeven configurável, níveis clássicos de stop-loss/take-profit medidos em passos de preço, e um trailing stop de proteção de lucro medido em dinheiro.
Lógica de trading
- Geração de sinais – duas médias móveis simples são calculadas na série de candles principal. Quando a média rápida está abaixo da lenta, a estratégia busca entradas compradas. Quando está acima, busca entradas vendidas. Novos trades não são realizados enquanto há uma posição ativa.
- Sequenciamento martingale – após cada trade concluído, o tamanho da próxima posição é atualizado. Se o último trade fechou com perda, o próximo volume é multiplicado ou incrementado (dependendo das configurações). Trades vencedores redefiniam o tamanho da posição ao valor inicial.
- Seleção de modo – duas variantes de martingale são fornecidas:
Martingale1: o próximo trade sempre segue a direção atual da média móvel, mesmo após uma perda.Martingale2: após uma perda, o próximo trade é revertido em relação à direção perdedora. Isso espelha o comportamento da segunda opção do Expert Advisor original.
- Controles de risco – enquanto uma posição está aberta, a estratégia avalia continuamente:
- níveis clássicos de stop-loss e take-profit expressos em passos de preço;
- um trailing stop opcional que segue o preço extremo com uma distância de passo fixa;
- um movimento de breakeven que desloca o nível de saída depois que a posição se move a favor em uma distância configurável;
- alvos globais de lucro baseados em dinheiro e porcentagem que fecham a posição quando o PnL flutuante agregado excede os limites;
- um trailing stop adicional em dinheiro que assegura o lucro acumulado assim que o ganho flutuante atinge o nível de ativação.
Parâmetros
| Parâmetro | Descrição |
|---|---|
UseTakeProfitMoney |
Habilita um alvo de take-profit de dinheiro fixo. |
TakeProfitMoney |
Quantidade de dinheiro que fecha o trade quando UseTakeProfitMoney está ativo. |
UseTakeProfitPercent |
Habilita um alvo de lucro expresso como porcentagem do valor inicial do portfólio. |
TakeProfitPercent |
Porcentagem usada quando UseTakeProfitPercent está habilitado. |
EnableTrailing |
Habilita o trailing stop baseado em dinheiro. |
TrailingTakeProfitMoney |
Lucro flutuante necessário para armar o trailing stop de dinheiro. |
TrailingStopMoney |
Queda permitida a partir do pico de lucro flutuante após o trailing stop estar ativo. |
MartingaleModes |
Seleciona entre o comportamento Martingale1 e Martingale2. |
UseMoveToBreakeven |
Habilita o ajuste de stop de breakeven. |
MoveToBreakevenTrigger |
Passos de preço que o trade deve se mover a favor antes de a proteção de breakeven ser ativada. |
BreakevenOffset |
Distância adicionada ao preço de entrada quando o stop de breakeven é colocado. |
Multiply |
Multiplicador aplicado ao próximo volume após uma perda quando DoubleLotSize é true. |
InitialVolume |
Volume de ordem base usado para o primeiro trade e após ganhos. |
DoubleLotSize |
Alterna entre dimensionamento martingale multiplicativo (true) e aditivo (false). |
LotSizeIncrement |
Incremento de volume aplicado após uma perda quando DoubleLotSize é false. |
TrailingStopSteps |
Distância do trailing stop em passos de preço. |
StopLossSteps |
Distância clássica de stop-loss em passos de preço. |
TakeProfitSteps |
Distância clássica de take-profit em passos de preço. |
FastPeriod |
Período da média móvel simples rápida. |
SlowPeriod |
Período da média móvel simples lenta. |
CandleType |
Série de candles usada para todos os cálculos de indicadores. |
Notas
- O volume de posição é alinhado com o passo de volume do instrumento, os limites mínimos e máximos.
- Os cálculos de lucro flutuante dependem do
PriceStepeStepPricedo instrumento. Se forem zero, as proteções baseadas em dinheiro são automaticamente ignoradas. - Apenas a implementação em C# é fornecida. O port em Python é omitido intencionalmente conforme os requisitos da tarefa.
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()