Стратегия Martingale Bone Crusher
Обзор
Martingale Bone Crusher Strategy переносит логику оригинального советника MetaTrader в среду StockSharp. Стратегия определяет направление торговли по сравнению быстрой и медленной скользящих средних и использует модель мартингейла, увеличивая объем следующей сделки после убыточного исхода. Для управления рисками предусмотрены фиксированные денежные цели, цели в процентах, перенос стопа в безубыток, классические стоп-лосс/тейк-профит в шагах цены и защитный трейлинг по прибыли в деньгах.
Торговая логика
- Генерация сигналов. На основной серии свечей рассчитываются две простые скользящие средние. Если быстрая ниже медленной — ищется покупка; если выше — продажа. Новые сделки не открываются, пока позиция не закрыта.
- Мартингейл. После каждой закрытой сделки рассчитывается объем следующего входа. Убыток приводит к увеличению объема (по множителю или с шагом), прибыль возвращает объем к начальному значению.
- Режимы работы. Доступны два варианта мартингейла:
Martingale1— каждая новая сделка следует текущему направлению скользящих средних.Martingale2— после убытка следующая сделка открывается в противоположную сторону, как в оригинальном советнике.
- Защита позиции. Во время удержания позиции стратегия контролирует:
- фиксированные стоп-лосс и тейк-профит в шагах цены;
- опциональный трейлинг по экстремумам с фиксированным отступом;
- перенос стопа в безубыток после прохождения заданного расстояния;
- закрытие по суммарной плавающей прибыли в деньгах и процентах;
- трейлинг по прибыли в деньгах после достижения заданного порога.
Параметры
| Параметр | Описание |
|---|---|
UseTakeProfitMoney |
Включает денежный тейк-профит. |
TakeProfitMoney |
Величина прибыли, при которой позиция закрывается (если UseTakeProfitMoney = true). |
UseTakeProfitPercent |
Включает тейк-профит в процентах от начального капитала. |
TakeProfitPercent |
Значение процента для UseTakeProfitPercent. |
EnableTrailing |
Включает трейлинг-стоп по прибыли в деньгах. |
TrailingTakeProfitMoney |
Плавающая прибыль, необходимая для активации денежного трейлинга. |
TrailingStopMoney |
Допустимая просадка прибыли после активации денежного трейлинга. |
MartingaleModes |
Выбор режима Martingale1 или Martingale2. |
UseMoveToBreakeven |
Включает перенос стопа в безубыток. |
MoveToBreakevenTrigger |
Количество шагов цены до активации безубытка. |
BreakevenOffset |
Отступ от цены входа при установке безубытка. |
Multiply |
Множитель объема после убыточной сделки (используется при DoubleLotSize = true). |
InitialVolume |
Базовый объем первой сделки и сделок после прибыли. |
DoubleLotSize |
Выбор между умножением (true) и приращением (false) объема. |
LotSizeIncrement |
Приращение объема после убытка, если DoubleLotSize = false. |
TrailingStopSteps |
Отступ трейлинг-стопа в шагах цены. |
StopLossSteps |
Стоп-лосс в шагах цены. |
TakeProfitSteps |
Тейк-профит в шагах цены. |
FastPeriod |
Период быстрой SMA. |
SlowPeriod |
Период медленной SMA. |
CandleType |
Тип свечей для расчетов. |
Примечания
- Объем автоматически приводится к шагу объема и ограничивается допустимыми минимумом и максимумом инструмента.
- Расчет прибыли в деньгах опирается на
PriceStepиStepPrice. Если значения равны нулю, денежные ограничения пропускаются. - По требованию предоставлена только C# версия, Python-реализация не создавалась.
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()