Up3x1動的ポジションサイジング戦略
概要
- MetaTrader 5エキスパートアドバイザー
up3x1.mq5をStockSharp高レベルAPIに変換。 - 三重指数移動平均(EMA)クロスオーバーによる取引とストップロス、テイクプロフィット、トレーリングストップ管理。
- バーごとに1つの決定を強制した元の
iTickVolume(0) > 1ガードをエミュレートするために完成したローソク足のみを処理します。 - デフォルトのローソク足シリーズは1時間ですが、時間軸は
CandleTypeパラメーターで設定可能です。
取引ロジック
- インジケーター
- 速いEMA(
FastPeriod、デフォルト24)。 - 中間EMA(
MediumPeriod、デフォルト60)。 - 遅いEMA(
SlowPeriod、デフォルト120)。
- 速いEMA(
- ロングエントリー
- 前のバー:速いEMAが中間EMAより下で、中間が遅いEMAより下(
EMAfast₍t-1₎ < EMAmedium₍t-1₎ < EMAslow₍t-1₎)。 - 現在のバー:速いEMAが遅いEMAより下のままで中間EMAが速いEMAより下(
EMAmedium₍t₎ < EMAfast₍t₎ < EMAslow₍t₎)。
- 前のバー:速いEMAが中間EMAより下で、中間が遅いEMAより下(
- ショートエントリー
- 前のバー:速いEMAが中間EMAより上で、中間が遅いEMAより上(
EMAfast₍t-1₎ > EMAmedium₍t-1₎ > EMAslow₍t-1₎)。 - 現在のバー:中間EMAが速いEMAを上方クロスし、両方が遅いEMAより上に留まる(
EMAmedium₍t₎ > EMAfast₍t₎ > EMAslow₍t₎)。
- 前のバー:速いEMAが中間EMAより上で、中間が遅いEMAより上(
- 両方向のエグジットロジック
- 価格がエントリーから
TakeProfitOffset前進したらテイクプロフィット(ロングはローソク足の高値、ショートは安値を使用)。 - 価格がエントリーから
StopLossOffset戻ったらストップロス(ロングはローソク足の安値、ショートは高値を使用)。 - トレーリングストップはポジションが
TrailingStopOffsetより有利に動いたら起動し、その後ローソク足の極値で評価された固定距離で価格に追従します。 - 速いEMAが遅いEMAより上に留まりながら中間EMAより下にクロスバックしたらフォールバックエグジット(MQLバージョンの
ma_one_1 > ma_two_1 > ma_three_1チェックを反映)。
- 価格がエントリーから
ポジションサイジングとリスク管理
RiskFraction(デフォルト0.02)は現在のポートフォリオ価値に乗算して元のFreeMargin * 0.02 / 1000ロットサイジングを近似します。BaseVolume(デフォルト0.1)はポートフォリオデータが利用できないか計算されたサイズが正でない場合のフォールバックとして機能します。- 1回以上の損失エグジット後、ボリュームは
volume * losses / 3で削減され、スクリプトの累積lossesカウンターを模倣します(カウンターは元のコードと同様に利益トレードの後にリセットされません)。 - ボリュームは
Security.VolumeStepに切り捨てられ、Security.MinVolume/Security.MaxVolumeで制限され、インストゥルメントの最小値が満たせない場合はゼロになります。
パラメーター
| パラメーター | デフォルト | 説明 |
|---|---|---|
FastPeriod |
24 | 最も速いEMAの長さ。 |
MediumPeriod |
60 | 中間EMAの長さ。 |
SlowPeriod |
120 | 長期トレンドフィルターとして使用される遅いEMAの長さ。 |
TakeProfitOffset |
0.015 | テイクプロフィット注文の絶対価格距離(インストゥルメントの見積もりに適応)。 |
StopLossOffset |
0.01 | ストップロス注文の絶対価格距離。 |
TrailingStopOffset |
0.004 | 価格が十分に前進したら利益を確保するトレーリング距離。無効にするには0に設定。 |
BaseVolume |
0.1 | 動的サイジングが計算できない場合のフォールバック取引サイズ。 |
RiskFraction |
0.02 | 動的サイジング式に適用されるポートフォリオ価値の割合。 |
CandleType |
1時間時間軸 | インジケーター計算と意思決定に使用されるローソク足シリーズ。 |
変換に関する注意事項
- トレーリングストップと保護的エグジットは高レベルAPIが完成したローソク足を処理するため、生のティックではなくローソク足の高値/安値を使用します。これによりバックテストとライブ実行全体で動作が決定論的に保たれます。
- ストップロスとテイクプロフィットは、個別の保護注文を配置するのではなく、評価されたしきい値での成行フラット化コマンドで実行され、高レベル戦略フローとの互換性を確保します。
- 動的ポジションサイジングは
Portfolio.CurrentValueに依存します。利用できない場合、戦略は元のLotCheckの手動Lots入力へのフォールバックと同様にBaseVolumeにフォールバックします。 lossesカウンターはMQL実装に従うため意図的に累積(勝ちトレードでリセットされない)です。- すべてのコメントはプロジェクトのガイドラインに従い英語です。
使用のヒント
- 戦略をインストゥルメントとポートフォリオに付加し、MT5からエミュレートしたいチャート解像度に合わせて
CandleTypeを設定します。 - インストゥルメントのティックサイズを反映するように価格オフセットを確認してください(例:5桁のForexペアの場合、0.015はソースエキスパートと同様に150ポイントに相当)。
- アカウントに対して現実的なポジションサイズを達成するために
RiskFraction/BaseVolumeを調整します。 - オプション:
TrailingStopOffsetをゼロに設定してトレーリングを無効にします。 - MetaTraderの
Print診断を反映する「Enter long」や「Exit short」などのメッセージについてログを監視してください。
リポジトリ構造
API/2512_Up3x1/
├── CS/Up3x1DynamicSizingStrategy.cs # 変換されたC#戦略
├── README.md # 英語ドキュメント(このファイル)
├── README_zh.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>
/// EMA triple crossover strategy converted from the MetaTrader 5 "up3x1" expert.
/// Uses three exponential moving averages with optional stop loss, take profit and trailing logic.
/// Position size is reduced after losing trades similar to the original lot optimization routine.
/// </summary>
public class Up3x1DynamicSizingStrategy : Strategy
{
private readonly StrategyParam<int> _fastPeriod;
private readonly StrategyParam<int> _mediumPeriod;
private readonly StrategyParam<int> _slowPeriod;
private readonly StrategyParam<decimal> _takeProfitOffset;
private readonly StrategyParam<decimal> _stopLossOffset;
private readonly StrategyParam<decimal> _trailingStopOffset;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<decimal> _riskFraction;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _fastEma;
private ExponentialMovingAverage _mediumEma;
private ExponentialMovingAverage _slowEma;
private bool _hasPrevValues;
private decimal _prevFast;
private decimal _prevMedium;
private decimal _prevSlow;
private decimal _entryPrice;
private decimal _highestPrice;
private decimal _lowestPrice;
private int _losses;
/// <summary>
/// Fast EMA period.
/// </summary>
public int FastPeriod
{
get => _fastPeriod.Value;
set => _fastPeriod.Value = value;
}
/// <summary>
/// Medium EMA period.
/// </summary>
public int MediumPeriod
{
get => _mediumPeriod.Value;
set => _mediumPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int SlowPeriod
{
get => _slowPeriod.Value;
set => _slowPeriod.Value = value;
}
/// <summary>
/// Absolute price distance for take profit.
/// </summary>
public decimal TakeProfitOffset
{
get => _takeProfitOffset.Value;
set => _takeProfitOffset.Value = value;
}
/// <summary>
/// Absolute price distance for stop loss.
/// </summary>
public decimal StopLossOffset
{
get => _stopLossOffset.Value;
set => _stopLossOffset.Value = value;
}
/// <summary>
/// Absolute trailing stop distance.
/// </summary>
public decimal TrailingStopOffset
{
get => _trailingStopOffset.Value;
set => _trailingStopOffset.Value = value;
}
/// <summary>
/// Base volume used when dynamic sizing cannot be calculated.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Fraction of portfolio value used for dynamic position sizing.
/// </summary>
public decimal RiskFraction
{
get => _riskFraction.Value;
set => _riskFraction.Value = value;
}
/// <summary>
/// Candle type to subscribe.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes strategy parameters.
/// </summary>
public Up3x1DynamicSizingStrategy()
{
_fastPeriod = Param(nameof(FastPeriod), 24)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Period of the fastest EMA", "Indicators");
_mediumPeriod = Param(nameof(MediumPeriod), 60)
.SetGreaterThanZero()
.SetDisplay("Medium EMA", "Period of the middle EMA", "Indicators");
_slowPeriod = Param(nameof(SlowPeriod), 120)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Period of the slowest EMA", "Indicators");
_takeProfitOffset = Param(nameof(TakeProfitOffset), 0.015m)
.SetDisplay("Take Profit", "Absolute take profit distance in price units", "Risk");
_stopLossOffset = Param(nameof(StopLossOffset), 0.01m)
.SetDisplay("Stop Loss", "Absolute stop loss distance in price units", "Risk");
_trailingStopOffset = Param(nameof(TrailingStopOffset), 0.004m)
.SetDisplay("Trailing", "Trailing stop distance that follows price", "Risk");
_baseVolume = Param(nameof(BaseVolume), 0.1m)
.SetDisplay("Base Volume", "Fallback trade volume if dynamic sizing fails", "Money Management");
_riskFraction = Param(nameof(RiskFraction), 0.02m)
.SetDisplay("Risk Fraction", "Fraction of portfolio value used for sizing", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Candle series used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
Volume = BaseVolume;
ResetState();
_losses = 0;
_hasPrevValues = false;
_prevFast = 0m;
_prevMedium = 0m;
_prevSlow = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
Volume = BaseVolume;
_fastEma = new EMA
{
Length = FastPeriod
};
_mediumEma = new EMA
{
Length = MediumPeriod
};
_slowEma = new EMA
{
Length = SlowPeriod
};
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_fastEma, _mediumEma, _slowEma, ProcessCandle)
.Start();
StartProtection(null, null);
}
private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal mediumValue, decimal slowValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_fastEma.IsFormed || !_mediumEma.IsFormed || !_slowEma.IsFormed)
return;
if (!_hasPrevValues)
{
_prevFast = fastValue;
_prevMedium = mediumValue;
_prevSlow = slowValue;
_hasPrevValues = true;
return;
}
if (!IsFormedAndOnlineAndAllowTrading())
{
_prevFast = fastValue;
_prevMedium = mediumValue;
_prevSlow = slowValue;
return;
}
if (Position > 0)
{
if (TryHandleLongExit(candle, fastValue, mediumValue, slowValue))
{
_prevFast = fastValue;
_prevMedium = mediumValue;
_prevSlow = slowValue;
return;
}
}
else if (Position < 0)
{
if (TryHandleShortExit(candle, fastValue, mediumValue, slowValue))
{
_prevFast = fastValue;
_prevMedium = mediumValue;
_prevSlow = slowValue;
return;
}
}
else
{
var bullishSetup = _prevFast < _prevMedium && _prevMedium < _prevSlow && mediumValue < fastValue && fastValue < slowValue;
var bearishSetup = _prevFast > _prevMedium && _prevMedium > _prevSlow && mediumValue > fastValue && fastValue > slowValue;
if (bullishSetup)
{
TryEnterLong(candle);
}
else if (bearishSetup)
{
TryEnterShort(candle);
}
}
_prevFast = fastValue;
_prevMedium = mediumValue;
_prevSlow = slowValue;
}
private void TryEnterLong(ICandleMessage candle)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
{
LogInfo("Skipped long entry because calculated volume is below minimum.");
return;
}
BuyMarket();
_entryPrice = candle.ClosePrice;
_highestPrice = candle.HighPrice;
_lowestPrice = candle.LowPrice;
LogInfo($"Enter long at {candle.ClosePrice} with volume {volume}. Loss counter: {_losses}.");
}
private void TryEnterShort(ICandleMessage candle)
{
var volume = CalculateOrderVolume();
if (volume <= 0m)
{
LogInfo("Skipped short entry because calculated volume is below minimum.");
return;
}
SellMarket();
_entryPrice = candle.ClosePrice;
_highestPrice = candle.HighPrice;
_lowestPrice = candle.LowPrice;
LogInfo($"Enter short at {candle.ClosePrice} with volume {volume}. Loss counter: {_losses}.");
}
private bool TryHandleLongExit(ICandleMessage candle, decimal fastValue, decimal mediumValue, decimal slowValue)
{
if (_entryPrice <= 0m)
return false;
var exitPrice = 0m;
var reason = string.Empty;
if (TakeProfitOffset > 0m)
{
var target = _entryPrice + TakeProfitOffset;
if (candle.HighPrice >= target)
{
exitPrice = target;
reason = "Take profit reached";
}
}
if (exitPrice == 0m && StopLossOffset > 0m)
{
var stop = _entryPrice - StopLossOffset;
if (candle.LowPrice <= stop)
{
exitPrice = stop;
reason = "Stop loss triggered";
}
}
_highestPrice = candle.HighPrice > _highestPrice ? candle.HighPrice : _highestPrice;
if (exitPrice == 0m && TrailingStopOffset > 0m && _highestPrice - _entryPrice > TrailingStopOffset)
{
var trail = _highestPrice - TrailingStopOffset;
if (candle.LowPrice <= trail)
{
exitPrice = trail;
reason = "Trailing stop hit";
}
}
if (exitPrice == 0m)
{
var reversal = _prevFast > _prevMedium && _prevMedium > _prevSlow && slowValue < fastValue && fastValue < mediumValue;
if (reversal)
{
exitPrice = candle.ClosePrice;
reason = "EMA reversal";
}
}
if (exitPrice == 0m)
return false;
ExitPosition(exitPrice, reason);
return true;
}
private bool TryHandleShortExit(ICandleMessage candle, decimal fastValue, decimal mediumValue, decimal slowValue)
{
if (_entryPrice <= 0m)
return false;
var exitPrice = 0m;
var reason = string.Empty;
if (TakeProfitOffset > 0m)
{
var target = _entryPrice - TakeProfitOffset;
if (candle.LowPrice <= target)
{
exitPrice = target;
reason = "Take profit reached";
}
}
if (exitPrice == 0m && StopLossOffset > 0m)
{
var stop = _entryPrice + StopLossOffset;
if (candle.HighPrice >= stop)
{
exitPrice = stop;
reason = "Stop loss triggered";
}
}
_lowestPrice = _lowestPrice == 0m || candle.LowPrice < _lowestPrice ? candle.LowPrice : _lowestPrice;
if (exitPrice == 0m && TrailingStopOffset > 0m && _entryPrice - _lowestPrice > TrailingStopOffset)
{
var trail = _lowestPrice + TrailingStopOffset;
if (candle.HighPrice >= trail)
{
exitPrice = trail;
reason = "Trailing stop hit";
}
}
if (exitPrice == 0m)
{
var reversal = _prevFast > _prevMedium && _prevMedium > _prevSlow && slowValue < fastValue && fastValue < mediumValue;
if (reversal)
{
exitPrice = candle.ClosePrice;
reason = "EMA reversal";
}
}
if (exitPrice == 0m)
return false;
ExitPosition(exitPrice, reason);
return true;
}
private void ExitPosition(decimal exitPrice, string reason)
{
var isLong = Position > 0;
var volume = Math.Abs(Position);
if (volume <= 0m)
return;
var pnl = isLong
? (exitPrice - _entryPrice) * volume
: (_entryPrice - exitPrice) * volume;
if (isLong)
{
SellMarket();
}
else
{
BuyMarket();
}
LogInfo($"Exit {(isLong ? "long" : "short")} at {exitPrice} because {reason}. Approx PnL: {pnl}.");
if (pnl < 0m)
_losses++;
ResetState();
}
private decimal CalculateOrderVolume()
{
var volume = 0m;
var portfolioValue = Portfolio?.CurrentValue ?? 0m;
if (portfolioValue > 0m && RiskFraction > 0m)
volume = portfolioValue * RiskFraction / 1000m;
if (volume <= 0m)
volume = BaseVolume;
if (_losses > 1)
{
var reduction = volume * _losses / 3m;
volume -= reduction;
if (volume <= 0m)
volume = BaseVolume;
}
volume = AdjustVolumeToInstrument(volume);
return volume;
}
private decimal AdjustVolumeToInstrument(decimal volume)
{
var security = Security;
if (security == null)
return volume;
var step = security.VolumeStep ?? 0m;
if (step > 0m)
volume = Math.Floor(volume / step) * step;
var minVolume = security.MinVolume ?? 0m;
if (minVolume > 0m && volume < minVolume)
return 0m;
var maxVolume = security.MaxVolume ?? 0m;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
return volume;
}
private void ResetState()
{
_entryPrice = 0m;
_highestPrice = 0m;
_lowestPrice = 0m;
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class up3x1_dynamic_sizing_strategy(Strategy):
def __init__(self):
super(up3x1_dynamic_sizing_strategy, self).__init__()
self._fast_period = self.Param("FastPeriod", 24)
self._medium_period = self.Param("MediumPeriod", 60)
self._slow_period = self.Param("SlowPeriod", 120)
self._take_profit_offset = self.Param("TakeProfitOffset", 0.015)
self._stop_loss_offset = self.Param("StopLossOffset", 0.01)
self._trailing_stop_offset = self.Param("TrailingStopOffset", 0.004)
self._base_volume = self.Param("BaseVolume", 0.1)
self._risk_fraction = self.Param("RiskFraction", 0.02)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._has_prev_values = False
self._prev_fast = 0.0
self._prev_medium = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._losses = 0
@property
def FastPeriod(self):
return self._fast_period.Value
@FastPeriod.setter
def FastPeriod(self, value):
self._fast_period.Value = value
@property
def MediumPeriod(self):
return self._medium_period.Value
@MediumPeriod.setter
def MediumPeriod(self, value):
self._medium_period.Value = value
@property
def SlowPeriod(self):
return self._slow_period.Value
@SlowPeriod.setter
def SlowPeriod(self, value):
self._slow_period.Value = value
@property
def TakeProfitOffset(self):
return self._take_profit_offset.Value
@TakeProfitOffset.setter
def TakeProfitOffset(self, value):
self._take_profit_offset.Value = value
@property
def StopLossOffset(self):
return self._stop_loss_offset.Value
@StopLossOffset.setter
def StopLossOffset(self, value):
self._stop_loss_offset.Value = value
@property
def TrailingStopOffset(self):
return self._trailing_stop_offset.Value
@TrailingStopOffset.setter
def TrailingStopOffset(self, value):
self._trailing_stop_offset.Value = value
@property
def BaseVolume(self):
return self._base_volume.Value
@BaseVolume.setter
def BaseVolume(self, value):
self._base_volume.Value = value
@property
def RiskFraction(self):
return self._risk_fraction.Value
@RiskFraction.setter
def RiskFraction(self, value):
self._risk_fraction.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(up3x1_dynamic_sizing_strategy, self).OnStarted2(time)
self._has_prev_values = False
self._prev_fast = 0.0
self._prev_medium = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
fast_ema = ExponentialMovingAverage()
fast_ema.Length = self.FastPeriod
medium_ema = ExponentialMovingAverage()
medium_ema.Length = self.MediumPeriod
slow_ema = ExponentialMovingAverage()
slow_ema.Length = self.SlowPeriod
self._fast_ema = fast_ema
self._medium_ema = medium_ema
self._slow_ema = slow_ema
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ema, medium_ema, slow_ema, self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle, fast_value, medium_value, slow_value):
if candle.State != CandleStates.Finished:
return
fast_val = float(fast_value)
medium_val = float(medium_value)
slow_val = float(slow_value)
if not self._fast_ema.IsFormed or not self._medium_ema.IsFormed or not self._slow_ema.IsFormed:
return
if not self._has_prev_values:
self._prev_fast = fast_val
self._prev_medium = medium_val
self._prev_slow = slow_val
self._has_prev_values = True
return
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tp_offset = float(self.TakeProfitOffset)
sl_offset = float(self.StopLossOffset)
trail_offset = float(self.TrailingStopOffset)
if self.Position > 0:
if self._try_handle_long_exit(candle, fast_val, medium_val, slow_val):
self._prev_fast = fast_val
self._prev_medium = medium_val
self._prev_slow = slow_val
return
elif self.Position < 0:
if self._try_handle_short_exit(candle, fast_val, medium_val, slow_val):
self._prev_fast = fast_val
self._prev_medium = medium_val
self._prev_slow = slow_val
return
else:
bullish_setup = (self._prev_fast < self._prev_medium and
self._prev_medium < self._prev_slow and
medium_val < fast_val and fast_val < slow_val)
bearish_setup = (self._prev_fast > self._prev_medium and
self._prev_medium > self._prev_slow and
medium_val > fast_val and fast_val > slow_val)
if bullish_setup:
self.BuyMarket()
self._entry_price = close
self._highest_price = high
self._lowest_price = low
elif bearish_setup:
self.SellMarket()
self._entry_price = close
self._highest_price = high
self._lowest_price = low
self._prev_fast = fast_val
self._prev_medium = medium_val
self._prev_slow = slow_val
def _try_handle_long_exit(self, candle, fast_val, medium_val, slow_val):
if self._entry_price <= 0.0:
return False
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tp_offset = float(self.TakeProfitOffset)
sl_offset = float(self.StopLossOffset)
trail_offset = float(self.TrailingStopOffset)
exit_price = 0.0
if tp_offset > 0.0:
target = self._entry_price + tp_offset
if high >= target:
exit_price = target
if exit_price == 0.0 and sl_offset > 0.0:
stop = self._entry_price - sl_offset
if low <= stop:
exit_price = stop
if high > self._highest_price:
self._highest_price = high
if exit_price == 0.0 and trail_offset > 0.0 and self._highest_price - self._entry_price > trail_offset:
trail = self._highest_price - trail_offset
if low <= trail:
exit_price = trail
if exit_price == 0.0:
reversal = (self._prev_fast > self._prev_medium and
self._prev_medium > self._prev_slow and
slow_val < fast_val and fast_val < medium_val)
if reversal:
exit_price = close
if exit_price == 0.0:
return False
self._exit_position(exit_price)
return True
def _try_handle_short_exit(self, candle, fast_val, medium_val, slow_val):
if self._entry_price <= 0.0:
return False
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
tp_offset = float(self.TakeProfitOffset)
sl_offset = float(self.StopLossOffset)
trail_offset = float(self.TrailingStopOffset)
exit_price = 0.0
if tp_offset > 0.0:
target = self._entry_price - tp_offset
if low <= target:
exit_price = target
if exit_price == 0.0 and sl_offset > 0.0:
stop = self._entry_price + sl_offset
if high >= stop:
exit_price = stop
if self._lowest_price == 0.0 or low < self._lowest_price:
self._lowest_price = low
if exit_price == 0.0 and trail_offset > 0.0 and self._entry_price - self._lowest_price > trail_offset:
trail = self._lowest_price + trail_offset
if high >= trail:
exit_price = trail
if exit_price == 0.0:
reversal = (self._prev_fast > self._prev_medium and
self._prev_medium > self._prev_slow and
slow_val < fast_val and fast_val < medium_val)
if reversal:
exit_price = close
if exit_price == 0.0:
return False
self._exit_position(exit_price)
return True
def _exit_position(self, exit_price):
is_long = self.Position > 0
if is_long:
pnl = exit_price - self._entry_price
self.SellMarket()
else:
pnl = self._entry_price - exit_price
self.BuyMarket()
if pnl < 0.0:
self._losses += 1
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
def OnReseted(self):
super(up3x1_dynamic_sizing_strategy, self).OnReseted()
self._has_prev_values = False
self._prev_fast = 0.0
self._prev_medium = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._highest_price = 0.0
self._lowest_price = 0.0
self._losses = 0
def CreateClone(self):
return up3x1_dynamic_sizing_strategy()