Martin 1 戦略
MetaTrader 5エキスパートアドバイザー「Martin 1」をStockSharpの高レベル戦略APIに変換したものです。アルゴリズムは継続的にエクスポージャーを維持し、ヘッジスタイルのマーチンゲールステップを使用してドローダウンから回復しながら、収益性の高いトレンドでピラミッドします。
トレードロジック
- 初期エクスポージャー – 戦略がフラットな場合、タイムフィルターに関係なく
StartDirectionで定義された方向に即座にポジションを開きます。ベースの注文サイズはインスツルメントのボリュームステップに丸めた後、InitialVolumeから取得されます。 - 時間窓フィルター –
UseTradingHoursが有効の場合、ローソク足のタイムスタンプに含まれるエクスチェンジ時間を使用して、StartHourからEndHour(両端含む)の間のみスケーリングアクション(ピラミッドまたはヘッジング)が許可されます。 - 勝者のピラミッド – 各オープンポジションは完成したローソク足ごとに評価されます。ロングポジションの浮動利益がテイクプロフィット距離を超えてプラスのままである場合、現在のボリュームで追加のロング注文が送信されます。ショートポジションは対称的に動作します。新しい注文の価格は現在のローソク足のクローズと仮定されます。
- ヘッジングマーチンゲール – 開始方向がロングで、ロングポジションが
(StopLossPips × pip サイズ × (乗数インデックス + 1))以上を失う場合、戦略は反対のショート注文を開きます。ヘッジを配置する前に、ボリュームはLotMultiplierで乗算され、許可されたステップに丸められ、乗数カウンターが増加します。同じロジックがショート開始方向に対して逆に適用されます。MaxMultiplicationsステップに達するとヘッジングは停止します。 - グローバル利益目標 – すべての残りポジションの未実現利益(
PriceStep/StepPriceを使用してマネーに変換)が合計されます。MinProfitを超えると、各オープンポジションは反対方向の市場注文を発行することで閉じられ、マーチンゲール状態がリセットされます。
リスク・資金管理
- pip サイズはインスツルメントのセキュリティ価格ステップから計算されます。3桁および5桁の相場はステップを10倍して、元のMetaTrader pip 調整をエミュレートします。
- ボリュームは最も近い
VolumeStepに切り捨てられます。丸められた値がステップを下回ると、注文はスキップされます。 - マーチンゲールカウンターと現在のボリュームは、自然にまたはグローバル利益目標に達した後にブックがフラットになるたびにリセットされます。
- 利益推定はコミッションとスワップを無視し、純粋に浮動PnLに依存していた元のスクリプトの動作を反映しています。
パラメーター
| 名前 | 説明 | デフォルト値 |
|---|---|---|
CandleType |
すべての計算を駆動するローソク足タイプ。 | 1分時間軸 |
UseTradingHours |
時間窓フィルターを有効または無効にします。 | true |
StartHour |
タイムフィルターが新しいスケーリングアクションを許可する開始時間(含む)。 | 2 |
EndHour |
スケーリングアクションが停止する終了時間(含む)。 | 21 |
LotMultiplier |
ヘッジを開く前に現在のボリュームに適用される係数。 | 1.6 |
MaxMultiplications |
発動可能なヘッジングステップの最大数。 | 5 |
StartDirection |
戦略がフラットになった後の最初の注文の方向。 | Buy |
MinProfit |
すべてのポジションを強制的に閉じる浮動利益(マネー単位)。 | 1.5 |
InitialVolume |
最初の注文とリセット状態のベースボリューム。 | 0.1 |
StopLossPips |
次のマーチンゲールヘッジを発動するpip距離。 | 40 |
TakeProfitPips |
ピラミッドエントリーを発動するpip距離。 | 100 |
実装上の注意
ProcessCandleは高レベルローソク足サブスクリプションパイプライン(SubscribeCandles().Bind(...))を使用し、プラットフォームガイドラインに準拠して完成したローソク足のみで動作します。- ヘッジされたエクスポージャーは2つの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()