Risk Reward Ratio戦略
概要
Risk Reward Ratio戦略は、MetaTraderエキスパート「Risk Reward Ratio」のStockSharp高レベルポートです。この戦略は、複数のモメンタムおよびトレンド確認フィルターを規律あるリスク管理モジュールと組み合わせています。エントリーは、ストキャスティックオシレーターの合流、線形加重移動平均(LWMA)クロスオーバー、14期間RSIフィルター、およびMACDトレンド確認の組み合わせから生成されます。リスク制御は、pip基準のストップロス、自動リワードレシオのテイクプロフィット、オプションのトレーリングストップとブレイクイーブンロジック、および緊急フラットスイッチによって達成されます。
変換はMetaTraderエキスパートの本来の精神を保ちながら、StockSharpのロウソク足サブスクリプションとインジケーターバインディングAPIを使用します。すべてのインジケーター処理は確定したロウソク足で行われ、インジケーターバッファへの直接アクセスを避け、エンジンのストリーミングパラダイムを維持します。
トレードロジック
- ストキャスティック合流
- 速いストキャスティック (5, 2, 2) は %K ラインを使用して主要なモメンタムシグナルを提供します。
- 遅いストキャスティック (21, 10, 4) は平滑化された %D ラインを通じて方向バイアスを提供します。
- ロングセットアップでは速い %K が遅い %D の上に、ショートセットアップでは逆が必要です。
- RSI確認
- 14期間RSIはロング取引で50以上、ショート取引で50以下でなければならず、市場が提案された方向に整合していることを確認します。
- LWMAによるトレンドフィルター
- 2つの線形加重移動平均(長さ6と85)が方向を確認する必要があります:ロングでは速いLWMAが遅いLWMAの上、ショートでは下。
- MACDトレンド修飾子
- MACDヒストグラム (12, 26, 9) はシグナル方向と一致している必要があります。メイン線はシグナル線をリードしながらゼロの適切な側に留まらなければなりません。
- モメンタム偏差フィルター
- 14期間のモメンタムインジケーターが100からの距離を測定します。直近3つのモメンタム読み取りのうち少なくとも1つが設定可能な閾値を超えて、価格が取引を正当化するほど加速していることを証明する必要があります。
- ポジション制限
- ネットエクスポージャーは
MaxPositions * TradeVolumeによって上限が設定され、戦略が元のEAの制約を超えてピラミッドできないようにします。
- ネットエクスポージャーは
注文は BuyMarket と SellMarket を使用した成行執行として送信されます。戦略は未確定のロウソク足を無視し、StockSharpのイベント駆動アーキテクチャに従うためにすべての状態をクラスフィールド内に保持します。
リスク管理
- pipでのストップロス – すべてのエントリーは、約定価格から
StopLossPips * PriceStepの保護ストップを設置します。 - リワードレシオのテイクプロフィット – テイクプロフィット距離はストップ距離に
RewardRatioを掛けたもので、固定のリワードリスク比を維持します。 - トレーリングストップ – 有効化されると、市場がエントリーから少なくともその距離だけ前進した後、
TrailingStopPipsだけ価格の後ろにストップが移動します。 - ブレイクイーブン移動 – 有利な
BreakEvenTriggerPipsの移動後、ストップはエントリーに加えて追加のBreakEvenOffsetPipsクッション(ショートでは引く)まで押し上げられ、利益を確保します。 - 終了スイッチ –
ExitSwitchをtrueに設定すると、次の確定バーで現在のポジションをフラットにし、フラグがオフになるまで以降の処理を無効にします。
パラメーター
| 名前 | デフォルト値 | 説明 |
|---|---|---|
TradeVolume |
0.1 |
各成行注文のボリューム。 |
CandleType |
15m 時間軸 |
主要ロウソク足シリーズ。 |
FastMaPeriod |
6 |
速いLWMAの期間。 |
SlowMaPeriod |
85 |
遅いLWMAの期間。 |
MomentumThreshold |
0.3 |
エントリーを許可するために必要なモメンタムインジケーターの100からの最小絶対距離。 |
RewardRatio |
2 |
ストップロスに対するテイクプロフィットの倍率。 |
StopLossPips |
20 |
pip単位のストップロス距離(PriceStepの倍数)。 |
MaxPositions |
10 |
同時に許可されるボリュームユニット(TradeVolume)の最大数。 |
EnableTrailing |
true |
pip基準のトレーリングストップ更新を有効にします。 |
TrailingStopPips |
40 |
pip単位のトレーリングストップ距離。 |
EnableBreakEven |
true |
ブレイクイーブンストップ管理を有効にします。 |
BreakEvenTriggerPips |
30 |
ストップをブレイクイーブンに移動する前に必要な利益(pip単位)。 |
BreakEvenOffsetPips |
30 |
ストップをブレイクイーブンに移動するときの追加pipオフセット。 |
ExitSwitch |
false |
次の確定ロウソク足ですべてのエクスポージャーをフラットにするよう戦略に強制します。 |
ワークフロー
- 希望の銘柄とロウソク足シリーズを設定し、リスクパラメーターを設定します。
- 戦略を開始します。ロウソク足を購読し、インジケーターをバインドし、確定したバーの処理を開始します。
- エントリー条件が揃うと、エンジンは成行注文を送信し、ストップ/ターゲットレベルを保存します。
- 確定したロウソク足ごとに、リスクブロックがトレーリング、ブレイクイーブン、および緊急出口ルールを評価します。
- ストップ/テイクプロフィットレベルへの到達、トレーリング更新、ブレイクイーブン調整、または緊急スイッチによって出口がトリガーされます。
注意事項
- 変換はマニュアルバッファアクセスの代わりにStockSharpのインジケーターバインディングを活用し、各インジケーターが同期されたデータを受け取ることを確実にします。
- すべての計算は楽器の
PriceStepに依存します。ステップがゼロまたは欠落している場合、無効な価格レベルを避けるためにリスク距離は無効のままです。 - 戦略は保留中の注文を変更しません。単に成行注文を送信してポジションを開閉し、閾値に達したときに元のEAがエクスポージャーをフラットにした方法を反映します。
using System;
using System.Collections.Generic;
using System.Linq;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Multi-filter risk/reward strategy.
/// </summary>
public class RiskRewardRatioStrategy : Strategy
{
private readonly StrategyParam<decimal> _tradeVolume;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastMaPeriod;
private readonly StrategyParam<int> _slowMaPeriod;
private readonly StrategyParam<decimal> _momentumThreshold;
private readonly StrategyParam<decimal> _rewardRatio;
private readonly StrategyParam<int> _stopLossPips;
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<bool> _enableTrailing;
private readonly StrategyParam<int> _trailingStopPips;
private readonly StrategyParam<bool> _enableBreakEven;
private readonly StrategyParam<int> _breakEvenTriggerPips;
private readonly StrategyParam<int> _breakEvenOffsetPips;
private readonly StrategyParam<bool> _exitSwitch;
private readonly List<Leg> _legs = [];
private readonly List<decimal> _momentumDistances = [];
private WeightedMovingAverage _fastLwma;
private WeightedMovingAverage _slowLwma;
private RelativeStrengthIndex _rsi;
private MovingAverageConvergenceDivergenceSignal _macd;
private RateOfChange _momentum;
private StochasticK _fastStochasticK;
private StochasticK _slowStochasticK;
private SimpleMovingAverage _fastStochasticMain;
private SimpleMovingAverage _slowStochasticMain;
private SimpleMovingAverage _slowStochasticSignal;
public decimal TradeVolume { get => _tradeVolume.Value; set => _tradeVolume.Value = value; }
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastMaPeriod { get => _fastMaPeriod.Value; set => _fastMaPeriod.Value = value; }
public int SlowMaPeriod { get => _slowMaPeriod.Value; set => _slowMaPeriod.Value = value; }
public decimal MomentumThreshold { get => _momentumThreshold.Value; set => _momentumThreshold.Value = value; }
public decimal RewardRatio { get => _rewardRatio.Value; set => _rewardRatio.Value = value; }
public int StopLossPips { get => _stopLossPips.Value; set => _stopLossPips.Value = value; }
public int MaxPositions { get => _maxPositions.Value; set => _maxPositions.Value = value; }
public bool EnableTrailing { get => _enableTrailing.Value; set => _enableTrailing.Value = value; }
public int TrailingStopPips { get => _trailingStopPips.Value; set => _trailingStopPips.Value = value; }
public bool EnableBreakEven { get => _enableBreakEven.Value; set => _enableBreakEven.Value = value; }
public int BreakEvenTriggerPips { get => _breakEvenTriggerPips.Value; set => _breakEvenTriggerPips.Value = value; }
public int BreakEvenOffsetPips { get => _breakEvenOffsetPips.Value; set => _breakEvenOffsetPips.Value = value; }
public bool ExitSwitch { get => _exitSwitch.Value; set => _exitSwitch.Value = value; }
public RiskRewardRatioStrategy()
{
_tradeVolume = Param(nameof(TradeVolume), 0.1m).SetGreaterThanZero();
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame());
_fastMaPeriod = Param(nameof(FastMaPeriod), 6).SetGreaterThanZero();
_slowMaPeriod = Param(nameof(SlowMaPeriod), 85).SetGreaterThanZero();
_momentumThreshold = Param(nameof(MomentumThreshold), 0.3m).SetNotNegative();
_rewardRatio = Param(nameof(RewardRatio), 2m).SetGreaterThanZero();
_stopLossPips = Param(nameof(StopLossPips), 20).SetGreaterThanZero();
_maxPositions = Param(nameof(MaxPositions), 10).SetGreaterThanZero();
_enableTrailing = Param(nameof(EnableTrailing), true);
_trailingStopPips = Param(nameof(TrailingStopPips), 40).SetNotNegative();
_enableBreakEven = Param(nameof(EnableBreakEven), true);
_breakEvenTriggerPips = Param(nameof(BreakEvenTriggerPips), 30).SetNotNegative();
_breakEvenOffsetPips = Param(nameof(BreakEvenOffsetPips), 30).SetNotNegative();
_exitSwitch = Param(nameof(ExitSwitch), false);
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
protected override void OnReseted()
{
base.OnReseted();
_legs.Clear();
_momentumDistances.Clear();
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_fastLwma = new() { Length = FastMaPeriod };
_slowLwma = new() { Length = SlowMaPeriod };
_rsi = new() { Length = 14 };
_macd = new()
{
Macd =
{
ShortMa = { Length = 12 },
LongMa = { Length = 26 },
},
SignalMa = { Length = 9 },
};
_momentum = new() { Length = 14 };
_fastStochasticK = new() { Length = 5 };
_slowStochasticK = new() { Length = 21 };
_fastStochasticMain = new() { Length = 2 };
_slowStochasticMain = new() { Length = 4 };
_slowStochasticSignal = new() { Length = 10 };
SubscribeCandles(CandleType)
.BindEx(_fastLwma, _slowLwma, _rsi, _macd, _momentum, _fastStochasticK, _slowStochasticK, ProcessCandle, true)
.Start();
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Order is null || trade.Trade is null)
return;
var leg = _legs.FirstOrDefault(l => l.Order == trade.Order);
if (leg is not null)
AddFill(leg, trade);
}
private void ProcessCandle(ICandleMessage candle, IIndicatorValue fastLwmaValue, IIndicatorValue slowLwmaValue, IIndicatorValue rsiValue,
IIndicatorValue macdValue, IIndicatorValue momentumValue, IIndicatorValue fastKValue, IIndicatorValue slowKValue)
{
if (candle.State != CandleStates.Finished)
return;
var fastMain = UpdateFastStochastic(fastKValue);
var slowSignal = UpdateSlowStochastic(slowKValue);
UpdateMomentum(momentumValue);
if (ExitSwitch)
{
Flatten();
return;
}
if (ApplyRisk(candle))
return;
if (fastMain is not decimal fastK || slowSignal is not decimal slowD)
return;
if (!_fastLwma.IsFormed || !_slowLwma.IsFormed || !_rsi.IsFormed || !_macd.IsFormed || !_momentum.IsFormed)
return;
var macd = (IMovingAverageConvergenceDivergenceSignalValue)macdValue;
if (macd.Macd is not decimal macdMain || macd.Signal is not decimal macdSignal)
return;
var rsi = rsiValue.ToDecimal();
var fastLwma = fastLwmaValue.ToDecimal();
var slowLwma = slowLwmaValue.ToDecimal();
var momentumBurst = _momentumDistances.Count == 0 ? 0m : _momentumDistances.Max();
var longSignal =
fastK > slowD &&
rsi > 50m &&
fastLwma > slowLwma &&
macdMain > macdSignal && macdMain > 0m &&
momentumBurst >= MomentumThreshold;
var shortSignal =
fastK < slowD &&
rsi < 50m &&
fastLwma < slowLwma &&
macdMain < macdSignal && macdMain < 0m &&
momentumBurst >= MomentumThreshold;
var maxExposure = MaxPositions * TradeVolume;
if (longSignal && Position >= 0m && Position + TradeVolume <= maxExposure)
Enter(Sides.Buy, candle.OpenTime);
else if (shortSignal && Position <= 0m && Math.Abs(Position - TradeVolume) <= maxExposure)
Enter(Sides.Sell, candle.OpenTime);
}
private decimal? UpdateFastStochastic(IIndicatorValue fastKValue)
{
if (!_fastStochasticK.IsFormed || fastKValue.IsEmpty)
return null;
var main = _fastStochasticMain.Process(fastKValue);
return _fastStochasticMain.IsFormed && !main.IsEmpty ? main.ToDecimal() : null;
}
private decimal? UpdateSlowStochastic(IIndicatorValue slowKValue)
{
if (!_slowStochasticK.IsFormed || slowKValue.IsEmpty)
return null;
var main = _slowStochasticMain.Process(slowKValue);
if (!_slowStochasticMain.IsFormed || main.IsEmpty)
return null;
var signal = _slowStochasticSignal.Process(main);
return _slowStochasticSignal.IsFormed && !signal.IsEmpty ? signal.ToDecimal() : null;
}
private void UpdateMomentum(IIndicatorValue momentumValue)
{
if (!_momentum.IsFormed || momentumValue.IsEmpty)
return;
// |ROC| equals the momentum's distance from 100.
_momentumDistances.Add(Math.Abs(momentumValue.ToDecimal()));
if (_momentumDistances.Count > 3)
_momentumDistances.RemoveAt(0);
}
private void Enter(Sides side, DateTime candleTime)
{
var order = side == Sides.Buy ? BuyMarket(TradeVolume) : SellMarket(TradeVolume);
var leg = new Leg
{
Order = order,
Side = side,
EntryTime = candleTime,
};
_legs.Add(leg);
// The emulator can fill a market order before BuyMarket or SellMarket returns it.
foreach (var trade in MyTrades.Where(t => t.Order == order && t.Trade is not null).ToArray())
AddFill(leg, trade);
}
private void AddFill(Leg leg, MyTrade trade)
{
leg.FilledVolume += trade.Trade.Volume;
leg.FilledValue += trade.Trade.Price * trade.Trade.Volume;
leg.FillPrice = leg.FilledValue / leg.FilledVolume;
SetInitialLevels(leg, leg.FillPrice.Value);
}
private void SetInitialLevels(Leg leg, decimal fill)
{
leg.BestPrice = fill;
if (GetPip() is not decimal pip)
{
leg.StopPrice = null;
leg.TakePrice = null;
return;
}
var stopDistance = StopLossPips * pip;
var takeDistance = stopDistance * RewardRatio;
if (leg.Side == Sides.Buy)
{
leg.StopPrice = fill - stopDistance;
leg.TakePrice = fill + takeDistance;
}
else
{
leg.StopPrice = fill + stopDistance;
leg.TakePrice = fill - takeDistance;
}
}
private bool ApplyRisk(ICandleMessage candle)
{
var pip = GetPip();
var exitSell = 0m;
var exitBuy = 0m;
for (var i = _legs.Count - 1; i >= 0; i--)
{
var leg = _legs[i];
if (leg.FillPrice is not decimal fill || candle.OpenTime <= leg.EntryTime)
continue;
var isLong = leg.Side == Sides.Buy;
leg.BestPrice = isLong ? Math.Max(leg.BestPrice, candle.HighPrice) : Math.Min(leg.BestPrice, candle.LowPrice);
if (pip is decimal step)
MoveStop(leg, fill, step);
var hit = isLong
? (leg.StopPrice is decimal stop && candle.LowPrice <= stop) || (leg.TakePrice is decimal take && candle.HighPrice >= take)
: (leg.StopPrice is decimal shortStop && candle.HighPrice >= shortStop) || (leg.TakePrice is decimal shortTake && candle.LowPrice <= shortTake);
if (!hit)
continue;
if (isLong)
exitSell += leg.FilledVolume;
else
exitBuy += leg.FilledVolume;
_legs.RemoveAt(i);
}
if (exitSell > 0m)
SellMarket(exitSell);
if (exitBuy > 0m)
BuyMarket(exitBuy);
return exitSell > 0m || exitBuy > 0m;
}
private void MoveStop(Leg leg, decimal fill, decimal pip)
{
var isLong = leg.Side == Sides.Buy;
var advance = isLong ? leg.BestPrice - fill : fill - leg.BestPrice;
if (EnableBreakEven && advance >= BreakEvenTriggerPips * pip)
{
var offset = BreakEvenOffsetPips * pip;
TightenStop(leg, isLong ? fill + offset : fill - offset);
}
if (EnableTrailing && TrailingStopPips > 0)
{
var trail = TrailingStopPips * pip;
if (advance >= trail)
TightenStop(leg, isLong ? leg.BestPrice - trail : leg.BestPrice + trail);
}
}
private static void TightenStop(Leg leg, decimal candidate)
{
if (leg.StopPrice is not decimal stop || (leg.Side == Sides.Buy ? candidate > stop : candidate < stop))
leg.StopPrice = candidate;
}
private void Flatten()
{
if (Position > 0m)
SellMarket(Math.Abs(Position));
else if (Position < 0m)
BuyMarket(Math.Abs(Position));
_legs.Clear();
}
private decimal? GetPip()
{
var step = Security?.PriceStep;
return step > 0m ? step : null;
}
private sealed class Leg
{
public Order Order { get; init; }
public Sides Side { get; init; }
public DateTime EntryTime { get; init; }
public decimal FilledVolume { get; set; }
public decimal FilledValue { get; set; }
public decimal? FillPrice { get; set; }
public decimal BestPrice { get; set; }
public decimal? StopPrice { get; set; }
public decimal? TakePrice { get; set; }
}
}
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, Sides
from StockSharp.Algo.Indicators import (WeightedMovingAverage, RelativeStrengthIndex,
MovingAverageConvergenceDivergenceSignal, RateOfChange,
StochasticK, SimpleMovingAverage)
from StockSharp.Algo.Strategies import Strategy
class _leg(object):
def __init__(self, order, side, entry_time):
self.order = order
self.side = side
self.entry_time = entry_time
self.filled_volume = 0.0
self.filled_value = 0.0
self.fill_price = None
self.best_price = 0.0
self.stop_price = None
self.take_price = None
class risk_reward_ratio_strategy(Strategy):
def __init__(self):
super(risk_reward_ratio_strategy, self).__init__()
self._trade_volume = self.Param("TradeVolume", 0.1).SetGreaterThanZero()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(15)))
self._fast_ma = self.Param("FastMaPeriod", 6).SetGreaterThanZero()
self._slow_ma = self.Param("SlowMaPeriod", 85).SetGreaterThanZero()
self._momentum_threshold = self.Param("MomentumThreshold", 0.3).SetNotNegative()
self._reward_ratio = self.Param("RewardRatio", 2.0).SetGreaterThanZero()
self._stop_loss = self.Param("StopLossPips", 20).SetGreaterThanZero()
self._max_positions = self.Param("MaxPositions", 10).SetGreaterThanZero()
self._enable_trailing = self.Param("EnableTrailing", True)
self._trailing_stop = self.Param("TrailingStopPips", 40).SetNotNegative()
self._enable_break_even = self.Param("EnableBreakEven", True)
self._break_even_trigger = self.Param("BreakEvenTriggerPips", 30).SetNotNegative()
self._break_even_offset = self.Param("BreakEvenOffsetPips", 30).SetNotNegative()
self._exit_switch = self.Param("ExitSwitch", False)
self._legs = []
self._momentum_distances = []
self._fast_lwma = None
self._slow_lwma = None
self._rsi = None
self._macd = None
self._momentum = None
self._fast_stochastic_k = None
self._slow_stochastic_k = None
self._fast_stochastic_main = None
self._slow_stochastic_main = None
self._slow_stochastic_signal = None
def GetWorkingSecurities(self):
return [(self.Security, self._candle_type.Value)]
def OnReseted(self):
super(risk_reward_ratio_strategy, self).OnReseted()
self._legs = []
self._momentum_distances = []
def OnStarted2(self, time):
super(risk_reward_ratio_strategy, self).OnStarted2(time)
self._fast_lwma = WeightedMovingAverage()
self._fast_lwma.Length = int(self._fast_ma.Value)
self._slow_lwma = WeightedMovingAverage()
self._slow_lwma.Length = int(self._slow_ma.Value)
self._rsi = RelativeStrengthIndex()
self._rsi.Length = 14
self._macd = MovingAverageConvergenceDivergenceSignal()
self._macd.Macd.ShortMa.Length = 12
self._macd.Macd.LongMa.Length = 26
self._macd.SignalMa.Length = 9
self._momentum = RateOfChange()
self._momentum.Length = 14
self._fast_stochastic_k = StochasticK()
self._fast_stochastic_k.Length = 5
self._slow_stochastic_k = StochasticK()
self._slow_stochastic_k.Length = 21
self._fast_stochastic_main = SimpleMovingAverage()
self._fast_stochastic_main.Length = 2
self._slow_stochastic_main = SimpleMovingAverage()
self._slow_stochastic_main.Length = 4
self._slow_stochastic_signal = SimpleMovingAverage()
self._slow_stochastic_signal.Length = 10
self.SubscribeCandles(self._candle_type.Value).BindEx(
self._fast_lwma, self._slow_lwma, self._rsi, self._macd, self._momentum,
self._fast_stochastic_k, self._slow_stochastic_k, self._process, True).Start()
def OnOwnTradeReceived(self, trade):
super(risk_reward_ratio_strategy, self).OnOwnTradeReceived(trade)
if trade is None or trade.Order is None or trade.Trade is None:
return
for leg in self._legs:
if leg.order == trade.Order:
self._add_fill(leg, trade)
break
def _process(self, candle, fast_lwma_value, slow_lwma_value, rsi_value, macd_value, momentum_value,
fast_k_value, slow_k_value):
if candle.State != CandleStates.Finished:
return
fast_main = self._update_fast_stochastic(fast_k_value)
slow_signal = self._update_slow_stochastic(slow_k_value)
self._update_momentum(momentum_value)
if bool(self._exit_switch.Value):
self._flatten()
return
if self._apply_risk(candle):
return
if fast_main is None or slow_signal is None:
return
if (not self._fast_lwma.IsFormed or not self._slow_lwma.IsFormed or not self._rsi.IsFormed
or not self._macd.IsFormed or not self._momentum.IsFormed):
return
macd_main = macd_value.Macd
macd_signal = macd_value.Signal
if macd_main is None or macd_signal is None:
return
macd_main = float(macd_main)
macd_signal = float(macd_signal)
rsi = float(rsi_value)
fast_lwma = float(fast_lwma_value)
slow_lwma = float(slow_lwma_value)
burst = max(self._momentum_distances) if self._momentum_distances else 0.0
threshold = float(self._momentum_threshold.Value)
long_signal = (fast_main > slow_signal and rsi > 50.0 and fast_lwma > slow_lwma and
macd_main > macd_signal and macd_main > 0 and burst >= threshold)
short_signal = (fast_main < slow_signal and rsi < 50.0 and fast_lwma < slow_lwma and
macd_main < macd_signal and macd_main < 0 and burst >= threshold)
volume = float(self._trade_volume.Value)
max_exposure = int(self._max_positions.Value) * volume
if long_signal and self.Position >= 0 and float(self.Position) + volume <= max_exposure:
self._enter(Sides.Buy, candle.OpenTime)
elif short_signal and self.Position <= 0 and abs(float(self.Position) - volume) <= max_exposure:
self._enter(Sides.Sell, candle.OpenTime)
def _update_fast_stochastic(self, fast_k_value):
if not self._fast_stochastic_k.IsFormed or fast_k_value.IsEmpty:
return None
main = self._fast_stochastic_main.Process(fast_k_value)
if not self._fast_stochastic_main.IsFormed or main.IsEmpty:
return None
return float(main)
def _update_slow_stochastic(self, slow_k_value):
if not self._slow_stochastic_k.IsFormed or slow_k_value.IsEmpty:
return None
main = self._slow_stochastic_main.Process(slow_k_value)
if not self._slow_stochastic_main.IsFormed or main.IsEmpty:
return None
signal = self._slow_stochastic_signal.Process(main)
if not self._slow_stochastic_signal.IsFormed or signal.IsEmpty:
return None
return float(signal)
def _update_momentum(self, momentum_value):
if not self._momentum.IsFormed or momentum_value.IsEmpty:
return
# |ROC| equals the momentum's distance from 100.
self._momentum_distances.append(abs(float(momentum_value)))
if len(self._momentum_distances) > 3:
self._momentum_distances.pop(0)
def _enter(self, side, candle_time):
volume = self._trade_volume.Value
order = self.BuyMarket(volume) if side == Sides.Buy else self.SellMarket(volume)
leg = _leg(order, side, candle_time)
self._legs.append(leg)
# The emulator can fill a market order before BuyMarket or SellMarket returns it.
for trade in list(self.MyTrades):
if trade.Order == order and trade.Trade is not None:
self._add_fill(leg, trade)
def _add_fill(self, leg, trade):
price = float(trade.Trade.TradePrice)
volume = float(trade.Trade.TradeVolume)
leg.filled_volume += volume
leg.filled_value += price * volume
leg.fill_price = leg.filled_value / leg.filled_volume
self._set_initial_levels(leg, leg.fill_price)
def _set_initial_levels(self, leg, fill):
leg.best_price = fill
pip = self._pip()
if pip is None:
leg.stop_price = None
leg.take_price = None
return
stop_distance = int(self._stop_loss.Value) * pip
take_distance = stop_distance * float(self._reward_ratio.Value)
if leg.side == Sides.Buy:
leg.stop_price = fill - stop_distance
leg.take_price = fill + take_distance
else:
leg.stop_price = fill + stop_distance
leg.take_price = fill - take_distance
def _apply_risk(self, candle):
pip = self._pip()
high = float(candle.HighPrice)
low = float(candle.LowPrice)
exit_sell = 0.0
exit_buy = 0.0
for i in range(len(self._legs) - 1, -1, -1):
leg = self._legs[i]
if leg.fill_price is None or candle.OpenTime <= leg.entry_time:
continue
fill = leg.fill_price
is_long = leg.side == Sides.Buy
leg.best_price = max(leg.best_price, high) if is_long else min(leg.best_price, low)
if pip is not None:
self._move_stop(leg, fill, pip)
if is_long:
hit = ((leg.stop_price is not None and low <= leg.stop_price) or
(leg.take_price is not None and high >= leg.take_price))
else:
hit = ((leg.stop_price is not None and high >= leg.stop_price) or
(leg.take_price is not None and low <= leg.take_price))
if not hit:
continue
if is_long:
exit_sell += leg.filled_volume
else:
exit_buy += leg.filled_volume
del self._legs[i]
if exit_sell > 0:
self.SellMarket(exit_sell)
if exit_buy > 0:
self.BuyMarket(exit_buy)
return exit_sell > 0 or exit_buy > 0
def _move_stop(self, leg, fill, pip):
is_long = leg.side == Sides.Buy
advance = leg.best_price - fill if is_long else fill - leg.best_price
if bool(self._enable_break_even.Value) and advance >= int(self._break_even_trigger.Value) * pip:
offset = int(self._break_even_offset.Value) * pip
self._tighten_stop(leg, fill + offset if is_long else fill - offset)
if bool(self._enable_trailing.Value) and int(self._trailing_stop.Value) > 0:
trail = int(self._trailing_stop.Value) * pip
if advance >= trail:
self._tighten_stop(leg, leg.best_price - trail if is_long else leg.best_price + trail)
@staticmethod
def _tighten_stop(leg, candidate):
if leg.stop_price is None or (candidate > leg.stop_price if leg.side == Sides.Buy else candidate < leg.stop_price):
leg.stop_price = candidate
def _flatten(self):
if self.Position > 0:
self.SellMarket(Math.Abs(self.Position))
elif self.Position < 0:
self.BuyMarket(Math.Abs(self.Position))
self._legs = []
def _pip(self):
security = self.Security
if security is None or security.PriceStep is None:
return None
step = float(security.PriceStep)
return step if step > 0 else None
def CreateClone(self):
return risk_reward_ratio_strategy()