GitHub で見る

重心ローソク足戦略

この戦略は、StockSharpの高レベルAPIを使用してMetaTraderエキスパート「Exp_CenterOfGravityCandle」を再現します。エキスパートはCenter of Gravity Candleインジケーターが生成する合成ローソク足を使って取引します。各合成ローソク足は、ジョン・エーラーズの重心計算を始値・高値・安値・終値に適用し、結果を設定可能な移動平均で平滑化することで構築されます。合成ローソク足の色(強気、弱気、またはニュートラル)が唯一の取引シグナルです。

インジケーターロジック

  1. 各マーケットローソク足は完全に閉じた後に処理されます。
  2. 各価格要素(始値、高値、安値、終値)に対して、戦略は2つの移動平均を計算します:Periodで定義された期間の単純MAと線形加重MAです。
  3. これら2つの平均の積をインストゥルメントの価格ステップで割り、設定されたメソッド(Ma Method)と長さ(Smooth Period)で平滑化します。
  4. 合成高値と安値は合成始値/終値を含むよう強制され、ローソク足の本体がMetaTrader実装と一致するようにします。
  5. ローソク足の色は合成始値と終値の比較で決定されます:始値が終値より低い = 強気(色2)、始値が終値より高い = 弱気(色0)、それ以外はニュートラル(色1)。

取引ルール

  1. 戦略は合成ローソク足の色の履歴を保持し、Signal Barで定義されたバーを検査します(デフォルト = 前の完了したバー)。
  2. 検査された合成ローソク足が強気になり、前のローソク足が強気でなかった場合:
    • Enable Sell Closetrueの場合、既存のショートポジションをクローズします。
    • Enable Buy Opentrueの場合、新しいロングポジションをオープンします。
  3. 検査された合成ローソク足が弱気になり、前のローソク足が弱気でなかった場合:
    • Enable Buy Closetrueの場合、既存のロングポジションをクローズします。
    • Enable Sell Opentrueの場合、新しいショートポジションをオープンします。
  4. マーケットエントリーはMoney ManagementMargin Modeから計算されたボリュームを使用します。Money Managementの負の値は固定ロットサイズとして扱われます。損失ベースのモードでは、アルゴリズムは設定されたストップロス距離を使用してトレードごとのリスクを近似します。
  5. StartProtectionが有効化され、価格ステップで表現されたTake ProfitStop Loss距離に従って自動的にテイクプロフィットとストップロス注文を配置します。

パラメーター

  • Money Management – 注文ボリュームを導出するために使用される口座価値の割合(負の値 = 固定ロット)。最適化可能。
  • Margin Mode – マネーマネジメントパラメーターの解釈(エクイティベース、残高ベース、損失ベース、または固定ロット)。
  • Stop Loss – 価格ステップ単位のストップロス距離。保護注文とリスクベースのポジションサイジングの両方に使用されます。
  • Take Profit – 価格ステップ単位のテイクプロフィット距離。StartProtectionを介して適用されます。
  • Open Long / Open Short – それぞれのシグナルでロング/ショートポジションのオープンを許可します。
  • Close Long / Close Short – 反対のシグナルが現れたときにロング/ショートポジションのクローズを許可します。
  • Candle Type – インジケーター計算に使用するローソク足の時間軸。
  • Center of Gravity Period – 単純移動平均と線形加重移動平均の基本期間。最適化可能。
  • Smoothing Period – 合成ローソク足に適用される平滑化移動平均の長さ。最適化可能。
  • Smoothing Method – 平滑化段階で使用される移動平均タイプ(SMA、EMA、SMMA、LWMA)。
  • Signal Bar – シグナル生成に使用する合成ローソク足のインデックス(0 = 現在、1 = 前など)。

注記

  • インジケーターの計算はC#で実装されており、手動バッファや履歴コレクションを使用せずに元のMetaTraderロジックを再現します。
  • ボリューム計算はStockSharpのポートフォリオ情報を使用しており、プラットフォームの違いによりMetaTraderの結果とわずかに異なる場合があります。
  • 戦略は完全に完成したローソク足のみで動作し、未完成のバーでは取引しません。
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>
/// Strategy that trades synthetic candles generated by Center of Gravity calculation.
/// Uses SMA and LWMA products smoothed to create synthetic OHLC, then trades color changes.
/// </summary>
public class CenterOfGravityCandleStrategy : Strategy
{
	private readonly StrategyParam<DataType> _candleType;
	private readonly StrategyParam<int> _period;
	private readonly StrategyParam<int> _smoothPeriod;

	// Internal indicators for computing synthetic candle
	private SimpleMovingAverage _openSma;
	private SimpleMovingAverage _highSma;
	private SimpleMovingAverage _lowSma;
	private SimpleMovingAverage _closeSma;
	private WeightedMovingAverage _openLwma;
	private WeightedMovingAverage _highLwma;
	private WeightedMovingAverage _lowLwma;
	private WeightedMovingAverage _closeLwma;
	private SimpleMovingAverage _openSmooth;
	private SimpleMovingAverage _highSmooth;
	private SimpleMovingAverage _lowSmooth;
	private SimpleMovingAverage _closeSmooth;

	private int _prevColor; // 0=neutral, 1=bullish, -1=bearish
	private bool _hasPrev;

	public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
	public int Period { get => _period.Value; set => _period.Value = value; }
	public int SmoothPeriod { get => _smoothPeriod.Value; set => _smoothPeriod.Value = value; }

	public CenterOfGravityCandleStrategy()
	{
		_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
			.SetDisplay("Candle Type", "Timeframe", "General");

		_period = Param(nameof(Period), 10)
			.SetGreaterThanZero()
			.SetDisplay("CoG Period", "Center of gravity period", "Indicator");

		_smoothPeriod = Param(nameof(SmoothPeriod), 1)
			.SetGreaterThanZero()
			.SetDisplay("Smooth Period", "Smoothing period", "Indicator");
	}

	public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
	{
		return [(Security, CandleType)];
	}

	/// <inheritdoc />
	protected override void OnReseted()
	{
		base.OnReseted();

		_prevColor = 0;
		_hasPrev = false;
		_openSma = null;
		_highSma = null;
		_lowSma = null;
		_closeSma = null;
		_openLwma = null;
		_highLwma = null;
		_lowLwma = null;
		_closeLwma = null;
		_openSmooth = null;
		_highSmooth = null;
		_lowSmooth = null;
		_closeSmooth = null;
	}

	protected override void OnStarted2(DateTime time)
	{
		base.OnStarted2(time);

		var len = Period;
		var sLen = SmoothPeriod;

		_openSma = new SMA { Length = len };
		_highSma = new SMA { Length = len };
		_lowSma = new SMA { Length = len };
		_closeSma = new SMA { Length = len };
		_openLwma = new WeightedMovingAverage { Length = len };
		_highLwma = new WeightedMovingAverage { Length = len };
		_lowLwma = new WeightedMovingAverage { Length = len };
		_closeLwma = new WeightedMovingAverage { Length = len };
		_openSmooth = new SMA { Length = sLen };
		_highSmooth = new SMA { Length = sLen };
		_lowSmooth = new SMA { Length = sLen };
		_closeSmooth = new SMA { Length = sLen };

		_prevColor = 0;
		_hasPrev = false;

		var sub = SubscribeCandles(CandleType);
		sub.Bind(ProcessCandle).Start();

		var area = CreateChartArea();
		if (area != null)
		{
			DrawCandles(area, sub);
			DrawOwnTrades(area);
		}
	}

	private void ProcessCandle(ICandleMessage candle)
	{
		if (candle.State != CandleStates.Finished)
			return;

		var t = candle.OpenTime;
		var point = Security?.PriceStep ?? 1m;
		if (point <= 0m) point = 1m;

		// Compute SMA and LWMA for each OHLC component
		var oSma = _openSma.Process(candle.OpenPrice, t, true);
		var oLwma = _openLwma.Process(candle.OpenPrice, t, true);
		var hSma = _highSma.Process(candle.HighPrice, t, true);
		var hLwma = _highLwma.Process(candle.HighPrice, t, true);
		var lSma = _lowSma.Process(candle.LowPrice, t, true);
		var lLwma = _lowLwma.Process(candle.LowPrice, t, true);
		var cSma = _closeSma.Process(candle.ClosePrice, t, true);
		var cLwma = _closeLwma.Process(candle.ClosePrice, t, true);

		if (!_openSma.IsFormed || !_openLwma.IsFormed ||
		    !_highSma.IsFormed || !_highLwma.IsFormed ||
		    !_lowSma.IsFormed || !_lowLwma.IsFormed ||
		    !_closeSma.IsFormed || !_closeLwma.IsFormed)
			return;

		// Use average of SMA and LWMA instead of product (avoids overflow with high prices)
		var openProd = (oSma.ToDecimal() + oLwma.ToDecimal()) / 2m;
		var highProd = (hSma.ToDecimal() + hLwma.ToDecimal()) / 2m;
		var lowProd = (lSma.ToDecimal() + lLwma.ToDecimal()) / 2m;
		var closeProd = (cSma.ToDecimal() + cLwma.ToDecimal()) / 2m;

		// Smooth
		var oSmooth = _openSmooth.Process(openProd, t, true);
		var hSmooth = _highSmooth.Process(highProd, t, true);
		var lSmooth = _lowSmooth.Process(lowProd, t, true);
		var cSmooth = _closeSmooth.Process(closeProd, t, true);

		if (!_openSmooth.IsFormed || !_highSmooth.IsFormed ||
		    !_lowSmooth.IsFormed || !_closeSmooth.IsFormed)
			return;

		var openVal = oSmooth.ToDecimal();
		var closeVal = cSmooth.ToDecimal();

		// Determine color
		int color;
		if (openVal < closeVal)
			color = 1; // bullish
		else if (openVal > closeVal)
			color = -1; // bearish
		else
			color = 0; // neutral

		if (_hasPrev)
		{
			// Bullish color change
			if (color == 1 && _prevColor != 1 && Position <= 0)
				BuyMarket();
			// Bearish color change
			else if (color == -1 && _prevColor != -1 && Position >= 0)
				SellMarket();
		}

		_prevColor = color;
		_hasPrev = true;
	}
}