Dual Lot Step Hedge 策略
概述
Dual Lot Step Hedge 策略是 MetaTrader 5 专家顾问 “x1 lot from high to low” 和 “x1 lot from low to high”(目录 MQL/19543)的 C# 版本。原始程序会立即同时开多单和空单,按照预设的步进方式调整手数,并在达到固定收益目标时一次性平掉所有仓位。本实现基于 StockSharp 的高级 API,完整复刻这一流程,并提供清晰的参数与状态控制。
策略提供两种运行模式:
- HighToLow:以最大手数乘数启动,首轮对冲仓位使用最大量,之后每次新建仓位时手数减少一个步长。
- LowToHigh:以最小手数步长启动,每次新建仓位时手数增加一个步长,直到达到乘数上限,此后保持该手数。
策略会始终维持多空双腿,分别计算止损和止盈,并通过监控账户权益在达到盈利目标时关闭整套仓位。
交易逻辑
- 当没有持仓时,同时以当前手数开多单和空单(市价单)。
- 若仅有其中一条腿持仓(例如另一条腿被止损),则立即以当前手数补齐缺失的一条腿。
- 每次成功建仓后,根据所选模式更新手数。
- 在每个成交(tick)到来时检查保护条件:
- 多单价格跌破平均建仓价减去
StopLossPips(以点为单位)时止损,或上涨超过平均价加上TakeProfitPips时止盈。 - 空单价格上破平均建仓价加上
StopLossPips时止损,或下破平均价减去TakeProfitPips时止盈。
- 多单价格跌破平均建仓价减去
- 当账户权益增量超过
MinProfit时,平掉全部仓位并将手数恢复到模式对应的起始值。 - 若意外检测到同方向存在多个持仓,策略会立即平仓并重置状态,以保持与原策略一致的“一腿最多一单”约束。
所有委托均通过 BuyMarket 与 SellMarket 方法下单。OnOwnTradeReceived 用于跟踪成交,维护每条腿的累计仓位,并在有未完成的开仓或平仓订单时阻止重复下单。
参数说明
| 参数 | 说明 |
|---|---|
LotMultiplier |
最大手数乘数,按最小交易量的整数倍表示(默认 10)。 |
StopLossPips |
单腿止损距离,单位为点(默认 50,设为 0 关闭止损)。 |
TakeProfitPips |
单腿止盈距离,单位为点(默认 150,设为 0 关闭止盈)。 |
MinProfit |
账户货币计价的总利润目标,达到后平掉全部仓位(默认 27)。 |
ScalingMode |
手数调整模式:HighToLow 对应“x1 lot from high to low”,LowToHigh 对应“x1 lot from low to high”。 |
策略会自动读取 Security.VolumeStep 作为最小交易量,并根据价格步长自动换算点值(对 3/5 位小数的外汇品种自动乘以 10)。
手数循环
- HighToLow:首轮对冲使用最大手数(
VolumeStep * LotMultiplier)。每次建仓后手数减一档。在达到利润目标并清仓后,将手数重置为0,下一轮从最大手数重新开始。 - LowToHigh:从最小手数开始,每次建仓后增加一个步长,直到达到乘数上限。利润目标达成后手数重置为最小步长。
使用建议
- 策略订阅的是逐笔成交(
DataType.Ticks),需要确保历史或实时数据源能提供 tick 数据。 - 止损和止盈在策略内部判断,不会额外向交易所发送保护性委托。
- 由于同时开多与开空,适用于支持对冲仓位、点差较小的券商。在净额制度账户上也能运行,但多空腿会互相抵消,直到其中一条腿因策略逻辑被关闭。
- 默认参数与原 MQL 程序一致。实际使用时请结合标的波动性谨慎调整,大手数对冲策略可能在触及盈利目标前经历较大回撤。
与 MQL 原版的对应关系
| MQL 变量 | C# 中的实现 |
|---|---|
InpLots |
LotMultiplier,自动处理最小交易步长。 |
InpStopLoss 与 InpTakeProfit |
StopLossPips 与 TakeProfitPips,按价格步长转换为实际价差。 |
InpMinProfit |
MinProfit 与权益增量检查。 |
LotCheck |
LotCheck 辅助函数,保证手数满足最小步长并不超过最大限制。 |
CalculatePositions |
通过 OnOwnTradeReceived 维护多腿仓位数量。 |
CloseAllPositions() |
CloseAllPositions 方法,负责协调平仓订单并在平仓后复位状态。 |
风险提示
策略刻意保持多空双腿,意味着持续承担点差与隔夜利息成本。实盘前请务必:
- 在 StockSharp 模拟器或模拟账户中充分测试。
- 确认经纪商允许对冲仓位,否则多空单会立即被净额抵消。
- 根据标的波动性合理设置止损、止盈和利润目标。
- 监控保证金占用,多空同时持有会导致名义敞口翻倍。
文件列表
CS/DualLotStepHedgeStrategy.cs— 策略代码,包含详细英文注释。README.md— 英文文档。README_ru.md— 俄文文档。
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>
/// Recreates the "x1 lot from high to low" and "x1 lot from low to high" MetaTrader robots.
/// Opens hedged long/short positions with adjustable lot cycling and closes the basket once
/// a profit target is achieved.
/// </summary>
public class DualLotStepHedgeStrategy : Strategy
{
private readonly StrategyParam<int> _lotMultiplier;
private readonly StrategyParam<decimal> _stopLossPips;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<LotScalingModes> _scalingMode;
private decimal _volumeStep;
private decimal _maxVolume;
private decimal _currentVolume;
private decimal _pipValue;
private decimal _initialEquity;
private decimal _longVolume;
private decimal _shortVolume;
private decimal _longAveragePrice;
private decimal _shortAveragePrice;
private bool _longEntryInProgress;
private bool _shortEntryInProgress;
private bool _longExitInProgress;
private bool _shortExitInProgress;
private decimal _pendingLongEntryVolume;
private decimal _pendingShortEntryVolume;
private decimal _pendingLongExitVolume;
private decimal _pendingShortExitVolume;
private bool _resetRequested;
/// <summary>
/// Defines the lot stepping mode that matches the original MetaTrader experts.
/// </summary>
public enum LotScalingModes
{
/// <summary>
/// Start with the maximum lot multiplier and drop to the next step after the first cycle.
/// </summary>
HighToLow,
/// <summary>
/// Start with the minimum lot step and grow until the configured multiplier is reached.
/// </summary>
LowToHigh,
}
/// <summary>
/// Maximum lot multiplier expressed in minimal volume steps.
/// </summary>
public int LotMultiplier
{
get => _lotMultiplier.Value;
set => _lotMultiplier.Value = value;
}
/// <summary>
/// Stop loss distance in pips from the average entry price of the leg.
/// </summary>
public decimal StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take profit distance in pips from the average entry price of the leg.
/// </summary>
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Basket profit target in account currency.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Selected lot stepping mode.
/// </summary>
public LotScalingModes ScalingMode
{
get => _scalingMode.Value;
set => _scalingMode.Value = value;
}
/// <summary>
/// Initializes a new instance of <see cref="DualLotStepHedgeStrategy"/>.
/// </summary>
public DualLotStepHedgeStrategy()
{
_lotMultiplier = Param(nameof(LotMultiplier), 10)
.SetGreaterThanZero()
.SetDisplay("Lot Multiplier", "Maximum lot multiplier over the minimal step", "Trading")
.SetOptimize(1, 20, 1);
_stopLossPips = Param(nameof(StopLossPips), 50m)
.SetDisplay("Stop Loss (pips)", "Stop loss distance for each leg", "Risk")
.SetOptimize(10m, 200m, 10m);
_takeProfitPips = Param(nameof(TakeProfitPips), 150m)
.SetDisplay("Take Profit (pips)", "Take profit distance for each leg", "Risk")
.SetOptimize(20m, 400m, 20m);
_minProfit = Param(nameof(MinProfit), 27m)
.SetDisplay("Basket Profit", "Target profit in account currency", "Trading")
.SetOptimize(5m, 200m, 5m);
_scalingMode = Param(nameof(ScalingMode), LotScalingModes.HighToLow)
.SetDisplay("Scaling Mode", "How the lot size evolves after entries", "Trading");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, DataType.Ticks)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_volumeStep = 0m;
_maxVolume = 0m;
_currentVolume = 0m;
_pipValue = 0m;
_initialEquity = 0m;
_longVolume = 0m;
_shortVolume = 0m;
_longAveragePrice = 0m;
_shortAveragePrice = 0m;
_longEntryInProgress = false;
_shortEntryInProgress = false;
_longExitInProgress = false;
_shortExitInProgress = false;
_pendingLongEntryVolume = 0m;
_pendingShortEntryVolume = 0m;
_pendingLongExitVolume = 0m;
_pendingShortExitVolume = 0m;
_resetRequested = false;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_volumeStep = Security.VolumeStep ?? 0m;
if (_volumeStep <= 0m)
_volumeStep = 1m;
_maxVolume = LotCheck(_volumeStep * LotMultiplier);
if (_maxVolume <= 0m)
_maxVolume = _volumeStep;
_currentVolume = ScalingMode == LotScalingModes.HighToLow ? _maxVolume : _volumeStep;
_pipValue = CalculatePipValue();
var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
subscription.Bind(ProcessCandle).Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var price = candle.ClosePrice;
if (_volumeStep <= 0m)
return;
if (_initialEquity <= 0m)
_initialEquity = Portfolio.CurrentValue ?? 0m;
CheckProtectiveLevels(price);
if (_longExitInProgress || _shortExitInProgress)
return;
if (CheckProfitTarget())
return;
ResetCurrentVolumeIfNeeded();
var buyCount = _longVolume > 0m ? 1 : 0;
var sellCount = _shortVolume > 0m ? 1 : 0;
if (buyCount > 1 || sellCount > 1)
{
CloseAllPositions();
return;
}
if (_longEntryInProgress || _shortEntryInProgress)
return;
if (buyCount == 0 && sellCount == 0)
{
TryOpenHedge();
}
else if (buyCount == 1 && sellCount == 0)
{
OpenShortIfNeeded();
}
else if (buyCount == 0 && sellCount == 1)
{
OpenLongIfNeeded();
}
}
private bool CheckProfitTarget()
{
if (_initialEquity <= 0m || MinProfit <= 0m)
return false;
var currentEquity = Portfolio.CurrentValue ?? 0m;
if (currentEquity - _initialEquity >= MinProfit)
{
CloseAllPositions();
return true;
}
return false;
}
private void TryOpenHedge()
{
if (_longEntryInProgress || _shortEntryInProgress)
return;
var volume = LotCheck(_currentVolume);
if (volume <= 0m)
return;
var buyOk = ExecuteBuy(volume, true);
var sellOk = ExecuteSell(volume, true);
if (buyOk && sellOk)
AdjustVolumeAfterEntry();
}
private void OpenLongIfNeeded()
{
if (_longEntryInProgress)
return;
var volume = LotCheck(_currentVolume);
if (volume <= 0m)
return;
if (ExecuteBuy(volume, true))
AdjustVolumeAfterEntry();
}
private void OpenShortIfNeeded()
{
if (_shortEntryInProgress)
return;
var volume = LotCheck(_currentVolume);
if (volume <= 0m)
return;
if (ExecuteSell(volume, true))
AdjustVolumeAfterEntry();
}
private void AdjustVolumeAfterEntry()
{
if (ScalingMode == LotScalingModes.HighToLow)
{
_currentVolume = LotCheck(_currentVolume - _volumeStep);
}
else
{
_currentVolume = LotCheck(_currentVolume + _volumeStep);
}
}
private void CloseAllPositions()
{
if (_longVolume <= 0m && _shortVolume <= 0m && !_longExitInProgress && !_shortExitInProgress)
{
_resetRequested = true;
ApplyResetIfFlat();
return;
}
if (_longVolume > 0m && !_longExitInProgress)
{
if (ExecuteSell(_longVolume, false))
_resetRequested = true;
}
if (_shortVolume > 0m && !_shortExitInProgress)
{
if (ExecuteBuy(_shortVolume, false))
_resetRequested = true;
}
}
private void CloseLong()
{
if (_longVolume <= 0m || _longExitInProgress)
return;
ExecuteSell(_longVolume, false);
}
private void CloseShort()
{
if (_shortVolume <= 0m || _shortExitInProgress)
return;
ExecuteBuy(_shortVolume, false);
}
private bool ExecuteBuy(decimal volume, bool openingLong)
{
if (volume <= 0m)
return false;
var order = BuyMarket(volume);
if (order == null)
return false;
if (openingLong)
{
_longEntryInProgress = true;
_pendingLongEntryVolume += volume;
}
else
{
_shortExitInProgress = true;
_pendingShortExitVolume += volume;
}
return true;
}
private bool ExecuteSell(decimal volume, bool openingShort)
{
if (volume <= 0m)
return false;
var order = SellMarket(volume);
if (order == null)
return false;
if (openingShort)
{
_shortEntryInProgress = true;
_pendingShortEntryVolume += volume;
}
else
{
_longExitInProgress = true;
_pendingLongExitVolume += volume;
}
return true;
}
private void CheckProtectiveLevels(decimal price)
{
if (_pipValue <= 0m)
return;
if (_longVolume > 0m && !_longExitInProgress)
{
var stop = StopLossPips > 0m ? _longAveragePrice - StopLossPips * _pipValue : decimal.MinValue;
var take = TakeProfitPips > 0m ? _longAveragePrice + TakeProfitPips * _pipValue : decimal.MaxValue;
if (StopLossPips > 0m && price <= stop)
{
CloseLong();
return;
}
if (TakeProfitPips > 0m && price >= take)
{
CloseLong();
return;
}
}
if (_shortVolume > 0m && !_shortExitInProgress)
{
var stop = StopLossPips > 0m ? _shortAveragePrice + StopLossPips * _pipValue : decimal.MaxValue;
var take = TakeProfitPips > 0m ? _shortAveragePrice - TakeProfitPips * _pipValue : decimal.MinValue;
if (StopLossPips > 0m && price >= stop)
{
CloseShort();
return;
}
if (TakeProfitPips > 0m && price <= take)
{
CloseShort();
}
}
}
private void ResetCurrentVolumeIfNeeded()
{
if (ScalingMode == LotScalingModes.HighToLow)
{
if (_currentVolume < _volumeStep)
_currentVolume = _maxVolume;
}
else
{
if (_currentVolume < _volumeStep)
_currentVolume = _volumeStep;
else if (_currentVolume > _maxVolume)
_currentVolume = _volumeStep;
}
}
private decimal LotCheck(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = _volumeStep;
if (step <= 0m)
return 0m;
var ratio = Math.Floor(volume / step);
var normalized = ratio * step;
if (normalized < step)
normalized = 0m;
if (normalized > _maxVolume)
normalized = _maxVolume;
return normalized;
}
private decimal CalculatePipValue()
{
var step = Security.PriceStep ?? 0m;
if (step <= 0m)
return 1m;
double stepDouble;
try
{
stepDouble = Convert.ToDouble(step);
}
catch
{
return step;
}
if (stepDouble <= 0d)
return step;
var decimals = (int)Math.Round(-Math.Log10(stepDouble));
if (decimals == 3 || decimals == 5)
return step * 10m;
return step;
}
private void ApplyResetIfFlat()
{
if (!_resetRequested)
return;
if (_longVolume > 0m || _shortVolume > 0m)
return;
if (_longExitInProgress || _shortExitInProgress)
return;
if (_pendingLongEntryVolume > 0m || _pendingShortEntryVolume > 0m)
return;
_resetRequested = false;
_initialEquity = 0m;
if (ScalingMode == LotScalingModes.HighToLow)
{
_currentVolume = 0m;
}
else
{
_currentVolume = _volumeStep;
}
}
private void ApplyLongOpen(decimal volume, decimal price)
{
if (volume <= 0m)
return;
var total = _longVolume + volume;
_longAveragePrice = _longVolume <= 0m
? price
: (_longAveragePrice * _longVolume + price * volume) / total;
_longVolume = total;
}
private void ApplyShortOpen(decimal volume, decimal price)
{
if (volume <= 0m)
return;
var total = _shortVolume + volume;
_shortAveragePrice = _shortVolume <= 0m
? price
: (_shortAveragePrice * _shortVolume + price * volume) / total;
_shortVolume = total;
}
private void ApplyLongClose(decimal volume)
{
if (volume <= 0m || _longVolume <= 0m)
return;
var closed = Math.Min(_longVolume, volume);
_longVolume -= closed;
if (_longVolume <= 0m)
{
_longVolume = 0m;
_longAveragePrice = 0m;
}
}
private void ApplyShortClose(decimal volume)
{
if (volume <= 0m || _shortVolume <= 0m)
return;
var closed = Math.Min(_shortVolume, volume);
_shortVolume -= closed;
if (_shortVolume <= 0m)
{
_shortVolume = 0m;
_shortAveragePrice = 0m;
}
}
/// <inheritdoc />
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade.Order.Security != Security)
return;
var volume = trade.Trade.Volume;
if (volume <= 0m)
return;
var price = trade.Trade.Price;
if (trade.Order.Side == Sides.Buy)
{
ProcessBuyTrade(volume, price);
}
else if (trade.Order.Side == Sides.Sell)
{
ProcessSellTrade(volume, price);
}
ApplyResetIfFlat();
}
private void ProcessBuyTrade(decimal volume, decimal price)
{
var remaining = volume;
if (_pendingShortExitVolume > 0m)
{
var closing = Math.Min(_pendingShortExitVolume, remaining);
ApplyShortClose(closing);
_pendingShortExitVolume -= closing;
remaining -= closing;
if (_pendingShortExitVolume <= 0m)
_shortExitInProgress = false;
}
if (remaining <= 0m)
return;
if (_pendingLongEntryVolume > 0m)
{
var opening = Math.Min(_pendingLongEntryVolume, remaining);
ApplyLongOpen(opening, price);
_pendingLongEntryVolume -= opening;
remaining -= opening;
if (_pendingLongEntryVolume <= 0m)
_longEntryInProgress = false;
}
if (remaining > 0m)
ApplyLongOpen(remaining, price);
}
private void ProcessSellTrade(decimal volume, decimal price)
{
var remaining = volume;
if (_pendingLongExitVolume > 0m)
{
var closing = Math.Min(_pendingLongExitVolume, remaining);
ApplyLongClose(closing);
_pendingLongExitVolume -= closing;
remaining -= closing;
if (_pendingLongExitVolume <= 0m)
_longExitInProgress = false;
}
if (remaining <= 0m)
return;
if (_pendingShortEntryVolume > 0m)
{
var opening = Math.Min(_pendingShortEntryVolume, remaining);
ApplyShortOpen(opening, price);
_pendingShortEntryVolume -= opening;
remaining -= opening;
if (_pendingShortEntryVolume <= 0m)
_shortEntryInProgress = false;
}
if (remaining > 0m)
ApplyShortOpen(remaining, price);
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan
from StockSharp.Messages import DataType, CandleStates, Sides
from StockSharp.Algo.Strategies import Strategy
class dual_lot_step_hedge_strategy(Strategy):
def __init__(self):
super(dual_lot_step_hedge_strategy, self).__init__()
self._lot_multiplier = self.Param("LotMultiplier", 10)
self._stop_loss_pips = self.Param("StopLossPips", 50.0)
self._take_profit_pips = self.Param("TakeProfitPips", 150.0)
self._min_profit = self.Param("MinProfit", 27.0)
self._scaling_mode = self.Param("ScalingMode", 0)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._volume_step = 0.0
self._max_volume = 0.0
self._current_volume = 0.0
self._pip_value = 0.0
self._initial_equity = 0.0
self._long_volume = 0.0
self._short_volume = 0.0
self._long_avg_price = 0.0
self._short_avg_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def LotMultiplier(self):
return self._lot_multiplier.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def MinProfit(self):
return self._min_profit.Value
@property
def ScalingMode(self):
return self._scaling_mode.Value
def OnStarted2(self, time):
super(dual_lot_step_hedge_strategy, self).OnStarted2(time)
sec = self.Security
vs = float(sec.VolumeStep) if sec is not None and sec.VolumeStep is not None else 0.0
if vs <= 0:
vs = 1.0
self._volume_step = vs
self._max_volume = self._lot_check(vs * self.LotMultiplier)
if self._max_volume <= 0:
self._max_volume = vs
self._current_volume = self._max_volume if self.ScalingMode == 0 else vs
self._pip_value = self._calculate_pip_value()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._process_candle).Start()
def _process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
price = float(candle.ClosePrice)
if self._volume_step <= 0:
return
if self._initial_equity <= 0:
pf = self.Portfolio
if pf is not None and pf.CurrentValue is not None:
self._initial_equity = float(pf.CurrentValue)
self._check_protective_levels(price)
if self._check_profit_target():
return
self._reset_current_volume_if_needed()
buy_count = 1 if self._long_volume > 0 else 0
sell_count = 1 if self._short_volume > 0 else 0
if buy_count > 1 or sell_count > 1:
self._close_all()
return
if buy_count == 0 and sell_count == 0:
self._try_open_hedge(price)
elif buy_count == 1 and sell_count == 0:
self._open_short_if_needed(price)
elif buy_count == 0 and sell_count == 1:
self._open_long_if_needed(price)
def _check_profit_target(self):
if self._initial_equity <= 0 or self.MinProfit <= 0:
return False
pf = self.Portfolio
if pf is None or pf.CurrentValue is None:
return False
current = float(pf.CurrentValue)
if current - self._initial_equity >= self.MinProfit:
self._close_all()
return True
return False
def _try_open_hedge(self, price):
vol = self._lot_check(self._current_volume)
if vol <= 0:
return
self.BuyMarket()
self._apply_long_open(vol, price)
self.SellMarket()
self._apply_short_open(vol, price)
self._adjust_volume_after_entry()
def _open_long_if_needed(self, price):
vol = self._lot_check(self._current_volume)
if vol <= 0:
return
self.BuyMarket()
self._apply_long_open(vol, price)
self._adjust_volume_after_entry()
def _open_short_if_needed(self, price):
vol = self._lot_check(self._current_volume)
if vol <= 0:
return
self.SellMarket()
self._apply_short_open(vol, price)
self._adjust_volume_after_entry()
def _adjust_volume_after_entry(self):
if self.ScalingMode == 0:
self._current_volume = self._lot_check(self._current_volume - self._volume_step)
else:
self._current_volume = self._lot_check(self._current_volume + self._volume_step)
def _close_all(self):
if self._long_volume > 0:
self.SellMarket()
if self._short_volume > 0:
self.BuyMarket()
self._long_volume = 0.0
self._short_volume = 0.0
self._long_avg_price = 0.0
self._short_avg_price = 0.0
self._initial_equity = 0.0
if self.ScalingMode == 0:
self._current_volume = 0.0
else:
self._current_volume = self._volume_step
def _check_protective_levels(self, price):
if self._pip_value <= 0:
return
if self._long_volume > 0:
if self.StopLossPips > 0 and price <= self._long_avg_price - self.StopLossPips * self._pip_value:
self.SellMarket()
self._long_volume = 0.0
self._long_avg_price = 0.0
return
if self.TakeProfitPips > 0 and price >= self._long_avg_price + self.TakeProfitPips * self._pip_value:
self.SellMarket()
self._long_volume = 0.0
self._long_avg_price = 0.0
return
if self._short_volume > 0:
if self.StopLossPips > 0 and price >= self._short_avg_price + self.StopLossPips * self._pip_value:
self.BuyMarket()
self._short_volume = 0.0
self._short_avg_price = 0.0
return
if self.TakeProfitPips > 0 and price <= self._short_avg_price - self.TakeProfitPips * self._pip_value:
self.BuyMarket()
self._short_volume = 0.0
self._short_avg_price = 0.0
return
def _reset_current_volume_if_needed(self):
if self.ScalingMode == 0:
if self._current_volume < self._volume_step:
self._current_volume = self._max_volume
else:
if self._current_volume < self._volume_step:
self._current_volume = self._volume_step
elif self._current_volume > self._max_volume:
self._current_volume = self._volume_step
def _apply_long_open(self, volume, price):
if volume <= 0:
return
total = self._long_volume + volume
if self._long_volume <= 0:
self._long_avg_price = price
else:
self._long_avg_price = (self._long_avg_price * self._long_volume + price * volume) / total
self._long_volume = total
def _apply_short_open(self, volume, price):
if volume <= 0:
return
total = self._short_volume + volume
if self._short_volume <= 0:
self._short_avg_price = price
else:
self._short_avg_price = (self._short_avg_price * self._short_volume + price * volume) / total
self._short_volume = total
def _lot_check(self, volume):
if volume <= 0:
return 0.0
step = self._volume_step
if step <= 0:
return 0.0
ratio = math.floor(volume / step)
normalized = ratio * step
if normalized < step:
normalized = 0.0
if normalized > self._max_volume:
normalized = self._max_volume
return normalized
def _calculate_pip_value(self):
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if step <= 0:
return 1.0
decimals = sec.Decimals if sec is not None and sec.Decimals is not None else 0
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def OnReseted(self):
super(dual_lot_step_hedge_strategy, self).OnReseted()
self._volume_step = 0.0
self._max_volume = 0.0
self._current_volume = 0.0
self._pip_value = 0.0
self._initial_equity = 0.0
self._long_volume = 0.0
self._short_volume = 0.0
self._long_avg_price = 0.0
self._short_avg_price = 0.0
def CreateClone(self):
return dual_lot_step_hedge_strategy()