ホーム
/
戦略のサンプル
GitHub で見る
Exp FisherCG オシレーター戦略
この戦略は、Exp_FisherCGOscillator MetaTrader 5エキスパートアドバイザーをStockSharpの高レベルAPIに移植します。Fisher Center of Gravityオシレーターとそのトリガーラインを再現し、設定可能な履歴バーでシグナルを評価し、StockSharpの注文とリスクヘルパーで元のストップ/テイクワークフローを再現します。
動作方法
インジケーターパイプライン – 完了した各ローソク足がFisher CGオシレーターを通過します:中央値価格が重心ループに供給され、値が最後のLengthバーで正規化され、フィッシャー変換がオシレーターラインを生成します。トリガーラインは単純に1バー遅延したオシレーターです。
シグナル抽出 – 戦略はSignalBarで定義された2つの履歴読み取りを検査します。古いオシレーター値(SignalBar + 1)がそのトリガーより上にある間、新しい値(SignalBar)がトリガーより上に再度クロスすると、強気の転換を示すとしてロングを開きます。ショートはこのロジックを弱気側に反転させます。
エグジット処理 – 古いオシレーターがトリガー以下に落ちるとすぐにロングエグジットが発生し、トリガー以上に上がるとショートエグジットが発動し、EAの即時クローズフラグに対応します。反対のエントリーは反転前にアクティブなポジションをクローズします。
バーごとの処理 – すべてがCandleTypeの完了したローソク足で実行されます;イントラバートレードは生成されず、決定論的なバックテストを確保し、EAの「新しいバー」ゲートと一致します。
リスク管理とポジションサイジング
ストップ/目標 – StopLossPointsとTakeProfitPointsは計器ステップで表され、Security.PriceStepを通じて絶対価格距離に変換されます。
ボリューム制御 – SizingMode = FixedVolumeは定数のFixedVolumeを送ります。SizingMode = PortfolioShareは最新のクローズとVolumeStepを使用して、現在のポートフォリオ価値のDepositShareを契約に変換します。
単一ポジション – 戦略は常に反対側に入る前にフラット化し、同時ヘッジポジションを避けます。
パラメーター
パラメーター
説明
CandleType
ローソク足とインジケーター計算のための購読済みタイムフレーム。
Length
Fisher CGオシレーター期間(正規化ウィンドウにも使用)。
SignalBar
シグナル読み取りに使用するクローズ済みローソク足の数;1はEAのデフォルトと一致します。
AllowLongEntry / AllowShortEntry
ロング/ショートエントリーを切り替えます。
AllowLongExit / AllowShortExit
ロング/ショートポジションの自動エグジットを切り替えます。
StopLossPoints / TakeProfitPoints
価格ステップでの保護ストップとターゲット距離。無効にするには0に設定します。
FixedVolume
固定サイジングモードで使用するボリューム。
DepositShare
PortfolioShareモードでのトレードあたりのポートフォリオ割合。
SizingMode
固定ボリュームとシェアベースのポジションサイジングの間で選択します。
使用上の注意
CandleTypeとSignalBarを元のインジケーターが使用するタイムフレームに合わせます(デフォルトはH8とバーシフト1)。
オシレーターが十分な履歴を形成できるように短いウォームアップ期間を設けてください;インジケーターが完全に初期化されるまで戦略はトレードを無視します。
ストップとターゲットはローソク足のクローズで動作します。お使いの計器のティックサイズに合わせてポイント値を調整してください。
PortfolioShareサイジングが選択された場合、ポートフォリオ評価が利用可能であることを確認してください;そうでなければ戦略は固定ボリュームにフォールバックします。
元のEAとの違い
注文はスリッページパラメーターDeviation_なしで成行注文として送信されます;StockSharpは独自のスリッページ設定で実行を処理します。
マネー管理は2つのサイジングモード(FixedVolumeとPortfolioShare)に簡略化されます。EAの損失パーセンテージオプションは意図的に省略されます。
保留中の注文タイムスタンプ(UpSignalTime/DnSignalTime)は使用されません。シグナルは処理されたローソク足で即座に実行されます。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
using StockSharp.Algo.Candles;
/// <summary>
/// Fisher Center of Gravity oscillator crossover strategy.
/// </summary>
public class ExpFisherCgOscillatorStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _medianPrices = new();
private readonly List<decimal> _cgValues = new();
private readonly decimal[] _valueBuffer = new decimal[4];
private int _valueCount;
private decimal? _previousFisher;
private readonly List<(decimal Main, decimal Trigger)> _oscillatorHistory = new();
private decimal? _entryPrice;
private int _length = 10;
public DataType CandleType { get => _candleType.Value; set => _candleType.Value = value; }
public ExpFisherCgOscillatorStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame()).SetDisplay("Candle Type", "Timeframe", "General");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities() => [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_medianPrices.Clear();
_cgValues.Clear();
Array.Clear(_valueBuffer);
_valueCount = 0;
_previousFisher = null;
_oscillatorHistory.Clear();
_entryPrice = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
OnReseted();
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(ProcessCandle)
.Start();
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
// Calculate Fisher CG oscillator inline
var price = (candle.HighPrice + candle.LowPrice) / 2m;
_medianPrices.Add(price);
while (_medianPrices.Count > _length)
_medianPrices.RemoveAt(0);
if (_medianPrices.Count < _length)
return;
decimal num = 0m;
decimal denom = 0m;
var weight = 1;
for (var index = _medianPrices.Count - 1; index >= 0; index--)
{
var median = _medianPrices[index];
num += weight * median;
denom += median;
weight++;
}
decimal cg;
if (denom != 0m)
cg = -num / denom + (_length + 1m) / 2m;
else
cg = 0m;
_cgValues.Add(cg);
while (_cgValues.Count > _length)
_cgValues.RemoveAt(0);
var high = cg;
var low = cg;
for (var i = 0; i < _cgValues.Count; i++)
{
var v = _cgValues[i];
if (v > high) high = v;
if (v < low) low = v;
}
decimal normalized;
if (high != low)
normalized = (cg - low) / (high - low);
else
normalized = 0m;
var limit = Math.Min(_valueCount, 3);
for (var shift = limit; shift > 0; shift--)
_valueBuffer[shift] = _valueBuffer[shift - 1];
_valueBuffer[0] = normalized;
if (_valueCount < 4)
_valueCount++;
if (_valueCount < 4)
return;
var value2 = (4m * _valueBuffer[0] + 3m * _valueBuffer[1] + 2m * _valueBuffer[2] + _valueBuffer[3]) / 10m;
var x = 1.98m * (value2 - 0.5m);
if (x > 0.999m)
x = 0.999m;
else if (x < -0.999m)
x = -0.999m;
var numerator = 1m + x;
var denominator = 1m - x;
if (denominator == 0m)
denominator = 0.0000001m;
var ratio = numerator / denominator;
if (ratio <= 0m)
ratio = 0.0000001m;
var fisher = 0.5m * (decimal)Math.Log((double)ratio);
var trigger = _previousFisher ?? fisher;
_previousFisher = fisher;
// Store history
_oscillatorHistory.Add((fisher, trigger));
while (_oscillatorHistory.Count > 10)
_oscillatorHistory.RemoveAt(0);
if (_oscillatorHistory.Count < 3)
return;
// Handle risk management
HandleRiskManagement(candle.ClosePrice);
if (!IsFormedAndOnlineAndAllowTrading())
return;
var current = _oscillatorHistory[^1];
var previous = _oscillatorHistory[^2];
var previousAbove = previous.Main > previous.Trigger;
var previousBelow = previous.Main < previous.Trigger;
var buyOpen = previousAbove && current.Main <= current.Trigger;
var sellOpen = previousBelow && current.Main >= current.Trigger;
var buyClose = previousBelow;
var sellClose = previousAbove;
if (sellClose && Position < 0)
{
BuyMarket();
_entryPrice = null;
}
if (buyClose && Position > 0)
{
SellMarket();
_entryPrice = null;
}
if (buyOpen && Position <= 0)
{
if (Position < 0)
{
BuyMarket();
_entryPrice = null;
return;
}
BuyMarket();
_entryPrice = candle.ClosePrice;
}
else if (sellOpen && Position >= 0)
{
if (Position > 0)
{
SellMarket();
_entryPrice = null;
return;
}
SellMarket();
_entryPrice = candle.ClosePrice;
}
}
private void HandleRiskManagement(decimal closePrice)
{
if (_entryPrice is null || Position == 0)
return;
var step = Security?.PriceStep ?? 1m;
if (step <= 0m) step = 1m;
var stopDistance = 1000 * step;
var takeDistance = 2000 * step;
if (Position > 0)
{
if (closePrice <= _entryPrice.Value - stopDistance)
{
SellMarket();
_entryPrice = null;
return;
}
if (closePrice >= _entryPrice.Value + takeDistance)
{
SellMarket();
_entryPrice = null;
}
}
else if (Position < 0)
{
if (closePrice >= _entryPrice.Value + stopDistance)
{
BuyMarket();
_entryPrice = null;
return;
}
if (closePrice <= _entryPrice.Value - takeDistance)
{
BuyMarket();
_entryPrice = null;
}
}
}
}
import clr
import math
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
class exp_fisher_cg_oscillator_strategy(Strategy):
def __init__(self):
super(exp_fisher_cg_oscillator_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Timeframe", "General")
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
self._length = 10
@property
def CandleType(self):
return self._candle_type.Value
def OnReseted(self):
super(exp_fisher_cg_oscillator_strategy, self).OnReseted()
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
def OnStarted2(self, time):
super(exp_fisher_cg_oscillator_strategy, self).OnStarted2(time)
self._median_prices = []
self._cg_values = []
self._value_buffer = [0.0, 0.0, 0.0, 0.0]
self._value_count = 0
self._previous_fisher = None
self._oscillator_history = []
self._entry_price = None
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._on_process).Start()
def _on_process(self, candle):
if candle.State != CandleStates.Finished:
return
price = (float(candle.HighPrice) + float(candle.LowPrice)) / 2.0
self._median_prices.append(price)
while len(self._median_prices) > self._length:
self._median_prices.pop(0)
if len(self._median_prices) < self._length:
return
num = 0.0
denom = 0.0
weight = 1
for i in range(len(self._median_prices) - 1, -1, -1):
median = self._median_prices[i]
num += weight * median
denom += median
weight += 1
if denom != 0.0:
cg = -num / denom + (self._length + 1.0) / 2.0
else:
cg = 0.0
self._cg_values.append(cg)
while len(self._cg_values) > self._length:
self._cg_values.pop(0)
high = cg
low = cg
for v in self._cg_values:
if v > high:
high = v
if v < low:
low = v
if high != low:
normalized = (cg - low) / (high - low)
else:
normalized = 0.0
limit = min(self._value_count, 3)
shift = limit
while shift > 0:
self._value_buffer[shift] = self._value_buffer[shift - 1]
shift -= 1
self._value_buffer[0] = normalized
if self._value_count < 4:
self._value_count += 1
if self._value_count < 4:
return
value2 = (4.0 * self._value_buffer[0] + 3.0 * self._value_buffer[1] + 2.0 * self._value_buffer[2] + self._value_buffer[3]) / 10.0
x = 1.98 * (value2 - 0.5)
if x > 0.999:
x = 0.999
elif x < -0.999:
x = -0.999
numerator = 1.0 + x
denominator = 1.0 - x
if denominator == 0.0:
denominator = 0.0000001
ratio = numerator / denominator
if ratio <= 0.0:
ratio = 0.0000001
fisher = 0.5 * math.log(ratio)
trigger = self._previous_fisher if self._previous_fisher is not None else fisher
self._previous_fisher = fisher
self._oscillator_history.append((fisher, trigger))
while len(self._oscillator_history) > 10:
self._oscillator_history.pop(0)
if len(self._oscillator_history) < 3:
return
self._handle_risk_management(float(candle.ClosePrice))
current = self._oscillator_history[-1]
previous = self._oscillator_history[-2]
previous_above = previous[0] > previous[1]
previous_below = previous[0] < previous[1]
buy_open = previous_above and current[0] <= current[1]
sell_open = previous_below and current[0] >= current[1]
if previous_above and self.Position < 0:
self.BuyMarket()
self._entry_price = None
if previous_below and self.Position > 0:
self.SellMarket()
self._entry_price = None
if buy_open and self.Position <= 0:
if self.Position < 0:
self.BuyMarket()
self._entry_price = None
return
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
elif sell_open and self.Position >= 0:
if self.Position > 0:
self.SellMarket()
self._entry_price = None
return
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
def _handle_risk_management(self, close_price):
if self._entry_price is None or self.Position == 0:
return
sec = self.Security
step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None else 1.0
if step <= 0.0:
step = 1.0
stop_distance = 1000 * step
take_distance = 2000 * step
if self.Position > 0:
if close_price <= self._entry_price - stop_distance:
self.SellMarket()
self._entry_price = None
return
if close_price >= self._entry_price + take_distance:
self.SellMarket()
self._entry_price = None
elif self.Position < 0:
if close_price >= self._entry_price + stop_distance:
self.BuyMarket()
self._entry_price = None
return
if close_price <= self._entry_price - take_distance:
self.BuyMarket()
self._entry_price = None
def CreateClone(self):
return exp_fisher_cg_oscillator_strategy()