Currencyprofits 高値-安値チャネル戦略
概要
この戦略は、MetaTraderのエキスパートアドバイザー Currencyprofits_01.1 のStockSharpポートです。高速/低速の移動平均トレンドフィルターと最近のチャネル極値のブレイクアウトを組み合わせています。高速移動平均が低速移動平均を上回っている場合、戦略は強気環境を予想し、前のチャネルウィンドウの最安値に価格が再テストするのを待ちます。ショートトレードは、高速平均が低速平均を下回り、価格がチャネルの最高値を再テストするときに取られます。
実装はローソク足データを提供するあらゆる銘柄で機能します。すべての計算はバックテストとライブ取引の両方で安定性を確保するために、クローズしたローソク足で実行されます。
トレードロジック
- 設定されたローソク足タイプを購読し、前の
ChannelLengthローソク足(デフォルト:6バー)に基づいて2つの移動平均とDonchianスタイルのチャネルを計算します。 - 1バーシフトを使用するオリジナルのMQLロジックを模倣するために、インジケーターからの前のローソク足値を保存します。
- ロングエントリー: 前の高速MAが前の低速MAより大きく、現在のローソク足の安値が前のチャネルの安値に触れるまたは突破する場合。
- ショートエントリー: 前の高速MAが前の低速MAより小さく、現在のローソク足の高値が前のチャネルの高値に触れるまたは突破する場合。
- エグジットルール:
- 次のローソク足が保存されたチャネルの高値を超えて終値を付けた場合、または保護ストップが到達した場合にロングポジションをクローズします。
- 次のローソク足が保存されたチャネルの安値を下回って終値を付けた場合、またはストップロスが発動した場合にショートポジションをクローズします。
- 一度に一つのポジションのみがアクティブです;トレードが開いている間、戦略は新しいシグナルを無視します。
ポジションサイジング
RiskPercentはトレードごとにリスクを取れるポートフォリオ価値の割合を定義します(デフォルト0.14、すなわち14%)。- ストップロス距離は
StopLossPointsに証券のPriceStepを掛けて導出されます(メタデータが利用できない場合はポイントを使用)。 - 1契約あたりの現金リスクは取引所ステップ値(
StepPrice)で推定されます。証券がこの情報を公開していない場合、生の価格距離が使用されます。 - 最終的な注文数量は銘柄の取引制約(
VolumeStep、MinVolume、MaxVolume)に合わせて調整されます。リスクベースのサイジングが計算できない場合、戦略の基本Volumeが使用されます。
パラメーター
FastLength– トレンドを検出するために使用する高速移動平均の長さ(デフォルト32)。FastMaType– 高速移動平均のタイプ(Simple、Exponential、Smoothed、Weighted)。SlowLength– 低速移動平均の長さ(デフォルト86)。SlowMaType– 低速移動平均のタイプ。PriceSource– 両方の移動平均に適用するローソク足価格(デフォルト:終値)。ChannelLength– 高値/安値チャネルを形成する前のローソク足の数(デフォルト6)。StopLossPoints– 価格に変換される前の銘柄ポイントで表したストップ距離(デフォルト170)。RiskPercent– トレードごとにリスクを取る資本の割合(デフォルト0.14 → 14%)。CandleType– すべての計算に使用するローソク足の時間軸(デフォルト1時間、希望するチャート期間に合わせて変更可能)。
使用上の注意
- 正確なポジションサイジングのために
Security.PriceStep、Security.StepPrice、および出来高メタデータが入力されていることを確認します。 - リスクベースのサイジングが無効の場合(例:
RiskPercent = 0)、戦略のVolumeに合理的なフォールバック値を設定します。 - ロジックはクローズしたローソク足で取引します;ライブ実行はシグナルを確認するバークローズ時に発生します。
- ストップロスは内部で管理されます;ソースのエキスパートアドバイザーを反映して、別のテイクプロフィットはありません。
ソース
可読性とStockSharpの高レベルAPIとの互換性を重視して MQL/17641/Currencyprofits_01.1.mq5 から変換されました。
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>
/// Currencyprofits strategy that trades trend pullbacks into the recent channel extremes.
/// </summary>
public class CurrencyprofitsHighLowChannelStrategy : Strategy
{
private readonly StrategyParam<int> _fastLength;
private readonly StrategyParam<int> _slowLength;
private readonly StrategyParam<int> _channelLength;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<DataType> _candleType;
private readonly StrategyParam<CandlePrices> _priceSource;
private readonly StrategyParam<MovingAverageTypes> _fastMaType;
private readonly StrategyParam<MovingAverageTypes> _slowMaType;
private readonly StrategyParam<int> _signalCooldownBars;
private decimal? _previousFast;
private decimal? _previousSlow;
private decimal? _previousHighest;
private decimal? _previousLowest;
private decimal _entryPrice;
private decimal _stopPrice;
private int _processedCandles;
private int _cooldownRemaining;
public int FastLength
{
get => _fastLength.Value;
set => _fastLength.Value = value;
}
public int SlowLength
{
get => _slowLength.Value;
set => _slowLength.Value = value;
}
public int ChannelLength
{
get => _channelLength.Value;
set => _channelLength.Value = value;
}
public decimal StopLossPoints
{
get => _stopLossPoints.Value;
set => _stopLossPoints.Value = value;
}
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public CandlePrices PriceSource
{
get => _priceSource.Value;
set => _priceSource.Value = value;
}
public MovingAverageTypes FastMaType
{
get => _fastMaType.Value;
set => _fastMaType.Value = value;
}
public MovingAverageTypes SlowMaType
{
get => _slowMaType.Value;
set => _slowMaType.Value = value;
}
public int SignalCooldownBars
{
get => _signalCooldownBars.Value;
set => _signalCooldownBars.Value = value;
}
private int RequiredBars => Math.Max(Math.Max(FastLength, SlowLength), ChannelLength) + 1;
public CurrencyprofitsHighLowChannelStrategy()
{
_fastLength = Param(nameof(FastLength), 32)
.SetDisplay("Fast MA Length", "Length of the fast moving average", "Indicators")
.SetOptimize(10, 120, 2);
_slowLength = Param(nameof(SlowLength), 86)
.SetDisplay("Slow MA Length", "Length of the slow moving average", "Indicators")
.SetOptimize(20, 200, 2);
_channelLength = Param(nameof(ChannelLength), 12)
.SetDisplay("Channel Lookback", "Number of previous candles for high/low channel", "Indicators")
.SetOptimize(3, 20, 1);
_stopLossPoints = Param(nameof(StopLossPoints), 170m)
.SetDisplay("Stop Loss (points)", "Distance to stop loss expressed in price steps", "Risk");
_riskPercent = Param(nameof(RiskPercent), 0.14m)
.SetDisplay("Risk Fraction", "Fraction of portfolio capital risked per trade", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(30).TimeFrame())
.SetDisplay("Candle Type", "Primary timeframe for calculations", "General");
_priceSource = Param(nameof(PriceSource), CandlePrices.Close)
.SetDisplay("MA Price Source", "Price source used by both moving averages", "Indicators");
_fastMaType = Param(nameof(FastMaType), MovingAverageTypes.Simple)
.SetDisplay("Fast MA Type", "Moving average algorithm for the fast line", "Indicators");
_slowMaType = Param(nameof(SlowMaType), MovingAverageTypes.Simple)
.SetDisplay("Slow MA Type", "Moving average algorithm for the slow line", "Indicators");
_signalCooldownBars = Param(nameof(SignalCooldownBars), 4)
.SetNotNegative()
.SetDisplay("Signal Cooldown Bars", "Closed candles to wait before the next entry", "Risk");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_previousFast = null;
_previousSlow = null;
_previousHighest = null;
_previousLowest = null;
_entryPrice = 0m;
_stopPrice = 0m;
_processedCandles = 0;
_cooldownRemaining = 0;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var fastMa = CreateMovingAverage(FastMaType, FastLength, PriceSource);
var slowMa = CreateMovingAverage(SlowMaType, SlowLength, PriceSource);
var highest = new Highest { Length = ChannelLength };
var lowest = new Lowest { Length = ChannelLength };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(fastMa, slowMa, highest, lowest, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal fast, decimal slow, decimal channelHigh, decimal channelLow)
{
if (candle.State != CandleStates.Finished)
return;
if (_cooldownRemaining > 0)
_cooldownRemaining--;
_processedCandles++;
if (_processedCandles <= RequiredBars)
{
// Collect enough history before taking any decisions.
_previousFast = fast;
_previousSlow = slow;
_previousHighest = channelHigh;
_previousLowest = channelLow;
return;
}
if (_previousFast is null || _previousSlow is null || _previousHighest is null || _previousLowest is null)
{
_previousFast = fast;
_previousSlow = slow;
_previousHighest = channelHigh;
_previousLowest = channelLow;
return;
}
if (Position > 0)
{
// Exit long trades when price breaks the opposite channel or the protective stop.
var exitByChannel = candle.ClosePrice >= _previousHighest.Value;
var exitByStop = _stopPrice > 0m && candle.LowPrice <= _stopPrice;
if (exitByChannel || exitByStop)
{
SellMarket(Position);
ResetTradeState();
_cooldownRemaining = SignalCooldownBars;
}
}
else if (Position < 0)
{
// Exit short trades when price hits the lower boundary or the stop.
var exitByChannel = candle.ClosePrice <= _previousLowest.Value;
var exitByStop = _stopPrice > 0m && candle.HighPrice >= _stopPrice;
if (exitByChannel || exitByStop)
{
BuyMarket(-Position);
ResetTradeState();
_cooldownRemaining = SignalCooldownBars;
}
}
else if (_cooldownRemaining == 0)
{
var stopDistance = GetStopDistance();
if (stopDistance > 0m)
{
var bullishTrend = _previousFast.Value > _previousSlow.Value && fast > slow;
var bearishTrend = _previousFast.Value < _previousSlow.Value && fast < slow;
var bullishReversal = candle.LowPrice <= _previousLowest.Value && candle.ClosePrice > candle.OpenPrice && candle.ClosePrice > fast;
var bearishReversal = candle.HighPrice >= _previousHighest.Value && candle.ClosePrice < candle.OpenPrice && candle.ClosePrice < fast;
// Long entries require a bullish trend and a pullback to the recent low channel.
if (bullishTrend && bullishReversal)
{
var volume = CalculatePositionSize(stopDistance);
if (volume > 0m)
{
BuyMarket(volume);
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - stopDistance;
_cooldownRemaining = SignalCooldownBars;
}
}
// Short entries require a bearish trend and a retest of the recent high channel.
else if (bearishTrend && bearishReversal)
{
var volume = CalculatePositionSize(stopDistance);
if (volume > 0m)
{
SellMarket(volume);
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + stopDistance;
_cooldownRemaining = SignalCooldownBars;
}
}
}
}
_previousFast = fast;
_previousSlow = slow;
_previousHighest = channelHigh;
_previousLowest = channelLow;
}
private decimal GetStopDistance()
{
if (StopLossPoints <= 0m)
return 0m;
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep > 0m)
return StopLossPoints * priceStep;
return StopLossPoints;
}
private decimal CalculatePositionSize(decimal stopDistance)
{
var defaultVolume = AdjustVolume(Volume);
if (stopDistance <= 0m)
return defaultVolume;
var portfolioValue = Portfolio?.CurrentValue;
if (portfolioValue is null || portfolioValue <= 0m || RiskPercent <= 0m)
return defaultVolume;
var riskCapital = portfolioValue.Value * RiskPercent;
var priceStep = Security?.PriceStep ?? 0m;
var stepPrice = GetSecurityValue<decimal?>(Level1Fields.StepPrice) ?? 0m;
decimal riskPerContract;
if (priceStep > 0m && stepPrice > 0m)
{
// Convert the stop distance into cash risk per contract using exchange specifications.
riskPerContract = stopDistance / priceStep * stepPrice;
}
else
{
// Fallback when the security does not expose step metadata.
riskPerContract = stopDistance;
}
if (riskPerContract <= 0m)
return defaultVolume;
var desiredVolume = riskCapital / riskPerContract;
return AdjustVolume(desiredVolume);
}
private decimal AdjustVolume(decimal volume)
{
if (volume <= 0m)
return 0m;
var step = Security?.VolumeStep ?? 0m;
if (step > 0m)
{
var steps = decimal.Floor(volume / step);
volume = steps * step;
}
var minVolume = Security?.MinVolume ?? 0m;
if (minVolume > 0m && volume < minVolume)
volume = minVolume;
var maxVolume = Security?.MaxVolume ?? 0m;
if (maxVolume > 0m && volume > maxVolume)
volume = maxVolume;
return volume;
}
private void ResetTradeState()
{
// Clear cached execution details after a position has been closed.
_entryPrice = 0m;
_stopPrice = 0m;
}
private static DecimalLengthIndicator CreateMovingAverage(MovingAverageTypes type, int length, CandlePrices price)
{
return type switch
{
MovingAverageTypes.Simple => new SMA { Length = length },
MovingAverageTypes.Exponential => new EMA { Length = length },
MovingAverageTypes.Smoothed => new SmoothedMovingAverage { Length = length },
MovingAverageTypes.Weighted => new WeightedMovingAverage { Length = length },
_ => new SMA { Length = length },
};
}
public enum MovingAverageTypes
{
Simple,
Exponential,
Smoothed,
Weighted,
}
public enum CandlePrices
{
Open,
High,
Low,
Close,
Median,
Typical,
Weighted
}
}
import clr
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Math
from StockSharp.Messages import DataType, CandleStates, Unit, UnitTypes
from StockSharp.Algo.Indicators import (SimpleMovingAverage, ExponentialMovingAverage,
SmoothedMovingAverage, WeightedMovingAverage, Highest, Lowest)
from StockSharp.Algo.Strategies import Strategy
MA_SIMPLE = 0
MA_EXPONENTIAL = 1
MA_SMOOTHED = 2
MA_WEIGHTED = 3
class currencyprofits_high_low_channel_strategy(Strategy):
def __init__(self):
super(currencyprofits_high_low_channel_strategy, self).__init__()
self._fast_length = self.Param("FastLength", 32)
self._slow_length = self.Param("SlowLength", 86)
self._channel_length = self.Param("ChannelLength", 12)
self._stop_loss_points = self.Param("StopLossPoints", 170.0)
self._risk_percent = self.Param("RiskPercent", 0.14)
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(30)))
self._fast_ma_type = self.Param("FastMaType", MA_SIMPLE)
self._slow_ma_type = self.Param("SlowMaType", MA_SIMPLE)
self._signal_cooldown_bars = self.Param("SignalCooldownBars", 4)
self._previous_fast = None
self._previous_slow = None
self._previous_highest = None
self._previous_lowest = None
self._entry_price = 0.0
self._stop_price = 0.0
self._processed_candles = 0
self._cooldown_remaining = 0
@property
def FastLength(self):
return self._fast_length.Value
@FastLength.setter
def FastLength(self, value):
self._fast_length.Value = value
@property
def SlowLength(self):
return self._slow_length.Value
@SlowLength.setter
def SlowLength(self, value):
self._slow_length.Value = value
@property
def ChannelLength(self):
return self._channel_length.Value
@ChannelLength.setter
def ChannelLength(self, value):
self._channel_length.Value = value
@property
def StopLossPoints(self):
return self._stop_loss_points.Value
@StopLossPoints.setter
def StopLossPoints(self, value):
self._stop_loss_points.Value = value
@property
def RiskPercent(self):
return self._risk_percent.Value
@RiskPercent.setter
def RiskPercent(self, value):
self._risk_percent.Value = value
@property
def CandleType(self):
return self._candle_type.Value
@CandleType.setter
def CandleType(self, value):
self._candle_type.Value = value
@property
def FastMaType(self):
return self._fast_ma_type.Value
@FastMaType.setter
def FastMaType(self, value):
self._fast_ma_type.Value = value
@property
def SlowMaType(self):
return self._slow_ma_type.Value
@SlowMaType.setter
def SlowMaType(self, value):
self._slow_ma_type.Value = value
@property
def SignalCooldownBars(self):
return self._signal_cooldown_bars.Value
@SignalCooldownBars.setter
def SignalCooldownBars(self, value):
self._signal_cooldown_bars.Value = value
def _create_ma(self, ma_type, length):
t = int(ma_type)
if t == MA_EXPONENTIAL:
ind = ExponentialMovingAverage()
ind.Length = length
return ind
elif t == MA_SMOOTHED:
ind = SmoothedMovingAverage()
ind.Length = length
return ind
elif t == MA_WEIGHTED:
ind = WeightedMovingAverage()
ind.Length = length
return ind
else:
ind = SimpleMovingAverage()
ind.Length = length
return ind
def OnStarted2(self, time):
super(currencyprofits_high_low_channel_strategy, self).OnStarted2(time)
fast_ma = self._create_ma(self.FastMaType, self.FastLength)
slow_ma = self._create_ma(self.SlowMaType, self.SlowLength)
highest = Highest()
highest.Length = self.ChannelLength
lowest = Lowest()
lowest.Length = self.ChannelLength
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(fast_ma, slow_ma, highest, lowest, self.ProcessCandle).Start()
self.StartProtection(
Unit(2000.0, UnitTypes.Absolute),
Unit(1000.0, UnitTypes.Absolute))
def ProcessCandle(self, candle, fast, slow, channel_high, channel_low):
if candle.State != CandleStates.Finished:
return
if self._cooldown_remaining > 0:
self._cooldown_remaining -= 1
self._processed_candles += 1
fast_val = float(fast)
slow_val = float(slow)
ch_high = float(channel_high)
ch_low = float(channel_low)
close = float(candle.ClosePrice)
open_price = float(candle.OpenPrice)
high = float(candle.HighPrice)
low = float(candle.LowPrice)
required = max(max(int(self.FastLength), int(self.SlowLength)), int(self.ChannelLength)) + 1
if self._processed_candles <= required:
self._previous_fast = fast_val
self._previous_slow = slow_val
self._previous_highest = ch_high
self._previous_lowest = ch_low
return
if self._previous_fast is None or self._previous_slow is None or self._previous_highest is None or self._previous_lowest is None:
self._previous_fast = fast_val
self._previous_slow = slow_val
self._previous_highest = ch_high
self._previous_lowest = ch_low
return
if self.Position > 0:
exit_by_channel = close >= self._previous_highest
exit_by_stop = self._stop_price > 0.0 and low <= self._stop_price
if exit_by_channel or exit_by_stop:
self.SellMarket()
self._reset_trade_state()
self._cooldown_remaining = int(self.SignalCooldownBars)
elif self.Position < 0:
exit_by_channel = close <= self._previous_lowest
exit_by_stop = self._stop_price > 0.0 and high >= self._stop_price
if exit_by_channel or exit_by_stop:
self.BuyMarket()
self._reset_trade_state()
self._cooldown_remaining = int(self.SignalCooldownBars)
elif self._cooldown_remaining == 0:
stop_distance = self._get_stop_distance()
if stop_distance > 0.0:
bullish_trend = self._previous_fast > self._previous_slow and fast_val > slow_val
bearish_trend = self._previous_fast < self._previous_slow and fast_val < slow_val
bullish_reversal = low <= self._previous_lowest and close > open_price and close > fast_val
bearish_reversal = high >= self._previous_highest and close < open_price and close < fast_val
if bullish_trend and bullish_reversal:
self.BuyMarket()
self._entry_price = close
self._stop_price = self._entry_price - stop_distance
self._cooldown_remaining = int(self.SignalCooldownBars)
elif bearish_trend and bearish_reversal:
self.SellMarket()
self._entry_price = close
self._stop_price = self._entry_price + stop_distance
self._cooldown_remaining = int(self.SignalCooldownBars)
self._previous_fast = fast_val
self._previous_slow = slow_val
self._previous_highest = ch_high
self._previous_lowest = ch_low
def _get_stop_distance(self):
sl = float(self.StopLossPoints)
if sl <= 0.0:
return 0.0
ps = float(self.Security.PriceStep) if self.Security is not None and self.Security.PriceStep is not None else 0.0
if ps > 0.0:
return sl * ps
return sl
def _reset_trade_state(self):
self._entry_price = 0.0
self._stop_price = 0.0
def OnReseted(self):
super(currencyprofits_high_low_channel_strategy, self).OnReseted()
self._previous_fast = None
self._previous_slow = None
self._previous_highest = None
self._previous_lowest = None
self._entry_price = 0.0
self._stop_price = 0.0
self._processed_candles = 0
self._cooldown_remaining = 0
def CreateClone(self):
return currencyprofits_high_low_channel_strategy()