GitHub で見る
Expert NEWSエキスパート戦略
概要
Expert NEWS戦略はMQL5ロボット「Expert_NEWS」の直接移植版です。この戦略は現在の市場価格の上下に対称的なストップ注文を継続的に配置し、ブレイクイーブン保護、トレーリングストップ、および保留注文のスケジュール更新によって結果ポジションを管理します。実装はLevel1クォートに依存し、デフォルト取引量は0.1ロットに設定されています。
取引ロジック
- クォート購読 – 戦略は最良のビッド/アスク更新を監視し、最新値からオーダー価格を計算します。
- 初期ストップ注文 – ロングポジションまたはバイストップが存在しない場合、
ask + EntryOffsetTicks * PriceStepに新しいバイストップを配置します。ショートポジションまたはセルストップが存在しない場合、bid - EntryOffsetTicks * PriceStepにセルストップを配置します。
- 注文更新 –
OrderRefreshSecondsごとに、必要価格がTrailingStepTicksティック以上乖離した場合、戦略は保留中のストップをキャンセルして再作成します。
- ポジション保護 – 約定後、要求距離が
MinimumStopTicks制約を満たす場合、戦略は保護的なストップとテイクプロフィット注文を開きます。
- ブレイクイーブン制御 –
UseBreakEvenが有効な場合、市場が十分に動き、新しいストップが現在のクォートからの最小距離を尊重すれば、ストップはエントリー ± BreakEvenProfitTicksに引き寄せられます。
- トレーリングストップ – 価格が
TrailingStartTicks進むと、ストップはTrailingStopTicksを距離として、TrailingStepTicksを最小改善ステップとして追従します。
- クリーンアップ – ポジションを閉じると残りの保護注文がすべてキャンセルされます。
パラメーター
| パラメーター |
説明 |
StopLossTicks |
初期保護ストップ距離(ティック)。初期ストップ注文を無効にするにはゼロに設定します。 |
TakeProfitTicks |
初期テイクプロフィット距離(ティック)。ターゲット注文を無効にするにはゼロに設定します。 |
TrailingStopTicks |
トレーリングストップの距離(ティック)。 |
TrailingStartTicks |
トレーリングロジックが有効になる前に必要な利益(ティック)。 |
TrailingStepTicks |
トレーリングストップまたは保留中のエントリー注文を更新する際の最小改善量。 |
UseBreakEven |
十分な利益がある場合にストップのブレイクイーブンシフトを有効にします。 |
BreakEvenProfitTicks |
ストップをブレイクイーブンに移動する際の追加利益クッション。 |
EntryOffsetTicks |
現在のクォートと各新規ストップエントリー注文の間の距離。 |
OrderRefreshSeconds |
保留中のストップ注文の自動更新試行間隔。 |
MinimumStopTicks |
ブローカーのストップレベル要件の手動フォールバック。この距離より近いストップは送信されません。 |
ポジション管理
- 保護注文は常にネットポジション量と一致します。部分約定はストップとテイクプロフィット注文を自動的にリサイズします。
- ブレイクイーブンとトレーリングロジックは初期ストップが無効な場合でも機能します;ルールが満たされるとストップは動的に作成されます。
- 戦略はトレーリング更新が単調な動作を維持するよう、最新のストップ価格をメモリに保持します。
使用上の注意
Security.PriceStepが設定されていることを確認してください;すべてのティック距離パラメーターはこの値で乗算されます。
- デフォルトの取引量は元のロボットを反映した
0.1です。別のサイズが必要な場合はVolumeプロパティを調整してください。
- 取引会場がストップレベル要件を課す場合は
MinimumStopTicksをその値に設定してください。最も厳しいストップを許可するにはゼロのままにしてください。
- アルゴリズムは過去のバーに依存せず、ストリーミングクォートのみで動作できます。
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>
/// Breakout strategy converted from the MQL Expert NEWS robot.
/// Detects breakouts above/below a reference price range and enters positions.
/// Uses stop loss and take profit for position management.
/// </summary>
public class ExpertNewsStrategy : Strategy
{
private readonly StrategyParam<decimal> _entryOffset;
private readonly StrategyParam<decimal> _stopLoss;
private readonly StrategyParam<decimal> _takeProfit;
private readonly StrategyParam<int> _lookbackPeriod;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _highs = new();
private readonly List<decimal> _lows = new();
private decimal _entryPrice;
private int _lastSignal;
/// <summary>
/// Entry offset from the high/low range.
/// </summary>
public decimal EntryOffset
{
get => _entryOffset.Value;
set => _entryOffset.Value = value;
}
/// <summary>
/// Stop loss distance in price units.
/// </summary>
public decimal StopLoss
{
get => _stopLoss.Value;
set => _stopLoss.Value = value;
}
/// <summary>
/// Take profit distance in price units.
/// </summary>
public decimal TakeProfit
{
get => _takeProfit.Value;
set => _takeProfit.Value = value;
}
/// <summary>
/// Number of bars to determine high/low range.
/// </summary>
public int LookbackPeriod
{
get => _lookbackPeriod.Value;
set => _lookbackPeriod.Value = value;
}
/// <summary>
/// Candle type for calculations.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public ExpertNewsStrategy()
{
_entryOffset = Param(nameof(EntryOffset), 200m)
.SetGreaterThanZero()
.SetDisplay("Entry Offset", "Offset from range high/low for entry", "Parameters");
_stopLoss = Param(nameof(StopLoss), 1000m)
.SetGreaterThanZero()
.SetDisplay("Stop Loss", "Stop loss in price units", "Risk");
_takeProfit = Param(nameof(TakeProfit), 2000m)
.SetGreaterThanZero()
.SetDisplay("Take Profit", "Take profit in price units", "Risk");
_lookbackPeriod = Param(nameof(LookbackPeriod), 20)
.SetGreaterThanZero()
.SetDisplay("Lookback Period", "Bars for range calculation", "Parameters");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(1).TimeFrame())
.SetDisplay("Candle Type", "Type of candles", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, CandleType)];
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_highs.Clear();
_lows.Clear();
_entryPrice = 0m;
_lastSignal = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
SubscribeCandles(CandleType)
.Bind(ProcessCandle)
.Start();
StartProtection(
new Unit(TakeProfit, UnitTypes.Absolute),
new Unit(StopLoss, UnitTypes.Absolute));
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_highs.Add(candle.HighPrice);
_lows.Add(candle.LowPrice);
if (_highs.Count > LookbackPeriod + 1)
_highs.RemoveAt(0);
if (_lows.Count > LookbackPeriod + 1)
_lows.RemoveAt(0);
if (_highs.Count <= LookbackPeriod)
return;
// Compute range from prior bars (excluding current)
var rangeHigh = decimal.MinValue;
var rangeLow = decimal.MaxValue;
for (int i = 0; i < _highs.Count - 1; i++)
{
if (_highs[i] > rangeHigh) rangeHigh = _highs[i];
if (_lows[i] < rangeLow) rangeLow = _lows[i];
}
var close = candle.ClosePrice;
var breakoutUp = close > rangeHigh + EntryOffset;
var breakoutDown = close < rangeLow - EntryOffset;
if (breakoutUp && _lastSignal != 1 && Position <= 0)
{
BuyMarket();
_entryPrice = close;
_lastSignal = 1;
}
else if (breakoutDown && _lastSignal != -1 && Position >= 0)
{
SellMarket();
_entryPrice = close;
_lastSignal = -1;
}
else if (!breakoutUp && !breakoutDown)
_lastSignal = 0;
}
}
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, Unit, UnitTypes
from StockSharp.Algo.Strategies import Strategy
class expert_news_strategy(Strategy):
def __init__(self):
super(expert_news_strategy, self).__init__()
self._entry_offset = self.Param("EntryOffset", 200.0)
self._stop_loss = self.Param("StopLoss", 1000.0)
self._take_profit = self.Param("TakeProfit", 2000.0)
self._lookback_period = self.Param("LookbackPeriod", 20)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(1)))
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
@property
def EntryOffset(self):
return self._entry_offset.Value
@EntryOffset.setter
def EntryOffset(self, value):
self._entry_offset.Value = value
@property
def StopLoss(self):
return self._stop_loss.Value
@StopLoss.setter
def StopLoss(self, value):
self._stop_loss.Value = value
@property
def TakeProfit(self):
return self._take_profit.Value
@TakeProfit.setter
def TakeProfit(self, value):
self._take_profit.Value = value
@property
def LookbackPeriod(self):
return self._lookback_period.Value
@LookbackPeriod.setter
def LookbackPeriod(self, value):
self._lookback_period.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
def OnStarted2(self, time):
super(expert_news_strategy, self).OnStarted2(time)
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.ProcessCandle).Start()
self.StartProtection(
Unit(self.TakeProfit, UnitTypes.Absolute),
Unit(self.StopLoss, UnitTypes.Absolute))
def ProcessCandle(self, candle):
if candle.State != CandleStates.Finished:
return
h = float(candle.HighPrice)
l = float(candle.LowPrice)
close = float(candle.ClosePrice)
self._highs.append(h)
self._lows.append(l)
period = int(self.LookbackPeriod)
if len(self._highs) > period + 1:
self._highs.pop(0)
if len(self._lows) > period + 1:
self._lows.pop(0)
if len(self._highs) <= period:
return
range_high = -1e18
range_low = 1e18
for i in range(len(self._highs) - 1):
if self._highs[i] > range_high:
range_high = self._highs[i]
if self._lows[i] < range_low:
range_low = self._lows[i]
offset = float(self.EntryOffset)
breakout_up = close > range_high + offset
breakout_down = close < range_low - offset
if breakout_up and self._last_signal != 1 and self.Position <= 0:
self.BuyMarket()
self._entry_price = close
self._last_signal = 1
elif breakout_down and self._last_signal != -1 and self.Position >= 0:
self.SellMarket()
self._entry_price = close
self._last_signal = -1
elif not breakout_up and not breakout_down:
self._last_signal = 0
def OnReseted(self):
super(expert_news_strategy, self).OnReseted()
self._highs = []
self._lows = []
self._entry_price = 0.0
self._last_signal = 0
def CreateClone(self):
return expert_news_strategy()