GitHub で見る
Boring EA2アラート
概要
Boring EA2 Alertは、MetaTrader 4エキスパートアドバイザーboring-ea2の通知ロジックを再現します。戦略は確定ローソク足を監視し、3本の単純移動平均(SMA 3、SMA 20、SMA 150)を計算し、移動平均間でクロスが発生すると情報ログを出します。実装は意図的に注文配置を避けています。目的は、トレーダーが裁量執行や他の自動戦略と組み合わせられるタイムリーなアラートを提供することです。
戦略ロジック
移動平均の追跡
- 短期バイアス: 3期間SMAは直近の価格変化に反応します。
- 中期トレンド: 20期間SMAは短期スイングの範囲で価格を平滑化します。
- 長期トレンド: 150期間SMAは支配的な背景トレンドを表します。
クロス検出
- SMA3 vs SMA20: SMA3がSMA20を上回ると"crossed up"、下回ると"crossed down"を報告します。内部フラグにより各遷移は一度だけ報告されます。
- SMA3 vs SMA150: 長期平均に対して同じロジックを反映し、支配的トレンドに対するモメンタム急増または反転を検出します。
- SMA20 vs SMA150: 中長期の確認層を追加し、上位時間軸構造の変化が独自のアラートを発生させます。
- 初期化ガード: 最初の確定ローソク足は初期状態を設定するだけです。真の関係変化が観測されると、2本目の確定ローソク足からアラートが始まります。
通知形式
- アラートは元EAのメッセージを反映します:
Alert!!! - SYMBOL - TF - description。
- 時間軸コードは設定されたローソク足タイプから導かれます。利用可能な場合はMetaTrader風の標準ラベル(M1、M5、H1など)を使い、その他の時間軸はコンパクト表記(例:
M45またはD2)へフォールバックします。
- メッセージは
AddInfoLogで書き込まれ、ログビューアー、スクリプト、GUIダッシュボードへルーティングできます。
パラメーター
- Short SMA Length: 高速移動平均の期間数(既定
3)。
- Medium SMA Length: 中間移動平均の期間数(既定
20)。
- Long SMA Length: 低速移動平均の期間数(既定
150)。
- Candle Type: 移動平均の計算に使う時間軸。既定は1分足で、EAのティックベース確認に近い高い反応性を持ちます。
追加メモ
- 戦略は注文を送信、変更、キャンセルしません。純粋に情報提供用です。
Bindは確定値を供給するため、各クロスは確定ローソク足で評価されます。これにより、元EAがティック数で緩和していたノイズの多いバー内反転を避けます。
- ログベース通知は、ホストアプリケーション内で戦略ログイベントを購読することでカスタムハンドラーと統合できます。
- 現時点ではPython翻訳は提供されていません。APIパッケージにはC#版のみ含まれます。
using System;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Boring EA2 strategy: Triple SMA crossover.
/// Buys when fast crosses above medium while medium above slow.
/// Sells when fast crosses below medium while medium below slow.
/// </summary>
public class BoringEa2Strategy : Strategy
{
private readonly StrategyParam<DataType> _candleType;
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public BoringEa2Strategy()
{
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(15).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe", "General");
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fast = new SimpleMovingAverage { Length = 10 };
var med = new SimpleMovingAverage { Length = 20 };
var slow = new SimpleMovingAverage { Length = 40 };
decimal? prevFast = null;
decimal? prevMed = null;
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fast, med, slow, (candle, fastVal, medVal, slowVal) =>
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (prevFast.HasValue && prevMed.HasValue)
{
var fastCrossUp = prevFast.Value <= prevMed.Value && fastVal > medVal;
var fastCrossDown = prevFast.Value >= prevMed.Value && fastVal < medVal;
if (fastCrossUp && medVal > slowVal && Position <= 0)
BuyMarket();
else if (fastCrossDown && medVal < slowVal && Position >= 0)
SellMarket();
}
prevFast = fastVal;
prevMed = medVal;
})
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, fast);
DrawIndicator(area, med);
DrawIndicator(area, slow);
DrawOwnTrades(area);
}
}
}
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 SimpleMovingAverage
from StockSharp.Algo.Strategies import Strategy
class boring_ea2_strategy(Strategy):
def __init__(self):
super(boring_ea2_strategy, self).__init__()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnReseted(self):
super(boring_ea2_strategy, self).OnReseted()
self._fast = None
self._med = None
self._slow = None
self._prev_fast = None
self._prev_med = None
def OnStarted2(self, time):
super(boring_ea2_strategy, self).OnStarted2(time)
self._fast = SimpleMovingAverage()
self._fast.Length = 10
self._med = SimpleMovingAverage()
self._med.Length = 20
self._slow = SimpleMovingAverage()
self._slow.Length = 40
subscription = self.SubscribeCandles(DataType.TimeFrame(TimeSpan.FromMinutes(15)))
subscription.Bind(self._fast, self._med, self._slow, self._process_candle)
subscription.Start()
def _process_candle(self, candle, fast_value, med_value, slow_value):
if candle.State != CandleStates.Finished:
return
if not self._fast.IsFormed or not self._med.IsFormed or not self._slow.IsFormed:
return
fast_val = float(fast_value)
med_val = float(med_value)
slow_val = float(slow_value)
if self._prev_fast is not None and self._prev_med is not None:
fast_cross_up = self._prev_fast <= self._prev_med and fast_val > med_val
fast_cross_down = self._prev_fast >= self._prev_med and fast_val < med_val
if fast_cross_up and med_val > slow_val and self.Position <= 0:
self.BuyMarket()
elif fast_cross_down and med_val < slow_val and self.Position >= 0:
self.SellMarket()
self._prev_fast = fast_val
self._prev_med = med_val
def CreateClone(self):
return boring_ea2_strategy()