Martingale Bone Crusher 策略
概述
Martingale Bone Crusher 策略 复刻了原始 MetaTrader 智能交易系统的核心逻辑。策略通过比较一快一慢两条简单移动平均线来确定做多或做空方向,并在出现亏损后采用马丁格尔资金管理模型放大下一笔订单的手数。策略同时提供了多种风险控制手段,包括固定金额止盈、百分比止盈、保本移动、以价格步长计量的传统止损/止盈以及以盈利金额为基础的移动止盈。
交易逻辑
- 信号生成:在主图 K 线序列上计算两条简单移动平均线。当快线低于慢线时寻找做多信号;快线高于慢线时寻找做空信号;持仓未平仓前不会开出新的信号。
- 马丁格尔序列:每当一笔交易结束,都会重新计算下一次下单手数。若上一笔交易亏损,则按照设置选择乘法放大或加法递增手数;若盈利,则手数恢复到初始值。
- 模式选择:策略提供两个马丁格尔模式:
Martingale1:无论盈亏,下一笔始终跟随当前均线方向。Martingale2:若上一笔亏损,则下一笔会反向开仓,复现原始 EA 的第二种逻辑。
- 风险管理:持仓期间持续监控:
- 以价格步长定义的固定止损与止盈;
- 可选的价格追踪止损,使用固定步长追随极值;
- 当价格向有利方向移动指定距离后自动将止损移至保本的功能;
- 基于浮动盈亏的金额止盈与百分比止盈;
- 当浮动盈利达到激活值后,按金额跟踪锁定收益的移动止盈。
参数
| 参数 | 说明 |
|---|---|
UseTakeProfitMoney |
启用固定金额止盈。 |
TakeProfitMoney |
UseTakeProfitMoney 开启时触发平仓的金额。 |
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 |
快速简单移动平均的周期。 |
SlowPeriod |
慢速简单移动平均的周期。 |
CandleType |
指标计算所使用的 K 线类型。 |
说明
- 下单手数会按照交易品种的步长、最小手数与最大手数进行对齐。
- 浮动盈亏的金额计算依赖品种的
PriceStep与StepPrice。若二者为 0,则相关金额控制会自动跳过。 - 按要求仅提供 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()