E-フライデーセッション戦略
概要
E フライデー セッション戦略は、金曜日のみ取引する古典的な MetaTrader エキスパート アドバイザーを再現しています。前回の日足のローソク足を観察し、金曜日のセッション開始時に設定された時間にポジションをオープンします。方向は逆張りです。前日の終値が始値 (弱気のローソク足) を下回った場合、戦略は買いです。前日の終値が始値(強気のローソク足)を上回った場合、戦略は売りになります。ポジションは日中に管理され、設定可能な時間の経過後に自動的にクローズするか、保護ストップによってクローズすることができます。
取引ルール
- 毎日のローソク足 (デフォルト: 1 日) を収集して、前日の始値と終値を取得します。
- 金曜日に、日中のローソク足 (デフォルト: 1 分) を監視して、設定されたエントリー時間を検出します。
- 入場時間の最初のローソク足で:
- 前日が弱気だった場合はロングにします。
- 前日が強気だった場合はショートする。
- 前日が童子(始値と終値が等しい)だった場合は取引をスキップします。
- オプションで、設定された終了時間に達するとポジションを自動的にクローズします。
- 利益のアクティベーションやトレーリング ステップのしきい値を含む、オリジナルの Expert Advisor を模倣するストップロス、テイクプロフィット、およびオプションのトレーリング ストップ ロジックを使用してエグジットを管理します。
実装メモ
- 毎日のコンテキストと日中のタイミングの両方に、StockSharp の高レベルのキャンドル サブスクリプションを使用します。
- 証券の価格ステップを使用して、ポイントベースのリスク管理を MQL バージョンから絶対価格オフセットに変換します。
- コード内でトレーリング ストップを維持し、終了したローソク足ごとにトレーリング ストップを更新し、価格の極端な値を突破したときにポジションを閉じます。
- 毎日の状態を追跡することで、金曜日に 1 回の取引のみを保証します。
- ロングエントリーとショートエントリーの両方をサポートし、ストラテジーインスタンスごとに単一のシンボルを取引することで元のマジックナンバーゲーティングを尊重します。
パラメーター
| 名前 | 説明 | デフォルト |
|---|---|---|
Volume |
ロット/契約における取引サイズ。 | 0.1 |
StopLossPoints |
価格ステップのストップロス距離 (0 無効)。 | 75 |
TakeProfitPoints |
価格ステップでの利食い距離 (0 無効)。 | 0 |
HourOpen |
ポジションをオープンする時刻 (0 ~ 23)。 | 7 |
UseClosePositions |
終了時間後の自動閉鎖を有効にします。 | true |
HourClose |
有効な場合、ポジションを決済する時刻 (0 ~ 23)。 | 19 |
UseTrailing |
トレーリングストップ調整を有効にします。 | true |
ProfitTrailing |
トレーリングがアクティブになる前に、利益がトレーリング距離を超える必要があります。 | true |
TrailingStopPoints |
価格ステップでのトレーリングストップ距離。 | 60 |
TrailingStepPoints |
トレーリングストップを強める前に追加ポイントが必要。 | 5 |
IntradayCandleType |
日中のタイミングのローソク足タイプ (デフォルトは 1 分ローソク足)。 | TimeSpan.FromMinutes(1) |
DailyCandleType |
毎日のセンチメント検出用のローソクのタイプ (デフォルトは 1 日のローソク)。 | TimeSpan.FromDays(1) |
使用のヒント
- 金曜日のエントリー時間が希望する市場の開始時間と一致するように商品の取引セッションを調整します。
- ストップロスとトレーリングの値を設定するときは、シンボルの価格ステップで使用されるのと同じ「ポイント」で表現して、MetaTrader の動作を再現します。
- この戦略は金曜日に 1 回の取引を行うように設計されています。複数のシンボルを取引するには、シンボルごとに個別の戦略インスタンスを実行します。
オリジナルの EA との違い
- 意思決定にはローソク足の終値データが使用されますが、ティックごとの元のポーリング価格が使用されます。
- プロテクティブ・エグジットは、ローソク足がインターバル内にストップまたはターゲットレベルに触れたことを示したときに、成行注文を通じて実行されます。
- 戦略パラメータは、StockSharp の
StrategyParamシステムを通じて公開され、最適化と UI バインディングをサポートします。
約定判定の補足: 金曜日のエントリーは、始値の時刻が HourOpen に属する最初の確定済み分足で判定します。時刻の最初のティックでの判定ではありません。確定済みの木曜日の日足が必要です。保護決済後も含め、金曜日の新規エントリーは一度だけです。時刻決済、ストップ、利確、トレーリングは確定した分足の高値・安値を使い成行で決済します。トレーリングは指定幅を満たしたときだけ更新し、同一足内の過去の値動きによる約定を仮定しません。
namespace StockSharp.Samples.Strategies;
using System;
using System.Collections.Generic;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
/// <summary>
/// Takes at most one contrarian position on Friday at the configured hour,
/// using the previous completed daily candle; manages session and price exits.
/// </summary>
public class EFridaySessionStrategy : Strategy
{
private readonly StrategyParam<int> _hourOpen;
private readonly StrategyParam<int> _hourClose;
private readonly StrategyParam<bool> _useClosePositions;
private readonly StrategyParam<decimal> _stopLossPoints;
private readonly StrategyParam<decimal> _takeProfitPoints;
private readonly StrategyParam<bool> _useTrailing;
private readonly StrategyParam<bool> _profitTrailing;
private readonly StrategyParam<decimal> _trailingStopPoints;
private readonly StrategyParam<decimal> _trailingStepPoints;
private readonly StrategyParam<DataType> _intradayCandleType;
private readonly StrategyParam<DataType> _dailyCandleType;
private DateTime? _lastDailyDate;
private decimal _dailyOpen;
private decimal _dailyClose;
private DateTime? _tradedDate;
private decimal? _entryPrice;
private decimal? _trailingLevel;
private decimal _bestPrice;
private Order _entryOrder;
private Order _exitOrder;
public int HourOpen { get => _hourOpen.Value; set => _hourOpen.Value = value; }
public int HourClose { get => _hourClose.Value; set => _hourClose.Value = value; }
public bool UseClosePositions { get => _useClosePositions.Value; set => _useClosePositions.Value = value; }
public decimal StopLossPoints { get => _stopLossPoints.Value; set => _stopLossPoints.Value = value; }
public decimal TakeProfitPoints { get => _takeProfitPoints.Value; set => _takeProfitPoints.Value = value; }
public bool UseTrailing { get => _useTrailing.Value; set => _useTrailing.Value = value; }
public bool ProfitTrailing { get => _profitTrailing.Value; set => _profitTrailing.Value = value; }
public decimal TrailingStopPoints { get => _trailingStopPoints.Value; set => _trailingStopPoints.Value = value; }
public decimal TrailingStepPoints { get => _trailingStepPoints.Value; set => _trailingStepPoints.Value = value; }
public DataType IntradayCandleType { get => _intradayCandleType.Value; set => _intradayCandleType.Value = value; }
public DataType DailyCandleType { get => _dailyCandleType.Value; set => _dailyCandleType.Value = value; }
public EFridaySessionStrategy()
{
Volume = 0.1m;
_hourOpen = Param(nameof(HourOpen), 7).SetRange(0, 23);
_hourClose = Param(nameof(HourClose), 19).SetRange(0, 23);
_useClosePositions = Param(nameof(UseClosePositions), true);
_stopLossPoints = Param(nameof(StopLossPoints), 75m).SetNotNegative();
_takeProfitPoints = Param(nameof(TakeProfitPoints), 0m).SetNotNegative();
_useTrailing = Param(nameof(UseTrailing), true);
_profitTrailing = Param(nameof(ProfitTrailing), true);
_trailingStopPoints = Param(nameof(TrailingStopPoints), 60m).SetNotNegative();
_trailingStepPoints = Param(nameof(TrailingStepPoints), 5m).SetNotNegative();
_intradayCandleType = Param(nameof(IntradayCandleType), TimeSpan.FromMinutes(1).TimeFrame());
_dailyCandleType = Param(nameof(DailyCandleType), TimeSpan.FromDays(1).TimeFrame());
}
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
=> [(Security, IntradayCandleType), (Security, DailyCandleType)];
protected override void OnReseted()
{
base.OnReseted();
ClearState();
}
private void ClearState()
{
_lastDailyDate = null;
_dailyOpen = 0m;
_dailyClose = 0m;
_tradedDate = null;
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
_entryOrder = null;
_exitOrder = null;
}
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ClearState();
if ((Security?.PriceStep ?? 0m) <= 0m)
throw new InvalidOperationException("E-Friday requires a positive PriceStep.");
SubscribeCandles(DailyCandleType).Bind(ProcessDailyCandle).Start();
SubscribeCandles(IntradayCandleType).Bind(ProcessIntradayCandle).Start();
}
private void ProcessDailyCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
_lastDailyDate = candle.OpenTime.Date;
_dailyOpen = candle.OpenPrice;
_dailyClose = candle.ClosePrice;
}
private static bool IsPending(Order order) => order is not null &&
order.State is not (OrderStates.Done or OrderStates.Failed);
private void ProcessIntradayCandle(ICandleMessage candle)
{
if (candle.State != CandleStates.Finished)
return;
if (Position != 0m && !IsPending(_exitOrder))
{
if (UseClosePositions && candle.OpenTime.Hour >= HourClose)
{
ExitPosition("EFriday session close");
return;
}
if (HandleRiskExits(candle))
return;
}
var day = candle.OpenTime.Date;
if (candle.OpenTime.DayOfWeek != DayOfWeek.Friday ||
candle.OpenTime.Hour != HourOpen || _tradedDate == day ||
_lastDailyDate != day.AddDays(-1) ||
Position != 0m || IsPending(_entryOrder) || IsPending(_exitOrder))
return;
if (_dailyClose == _dailyOpen)
return;
var side = _dailyClose < _dailyOpen ? Sides.Buy : Sides.Sell;
var volume = Volume;
if (Security?.VolumeStep is decimal step && step > 0m)
volume = decimal.Floor(volume / step) * step;
if (Security?.MinVolume is decimal min && min > 0m)
volume = Math.Max(volume, min);
if (Security?.MaxVolume is decimal max && max > 0m)
volume = Math.Min(volume, max);
if (volume <= 0m)
return;
_entryOrder = Market(side, volume, "EFriday entry");
_tradedDate = day;
}
private bool HandleRiskExits(ICandleMessage candle)
{
if (_entryPrice is not decimal entry)
return false;
var point = Security?.PriceStep ?? 0m;
var longPosition = Position > 0m;
var stop = StopLossPoints * point;
var take = TakeProfitPoints * point;
if (stop > 0m && (longPosition ? candle.LowPrice <= entry - stop :
candle.HighPrice >= entry + stop))
{
ExitPosition("EFriday stop loss");
return true;
}
if (take > 0m && (longPosition ? candle.HighPrice >= entry + take :
candle.LowPrice <= entry - take))
{
ExitPosition("EFriday take profit");
return true;
}
// Check the previously established trailing level before using the
// latest bar's extremes: intrabar high/low order is not available.
if (_trailingLevel is decimal oldStop &&
(longPosition ? candle.LowPrice <= oldStop : candle.HighPrice >= oldStop))
{
ExitPosition("EFriday trailing stop");
return true;
}
if (!UseTrailing || TrailingStopPoints <= 0m)
return false;
var distance = TrailingStopPoints * point;
var improvement = TrailingStepPoints * point;
_bestPrice = longPosition ? Math.Max(_bestPrice, candle.HighPrice) :
Math.Min(_bestPrice, candle.LowPrice);
var profit = longPosition ? _bestPrice - entry : entry - _bestPrice;
if (ProfitTrailing && profit < distance)
return false;
var next = longPosition ? _bestPrice - distance : _bestPrice + distance;
if (_trailingLevel is not decimal current ||
(longPosition ? next >= current + improvement && next > current :
next <= current - improvement && next < current))
_trailingLevel = next;
return false;
}
private Order Market(Sides side, decimal volume, string comment)
{
var order = new Order
{
Security = Security,
Portfolio = Portfolio,
Type = OrderTypes.Market,
Side = side,
Volume = volume,
Comment = comment,
};
if (comment == "EFriday entry")
_entryOrder = order;
else
_exitOrder = order;
RegisterOrder(order);
return order;
}
private void ExitPosition(string comment)
{
if (Position == 0m || IsPending(_exitOrder))
return;
_exitOrder = Market(Position > 0m ? Sides.Sell : Sides.Buy, Math.Abs(Position), comment);
}
protected override void OnOwnTradeReceived(MyTrade trade)
{
base.OnOwnTradeReceived(trade);
if (trade?.Order == _entryOrder && trade.Trade is not null)
{
_entryPrice = trade.Trade.Price;
_bestPrice = _entryPrice.Value;
_trailingLevel = null;
}
else if (Position == 0m)
{
_entryPrice = null;
_trailingLevel = null;
_bestPrice = 0m;
_exitOrder = null;
}
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Strategies")
clr.AddReference("StockSharp.BusinessEntities")
from System import DateTime, DayOfWeek, Decimal, TimeSpan
from StockSharp.Messages import DataType, CandleStates, OrderStates, OrderTypes, Sides
from StockSharp.BusinessEntities import Order
from StockSharp.Algo.Strategies import Strategy
class e_friday_session_strategy(Strategy):
"""One contrarian Friday trade based on the previous completed daily candle."""
def __init__(self):
super(e_friday_session_strategy, self).__init__()
self.Volume = Decimal(0.1)
self._hour_open = self.Param("HourOpen", 7).SetRange(0, 23)
self._hour_close = self.Param("HourClose", 19).SetRange(0, 23)
self._use_close_positions = self.Param("UseClosePositions", True)
self._stop_loss_points = self.Param("StopLossPoints", 75.0).SetNotNegative()
self._take_profit_points = self.Param("TakeProfitPoints", 0.0).SetNotNegative()
self._use_trailing = self.Param("UseTrailing", True)
self._profit_trailing = self.Param("ProfitTrailing", True)
self._trailing_stop_points = self.Param("TrailingStopPoints", 60.0).SetNotNegative()
self._trailing_step_points = self.Param("TrailingStepPoints", 5.0).SetNotNegative()
self._intraday_candle_type = self.Param(
"IntradayCandleType", DataType.TimeFrame(TimeSpan.FromMinutes(1)))
self._daily_candle_type = self.Param(
"DailyCandleType", DataType.TimeFrame(TimeSpan.FromDays(1)))
self._clear_state()
def _clear_state(self):
self._last_daily_date = None
self._daily_open = 0.0
self._daily_close = 0.0
self._traded_date = None
self._entry_price = None
self._trailing_level = None
self._best_price = 0.0
self._entry_order = None
self._exit_order = None
def OnReseted(self):
super(e_friday_session_strategy, self).OnReseted()
self._clear_state()
def GetWorkingSecurities(self):
return [(self.Security, self._intraday_candle_type.Value),
(self.Security, self._daily_candle_type.Value)]
def OnStarted2(self, time):
super(e_friday_session_strategy, self).OnStarted2(time)
self._clear_state()
if float(self.Security.PriceStep or 0) <= 0:
raise ValueError("E-Friday requires a positive PriceStep.")
self.SubscribeCandles(self._daily_candle_type.Value).Bind(self._process_daily).Start()
self.SubscribeCandles(self._intraday_candle_type.Value).Bind(self._process_intraday).Start()
def _process_daily(self, candle):
if candle.State != CandleStates.Finished:
return
self._last_daily_date = candle.OpenTime.Date
self._daily_open = float(candle.OpenPrice)
self._daily_close = float(candle.ClosePrice)
@staticmethod
def _pending(order):
return order is not None and order.State not in (OrderStates.Done, OrderStates.Failed)
def _process_intraday(self, candle):
if candle.State != CandleStates.Finished:
return
if self.Position != 0 and not self._pending(self._exit_order):
if (self._use_close_positions.Value and
candle.OpenTime.Hour >= self._hour_close.Value):
self._exit("EFriday session close")
return
if self._handle_risk(candle):
return
day = candle.OpenTime.Date
if (candle.OpenTime.DayOfWeek != DayOfWeek.Friday or
candle.OpenTime.Hour != self._hour_open.Value or
self._traded_date == day or
self._last_daily_date != day.AddDays(-1) or
self.Position != 0 or
self._pending(self._entry_order) or self._pending(self._exit_order)):
return
if self._daily_open == self._daily_close:
return
side = Sides.Buy if self._daily_close < self._daily_open else Sides.Sell
volume = float(self.Volume)
step = float(self.Security.VolumeStep or 0)
if step > 0:
volume = math.floor(volume / step) * step
minimum = float(self.Security.MinVolume or 0)
maximum = float(self.Security.MaxVolume or 0)
if minimum > 0:
volume = max(volume, minimum)
if maximum > 0:
volume = min(volume, maximum)
if volume <= 0:
return
self._entry_order = self._market(side, volume, "EFriday entry")
self._traded_date = day
def _handle_risk(self, candle):
if self._entry_price is None:
return False
entry = self._entry_price
point = float(self.Security.PriceStep)
long_pos = self.Position > 0
stop = float(self._stop_loss_points.Value) * point
take = float(self._take_profit_points.Value) * point
if stop > 0 and (
float(candle.LowPrice) <= entry - stop if long_pos
else float(candle.HighPrice) >= entry + stop):
self._exit("EFriday stop loss")
return True
if take > 0 and (
float(candle.HighPrice) >= entry + take if long_pos
else float(candle.LowPrice) <= entry - take):
self._exit("EFriday take profit")
return True
old_stop = self._trailing_level
if old_stop is not None and (
float(candle.LowPrice) <= old_stop if long_pos
else float(candle.HighPrice) >= old_stop):
self._exit("EFriday trailing stop")
return True
if not self._use_trailing.Value or self._trailing_stop_points.Value <= 0:
return False
distance = float(self._trailing_stop_points.Value) * point
improvement = float(self._trailing_step_points.Value) * point
self._best_price = (max(self._best_price, float(candle.HighPrice)) if long_pos
else min(self._best_price, float(candle.LowPrice)))
profit = (self._best_price - entry if long_pos else entry - self._best_price)
if self._profit_trailing.Value and profit < distance:
return False
next_stop = (self._best_price - distance if long_pos else
self._best_price + distance)
if old_stop is None or (
next_stop >= old_stop + improvement and next_stop > old_stop if long_pos
else next_stop <= old_stop - improvement and next_stop < old_stop):
self._trailing_level = next_stop
return False
def _market(self, side, volume, comment):
order = Order()
order.Security = self.Security
order.Portfolio = self.Portfolio
order.Type = OrderTypes.Market
order.Side = side
order.Volume = Decimal(volume)
order.Comment = comment
if comment == "EFriday entry":
self._entry_order = order
else:
self._exit_order = order
self.RegisterOrder(order)
return order
def _exit(self, comment):
if self.Position == 0 or self._pending(self._exit_order):
return
self._exit_order = self._market(
Sides.Sell if self.Position > 0 else Sides.Buy,
abs(float(self.Position)), comment)
def OnOwnTradeReceived(self, trade):
super(e_friday_session_strategy, self).OnOwnTradeReceived(trade)
if trade is not None and trade.Order == self._entry_order and trade.Trade is not None:
self._entry_price = float(trade.Trade.Price)
self._best_price = self._entry_price
self._trailing_level = None
elif self.Position == 0:
self._entry_price = None
self._trailing_level = None
self._best_price = 0.0
self._exit_order = None
def CreateClone(self):
return e_friday_session_strategy()