JS Sistem 2戦略
概要
JS Sistem 2はMetaTrader 5向けに元々書かれたトレンドフォロー戦略です。StockSharpへのポートはエキスパートアドバイザーのマルチインジケーター確認ブロックを維持し、選択した時間軸のクローズした足で取引します。注文は固定ボリュームでサイジングされ、接続されたポートフォリオ残高が設定可能な閾値を下回った場合、オプションでブロックできます。リスクはピップスで表現されたハードなストップロスとテイクプロフィット距離と、足の影を追跡する適応的なトレーリングストップで制御されます。
インジケーターとフィルター
- EMA(55)、EMA(89)、EMA(144) – 方向性フィルターを形成します。ロング設定では高速EMAが中間の上、中間が低速ラインの上にある必要があり、高速と低速の曲線間の距離は
MinDifferencePips以下に留まる必要があります。 - MACDヒストグラム(OsMA) – MQLバージョンと同じ高速、低速、シグナルEMAの長さを使用します。ロング取引はヒストグラムが正であることを、ショート取引は負であることを要求します。
- 相対活力指数(RVI) – 期間
RviPeriodで計算され、RviSignalLengthを持つ追加の単純移動平均で平滑化されます。ロング取引にはRVIがシグナルラインより上、かつRviMax閾値より上にある必要があります。ショートは逆が必要です。 - スイング最高値/最安値エンベロープ –
VolatilityPeriod本の足にわたって最高の高値と最低の安値を追跡します。これらの値はトレーリングストップロジックを駆動し、元のエキスパートアドバイザーの影トレーリングモードを再現します。
トレードロジック
- 戦略は設定された
CandleTypeの完成した足のみを処理します。 - エントリーを評価する前に、最新のスイング極値を使用して既存ポジションのトレーリングストップを更新し、足の間にストップロスまたはテイクプロフィットレベルに達したかどうかを確認します。
- ロングエントリー条件:
- ポートフォリオ残高が
MinBalanceより上。 - EMA55 > EMA89 > EMA144で、EMA55とEMA144の差が
MinDifferencePips以下(インストゥルメントのpipサイズを通じて価格単位に変換)。 - MACDヒストグラム(
macdLine)がゼロより大きい。 - RVIがシグナルラインより上で、シグナルラインが
RviMax以上。 - 既存のロングポジションがない(
Position <= 0)。ショートポジションが存在する場合、ロングを開く前にフラット化されます。
- ポートフォリオ残高が
- ショートエントリー条件はロングルールを反転した比較で反映し、
RviMin閾値を使用します。 - エントリー時、戦略は参照として足のクローズ価格を保存し、
StopLossPipsとTakeProfitPipsでその価格をシフトしてバーチャルなストップロスとテイクプロフィットレベルを設定し、トレーリング状態をリセットします。
エグジットとトレーリング管理
- ハードストップロス / テイクプロフィット: 足のレンジが保存されたストップまたはターゲットレベルと重複するたびに、戦略はすぐにポジション全体をクローズします。
- トレーリングストップ:
TrailingEnabledが真の場合、戦略は利益の方向にストップを動かそうとします。ロングの場合、その安値がエントリー価格と前のストップの両方より少なくともTrailingIndentPips以上上にあれば、ストップは最後のVolatilityPeriod本の足の最低の安値に引き上げられます。ショートは最高の高値を使用した対称ルールに従います。これはMQLアドバイザーの「影トレーリング」を再現し、ストップが早期に締まることを防ぎます。 - 残高保護: 現在のポートフォリオ価値が
MinBalanceを下回ると、戦略は新しい注文の送信を控えますが、オープンポジションとトレーリングストップの管理は継続します。
パラメーター
| パラメーター | 説明 | デフォルト値 |
|---|---|---|
MinBalance |
新規エントリーに必要な最小ポートフォリオ残高。 | 100 |
Volume |
各取引で送信する注文ボリューム。 | 1 |
StopLossPips |
ピップスで測定したストップロス距離。無効にするには0に設定。 | 35 |
TakeProfitPips |
ピップスで測定したテイクプロフィット距離。無効にするには0に設定。 | 40 |
MinDifferencePips |
ピップスでの高速と低速EMA間の最大許容スプレッド。 | 28 |
VolatilityPeriod |
トレーリングストップ用のスイング高値と安値を計算するために使用する足の数。 | 15 |
TrailingEnabled |
トレーリングストップロジックを有効/無効にします。 | true |
TrailingIndentPips |
トレーリングストップを更新する際の価格、エントリー、ストップ間の最小ギャップ。 | 1 |
MaFastPeriod |
高速EMAの期間。 | 55 |
MaMediumPeriod |
中間EMAの期間。 | 89 |
MaSlowPeriod |
低速EMAの期間。 | 144 |
OsmaFastPeriod |
MACDヒストグラムの高速EMA長さ。 | 13 |
OsmaSlowPeriod |
MACDヒストグラムの低速EMA長さ。 | 55 |
OsmaSignalPeriod |
MACDヒストグラムのシグナル平滑化長さ。 | 21 |
RviPeriod |
相対活力指数の期間。 | 44 |
RviSignalLength |
シグナルラインを取得するためにRVIに適用するSMAの長さ。 | 4 |
RviMax |
ロングエントリーが許可される前にRVIシグナルが達する必要がある上限。 | 0.04 |
RviMin |
ショートエントリーが許可される前にRVIシグナルが達する必要がある下限。 | -0.04 |
CandleType |
すべての計算に使用する足の時間軸。 | 5分足 |
実装メモ
- pip距離はインストゥルメントの価格ステップから導出されます。3桁または5桁で見積もられたインストゥルメントは10の価格ステップに等しいpipを使用し、元のMQLロジックと一致します。
- ストップとターゲットの管理は戦略ループ内で行われます。このテンプレートではStockSharpが自動的にサーバーサイドの注文を送信しないためです。
- 戦略は起動時に
StartProtection()を呼び出して、基底クラスが予期しない切断と保留中のポジションを監視できるようにします。
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>
/// JS Sistem 2 trend-following strategy converted from MetaTrader 5.
/// Combines exponential moving averages, MACD histogram (OsMA), and Relative Vigor Index filters.
/// Includes trailing stop based on recent candle shadows and configurable stop/target distances.
/// </summary>
public class JsSistem2Strategy : Strategy
{
private readonly StrategyParam<decimal> _minBalance;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _takeProfitPips;
private readonly StrategyParam<int> _minDifferencePips;
private readonly StrategyParam<int> _volatilityPeriod;
private readonly StrategyParam<bool> _trailingEnabled;
private readonly StrategyParam<int> _trailingIndentPips;
private readonly StrategyParam<int> _maFastPeriod;
private readonly StrategyParam<int> _maMediumPeriod;
private readonly StrategyParam<int> _maSlowPeriod;
private readonly StrategyParam<int> _osmaFastPeriod;
private readonly StrategyParam<int> _osmaSlowPeriod;
private readonly StrategyParam<int> _osmaSignalPeriod;
private readonly StrategyParam<int> _rviPeriod;
private readonly StrategyParam<int> _rviSignalLength;
private readonly StrategyParam<decimal> _rviMax;
private readonly StrategyParam<decimal> _rviMin;
private readonly StrategyParam<DataType> _candleType;
private ExponentialMovingAverage _emaFast = null!;
private ExponentialMovingAverage _emaMedium = null!;
private ExponentialMovingAverage _emaSlow = null!;
private MovingAverageConvergenceDivergence _macd = null!;
private Highest _highest = null!;
private Lowest _lowest = null!;
private RelativeVigorIndex _rvi = null!;
private decimal? _stopPrice;
private decimal? _takePrice;
private decimal _entryPrice;
/// <summary>
/// Minimum account balance required to allow new entries.
/// </summary>
public decimal MinBalance
{
get => _minBalance.Value;
set => _minBalance.Value = value;
}
/// <summary>
/// Stop-loss distance in pips.
/// </summary>
public int StopLossPips
{
get => _stopLossPips.Value;
set => _stopLossPips.Value = value;
}
/// <summary>
/// Take-profit distance in pips.
/// </summary>
public int TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
/// <summary>
/// Maximum allowed spread between fast and slow EMA in pips.
/// </summary>
public int MinDifferencePips
{
get => _minDifferencePips.Value;
set => _minDifferencePips.Value = value;
}
/// <summary>
/// Lookback for trailing stop based on candle shadows.
/// </summary>
public int VolatilityPeriod
{
get => _volatilityPeriod.Value;
set => _volatilityPeriod.Value = value;
}
/// <summary>
/// Enables trailing stop management.
/// </summary>
public bool TrailingEnabled
{
get => _trailingEnabled.Value;
set => _trailingEnabled.Value = value;
}
/// <summary>
/// Offset applied when updating trailing stop levels.
/// </summary>
public int TrailingIndentPips
{
get => _trailingIndentPips.Value;
set => _trailingIndentPips.Value = value;
}
/// <summary>
/// Fast EMA period.
/// </summary>
public int MaFastPeriod
{
get => _maFastPeriod.Value;
set => _maFastPeriod.Value = value;
}
/// <summary>
/// Medium EMA period.
/// </summary>
public int MaMediumPeriod
{
get => _maMediumPeriod.Value;
set => _maMediumPeriod.Value = value;
}
/// <summary>
/// Slow EMA period.
/// </summary>
public int MaSlowPeriod
{
get => _maSlowPeriod.Value;
set => _maSlowPeriod.Value = value;
}
/// <summary>
/// Fast EMA length for the MACD/OsMA filter.
/// </summary>
public int OsmaFastPeriod
{
get => _osmaFastPeriod.Value;
set => _osmaFastPeriod.Value = value;
}
/// <summary>
/// Slow EMA length for the MACD/OsMA filter.
/// </summary>
public int OsmaSlowPeriod
{
get => _osmaSlowPeriod.Value;
set => _osmaSlowPeriod.Value = value;
}
/// <summary>
/// Signal length for the MACD/OsMA filter.
/// </summary>
public int OsmaSignalPeriod
{
get => _osmaSignalPeriod.Value;
set => _osmaSignalPeriod.Value = value;
}
/// <summary>
/// Relative Vigor Index period.
/// </summary>
public int RviPeriod
{
get => _rviPeriod.Value;
set => _rviPeriod.Value = value;
}
/// <summary>
/// Smoothing length for the RVI signal line.
/// </summary>
public int RviSignalLength
{
get => _rviSignalLength.Value;
set => _rviSignalLength.Value = value;
}
/// <summary>
/// Upper threshold for the RVI signal line.
/// </summary>
public decimal RviMax
{
get => _rviMax.Value;
set => _rviMax.Value = value;
}
/// <summary>
/// Lower threshold for the RVI signal line.
/// </summary>
public decimal RviMin
{
get => _rviMin.Value;
set => _rviMin.Value = value;
}
/// <summary>
/// Candle type used by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initializes a new instance of the <see cref="JsSistem2Strategy"/> class.
/// </summary>
public JsSistem2Strategy()
{
_minBalance = Param(nameof(MinBalance), 100m)
.SetDisplay("Min Balance", "Minimum balance to allow trading", "Risk")
;
_stopLossPips = Param(nameof(StopLossPips), 200)
.SetDisplay("Stop Loss", "Stop-loss distance in pips", "Risk")
;
_takeProfitPips = Param(nameof(TakeProfitPips), 300)
.SetDisplay("Take Profit", "Take-profit distance in pips", "Risk")
;
_minDifferencePips = Param(nameof(MinDifferencePips), 5000)
.SetGreaterThanZero()
.SetDisplay("EMA Spread", "Maximum fast-slow EMA spread", "Filters")
;
_volatilityPeriod = Param(nameof(VolatilityPeriod), 15)
.SetGreaterThanZero()
.SetDisplay("Trailing Range", "Number of candles for trailing", "Risk")
;
_trailingEnabled = Param(nameof(TrailingEnabled), true)
.SetDisplay("Trailing", "Enable trailing stop", "Risk");
_trailingIndentPips = Param(nameof(TrailingIndentPips), 1)
.SetGreaterThanZero()
.SetDisplay("Trailing Offset", "Indent from candle shadows", "Risk")
;
_maFastPeriod = Param(nameof(MaFastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("Fast EMA", "Fast EMA period", "Indicators")
;
_maMediumPeriod = Param(nameof(MaMediumPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("Medium EMA", "Medium EMA period", "Indicators")
;
_maSlowPeriod = Param(nameof(MaSlowPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("Slow EMA", "Slow EMA period", "Indicators")
;
_osmaFastPeriod = Param(nameof(OsmaFastPeriod), 12)
.SetGreaterThanZero()
.SetDisplay("OsMA Fast", "Fast EMA for MACD", "Indicators")
;
_osmaSlowPeriod = Param(nameof(OsmaSlowPeriod), 26)
.SetGreaterThanZero()
.SetDisplay("OsMA Slow", "Slow EMA for MACD", "Indicators")
;
_osmaSignalPeriod = Param(nameof(OsmaSignalPeriod), 9)
.SetGreaterThanZero()
.SetDisplay("OsMA Signal", "Signal period for MACD", "Indicators")
;
_rviPeriod = Param(nameof(RviPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("RVI Period", "Relative Vigor Index period", "Indicators")
;
_rviSignalLength = Param(nameof(RviSignalLength), 4)
.SetGreaterThanZero()
.SetDisplay("RVI Signal", "Smoothing for RVI signal", "Indicators")
;
_rviMax = Param(nameof(RviMax), 0.02m)
.SetDisplay("RVI Max", "Upper threshold for RVI signal", "Filters")
;
_rviMin = Param(nameof(RviMin), -0.02m)
.SetDisplay("RVI Min", "Lower threshold for RVI signal", "Filters")
;
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candles used for calculations", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_stopPrice = null;
_takePrice = null;
_entryPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaFast = new ExponentialMovingAverage { Length = MaFastPeriod };
_emaMedium = new ExponentialMovingAverage { Length = MaMediumPeriod };
_emaSlow = new ExponentialMovingAverage { Length = MaSlowPeriod };
_macd = new MovingAverageConvergenceDivergence
{
ShortMa = { Length = OsmaFastPeriod },
LongMa = { Length = OsmaSlowPeriod },
};
_highest = new Highest { Length = VolatilityPeriod };
_lowest = new Lowest { Length = VolatilityPeriod };
_rvi = new RelativeVigorIndex();
_rvi.Average.Length = RviPeriod;
_rvi.Signal.Length = RviSignalLength;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_emaFast, _emaMedium, _emaSlow, _macd, _highest, _lowest, ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle, decimal emaFast, decimal emaMedium, decimal emaSlow, decimal macdLine, decimal highestValue, decimal lowestValue)
{
if (candle.State != CandleStates.Finished)
return;
if (!_emaFast.IsFormed || !_emaMedium.IsFormed || !_emaSlow.IsFormed || !_macd.IsFormed || !_highest.IsFormed || !_lowest.IsFormed)
return;
var step = CalculatePipSize();
if (step == 0m)
{
step = Security.PriceStep ?? 0m;
}
if (step == 0m)
step = 1m;
var stopDistance = StopLossPips > 0 ? StopLossPips * step : 0m;
var takeDistance = TakeProfitPips > 0 ? TakeProfitPips * step : 0m;
var minDifference = MinDifferencePips * step;
var indent = TrailingIndentPips * step;
UpdateTrailingStops(candle, highestValue, lowestValue, indent);
HandleStopsAndTargets(candle);
var canTrade = (Portfolio?.CurrentValue ?? decimal.MaxValue) >= MinBalance;
var emaOrderLong = emaFast > emaMedium && emaMedium > emaSlow;
var emaOrderShort = emaFast < emaMedium && emaMedium < emaSlow;
var emaSpreadLong = Math.Abs(emaFast - emaSlow) < minDifference;
var emaSpreadShort = Math.Abs(emaSlow - emaFast) < minDifference;
var longCondition = canTrade && emaOrderLong && emaSpreadLong && macdLine > 0m;
var shortCondition = canTrade && emaOrderShort && emaSpreadShort && macdLine < 0m;
if (longCondition && Position <= 0)
{
if (Position < 0)
{
BuyMarket(Math.Abs(Position));
ResetOrders();
}
if (Volume > 0m)
{
BuyMarket(Volume);
_entryPrice = candle.ClosePrice;
_stopPrice = stopDistance > 0m ? _entryPrice - stopDistance : null;
_takePrice = takeDistance > 0m ? _entryPrice + takeDistance : null;
}
}
else if (shortCondition && Position >= 0)
{
if (Position > 0)
{
SellMarket(Math.Abs(Position));
ResetOrders();
}
if (Volume > 0m)
{
SellMarket(Volume);
_entryPrice = candle.ClosePrice;
_stopPrice = stopDistance > 0m ? _entryPrice + stopDistance : null;
_takePrice = takeDistance > 0m ? _entryPrice - takeDistance : null;
}
}
}
private void UpdateTrailingStops(ICandleMessage candle, decimal highestValue, decimal lowestValue, decimal indent)
{
if (!TrailingEnabled)
return;
if (Position > 0)
{
var newStop = lowestValue;
if (newStop > 0m && candle.ClosePrice - newStop > indent && newStop - _entryPrice > indent)
{
if (!_stopPrice.HasValue || newStop - _stopPrice.Value > indent)
{
_stopPrice = newStop;
}
}
}
else if (Position < 0)
{
var newStop = highestValue;
if (newStop > 0m && newStop - candle.ClosePrice > indent && _entryPrice - newStop > indent)
{
if (!_stopPrice.HasValue || _stopPrice.Value - newStop > indent)
{
_stopPrice = newStop;
}
}
}
}
private void HandleStopsAndTargets(ICandleMessage candle)
{
if (Position > 0)
{
if (_stopPrice.HasValue && candle.LowPrice <= _stopPrice.Value)
{
SellMarket(Math.Abs(Position));
ResetOrders();
return;
}
if (_takePrice.HasValue && candle.HighPrice >= _takePrice.Value)
{
SellMarket(Math.Abs(Position));
ResetOrders();
}
}
else if (Position < 0)
{
if (_stopPrice.HasValue && candle.HighPrice >= _stopPrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetOrders();
return;
}
if (_takePrice.HasValue && candle.LowPrice <= _takePrice.Value)
{
BuyMarket(Math.Abs(Position));
ResetOrders();
}
}
}
private void ResetOrders()
{
_stopPrice = null;
_takePrice = null;
_entryPrice = 0m;
}
private decimal CalculatePipSize()
{
var security = Security;
if (security is null)
return 0m;
var step = security.PriceStep ?? 0m;
if (step == 0m)
return 0m;
var decimals = security.Decimals;
return decimals == 3 || decimals == 5 ? step * 10m : step;
}
}
import clr
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.Indicators import (
ExponentialMovingAverage, MovingAverageConvergenceDivergence,
Highest, Lowest
)
from StockSharp.Algo.Strategies import Strategy
class js_sistem2_strategy(Strategy):
def __init__(self):
super(js_sistem2_strategy, self).__init__()
self._min_balance = self.Param("MinBalance", 100.0)
self._stop_loss_pips = self.Param("StopLossPips", 200)
self._take_profit_pips = self.Param("TakeProfitPips", 300)
self._min_difference_pips = self.Param("MinDifferencePips", 5000)
self._volatility_period = self.Param("VolatilityPeriod", 15)
self._trailing_enabled = self.Param("TrailingEnabled", True)
self._trailing_indent_pips = self.Param("TrailingIndentPips", 1)
self._ma_fast_period = self.Param("MaFastPeriod", 12)
self._ma_medium_period = self.Param("MaMediumPeriod", 26)
self._ma_slow_period = self.Param("MaSlowPeriod", 50)
self._osma_fast_period = self.Param("OsmaFastPeriod", 12)
self._osma_slow_period = self.Param("OsmaSlowPeriod", 26)
self._osma_signal_period = self.Param("OsmaSignalPeriod", 9)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4)))
self._ema_fast = None
self._ema_medium = None
self._ema_slow = None
self._macd = None
self._highest = None
self._lowest = None
self._stop_price = None
self._take_price = None
self._entry_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 MinBalance(self):
return self._min_balance.Value
@property
def StopLossPips(self):
return self._stop_loss_pips.Value
@property
def TakeProfitPips(self):
return self._take_profit_pips.Value
@property
def MinDifferencePips(self):
return self._min_difference_pips.Value
@property
def VolatilityPeriod(self):
return self._volatility_period.Value
@property
def TrailingEnabled(self):
return self._trailing_enabled.Value
@property
def TrailingIndentPips(self):
return self._trailing_indent_pips.Value
@property
def MaFastPeriod(self):
return self._ma_fast_period.Value
@property
def MaMediumPeriod(self):
return self._ma_medium_period.Value
@property
def MaSlowPeriod(self):
return self._ma_slow_period.Value
@property
def OsmaFastPeriod(self):
return self._osma_fast_period.Value
@property
def OsmaSlowPeriod(self):
return self._osma_slow_period.Value
@property
def OsmaSignalPeriod(self):
return self._osma_signal_period.Value
def OnStarted2(self, time):
super(js_sistem2_strategy, self).OnStarted2(time)
self._ema_fast = ExponentialMovingAverage()
self._ema_fast.Length = self.MaFastPeriod
self._ema_medium = ExponentialMovingAverage()
self._ema_medium.Length = self.MaMediumPeriod
self._ema_slow = ExponentialMovingAverage()
self._ema_slow.Length = self.MaSlowPeriod
slow_ma = ExponentialMovingAverage()
slow_ma.Length = self.OsmaSlowPeriod
fast_ma = ExponentialMovingAverage()
fast_ma.Length = self.OsmaFastPeriod
self._macd = MovingAverageConvergenceDivergence(slow_ma, fast_ma)
self._highest = Highest()
self._highest.Length = self.VolatilityPeriod
self._lowest = Lowest()
self._lowest.Length = self.VolatilityPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(
self._ema_fast, self._ema_medium, self._ema_slow,
self._macd, self._highest, self._lowest,
self._process_candle
).Start()
def _process_candle(self, candle, ema_fast_v, ema_medium_v, ema_slow_v, macd_v, highest_v, lowest_v):
if candle.State != CandleStates.Finished:
return
if (not self._ema_fast.IsFormed or not self._ema_medium.IsFormed or
not self._ema_slow.IsFormed or not self._macd.IsFormed or
not self._highest.IsFormed or not self._lowest.IsFormed):
return
step = self._calculate_pip_size()
if step == 0:
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 0.0
if step == 0:
step = 1.0
ef = float(ema_fast_v)
em = float(ema_medium_v)
es = float(ema_slow_v)
macd_line = float(macd_v)
hv = float(highest_v)
lv = float(lowest_v)
stop_dist = self.StopLossPips * step if self.StopLossPips > 0 else 0.0
take_dist = self.TakeProfitPips * step if self.TakeProfitPips > 0 else 0.0
min_diff = self.MinDifferencePips * step
indent = self.TrailingIndentPips * step
self._update_trailing(candle, hv, lv, indent)
self._handle_stops(candle)
pf = self.Portfolio
can_trade = True
if pf is not None and pf.CurrentValue is not None:
can_trade = float(pf.CurrentValue) >= self.MinBalance
ema_order_long = ef > em and em > es
ema_order_short = ef < em and em < es
ema_spread_long = abs(ef - es) < min_diff
ema_spread_short = abs(es - ef) < min_diff
long_cond = can_trade and ema_order_long and ema_spread_long and macd_line > 0
short_cond = can_trade and ema_order_short and ema_spread_short and macd_line < 0
close_price = float(candle.ClosePrice)
if long_cond and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self._reset_orders()
if self.Volume > 0:
self.BuyMarket()
self._entry_price = close_price
self._stop_price = self._entry_price - stop_dist if stop_dist > 0 else None
self._take_price = self._entry_price + take_dist if take_dist > 0 else None
elif short_cond and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self._reset_orders()
if self.Volume > 0:
self.SellMarket()
self._entry_price = close_price
self._stop_price = self._entry_price + stop_dist if stop_dist > 0 else None
self._take_price = self._entry_price - take_dist if take_dist > 0 else None
def _update_trailing(self, candle, highest_v, lowest_v, indent):
if not self.TrailingEnabled:
return
close = float(candle.ClosePrice)
if self.Position > 0:
new_stop = lowest_v
if new_stop > 0 and close - new_stop > indent and new_stop - self._entry_price > indent:
if self._stop_price is None or new_stop - self._stop_price > indent:
self._stop_price = new_stop
elif self.Position < 0:
new_stop = highest_v
if new_stop > 0 and new_stop - close > indent and self._entry_price - new_stop > indent:
if self._stop_price is None or self._stop_price - new_stop > indent:
self._stop_price = new_stop
def _handle_stops(self, candle):
if self.Position > 0:
if self._stop_price is not None and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(); self._reset_orders(); return
if self._take_price is not None and float(candle.HighPrice) >= self._take_price:
self.SellMarket(); self._reset_orders()
elif self.Position < 0:
if self._stop_price is not None and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(); self._reset_orders(); return
if self._take_price is not None and float(candle.LowPrice) <= self._take_price:
self.BuyMarket(); self._reset_orders()
def _reset_orders(self):
self._stop_price = None
self._take_price = None
self._entry_price = 0.0
def _calculate_pip_size(self):
sec = self.Security
if sec is None:
return 0.0
step = float(sec.PriceStep) if sec.PriceStep is not None else 0.0
if step == 0:
return 0.0
decimals = sec.Decimals if sec.Decimals is not None else 0
return step * 10.0 if (decimals == 3 or decimals == 5) else step
def OnReseted(self):
super(js_sistem2_strategy, self).OnReseted()
self._stop_price = None
self._take_price = None
self._entry_price = 0.0
def CreateClone(self):
return js_sistem2_strategy()