Стратегия Martin 1
Конвертация советника MetaTrader 5 «Martin 1» на высокоуровневый API StockSharp. Алгоритм постоянно поддерживает позицию и использует хеджирующие мартингейл-шаги для выхода из просадок, одновременно наращивая прибыльные тренды пирамидингом.
Логика торговли
- Начальная позиция – при отсутствии открытых сделок стратегия немедленно открывает позицию в направлении
StartDirectionнезависимо от фильтра времени. Базовый объём берётся изInitialVolumeи округляется вниз до шага объёма инструмента. - Временное окно – при включённом
UseTradingHoursпирамидинг и хеджирование выполняются только междуStartHourиEndHourвключительно, используя биржевое время из свечей. - Пирамидинг прибыли – каждая открытая позиция оценивается на закрытии свечи. Если плавающая прибыль по лонгу превышает дистанцию тейк-профита и остаётся положительной, отправляется дополнительный рыночный ордер тем же объёмом. Шортовые позиции работают зеркально. Цена исполнения принимается равной цене закрытия текущей свечи.
- Хеджирующий мартингейл – если начальное направление лонг и убыток по позиции превышает
(StopLossPips × размер пункта × (текущий шаг + 1)), открывается противоположный шорт. Перед выставлением объём умножается наLotMultiplier, округляется по шагу и счётчик шагов увеличивается. Аналогичное правило действует для стартового шорта. Хеджирование прекращается при достиженииMaxMultiplicationsшагов. - Глобальная цель прибыли – нереализованная прибыль всех открытых позиций переводится в деньги через
PriceStep/StepPrice. Если сумма превышаетMinProfit, каждая позиция закрывается встречным рыночным ордером, а состояние мартингейла сбрасывается.
Риск-менеджмент и мани-менеджмент
- Размер пункта вычисляется из шага цены инструмента. Для трёх- и пятизнаковых котировок шаг умножается на десять, что повторяет поведение оригинального робота.
- Объёмы округляются вниз до ближайшего
VolumeStep. Если результат меньше шага, ордер не отправляется. - Счётчик мартингейла и текущий объём сбрасываются при полном закрытии позиций естественным путём либо после достижения глобальной цели по прибыли.
- Прибыль оценивается без учёта комиссий и свопов, аналогично оригинальной реализации, которая опиралась только на плавающий PnL.
Параметры
| Имя | Описание | Значение по умолчанию |
|---|---|---|
CandleType |
Тип свечей, на основе которых принимаются решения. | 1 минута |
UseTradingHours |
Включение фильтра торговых часов. | true |
StartHour |
Час, начиная с которого разрешены хеджирование и пирамидинг. | 2 |
EndHour |
Час, после которого новые сделки не открываются. | 21 |
LotMultiplier |
Множитель объёма перед открытием хеджа. | 1.6 |
MaxMultiplications |
Максимальное количество мартингейл-шагов. | 5 |
StartDirection |
Направление первой позиции после выхода в ноль. | Buy |
MinProfit |
Требуемая плавающая прибыль (в деньгах) для полного закрытия. | 1.5 |
InitialVolume |
Базовый объём стартовой сделки и состояния после сброса. | 0.1 |
StopLossPips |
Дистанция в пунктах, запускающая следующий хедж. | 40 |
TakeProfitPips |
Дистанция в пунктах для пирамидинга. | 100 |
Особенности реализации
- Метод
ProcessCandleиспользует высокоуровневую подпискуSubscribeCandles().Bind(...)и обрабатывает только закрытые свечи, что соответствует требованиям платформы. - Хеджированные позиции отслеживаются во внутренних списках FIFO, поэтому стратегия имитирует поведение MetaTrader даже на неттинговых счетах.
- Пересчёт прибыли опирается на
Security.PriceStepиSecurity.StepPrice. При их отсутствии используется произведение разницы цены на объём как запасной вариант. - Стратегия работает непрерывно; отключение фильтра времени или расширение торговых часов делает её поведение идентичным оригинальному советнику, работающему без пауз.
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>
/// Hedging martingale strategy converted from the MetaTrader script "Martin 1".
/// Adds pyramid entries in profit and opens opposite hedges with increased volume after drawdowns.
/// </summary>
public class Martin1Strategy : Strategy
{
private sealed class PositionRecord
{
public decimal Volume { get; set; }
public decimal EntryPrice { get; set; }
}
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<bool> _useTradingHours;
private readonly StrategyParam<int> _startHour;
private readonly StrategyParam<int> _endHour;
private readonly StrategyParam<decimal> _lotMultiplier;
private readonly StrategyParam<int> _maxMultiplications;
private readonly StrategyParam<Sides> _startDirection;
private readonly StrategyParam<decimal> _minProfit;
private readonly StrategyParam<decimal> _initialVolume;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly List<PositionRecord> _longPositions = new();
private readonly List<PositionRecord> _shortPositions = new();
private decimal _currentVolume;
private int _multiplicationCount;
/// <summary>
/// Candle type for driving the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Enable the trading hour filter.
/// </summary>
public bool UseTradingHours
{
get => _useTradingHours.Value;
set => _useTradingHours.Value = value;
}
/// <summary>
/// Inclusive hour (exchange time) when the trading window opens.
/// </summary>
public int StartHour
{
get => _startHour.Value;
set => _startHour.Value = value;
}
/// <summary>
/// Inclusive hour (exchange time) when the trading window closes.
/// </summary>
public int EndHour
{
get => _endHour.Value;
set => _endHour.Value = value;
}
/// <summary>
/// Multiplier applied to the current volume after each hedging step.
/// </summary>
public decimal LotMultiplier
{
get => _lotMultiplier.Value;
set => _lotMultiplier.Value = value;
}
/// <summary>
/// Maximum number of hedging multiplications that can be triggered.
/// </summary>
public int MaxMultiplications
{
get => _maxMultiplications.Value;
set => _maxMultiplications.Value = value;
}
/// <summary>
/// Direction of the very first position that is opened when flat.
/// </summary>
public Sides StartDirection
{
get => _startDirection.Value;
set => _startDirection.Value = value;
}
/// <summary>
/// Minimum floating profit that triggers closing all positions.
/// </summary>
public decimal MinProfit
{
get => _minProfit.Value;
set => _minProfit.Value = value;
}
/// <summary>
/// Base order volume used for the initial trade.
/// </summary>
public decimal InitialVolume
{
get => _initialVolume.Value;
set => _initialVolume.Value = value;
}
/// <summary>
/// Stop-loss distance expressed in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance expressed in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="Martin1Strategy"/> class.
/// </summary>
public Martin1Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used to evaluate conditions", "General");
_useTradingHours = Param(nameof(UseTradingHours), false)
.SetDisplay("Use Trading Hours", "Restrict entries to a time window", "General");
_startHour = Param(nameof(StartHour), 2)
.SetRange(0, 23)
.SetDisplay("Start Hour", "Hour to start monitoring for new trades", "General");
_endHour = Param(nameof(EndHour), 21)
.SetRange(0, 23)
.SetDisplay("End Hour", "Hour to stop opening hedges/pyramids", "General");
_lotMultiplier = Param(nameof(LotMultiplier), 1.6m)
.SetGreaterThanZero()
.SetDisplay("Lot Multiplier", "Factor applied to volume after a loss", "Money Management")
.SetOptimize(1.1m, 3m, 0.1m);
_maxMultiplications = Param(nameof(MaxMultiplications), 5)
.SetGreaterThanZero()
.SetDisplay("Max Multiplications", "Maximum hedging steps", "Money Management")
.SetOptimize(1, 10, 1);
_startDirection = Param(nameof(StartDirection), Sides.Buy)
.SetDisplay("Start Direction", "Side of the initial order", "Trading");
_minProfit = Param(nameof(MinProfit), 1.5m)
.SetDisplay("Min Profit", "Floating profit target to flatten", "Risk")
.SetOptimize(0.5m, 10m, 0.5m);
_initialVolume = Param(nameof(InitialVolume), 0.1m)
.SetGreaterThanZero()
.SetDisplay("Initial Volume", "Baseline order size", "Money Management");
_stopLossPips = Param(nameof(StopLossPips), 400)
.SetGreaterThanZero()
.SetDisplay("Stop Loss (pips)", "Distance before hedging the opposite side", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 1000)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Distance to pyramid in the same direction", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_longPositions.Clear();
_shortPositions.Clear();
_multiplicationCount = 0;
_currentVolume = AdjustVolume(InitialVolume);
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
if (UseTradingHours && StartHour >= EndHour)
throw new InvalidOperationException("Start hour must be less than end hour when the filter is enabled.");
_longPositions.Clear();
_shortPositions.Clear();
_multiplicationCount = 0;
_currentVolume = AdjustVolume(InitialVolume);
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// No indicators to check.
var closePrice = candle.ClosePrice;
var totalProfit = CalculateOpenProfit(closePrice);
var withinHours = !UseTradingHours || IsWithinTradingHours(candle.CloseTime);
if (withinHours)
{
if (_longPositions.Count > 0)
EvaluateLongPositions(closePrice);
if (_shortPositions.Count > 0)
EvaluateShortPositions(closePrice);
}
if (_longPositions.Count == 0 && _shortPositions.Count == 0)
{
ResetMartingale();
OpenInitialPosition(closePrice);
return;
}
if ((_longPositions.Count > 0 || _shortPositions.Count > 0) && totalProfit > MinProfit)
{
CloseAllPositions(closePrice);
ResetMartingale();
}
}
private void EvaluateLongPositions(decimal closePrice)
{
var takeProfitDistance = GetTakeProfitDistance();
var stopLossDistance = GetStopLossDistance();
var snapshot = _longPositions.ToArray();
foreach (var position in snapshot)
{
var priceGain = closePrice - position.EntryPrice;
var profit = ConvertPriceToMoney(priceGain, position.Volume);
if (profit > 0m && priceGain > takeProfitDistance)
ExecuteOrder(Sides.Buy, _currentVolume, closePrice);
if (StartDirection == Sides.Buy && stopLossDistance > 0m)
{
var lossDistance = position.EntryPrice - closePrice;
if (lossDistance > stopLossDistance * (_multiplicationCount + 1) &&
_multiplicationCount + 1 <= MaxMultiplications)
{
var newVolume = AdjustVolume(_currentVolume * LotMultiplier);
if (newVolume > 0m)
{
_multiplicationCount++;
_currentVolume = newVolume;
ExecuteOrder(Sides.Sell, newVolume, closePrice);
}
}
}
}
}
private void EvaluateShortPositions(decimal closePrice)
{
var takeProfitDistance = GetTakeProfitDistance();
var stopLossDistance = GetStopLossDistance();
var snapshot = _shortPositions.ToArray();
foreach (var position in snapshot)
{
var priceGain = position.EntryPrice - closePrice;
var profit = ConvertPriceToMoney(priceGain, position.Volume);
if (profit > 0m && priceGain > takeProfitDistance)
ExecuteOrder(Sides.Sell, _currentVolume, closePrice);
if (StartDirection == Sides.Sell && stopLossDistance > 0m)
{
var lossDistance = closePrice - position.EntryPrice;
if (lossDistance > stopLossDistance * (_multiplicationCount + 1) &&
_multiplicationCount + 1 <= MaxMultiplications)
{
var newVolume = AdjustVolume(_currentVolume * LotMultiplier);
if (newVolume > 0m)
{
_multiplicationCount++;
_currentVolume = newVolume;
ExecuteOrder(Sides.Buy, newVolume, closePrice);
}
}
}
}
}
private void OpenInitialPosition(decimal price)
{
var volume = _currentVolume;
if (volume <= 0m)
return;
var side = StartDirection == Sides.Sell ? Sides.Sell : Sides.Buy;
ExecuteOrder(side, volume, price);
}
private void CloseAllPositions(decimal price)
{
var longVolume = GetTotalVolume(_longPositions);
if (longVolume > 0m)
{
ExecuteOrder(Sides.Sell, longVolume, price);
}
var shortVolume = GetTotalVolume(_shortPositions);
if (shortVolume > 0m)
{
ExecuteOrder(Sides.Buy, shortVolume, price);
}
}
private void ExecuteOrder(Sides side, decimal volume, decimal price)
{
if (volume <= 0m)
return;
Volume = volume;
if (side == Sides.Buy)
BuyMarket();
else
SellMarket();
UpdatePositions(side, volume, price);
}
private void UpdatePositions(Sides side, decimal volume, decimal price)
{
if (volume <= 0m)
return;
if (side == Sides.Buy)
{
var remaining = volume;
var index = 0;
while (remaining > 0m && index < _shortPositions.Count)
{
var position = _shortPositions[index];
var qty = Math.Min(position.Volume, remaining);
position.Volume -= qty;
remaining -= qty;
if (position.Volume <= 0m)
{
_shortPositions.RemoveAt(index);
continue;
}
index++;
}
if (remaining > 0m)
{
_longPositions.Add(new PositionRecord
{
Volume = remaining,
EntryPrice = price
});
}
}
else
{
var remaining = volume;
var index = 0;
while (remaining > 0m && index < _longPositions.Count)
{
var position = _longPositions[index];
var qty = Math.Min(position.Volume, remaining);
position.Volume -= qty;
remaining -= qty;
if (position.Volume <= 0m)
{
_longPositions.RemoveAt(index);
continue;
}
index++;
}
if (remaining > 0m)
{
_shortPositions.Add(new PositionRecord
{
Volume = remaining,
EntryPrice = price
});
}
}
}
private decimal CalculateOpenProfit(decimal currentPrice)
{
var profit = 0m;
foreach (var position in _longPositions)
{
var diff = currentPrice - position.EntryPrice;
profit += ConvertPriceToMoney(diff, position.Volume);
}
foreach (var position in _shortPositions)
{
var diff = position.EntryPrice - currentPrice;
profit += ConvertPriceToMoney(diff, position.Volume);
}
return profit;
}
private decimal ConvertPriceToMoney(decimal priceDifference, decimal volume)
{
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = priceStep;
if (priceStep <= 0m || stepPrice <= 0m)
return priceDifference * volume;
var steps = priceDifference / priceStep;
return steps * stepPrice * volume;
}
private decimal GetStopLossDistance()
{
var pip = GetPipSize();
return StopLossPips * pip;
}
private decimal GetTakeProfitDistance()
{
var pip = GetPipSize();
return TakeProfitPips * pip;
}
private decimal GetPipSize()
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
return 1m;
var step = priceStep;
var digits = 0;
while (step < 1m && digits < 10)
{
step *= 10m;
digits++;
}
if (digits == 3 || digits == 5)
return priceStep * 10m;
return priceStep;
}
private decimal AdjustVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
volume = Math.Floor(volume / step) * step;
if (volume < step)
return 0m;
}
return volume;
}
private static decimal GetTotalVolume(List<PositionRecord> positions)
{
var total = 0m;
foreach (var position in positions)
total += position.Volume;
return total;
}
private bool IsWithinTradingHours(DateTime time)
{
var hour = time.Hour;
return hour >= StartHour && hour <= EndHour;
}
private void ResetMartingale()
{
_multiplicationCount = 0;
_currentVolume = AdjustVolume(InitialVolume);
}
}
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
from StockSharp.Algo.Strategies import Strategy
class martin1_strategy(Strategy):
"""Martin 1: hedging martingale with pyramid entries on profit and opposite hedges on drawdown."""
def __init__(self):
super(martin1_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe used to evaluate conditions", "General")
self._use_trading_hours = self.Param("UseTradingHours", False) \
.SetDisplay("Use Trading Hours", "Restrict entries to a time window", "General")
self._start_hour = self.Param("StartHour", 2) \
.SetDisplay("Start Hour", "Hour to start monitoring for new trades", "General")
self._end_hour = self.Param("EndHour", 21) \
.SetDisplay("End Hour", "Hour to stop opening hedges/pyramids", "General")
self._lot_multiplier = self.Param("LotMultiplier", 1.6) \
.SetGreaterThanZero() \
.SetDisplay("Lot Multiplier", "Factor applied to volume after a loss", "Money Management")
self._max_multiplications = self.Param("MaxMultiplications", 5) \
.SetGreaterThanZero() \
.SetDisplay("Max Multiplications", "Maximum hedging steps", "Money Management")
# 0=Buy, 1=Sell
self._start_direction = self.Param("StartDirection", 0) \
.SetDisplay("Start Direction", "0=Buy, 1=Sell", "Trading")
self._min_profit = self.Param("MinProfit", 1.5) \
.SetDisplay("Min Profit", "Floating profit target to flatten", "Risk")
self._initial_volume = self.Param("InitialVolume", 0.1) \
.SetGreaterThanZero() \
.SetDisplay("Initial Volume", "Baseline order size", "Money Management")
self._stop_loss_pips = self.Param("StopLossPips", 400) \
.SetGreaterThanZero() \
.SetDisplay("Stop Loss (pips)", "Distance before hedging the opposite side", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 1000) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Distance to pyramid in same direction", "Risk")
self._long_positions = []
self._short_positions = []
self._current_volume = 0.0
self._multiplication_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@property
def UseTradingHours(self):
return self._use_trading_hours.Value
@property
def StartHour(self):
return int(self._start_hour.Value)
@property
def EndHour(self):
return int(self._end_hour.Value)
@property
def LotMultiplier(self):
return float(self._lot_multiplier.Value)
@property
def MaxMultiplications(self):
return int(self._max_multiplications.Value)
@property
def StartDirection(self):
return int(self._start_direction.Value)
@property
def MinProfit(self):
return float(self._min_profit.Value)
@property
def InitialVolume(self):
return float(self._initial_volume.Value)
@property
def StopLossPips(self):
return int(self._stop_loss_pips.Value)
@property
def TakeProfitPips(self):
return int(self._take_profit_pips.Value)
def _get_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 1.0
step = float(sec.PriceStep)
if step <= 0:
return 1.0
s = step
digits = 0
while s < 1.0 and digits < 10:
s *= 10
digits += 1
return step * 10.0 if (digits == 3 or digits == 5) else step
def _adjust_volume(self, volume):
if volume <= 0:
return 0.0
sec = self.Security
if sec is not None and sec.VolumeStep is not None:
step = float(sec.VolumeStep)
if step > 0:
volume = math.floor(volume / step) * step
if volume < step:
return 0.0
return volume
def OnStarted2(self, time):
super(martin1_strategy, self).OnStarted2(time)
self._long_positions = []
self._short_positions = []
self._multiplication_count = 0
self._current_volume = self._adjust_volume(self.InitialVolume)
self._pip_size = self._get_pip_size()
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close_price = float(candle.ClosePrice)
total_profit = self._calc_open_profit(close_price)
within_hours = not self.UseTradingHours or self._is_within_hours(candle.CloseTime)
if within_hours:
if len(self._long_positions) > 0:
self._eval_longs(close_price)
if len(self._short_positions) > 0:
self._eval_shorts(close_price)
if len(self._long_positions) == 0 and len(self._short_positions) == 0:
self._reset_martingale()
self._open_initial(close_price)
return
if (len(self._long_positions) > 0 or len(self._short_positions) > 0) and total_profit > self.MinProfit:
self._close_all(close_price)
self._reset_martingale()
def _eval_longs(self, close_price):
tp_dist = self.TakeProfitPips * self._pip_size
sl_dist = self.StopLossPips * self._pip_size
for pos in list(self._long_positions):
price_gain = close_price - pos["entry"]
profit = self._price_to_money(price_gain, pos["volume"])
if profit > 0 and price_gain > tp_dist:
self._execute_order("buy", self._current_volume, close_price)
if self.StartDirection == 0 and sl_dist > 0:
loss_dist = pos["entry"] - close_price
if (loss_dist > sl_dist * (self._multiplication_count + 1)
and self._multiplication_count + 1 <= self.MaxMultiplications):
new_vol = self._adjust_volume(self._current_volume * self.LotMultiplier)
if new_vol > 0:
self._multiplication_count += 1
self._current_volume = new_vol
self._execute_order("sell", new_vol, close_price)
def _eval_shorts(self, close_price):
tp_dist = self.TakeProfitPips * self._pip_size
sl_dist = self.StopLossPips * self._pip_size
for pos in list(self._short_positions):
price_gain = pos["entry"] - close_price
profit = self._price_to_money(price_gain, pos["volume"])
if profit > 0 and price_gain > tp_dist:
self._execute_order("sell", self._current_volume, close_price)
if self.StartDirection == 1 and sl_dist > 0:
loss_dist = close_price - pos["entry"]
if (loss_dist > sl_dist * (self._multiplication_count + 1)
and self._multiplication_count + 1 <= self.MaxMultiplications):
new_vol = self._adjust_volume(self._current_volume * self.LotMultiplier)
if new_vol > 0:
self._multiplication_count += 1
self._current_volume = new_vol
self._execute_order("buy", new_vol, close_price)
def _open_initial(self, price):
vol = self._current_volume
if vol <= 0:
return
side = "sell" if self.StartDirection == 1 else "buy"
self._execute_order(side, vol, price)
def _close_all(self, price):
long_vol = sum(p["volume"] for p in self._long_positions)
if long_vol > 0:
self._execute_order("sell", long_vol, price)
short_vol = sum(p["volume"] for p in self._short_positions)
if short_vol > 0:
self._execute_order("buy", short_vol, price)
def _execute_order(self, side, volume, price):
if volume <= 0:
return
if side == "buy":
self.BuyMarket()
else:
self.SellMarket()
self._update_positions(side, volume, price)
def _update_positions(self, side, volume, price):
if volume <= 0:
return
if side == "buy":
remaining = volume
while remaining > 0 and len(self._short_positions) > 0:
pos = self._short_positions[0]
qty = min(pos["volume"], remaining)
pos["volume"] -= qty
remaining -= qty
if pos["volume"] <= 0:
self._short_positions.pop(0)
if remaining > 0:
self._long_positions.append({"volume": remaining, "entry": price})
else:
remaining = volume
while remaining > 0 and len(self._long_positions) > 0:
pos = self._long_positions[0]
qty = min(pos["volume"], remaining)
pos["volume"] -= qty
remaining -= qty
if pos["volume"] <= 0:
self._long_positions.pop(0)
if remaining > 0:
self._short_positions.append({"volume": remaining, "entry": price})
def _calc_open_profit(self, current_price):
profit = 0.0
for pos in self._long_positions:
diff = current_price - pos["entry"]
profit += self._price_to_money(diff, pos["volume"])
for pos in self._short_positions:
diff = pos["entry"] - current_price
profit += self._price_to_money(diff, pos["volume"])
return profit
def _price_to_money(self, price_diff, volume):
sec = self.Security
if sec is not None and sec.PriceStep is not None:
step = float(sec.PriceStep)
if step > 0:
steps = price_diff / step
return steps * step * volume
return price_diff * volume
def _is_within_hours(self, time):
hour = time.Hour
return hour >= self.StartHour and hour <= self.EndHour
def _reset_martingale(self):
self._multiplication_count = 0
self._current_volume = self._adjust_volume(self.InitialVolume)
def OnReseted(self):
super(martin1_strategy, self).OnReseted()
self._long_positions = []
self._short_positions = []
self._current_volume = 0.0
self._multiplication_count = 0
def CreateClone(self):
return martin1_strategy()