直近N時間トレンドへの反対方向20ピップス戦略
このStockSharp戦略はMetaTraderエキスパートアドバイザー
**«20 Pips Opposite Last N Hour Trend»**の高レベル移植版です。時間足ローソク足を観察し、
直前のN時間の価格の動きを測定し、設定された取引時間が終了したときに
反対方向にポジションをオープンします。取引は
固定20pipsテイクプロフィットターゲットと時間制限で管理され、
連続した損失後にはマーチンゲールスタイルのボリュームスケールが適用されます。
実装はStockSharpのローソク足サブスクリプション、パラメーターシステム、
注文ヘルパー(BuyMarket, SellMarket)を使用しているため、
Designer、API、Runner、Shellのいずれでも変更なしに実行できます。
トレードロジック
- 戦略は選択されたローソク足タイプ(デフォルト:1時間バー)をサブスクライブします。
- 各完成したローソク足について、終値を内部履歴に保存します。
OpenTime.Hour == TradingHourのローソク足が完成し、十分な 履歴が利用可能な場合:HoursToCheckTrendバー前に発生した終値と 前の終値(1バー前)を比較します。- その窓で価格が下落した場合(弱気ドリフト)、戦略は買います; 価格が上昇した場合(強気ドリフト)は売ります。同じ終値の場合は取引をスキップします。
- 1日に1つの取引のみがオープンされ、設定された取引時間のみに限られます。 他のすべてのローソク足は純粋に管理のためのみに使用されます。
ポジション管理
- 20pipsターゲット(3/5桁シンボルに対して調整済み)はエントリー直後に 計算されます。完成したローソク足の高値/安値がターゲットに触れたことを示すと、 ポジションはそのレベルで閉じられます。
- ターゲットが次の時間中に達成されなければ、ポジションは オーバーナイトのエクスポージャーを避けるため、次のローソク足の終わりに閉じられます。
- 新しい取引日が始まると日次カウンターが自動的にリセットされ、 次の資格のあるシグナルが次のセッションで発動できるようになります。
資金管理
Volumeはベース注文サイズを設定します。MaxVolumeはマーチンゲールステップの結果サイズを上限にします。- 損失後の退場に続いて、戦略は適切な乗数によって次のポジションを増加させます:
最初の損失 →
FirstMultiplier、2番目の損失 →SecondMultiplierなど。5回を超える連続損失は5番目の 乗数を再使用します。いかなる利益またはトントンのクローズもシーケンスをリセットします。 - ボリューム計算は最後に実行されたポジション価格に依存するため、 完全なブローカーPnLデータなしでも利益/損失検出が決定論的に維持されます。
パラメーター
| パラメーター | デフォルト値 | 説明 |
|---|---|---|
MaxPositions |
9 | 1日に許可される最大取引数。0に設定すると取引が無効になります。 |
Volume |
0.1 | 連続の最初の取引のベースボリューム。 |
MaxVolume |
5 | 乗数後の調整されたボリュームのハードキャップ。 |
TakeProfitPips |
20 | pipsでのテイクプロフィット距離。0でTPを無効にします。 |
TradingHour |
7 | ポジションをオープンする資格がある時刻(0-23)。 |
HoursToCheckTrend |
24 | 直前のトレンドを測定するために使用される時間足終値の数。 |
FirstMultiplier |
2 | 最初の連続損失後に適用される乗数。 |
SecondMultiplier |
4 | 2番目の連続損失後に適用される乗数。 |
ThirdMultiplier |
8 | 3番目の連続損失後に適用される乗数。 |
FourthMultiplier |
16 | 4番目の連続損失後に適用される乗数。 |
FifthMultiplier |
32 | 5番目以降の連続損失から適用される乗数。 |
CandleType |
H1 | シグナル生成と管理に使用されるローソク足データタイプ。 |
追加の注意事項
- pip サイズは
Security.PriceStepと小数点以下の桁数から計算されるため、 20pipsターゲットは4桁および5桁のFXシンボルで正しく動作します。 StartProtection()は戦略開始時に呼び出され、StockSharpの組み込み 保護(無制限ポジションの自動ストップ、ポートフォリオリセット)を有効にします。- ロジックは完成したローソク足のみを使用し、インジケーター値を
直接読み取ることはなく、
AGENTS.mdのガイドラインに適合しています。
リスク免責事項: マーチンゲールスタイルのポジションサイジングは 実質的なドローダウンにつながる可能性があります。ライブ取引に展開する前に、 常にヒストリカルデータでパラメーターをテストし、慎重なリスク制限を使用してください。
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>
/// Strategy that trades against the last N hours trend with a fixed take profit.
/// </summary>
public class TwentyPipsOppositeLastNHourTrendStrategy : Strategy
{
private readonly StrategyParam<int> _maxPositions;
private readonly StrategyParam<decimal> _maxVolume;
private readonly StrategyParam<decimal> _takeProfitPips;
private readonly StrategyParam<int> _tradingHour;
private readonly StrategyParam<int> _hoursToCheckTrend;
private readonly StrategyParam<int> _firstMultiplier;
private readonly StrategyParam<int> _secondMultiplier;
private readonly StrategyParam<int> _thirdMultiplier;
private readonly StrategyParam<int> _fourthMultiplier;
private readonly StrategyParam<int> _fifthMultiplier;
private readonly StrategyParam<DataType> _candleType;
private readonly List<decimal> _closeHistory = new();
private decimal? _entryPrice;
private decimal? _takeProfitLevel;
private decimal _entryVolume;
private int _positionDirection;
private int _consecutiveLosses;
private DateTime? _currentDay;
private int _tradesToday;
public int MaxPositions
{
get => _maxPositions.Value;
set => _maxPositions.Value = value;
}
public decimal MaxVolume
{
get => _maxVolume.Value;
set => _maxVolume.Value = value;
}
public decimal TakeProfitPips
{
get => _takeProfitPips.Value;
set => _takeProfitPips.Value = value;
}
public int TradingHour
{
get => _tradingHour.Value;
set => _tradingHour.Value = value;
}
public int HoursToCheckTrend
{
get => _hoursToCheckTrend.Value;
set => _hoursToCheckTrend.Value = value;
}
public int FirstMultiplier
{
get => _firstMultiplier.Value;
set => _firstMultiplier.Value = value;
}
public int SecondMultiplier
{
get => _secondMultiplier.Value;
set => _secondMultiplier.Value = value;
}
public int ThirdMultiplier
{
get => _thirdMultiplier.Value;
set => _thirdMultiplier.Value = value;
}
public int FourthMultiplier
{
get => _fourthMultiplier.Value;
set => _fourthMultiplier.Value = value;
}
public int FifthMultiplier
{
get => _fifthMultiplier.Value;
set => _fifthMultiplier.Value = value;
}
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
public TwentyPipsOppositeLastNHourTrendStrategy()
{
_maxPositions = Param(nameof(MaxPositions), 9)
.SetGreaterThanZero()
.SetDisplay("Max Positions", "Maximum trades per day", "Trading");
_maxVolume = Param(nameof(MaxVolume), 5m)
.SetGreaterThanZero()
.SetDisplay("Max Volume", "Maximum allowed volume", "Risk");
_takeProfitPips = Param(nameof(TakeProfitPips), 20m)
.SetGreaterThanZero()
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Trading");
_tradingHour = Param(nameof(TradingHour), 8)
.SetRange(0, 23)
.SetDisplay("Trading Hour", "Hour (0-23) when entries are allowed", "Timing");
_hoursToCheckTrend = Param(nameof(HoursToCheckTrend), 6)
.SetRange(2, 240)
.SetDisplay("Hours To Check", "Lookback hours for trend calculation", "Signals");
_firstMultiplier = Param(nameof(FirstMultiplier), 2)
.SetGreaterThanZero()
.SetDisplay("First Multiplier", "Multiplier after first loss", "Money Management");
_secondMultiplier = Param(nameof(SecondMultiplier), 4)
.SetGreaterThanZero()
.SetDisplay("Second Multiplier", "Multiplier after second loss", "Money Management");
_thirdMultiplier = Param(nameof(ThirdMultiplier), 8)
.SetGreaterThanZero()
.SetDisplay("Third Multiplier", "Multiplier after third loss", "Money Management");
_fourthMultiplier = Param(nameof(FourthMultiplier), 16)
.SetGreaterThanZero()
.SetDisplay("Fourth Multiplier", "Multiplier after fourth loss", "Money Management");
_fifthMultiplier = Param(nameof(FifthMultiplier), 32)
.SetGreaterThanZero()
.SetDisplay("Fifth Multiplier", "Multiplier after fifth loss", "Money Management");
_candleType = Param(nameof(CandleType), TimeSpan.FromHours(4).TimeFrame())
.SetDisplay("Candle Type", "Candle timeframe to process", "Market Data");
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
protected override void OnReseted()
{
base.OnReseted();
_closeHistory.Clear();
_entryPrice = null;
_takeProfitLevel = null;
_entryVolume = 0m;
_positionDirection = 0;
_consecutiveLosses = 0;
_currentDay = null;
_tradesToday = 0;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
var subscription = SubscribeCandles(CandleType);
subscription.Bind(ProcessCandle).Start();
// no fixed protection needed
}
private void ProcessCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
var candleDay = candle.OpenTime.Date;
if (_currentDay != candleDay)
{
_currentDay = candleDay;
_tradesToday = 0;
}
if (_positionDirection != 0)
{
if (_takeProfitLevel is decimal target)
{
// Take profit when the candle range touches the desired level.
var hitTarget = _positionDirection > 0
? candle.HighPrice >= target
: candle.LowPrice <= target;
if (hitTarget)
{
ClosePosition(target);
}
}
if (_positionDirection != 0 && candle.OpenTime.Hour != TradingHour)
{
// Close remaining exposure when the configured session hour has passed.
ClosePosition(candle.ClosePrice);
}
}
if (_positionDirection != 0)
{
UpdateHistory(candle.ClosePrice);
return;
}
if (candle.OpenTime.Hour != TradingHour)
{
UpdateHistory(candle.ClosePrice);
return;
}
if (MaxPositions <= 0 || _tradesToday >= MaxPositions)
{
UpdateHistory(candle.ClosePrice);
return;
}
var requiredHistory = Math.Max(HoursToCheckTrend, 2);
if (_closeHistory.Count < requiredHistory)
{
UpdateHistory(candle.ClosePrice);
return;
}
var referenceClose = _closeHistory[_closeHistory.Count - HoursToCheckTrend];
var previousClose = _closeHistory[_closeHistory.Count - 1];
if (previousClose == referenceClose)
{
UpdateHistory(candle.ClosePrice);
return;
}
// Opposite trend logic: buy after bearish drift, sell after bullish drift.
var goLong = previousClose < referenceClose;
var orderVolume = CalculateOrderVolume();
if (orderVolume <= 0)
{
UpdateHistory(candle.ClosePrice);
return;
}
if (goLong)
{
Volume = orderVolume;
BuyMarket();
_positionDirection = 1;
}
else
{
Volume = orderVolume;
SellMarket();
_positionDirection = -1;
}
_entryPrice = candle.ClosePrice;
_entryVolume = orderVolume;
var distance = GetTakeProfitDistance();
if (distance > 0m)
{
_takeProfitLevel = _positionDirection > 0
? _entryPrice + distance
: _entryPrice - distance;
}
else
{
_takeProfitLevel = null;
}
_tradesToday++;
UpdateHistory(candle.ClosePrice);
}
private void ClosePosition(decimal exitPrice)
{
var direction = _positionDirection;
var entryPrice = _entryPrice;
var volume = Math.Abs(Position);
if (volume <= 0m && _entryVolume > 0m)
{
volume = _entryVolume;
}
if (volume <= 0m)
{
_positionDirection = 0;
_takeProfitLevel = null;
_entryPrice = null;
_entryVolume = 0m;
return;
}
if (direction > 0)
{
SellMarket();
}
else if (direction < 0)
{
BuyMarket();
}
if (entryPrice is decimal price)
{
var isLoss = direction > 0
? exitPrice < price
: exitPrice > price;
_consecutiveLosses = isLoss
? Math.Min(_consecutiveLosses + 1, 5)
: 0;
}
_positionDirection = 0;
_takeProfitLevel = null;
_entryPrice = null;
_entryVolume = 0m;
}
private void UpdateHistory(decimal closePrice)
{
_closeHistory.Add(closePrice);
var maxHistory = Math.Max(HoursToCheckTrend, 2);
if (_closeHistory.Count > maxHistory)
{
_closeHistory.RemoveRange(0, _closeHistory.Count - maxHistory);
}
}
private decimal CalculateOrderVolume()
{
if (Volume <= 0m)
{
return 0m;
}
var multiplier = _consecutiveLosses switch
{
>= 5 => (decimal)FifthMultiplier,
4 => (decimal)FourthMultiplier,
3 => (decimal)ThirdMultiplier,
2 => (decimal)SecondMultiplier,
1 => (decimal)FirstMultiplier,
_ => 1m
};
var desiredVolume = Volume * multiplier;
if (MaxVolume > 0m && desiredVolume > MaxVolume)
{
desiredVolume = MaxVolume;
}
return desiredVolume;
}
private decimal GetTakeProfitDistance()
{
var pipSize = GetPipSize();
return pipSize > 0m
? TakeProfitPips * pipSize
: 0m;
}
private decimal GetPipSize()
{
var priceStep = Security?.PriceStep ?? 0m;
if (priceStep <= 0m)
{
priceStep = 0.0001m;
}
var decimals = Security?.Decimals ?? 0;
if (decimals == 3 || decimals == 5)
{
return priceStep * 10m;
}
return priceStep;
}
}
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.Strategies import Strategy
class twenty_pips_opposite_last_n_hour_trend_strategy(Strategy):
"""20 Pips Opposite Last N Hour Trend: counter-trend martingale strategy."""
def __init__(self):
super(twenty_pips_opposite_last_n_hour_trend_strategy, self).__init__()
self._max_positions = self.Param("MaxPositions", 9) \
.SetGreaterThanZero() \
.SetDisplay("Max Positions", "Maximum trades per day", "Trading")
self._max_volume = self.Param("MaxVolume", 5.0) \
.SetGreaterThanZero() \
.SetDisplay("Max Volume", "Maximum allowed volume", "Risk")
self._take_profit_pips = self.Param("TakeProfitPips", 20.0) \
.SetGreaterThanZero() \
.SetDisplay("Take Profit (pips)", "Take profit distance in pips", "Trading")
self._trading_hour = self.Param("TradingHour", 8) \
.SetDisplay("Trading Hour", "Hour (0-23) when entries are allowed", "Timing")
self._hours_to_check_trend = self.Param("HoursToCheckTrend", 6) \
.SetDisplay("Hours To Check", "Lookback hours for trend calculation", "Signals")
self._first_multiplier = self.Param("FirstMultiplier", 2) \
.SetGreaterThanZero() \
.SetDisplay("First Multiplier", "Multiplier after first loss", "Money Management")
self._second_multiplier = self.Param("SecondMultiplier", 4) \
.SetGreaterThanZero() \
.SetDisplay("Second Multiplier", "Multiplier after second loss", "Money Management")
self._third_multiplier = self.Param("ThirdMultiplier", 8) \
.SetGreaterThanZero() \
.SetDisplay("Third Multiplier", "Multiplier after third loss", "Money Management")
self._fourth_multiplier = self.Param("FourthMultiplier", 16) \
.SetGreaterThanZero() \
.SetDisplay("Fourth Multiplier", "Multiplier after fourth loss", "Money Management")
self._fifth_multiplier = self.Param("FifthMultiplier", 32) \
.SetGreaterThanZero() \
.SetDisplay("Fifth Multiplier", "Multiplier after fifth loss", "Money Management")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromHours(4))) \
.SetDisplay("Candle Type", "Candle timeframe to process", "Market Data")
self._close_history = []
self._entry_price = None
self._take_profit_level = None
self._entry_volume = 0.0
self._position_direction = 0
self._consecutive_losses = 0
self._current_day = None
self._trades_today = 0
@property
def MaxPositions(self):
return int(self._max_positions.Value)
@property
def MaxVolume(self):
return float(self._max_volume.Value)
@property
def TakeProfitPips(self):
return float(self._take_profit_pips.Value)
@property
def TradingHour(self):
return int(self._trading_hour.Value)
@property
def HoursToCheckTrend(self):
return int(self._hours_to_check_trend.Value)
@property
def FirstMultiplier(self):
return int(self._first_multiplier.Value)
@property
def SecondMultiplier(self):
return int(self._second_multiplier.Value)
@property
def ThirdMultiplier(self):
return int(self._third_multiplier.Value)
@property
def FourthMultiplier(self):
return int(self._fourth_multiplier.Value)
@property
def FifthMultiplier(self):
return int(self._fifth_multiplier.Value)
@property
def CandleType(self):
return self._candle_type.Value
def _get_pip_size(self):
sec = self.Security
if sec is None or sec.PriceStep is None:
return 0.0001
step = float(sec.PriceStep)
if step <= 0:
return 0.0001
decimals = 0
if sec.Decimals is not None:
decimals = int(sec.Decimals)
if decimals == 3 or decimals == 5:
return step * 10.0
return step
def OnStarted2(self, time):
super(twenty_pips_opposite_last_n_hour_trend_strategy, self).OnStarted2(time)
self._close_history = []
self._entry_price = None
self._take_profit_level = None
self._entry_volume = 0.0
self._position_direction = 0
self._consecutive_losses = 0
self._current_day = None
self._trades_today = 0
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self.process_candle).Start()
def process_candle(self, candle):
if candle.State != CandleStates.Finished:
return
close = float(candle.ClosePrice)
candle_day = candle.OpenTime.Date
if self._current_day is None or self._current_day != candle_day:
self._current_day = candle_day
self._trades_today = 0
if self._position_direction != 0:
if self._take_profit_level is not None:
if self._position_direction > 0:
hit_target = float(candle.HighPrice) >= self._take_profit_level
else:
hit_target = float(candle.LowPrice) <= self._take_profit_level
if hit_target:
self._close_position(self._take_profit_level)
if self._position_direction != 0 and candle.OpenTime.Hour != self.TradingHour:
self._close_position(close)
if self._position_direction != 0:
self._update_history(close)
return
if candle.OpenTime.Hour != self.TradingHour:
self._update_history(close)
return
if self.MaxPositions <= 0 or self._trades_today >= self.MaxPositions:
self._update_history(close)
return
required_history = max(self.HoursToCheckTrend, 2)
if len(self._close_history) < required_history:
self._update_history(close)
return
reference_close = self._close_history[len(self._close_history) - self.HoursToCheckTrend]
previous_close = self._close_history[len(self._close_history) - 1]
if previous_close == reference_close:
self._update_history(close)
return
go_long = previous_close < reference_close
order_volume = self._calculate_order_volume()
if order_volume <= 0:
self._update_history(close)
return
if go_long:
self.BuyMarket()
self._position_direction = 1
else:
self.SellMarket()
self._position_direction = -1
self._entry_price = close
self._entry_volume = order_volume
distance = self._get_take_profit_distance()
if distance > 0:
if self._position_direction > 0:
self._take_profit_level = self._entry_price + distance
else:
self._take_profit_level = self._entry_price - distance
else:
self._take_profit_level = None
self._trades_today += 1
self._update_history(close)
def _close_position(self, exit_price):
direction = self._position_direction
entry_price = self._entry_price
volume = abs(self.Position)
if volume <= 0 and self._entry_volume > 0:
volume = self._entry_volume
if volume <= 0:
self._position_direction = 0
self._take_profit_level = None
self._entry_price = None
self._entry_volume = 0.0
return
if direction > 0:
self.SellMarket()
elif direction < 0:
self.BuyMarket()
if entry_price is not None:
if direction > 0:
is_loss = exit_price < entry_price
else:
is_loss = exit_price > entry_price
if is_loss:
self._consecutive_losses = min(self._consecutive_losses + 1, 5)
else:
self._consecutive_losses = 0
self._position_direction = 0
self._take_profit_level = None
self._entry_price = None
self._entry_volume = 0.0
def _update_history(self, close_price):
self._close_history.append(close_price)
max_history = max(self.HoursToCheckTrend, 2)
if len(self._close_history) > max_history:
self._close_history = self._close_history[len(self._close_history) - max_history:]
def _calculate_order_volume(self):
base_vol = self.Volume
if base_vol <= 0:
return 0.0
losses = self._consecutive_losses
if losses >= 5:
multiplier = float(self.FifthMultiplier)
elif losses == 4:
multiplier = float(self.FourthMultiplier)
elif losses == 3:
multiplier = float(self.ThirdMultiplier)
elif losses == 2:
multiplier = float(self.SecondMultiplier)
elif losses == 1:
multiplier = float(self.FirstMultiplier)
else:
multiplier = 1.0
desired_volume = float(base_vol) * multiplier
if self.MaxVolume > 0 and desired_volume > self.MaxVolume:
desired_volume = self.MaxVolume
return desired_volume
def _get_take_profit_distance(self):
pip_size = self._get_pip_size()
if pip_size > 0:
return self.TakeProfitPips * pip_size
return 0.0
def OnReseted(self):
super(twenty_pips_opposite_last_n_hour_trend_strategy, self).OnReseted()
self._close_history = []
self._entry_price = None
self._take_profit_level = None
self._entry_volume = 0.0
self._position_direction = 0
self._consecutive_losses = 0
self._current_day = None
self._trades_today = 0
def CreateClone(self):
return twenty_pips_opposite_last_n_hour_trend_strategy()