GitHub で見る

Tengri戦略(StockSharpポート)

この戦略はMetaTraderエキスパートアドバイザーTengriのStockSharp高レベル再現版です。元のアドバイザーはRSI、カスタム「Silence」ボラティリティフィルター、EMAトレンドゲージによって駆動されるグリッドとスケールアプローチでEURUSDとUSDCHFを取引します。C#版はStockSharpの規則とネットポジション会計に適応しながら動作コアを維持します。

コアアイデア

  • 方向バイアス – 現在のビッドを上位時間足ローソク足(デフォルト30分)の始値と比較します。正の差異は戦略をロングに傾け、負の差異はショートに傾けます。
  • モメンタムフィルター – 時間足ローソク足で計算された14期間のRSIは、ロングエントリーでは70未満、ショートエントリーでは30超を維持する必要があり、MetaTraderのロジックと一致します。
  • 静寂市場フィルター – 元のカスタム「Silence」インジケーターは、2つの異なる時間軸でEMAによって平滑化されたATR値で模倣されます。両フィルターはエントリーまたはスケールインを許可するために設定可能なしきい値以下に維持される必要があります。
  • トレンド確認 – 中間時間足のEMAにより、ロング追加はEMAより上でのみ、ショート追加はEMAより下でのみ発生することが保証されます。
  • グリッドとマーチンゲールのサイジング – 最初のトレードは固定ロットまたは資産比例ロットを使用します。追加トレードは設定可能な係数で前のボリュームを乗算します(デフォルトでStepX前は1.70、以降は2.08)。
  • Pipスペーシング – グリッド注文間の距離は2つのベースステップ(デフォルト10 pipと20 pip)に従い、PipStepExponentを通じて指数関数的に拡大できます。

取引ワークフロー

  1. エントリー評価EntryCandleTypeごと、デフォルトM1):
    • DealCandleTypeローソク足から方向を決定します。
    • RSIと最初のサイレンスフィルターを確認します。
    • 同じ方向にアクティブなトレードがないことを確認します(StockSharpポートフォリオはネットのため、反対方向のポジションを最初にフラット化します)。
    • 計算されたロットサイズで成行注文を提出します。最初のポジションはpipベースのテイクプロフィット目標を保存します。
  2. スケールイン評価ScaleCandleTypeごと、デフォルトM1):
    • EMAトレンドと2番目のサイレンスフィルターを確認します。
    • pip-stepルールを使用して、最後の約定価格が現在の市場から十分に離れているか確認します。
    • 方向が有効でトレード数がMaxTrades未満の間、マーチンゲールサイジングで別の成行注文を追加します。
  3. ポジション管理:
    • オプションのグローバル利益目標は、ロングとショート両方のスタックが存在し、合算未実現PnLがEquity / LimitDivisorを超えたときにポジションをクローズします。
    • 最初のトレードのテイクプロフィットは単純な決済として機能します: ビッド/アスクが保存された目標に達すると、ネットポジション全体がフラット化されます。
    • MetaTraderコードを反映して自動ストップロスは使用されません。

パラメーター

パラメーター 説明
DealCandleType 方向バイアスを定義する始値の時間足。
EntryCandleType エントリーシグナル評価の時間足。
ScaleCandleType グリッド追加確認の時間足。
MaCandleType EMAトレンドフィルターの時間足。
Silence1CandleType / Silence2CandleType ATRベースのボラティリティフィルターの時間足。
RsiPeriod RSIの長さ(デフォルト14)。
SilencePeriod1/2, SilenceInterpolation1/2, SilenceLevel1/2 2つのサイレンスフィルターを制御するATR平滑化としきい値。
MaPeriod EMAの期間。
PipStep, PipStep2, PipStepExponent スケールイントレード間の距離。
LotExponent1, LotExponent2, StepX 追加ポジションのマーチンゲール係数。
LotSize, FixLot, LotStep 最初のポジションのマネーマネジメント設定。
SlTpPips 最初のトレードのテイクプロフィット設定に使用するPip距離(0で無効)。
MaxTrades 方向ごとの最大エントリー数。
UseLimit, LimitDivisor グローバル利益ロック設定。
CloseFriday, CloseFridayHour オプションの金曜遅延エントリーロックアウト。

MetaTraderバージョンとの違い

  • Silenceインジケーターの置き換え – 独自の「Silence」インジケーターはEMAで平滑化されたATR値で近似されます。しきい値は同じ数値スケールを維持しますが、ATRプロキシの動作が異なる場合は調整できます。
  • ネットポジション会計 – StockSharpポートフォリオはネットのため、戦略は両サイドを同時にヘッジする代わりに、新しいスタックを開く前に反対方向をフラット化します。
  • テイクプロフィット処理 – MetaTraderはTPを最初の注文にのみ付けます。ポートはその目標が発動したときにネットポジション全体をクローズします。追加注文には意図的にTPがなく、元のリスクモデルと一致します。
  • シンボル選択 – 戦略は戦略インスタンスに割り当てられたSecurityを使用します。EURUSD、USDCHFまたは他の商品について個別のインスタンスを設定してください。

使用上の注意

  • ターゲット商品のボリュームステップとmin/maxボリュームを設定して、LotCheckスタイルの丸めがブローカー要件と一致するようにしてください。
  • 戦略はブローカーの気配値がベストビッド/アスク更新を提供することを前提としています。Level1データなしでは方向とTPのチェックが機能できません。
  • ストップロスがないため、外部のリスク管理(資産ストップ、手動監視など)と組み合わせて戦略を実行することを検討してください。

可視化

動作を分析するには、購読したローソク足シリーズ(方向、エントリー、スケーリング時間足)にチャートウィジェットを接続し、EMAとATRインジケーターをオーバーレイします。これは元のアドバイザーで使用された診断ツールを反映しています。

using System;
using System.Collections.Generic;

using Ecng.Common;

using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;

namespace StockSharp.Samples.Strategies;

/// <summary>
/// Tengri strategy using WMA crossover with EMA trend filter.
/// Buys when fast WMA crosses above slow WMA and price above EMA.
/// Sells on reverse conditions.
/// </summary>
public class TengriStrategy : Strategy
{
	private readonly StrategyParam<int> _fastPeriod;
	private readonly StrategyParam<int> _slowPeriod;
	private readonly StrategyParam<int> _emaPeriod;
	private readonly StrategyParam<int> _stopLossPoints;
	private readonly StrategyParam<int> _takeProfitPoints;

	private WeightedMovingAverage _fast;
	private WeightedMovingAverage _slow;
	private ExponentialMovingAverage _ema;

	private decimal _prevFast;
	private decimal _prevSlow;
	private decimal _entryPrice;
	private int _cooldown;

	public int FastPeriod { get => _fastPeriod.Value; set => _fastPeriod.Value = value; }
	public int SlowPeriod { get => _slowPeriod.Value; set => _slowPeriod.Value = value; }
	public int EmaPeriod { get => _emaPeriod.Value; set => _emaPeriod.Value = value; }
	public int StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
	public int TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }

	public TengriStrategy()
	{
		_fastPeriod = Param(nameof(FastPeriod), 15).SetGreaterThanZero().SetDisplay("Fast Period", "Fast WMA period", "Indicator");
		_slowPeriod = Param(nameof(SlowPeriod), 60).SetGreaterThanZero().SetDisplay("Slow Period", "Slow WMA period", "Indicator");
		_emaPeriod = Param(nameof(EmaPeriod), 200).SetGreaterThanZero().SetDisplay("EMA Period", "Trend EMA period", "Indicator");
		_stopLossPoints = Param(nameof(StopLossPoints), 200).SetNotNegative().SetDisplay("Stop Loss", "Stop-loss in price steps", "Risk");
		_takeProfitPoints = Param(nameof(TakeProfitPoints), 400).SetNotNegative().SetDisplay("Take Profit", "Take-profit in price steps", "Risk");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		yield return (Security, TimeSpan.FromMinutes(5).TimeFrame());
	}

	protected override void OnReseted()
	{
		base.OnReseted();
		_fast = null; _slow = null; _ema = null;
		_prevFast = 0; _prevSlow = 0; _entryPrice = 0; _cooldown = 0;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);
		_fast = new WeightedMovingAverage { Length = FastPeriod };
		_slow = new WeightedMovingAverage { Length = SlowPeriod };
		_ema = new ExponentialMovingAverage { Length = EmaPeriod };
		var subscription = SubscribeCandles(TimeSpan.FromMinutes(5).TimeFrame());
		subscription.Bind(_fast, _slow, _ema, ProcessCandle);
		subscription.Start();
	}

	private void ProcessCandle(ICandleMessage candle, decimal fastValue, decimal slowValue, decimal emaValue)
	{
		if (candle.State != CandleStates.Finished) return;
		if (!_fast.IsFormed || !_slow.IsFormed || !_ema.IsFormed) { _prevFast = fastValue; _prevSlow = slowValue; return; }
		if (_cooldown > 0) { _cooldown--; _prevFast = fastValue; _prevSlow = slowValue; return; }

		var close = candle.ClosePrice;
		var step = Security?.PriceStep ?? 1m;

		if (Position > 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close <= _entryPrice - StopLossPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close >= _entryPrice + TakeProfitPoints * step) { SellMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}
		else if (Position < 0 && _entryPrice > 0)
		{
			if (StopLossPoints > 0 && close >= _entryPrice + StopLossPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
			if (TakeProfitPoints > 0 && close <= _entryPrice - TakeProfitPoints * step) { BuyMarket(); _entryPrice = 0; _cooldown = 80; _prevFast = fastValue; _prevSlow = slowValue; return; }
		}

		if (_prevFast <= _prevSlow && fastValue > slowValue && close > emaValue && Position <= 0)
		{ if (Position < 0) BuyMarket(); BuyMarket(); _entryPrice = close; _cooldown = 80; }
		else if (_prevFast >= _prevSlow && fastValue < slowValue && close < emaValue && Position >= 0)
		{ if (Position > 0) SellMarket(); SellMarket(); _entryPrice = close; _cooldown = 80; }

		_prevFast = fastValue; _prevSlow = slowValue;
	}
}