GitHub で見る
トレーリングストップ FrCnSar 戦略
概要
トレーリング ストップ FrCnSar 戦略は、TrailingStopFrCnSARen_v4.mq4 および OrderBalansEN_v3_4.mq4 として出荷された MetaTrader ツールキットを移植します。エキスパート アドバイザーは、いくつかのテクニック (以前のローソク足、フラクタル、価格ベロシティ、または Parabolic SAR) を使用してストップロスを調整することで既存のポジションを管理し、コンパニオン インジケーターには現在の口座残高と未決済注文が表示されました。 StockSharp 変換はネット ポジションに焦点を当て、高レベルの API プリミティブを使用してトレーリング ロジックを再実装します。また、オプションの注文概要ロガーも提供するため、元のインジケーターからの情報オーバーレイをテキスト形式で利用できるようになります。
この戦略では、新しい取引が自動的に開始されるわけではありません。代わりに、Strategy.Security の現在位置を継続的に監視し、選択したモードとユーザー定義のフィルターに従って目的のトレーリング ストップ レベルを更新し、価格がトレーリング バリアに達するとエクスポージャーを閉じます。 StockSharp は個別のチケットではなくネット ポジションを処理するため、すべての計算は合計数量に適用されます。
取引ロジック
- 設定済みの
CandleType をサブスクライブし、完了したローソク足のみを処理して、早期ストップ調整を回避します。
- 禁止されたインジケーター メソッドを呼び出さずにフラクタルと最近の極値を取得できるように、ろうそくの高値と安値で短いローリング バッファーを維持します。
- オプションで、速度トレーリング モードがアクティブな場合に、平滑化された近接速度をポイント単位で計算します。
- 完了したローソク足ごとに、選択したモードに基づいてトレーリングストップ価格の候補を生成します。
- 最近のローソク足履歴から
DeltaPoints オフセットを引いた最低値。
DeltaPoints によって調整された最新の確認済みフラクタル。
- 終値は速度に応じた距離だけ変化します。
- 現在の Parabolic SAR の値は
DeltaPoints によってオフセットされます。
- 機器ポイントで表される固定距離。
- 資金管理フィルターに対して候補をチェックします。既存のストップを要求する、収益性の高いトレーリングのみを許可する、損益分岐点に達したらストップする、または平均エントリー価格に基づいて利益テストを行うなどです。
- 候補が既存のストップレベルを少なくとも
StepPoints 改善した場合、保存されているストップレベルを置き換えます。
- ローソク足が保存されたレベル (ロングの場合は安値、ショートの場合は高値) を超え、取引が許可される場合は、成行注文でネット ポジションを閉じます。
- オプションで、MetaTrader OrderBalans インジケーターをエミュレートして、残高、ポジション サイズ、エントリー価格、現在のストップ、未実現損益を含むテキストの概要をログに記録します。
トレーリングモード
- ローソク足 – 直近の重要なローソク足の極値の背後にある痕跡。オフセットは、ストップをサポート/レジスタンスからわずかに遠ざけるために、
DeltaPoints を介して適用されます。
- フラクタル – 処理されたタイムフレームで検出された最後の 5 バーのフラクタルを使用します。これはデフォルトの MetaTrader 実装を模倣していますが、ネット ポジションで動作します。
- 速度 –
VelocityPeriod にわたる終値付近の変化を平均することにより、価格速度を推定します。運動量が位置の方向に加速すると、VelocityMultiplier でスケールされた速度差に比例してストップが強化されます。
- Parabolic – StockSharp が管理する Parabolic SAR インジケーターに従います。ストップは SAR のドットを囲み、ステップと最大加速度のパラメーターを継承します。
- 固定ポイント – 価格から一定の距離を強制し、元のスクリプトの「>4 ピップ」の動作を効果的に反映します。
- オフ – トレーリングを無効にし、現在のストップをそのまま維持します。
パラメーター
| 名前 |
種類 |
デフォルト |
説明 |
Mode |
TrailingStopMode |
Candle |
どのトレーリング アルゴリズムがアクティブであるかを決定します。 |
CandleType |
DataType |
15分キャンドル |
ローソク足を分析し、トレーリングデータを計算するために使用される時間枠。 |
DeltaPoints |
int |
0 |
生のトレーリング価格の下/上に追加される追加距離 (商品ポイント単位)。 |
StepPoints |
int |
0 |
既存のトレーリングストップを更新する前に必要な最小限の改善 (ポイント単位)。 |
FixedDistancePoints |
int |
50 |
固定トレーリングモードの距離。他のモードでは無視されます。 |
TrailOnlyProfit |
bool |
true |
true の場合、トレーリングはストップがエントリー価格と比較して利益で終了した後にのみ開始されます。 |
TrailOnlyBreakEven |
bool |
false |
保存されたストップが損益分岐点を超えたら、更新を停止します。 |
RequireExistingStop |
bool |
false |
停止レベルが計算されるまで、後続の更新を無視します。 |
UseGeneralBreakEven |
bool |
false |
ネット ポジションの平均エントリー価格を使用して収益性フィルターを評価します (元の TProfit ヘルパーと同等)。 |
VelocityPeriod |
int |
30 |
速度モードで速度の平均化に使用されるクローズの数。 |
VelocityMultiplier |
decimal |
1 |
後続距離に適用される速度調整をスケールします。 |
ParabolicStep |
decimal |
0.02 |
Parabolic SAR インジケーターの加速ステップ。 |
ParabolicMaximum |
decimal |
0.2 |
Parabolic SAR インジケーターの最大加速度。 |
LogOrderSummary |
bool |
true |
OrderBalans パネルと同様のテキストログを有効にします。 |
TradeVolume |
decimal |
1 |
ヘルパー メソッドを介してポジションをフラット化するときに使用されるデフォルトのボリューム。 |
- コンバージョンは、個々のチケットではなく、StockSharp のネット ポジションで機能します。したがって、更新の停止は、構築方法に関係なく、ポジション全体に適用されます。
- マジック ナンバーと複数記号フィルターが削除されました。この戦略は
Strategy.Security のみを監視し、ポジションサイジングが外部で処理されることを前提としています。
- MetaTrader
Velocity カスタム インジケーターは、商品ポイントで測定された終値と終値の差の平均によって近似されます。これにより動作は直感的に保たれますが、独自のインジケーターと正確に一致しない可能性があります。
- 視覚的なチャート オブジェクト (傾向線、矢印、ラベル) はテキストのログ エントリに置き換えられました。
LogOrderSummary パラメータは、手動のチャート描画に依存せずに、OrderBalansEN_v3_4.mq4 によって生成される情報パネルを再作成します。
- プラットフォームは個々のチケットで MetaTrader の
OrderModify と直接同等のものを公開しないため、変更の停止には StockSharp ヘルパー メソッド (BuyMarket、SellMarket) が使用されます。
使い方のヒント
- 戦略をチャートに添付して、各トレーリング モードの効果を視覚化します。 Parabolic SAR の場合、チャート領域でドットと取引を同時に表示できるようにします。
- 楽器のティックサイズに応じて、
DeltaPoints と StepPoints を調整します。実装では、Security.PriceStep または Security.MinPriceStep を使用してポイントが自動的に変換されます。
- MetaTrader スクリプトはポジションが利益を生む前にストップを締めることを回避するため、元の動作を模倣する場合は
TrailOnlyProfit を有効にしておきます。
- より静かな出力を希望する場合、または数百の戦略を同時に実行する場合は、
LogOrderSummary を無効にします。
- さまざまな
VelocityMultiplier 値を使用して速度モードをテストします。乗数が大きいほど、トレーリングストップは突然の勢いの爆発に対してより速く反応します。
インジケーター
- Parabolic SAR (
ParabolicSar)
- ローリングローソク足の高値と安値 (ネイティブ データ バッファー)
- ろうそくの終値から導出されたオプションの終値間平均速度
using System;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Trailing Stop FrCnSar: EMA crossover with ATR trailing stops.
/// </summary>
public class TrailingStopFrCnSarStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _fastEmaLength;
private readonly StrategyParam<int> _slowEmaLength;
private readonly StrategyParam<int> _atrLength;
private decimal _prevFast;
private decimal _prevSlow;
private decimal _entryPrice;
private decimal _bestPrice;
public TrailingStopFrCnSarStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Timeframe.", "General");
_fastEmaLength = Param(nameof(FastEmaLength), 10)
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators");
_slowEmaLength = Param(nameof(SlowEmaLength), 30)
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators");
_atrLength = Param(nameof(AtrLength), 14)
.SetDisplay("ATR Length", "ATR period.", "Indicators");
}
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public int FastEmaLength { get => _fastEmaLength.Value; set => _fastEmaLength.Value = value; }
public int SlowEmaLength { get => _slowEmaLength.Value; set => _slowEmaLength.Value = value; }
public int AtrLength { get => _atrLength.Value; set => _atrLength.Value = value; }
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _bestPrice = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _bestPrice = 0;
var fastEma = new ExponentialMovingAverage { Length = FastEmaLength };
var slowEma = new ExponentialMovingAverage { Length = SlowEmaLength };
var atr = new AverageTrueRange { Length = AtrLength };
var subscription = SubscribeCandles(CandleType);
subscription.Bind(fastEma, slowEma, atr, ProcessCandle).Start();
var area = CreateChartArea();
if (area != null) { DrawCandles(area, subscription); DrawIndicator(area, fastEma); DrawIndicator(area, slowEma); DrawOwnTrades(area); }
}
private void ProcessCandle(ICandleMessage candle, decimal fastVal, decimal slowVal, decimal atrVal)
{
if (candle.State != CandleStates.Finished) return;
if (_prevFast == 0 || _prevSlow == 0 || atrVal <= 0) { _prevFast = fastVal; _prevSlow = slowVal; return; }
var close = candle.ClosePrice;
if (Position > 0)
{
if (close > _bestPrice) _bestPrice = close;
if (close <= _bestPrice - atrVal * 2m || (fastVal < slowVal && _prevFast >= _prevSlow)) { SellMarket(); _entryPrice = 0; _bestPrice = 0; }
}
else if (Position < 0)
{
if (close < _bestPrice) _bestPrice = close;
if (close >= _bestPrice + atrVal * 2m || (fastVal > slowVal && _prevFast <= _prevSlow)) { BuyMarket(); _entryPrice = 0; _bestPrice = 0; }
}
if (Position == 0)
{
if (fastVal > slowVal && _prevFast <= _prevSlow) { _entryPrice = close; _bestPrice = close; BuyMarket(); }
else if (fastVal < slowVal && _prevFast >= _prevSlow) { _entryPrice = close; _bestPrice = close; SellMarket(); }
}
_prevFast = fastVal; _prevSlow = slowVal;
}
}
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.Strategies import Strategy
from StockSharp.Algo.Indicators import ExponentialMovingAverage, AverageTrueRange
class trailing_stop_fr_cn_sar_strategy(Strategy):
def __init__(self):
super(trailing_stop_fr_cn_sar_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe.", "General")
self._fast_ema_length = self.Param("FastEmaLength", 10) \
.SetDisplay("Fast EMA Length", "Fast EMA period.", "Indicators")
self._slow_ema_length = self.Param("SlowEmaLength", 30) \
.SetDisplay("Slow EMA Length", "Slow EMA period.", "Indicators")
self._atr_length = self.Param("AtrLength", 14) \
.SetDisplay("ATR Length", "ATR period.", "Indicators")
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
@property
def CandleType(self):
return self._candle_type.Value
@property
def FastEmaLength(self):
return self._fast_ema_length.Value
@property
def SlowEmaLength(self):
return self._slow_ema_length.Value
@property
def AtrLength(self):
return self._atr_length.Value
def OnStarted2(self, time):
super(trailing_stop_fr_cn_sar_strategy, self).OnStarted2(time)
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
self._fast_ema = ExponentialMovingAverage()
self._fast_ema.Length = self.FastEmaLength
self._slow_ema = ExponentialMovingAverage()
self._slow_ema.Length = self.SlowEmaLength
self._atr = AverageTrueRange()
self._atr.Length = self.AtrLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._fast_ema, self._slow_ema, self._atr, self.ProcessCandle).Start()
def ProcessCandle(self, candle, fast_val, slow_val, atr_val):
if candle.State != CandleStates.Finished:
return
fv = float(fast_val)
sv = float(slow_val)
av = float(atr_val)
if self._prev_fast == 0 or self._prev_slow == 0 or av <= 0:
self._prev_fast = fv
self._prev_slow = sv
return
close = float(candle.ClosePrice)
if self.Position > 0:
if close > self._best_price:
self._best_price = close
if close <= self._best_price - av * 2.0 or (fv < sv and self._prev_fast >= self._prev_slow):
self.SellMarket()
self._entry_price = 0.0
self._best_price = 0.0
elif self.Position < 0:
if close < self._best_price:
self._best_price = close
if close >= self._best_price + av * 2.0 or (fv > sv and self._prev_fast <= self._prev_slow):
self.BuyMarket()
self._entry_price = 0.0
self._best_price = 0.0
if self.Position == 0:
if fv > sv and self._prev_fast <= self._prev_slow:
self._entry_price = close
self._best_price = close
self.BuyMarket()
elif fv < sv and self._prev_fast >= self._prev_slow:
self._entry_price = close
self._best_price = close
self.SellMarket()
self._prev_fast = fv
self._prev_slow = sv
def OnReseted(self):
super(trailing_stop_fr_cn_sar_strategy, self).OnReseted()
self._prev_fast = 0.0
self._prev_slow = 0.0
self._entry_price = 0.0
self._best_price = 0.0
def CreateClone(self):
return trailing_stop_fr_cn_sar_strategy()