GitHub で見る
KA-Gold ボット戦略
KA-Gold ボット戦略 は、元の MetaTrader 4「KA-Gold ボット」エキスパート アドバイザーの高レベルの StockSharp 変換です。ケルトナー スタイルのチャネルとトレンド フィルター、および固定ストップロス、テイクプロフィット、多段階のトレーリング保護を含む積極的なリスク管理を組み合わせています。取引は設定可能な日中ウィンドウ中にのみ許可され、ライブスプレッドがしきい値を超えると新しいポジションはブロックされます。
取引ロジック
インジケーターの準備
- 長さ
KeltnerPeriod の指数移動平均 (EMA) がチャネルの正中線を構築します。
- 同じ期間のローソク足の範囲(高値から安値を引いたもの)の単純移動平均により、チャネルの半値幅が推定されます。
- 短期および長期の指数移動平均 (
EmaShortPeriod および EmaLongPeriod) は、それぞれ速い勢いとより高い時間枠のトレンドを追跡します。
- すべてのインジケーター値は、MT4 シフトベースの計算を反映するために、最後に完了した 2 つのローソク足に対して記録されます。
エントリー条件
- 計算は、現在のローソク足が終了し、取引許可が付与されて戦略が市場に接続された場合にのみ実行されます。
- チャネルの上部バンドと下部バンドは、前の (
shift = 1) と前の (shift = 2) の両方のローソク足の EMA 正中線から平均範囲を加算または減算することによって導出されます。
- 長いセットアップ:
- 前回の終値は直近のアッパーバンドを上抜けます。
- 同じ終値は長期の EMA を上回っており、上昇トレンドが確認されています。
- 短い EMA は、古い上部バンドの下から最新のバンド(
EMA_short[2] < Upper[2] と EMA_short[1] > Upper[1])の上まで交差します。
- 簡単なセットアップ:
- 前回終値は最近の下値バンドを下回っています。
- 同じ終値はロングの EMA を下回っており、下降トレンドが確認されています。
- 短い EMA は、古い下のバンドの上から最新のバンドの下まで交差します (
EMA_short[2] > Lower[2] と EMA_short[1] < Lower[1])。
- 一度に許可されるポジションは 1 つだけです。取引がすでに開始されている場合、シグナルは無視されます。
タイミングフィルターとスプレッドフィルター
UseTimeFilter が有効な場合、新しいエントリは取引所現地時間を使用する [StartHour:StartMinute, EndHour:EndMinute) ウィンドウに制限されます。終了時刻が開始時刻より早い場合、夜間セッションがサポートされます。
- レベル 1 の見積もりサブスクリプションは、最良の買値/売値を追跡します。注文を行う前に、このストラテジーは現在のスプレッドを商品ポイントに変換し、それを
MaxSpreadPoints と比較します。しきい値に違反すると、注文はログに記録されてスキップされます。
リスク管理
- ポジションのサイジングのデフォルトは
FixedVolume です。 UseRiskPercent が true の場合、取引サイズはポートフォリオの資本から RiskPercent% / (riskPips * PipValue) として再計算されます。ここで、riskPips は StopLossPips と等しくなります (固定ストップが定義されていない場合は、TrailingStopPips にフォールバックします)。最終結果は機器の容量ステップに正規化され、最小交換限界と最大交換限界の間にクランプされます。
- ロングポジションがオープンされると、ストラテジーは以下を保存します。
- 最初のストップロスは
entry - StopLossPips * pipSize (定義されている場合)。
- 最初のテイクプロフィットは
entry + TakeProfitPips * pipSize (定義されている場合)。
- トレーリング状態フラグ。ショートサイド トラッカーをリセットします。
- ショートトレードは、価格の方向が反転した同じロジックを反映しています。
トレーリングプロテクション
- ライブ入札/売値更新は 2 つの後続エンジンにフィードされます。
- 変動利益が
TrailingTriggerPips を超えると、トレーリングがアクティブになります。
- トレーリングストップは現在の有利な価格から
TrailingStopPips 離れた位置にあり、動きが前のストップレベルを超えて TrailingStopPips + TrailingStepPips を超えた場合にのみ前進します。
- ロングポジションの場合、トレーリングストップは元の保護ストップを下回ることはなく、ショートポジションの場合、それを超えることはありません。
- 終了監視は、入ってくる相場と終了したローソク足の両方で実行されます。
- 価格がアクティブストップ(オリジナルまたはトレーリング)に達すると、ポジションはすぐにクローズされます。
- ローソク足の高値/安値が保存されたテイクプロフィットレベルに達すると、利益も確定します。
- ポジションを決済した後、データが古くならないように保護状態が完全にリセットされます。
パラメーター
| パラメータ |
説明 |
デフォルト |
CandleType |
実行時間枠を表すデータ型。 |
1分の時間枠 |
KeltnerPeriod |
EMA 正中線の期間とチャネルの範囲平均。 |
50 |
EmaShortPeriod |
高速の EMA の長さはクロスオーバーの確認に使用されます。 |
10 |
EmaLongPeriod |
トレンド フィルターとして機能する低速の EMA 長さ。 |
200 |
FixedVolume |
パーセンテージサイジングが無効になっている場合のフォールバック注文量。 |
1 |
UseRiskPercent |
パーセンテージベースのポジションサイジングを有効にします。 |
true |
RiskPercent |
取引ごとにリスクが生じる株式の割合。 |
1 |
StopLossPips |
固定ストップロスの距離 (ピップ単位) (0 は無効)。 |
500 |
TakeProfitPips |
ピップ単位での固定テイクプロフィットの距離 (0 を無効にします)。 |
500 |
TrailingTriggerPips |
トレーリングストップを有効にするために必要な利益 (pips)。 |
300 |
TrailingStopPips |
アクティブになった後の価格とトレーリングストップの間の距離。 |
300 |
TrailingStepPips |
トレーリングストップが前進する前の最小追加利益(ピップ単位)。 |
100 |
UseTimeFilter |
取引セッションフィルターを切り替えます。 |
true |
StartHour / StartMinute |
セッションは交換現地時間で開始されます。 |
02:30 |
EndHour / EndMinute |
セッションは交換現地時間で終了します。 |
21:00 |
MaxSpreadPoints |
機器ポイントで許容される最大スプレッド (0 はチェックを無効にします)。 |
65 |
PipValue |
1 ピップの金銭的価値。リスクベースのポジションサイジングに使用されます。 |
1 |
追加メモ
- ピップ変換は、為替商品の小数点以下に従います。5 桁の相場 (小数点以下の奇数) は、価格ステップを 10 倍して、MT4 ピップ サイズ ロジックをエミュレートします。
- この戦略はローソク足とレベル 1 データの両方をサブスクライブしますが、チャート上に追加のインジケーターを登録することはなく、高レベルの API ガイドラインに準拠しています。
- 保護的出口は、戦略によって発行された成行注文に依存します。取引所では個別のストップ注文や指値注文は行われません。
- Python サポートは、元のリクエストと一致するため、この配信には含まれていません。
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>
/// Simplified from "KA Gold Bot" MetaTrader expert.
/// Uses Keltner channel (EMA + ATR-based bands) with EMA crossover for entries.
/// Buys when short EMA crosses above long EMA and close is above Keltner center.
/// Sells on reverse conditions.
/// </summary>
public class KaGoldBotStrategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<int> _keltnerPeriod;
private readonly StrategyParam<int> _emaShortPeriod;
private readonly StrategyParam<int> _emaLongPeriod;
private ExponentialMovingAverage _emaShort;
private ExponentialMovingAverage _emaLong;
// Manual ATR-like range average for Keltner
private readonly Queue<decimal> _rangeQueue = new();
private decimal _rangeSum;
private ExponentialMovingAverage _emaKeltner;
private decimal? _prevEmaShort;
private decimal? _prevEmaLong;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public int KeltnerPeriod
{
get => _keltnerPeriod.Value;
set => _keltnerPeriod.Value = value;
}
public int EmaShortPeriod
{
get => _emaShortPeriod.Value;
set => _emaShortPeriod.Value = value;
}
public int EmaLongPeriod
{
get => _emaLongPeriod.Value;
set => _emaLongPeriod.Value = value;
}
public KaGoldBotStrategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe", "General");
_keltnerPeriod = Param(nameof(KeltnerPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Keltner Period", "EMA period for Keltner channel center", "Indicators");
_emaShortPeriod = Param(nameof(EmaShortPeriod), 10)
.SetGreaterThanZero()
.SetDisplay("EMA Short Period", "Short EMA for crossover signal", "Indicators");
_emaLongPeriod = Param(nameof(EmaLongPeriod), 50)
.SetGreaterThanZero()
.SetDisplay("EMA Long Period", "Long EMA for crossover signal", "Indicators");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_emaShort = new ExponentialMovingAverage { Length = EmaShortPeriod };
_emaLong = new ExponentialMovingAverage { Length = EmaLongPeriod };
_emaKeltner = new ExponentialMovingAverage { Length = KeltnerPeriod };
_rangeQueue.Clear();
_rangeSum = 0;
_prevEmaShort = null;
_prevEmaLong = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_emaShort, _emaLong, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _emaShort);
DrawIndicator(area, _emaLong);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal emaShortValue, decimal emaLongValue)
{
if (candle.State != CandleStates.Finished)
return;
var close = candle.ClosePrice;
// Process Keltner EMA manually
var keltnerInput = new DecimalIndicatorValue(_emaKeltner, close, candle.OpenTime);
var keltnerResult = _emaKeltner.Process(keltnerInput);
var emaKeltnerValue = keltnerResult.IsEmpty ? close : keltnerResult.GetValue<decimal>();
// Calculate range average (manual SMA of high-low) for Keltner bands
var range = candle.HighPrice - candle.LowPrice;
_rangeQueue.Enqueue(range);
_rangeSum += range;
while (_rangeQueue.Count > KeltnerPeriod)
_rangeSum -= _rangeQueue.Dequeue();
if (_prevEmaShort == null || _prevEmaLong == null)
{
_prevEmaShort = emaShortValue;
_prevEmaLong = emaLongValue;
return;
}
// Keltner bands
var rangeAvg = _rangeQueue.Count > 0 ? _rangeSum / _rangeQueue.Count : 0;
var upper = emaKeltnerValue + rangeAvg;
var lower = emaKeltnerValue - rangeAvg;
// Buy: short EMA crosses above long EMA and close above Keltner center
var buySignal = _prevEmaShort.Value <= _prevEmaLong.Value && emaShortValue > emaLongValue
&& close > emaKeltnerValue;
// Sell: short EMA crosses below long EMA and close below Keltner center
var sellSignal = _prevEmaShort.Value >= _prevEmaLong.Value && emaShortValue < emaLongValue
&& close < emaKeltnerValue;
if (buySignal)
{
if (Position < 0)
BuyMarket();
if (Position <= 0)
BuyMarket();
}
else if (sellSignal)
{
if (Position > 0)
SellMarket();
if (Position >= 0)
SellMarket();
}
_prevEmaShort = emaShortValue;
_prevEmaLong = emaLongValue;
}
/// <inheritdoc />
protected override void OnReseted()
{
_emaShort = null;
_emaLong = null;
_emaKeltner = null;
_rangeQueue.Clear();
_rangeSum = 0;
_prevEmaShort = null;
_prevEmaLong = null;
base.OnReseted();
}
}
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.Indicators import ExponentialMovingAverage
from StockSharp.Algo.Strategies import Strategy
class ka_gold_bot_strategy(Strategy):
def __init__(self):
super(ka_gold_bot_strategy, self).__init__()
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5)))
self._keltner_period = self.Param("KeltnerPeriod", 20)
self._ema_short_period = self.Param("EmaShortPeriod", 10)
self._ema_long_period = self.Param("EmaLongPeriod", 50)
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def KeltnerPeriod(self):
return self._keltner_period.Value
@KeltnerPeriod.setter
def KeltnerPeriod(self, value):
self._keltner_period.Value = value
@property
def EmaShortPeriod(self):
return self._ema_short_period.Value
@EmaShortPeriod.setter
def EmaShortPeriod(self, value):
self._ema_short_period.Value = value
@property
def EmaLongPeriod(self):
return self._ema_long_period.Value
@EmaLongPeriod.setter
def EmaLongPeriod(self, value):
self._ema_long_period.Value = value
def OnReseted(self):
super(ka_gold_bot_strategy, self).OnReseted()
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
def OnStarted2(self, time):
super(ka_gold_bot_strategy, self).OnStarted2(time)
self._prev_ema_short = None
self._prev_ema_long = None
self._range_queue = []
self._range_sum = 0.0
self._keltner_ema = 0.0
self._keltner_count = 0
ema_short = ExponentialMovingAverage()
ema_short.Length = self.EmaShortPeriod
ema_long = ExponentialMovingAverage()
ema_long.Length = self.EmaLongPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(ema_short, ema_long, self._process_candle).Start()
def _process_candle(self, candle, ema_short_value, ema_long_value):
if candle.State != CandleStates.Finished:
return
ema_short_val = float(ema_short_value)
ema_long_val = float(ema_long_value)
close = float(candle.ClosePrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
# Manual Keltner EMA
keltner_period = self.KeltnerPeriod
if self._keltner_count == 0:
self._keltner_ema = close
else:
alpha = 2.0 / (keltner_period + 1)
self._keltner_ema = close * alpha + self._keltner_ema * (1 - alpha)
self._keltner_count += 1
# Range average for Keltner bands
bar_range = high - low
self._range_queue.append(bar_range)
self._range_sum += bar_range
while len(self._range_queue) > keltner_period:
self._range_sum -= self._range_queue.pop(0)
if self._prev_ema_short is None or self._prev_ema_long is None:
self._prev_ema_short = ema_short_val
self._prev_ema_long = ema_long_val
return
# Buy: short EMA crosses above long EMA and close above Keltner center
buy_signal = (self._prev_ema_short <= self._prev_ema_long and
ema_short_val > ema_long_val and close > self._keltner_ema)
# Sell: short EMA crosses below long EMA and close below Keltner center
sell_signal = (self._prev_ema_short >= self._prev_ema_long and
ema_short_val < ema_long_val and close < self._keltner_ema)
if buy_signal:
if self.Position <= 0:
self.BuyMarket()
elif sell_signal:
if self.Position >= 0:
self.SellMarket()
self._prev_ema_short = ema_short_val
self._prev_ema_long = ema_long_val
def CreateClone(self):
return ka_gold_bot_strategy()