FTMOルールモニター
FTMOチャレンジのルールを追跡し、ATRリスクに基づいてトレードを管理する戦略。
ATRを使用してポジションサイズを決定し、チャレンジの目標が達成されると停止します。最大日次損失、総損失、利益目標、最低取引日数を監視します。
詳細
- エントリー条件: 強気ローソク足でロング、弱気ローソク足でショートを建てる。
- ロング/ショート: 両方向。
- エグジット条件: チャレンジ完了または逆シグナル。
- ストップ: ATRベース。
- デフォルト値:
AccountSize= 10000mRiskPercent= 1mAtrPeriod= 14AtrMultiplier= 2mCandleType= TimeSpan.FromMinutes(5)
- フィルター:
- カテゴリ: リスク管理
- 方向: 両方
- インジケーター: ATR
- ストップ: ATR
- 複雑さ: 基本
- 時間軸: イントラデイ (5m)
- 季節性: いいえ
- ニューラルネットワーク: いいえ
- ダイバージェンス: いいえ
- リスクレベル: 中
using System;
using System.Collections.Generic;
using Ecng.Common;
using StockSharp.Algo.Indicators;
using StockSharp.Algo.Strategies;
using StockSharp.BusinessEntities;
using StockSharp.Messages;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// FTMO Rules Monitor strategy.
/// A bullish candle goes long and a bearish candle goes short, reversing an opposite position. The volume risks RiskPercent of
/// AccountSize over a stop AtrMultiplier ATRs from the entry, and that stop closes the position. The FTMO challenge rules are
/// watched against AccountSize: a 5% daily loss halts trading for the day, a 10% total loss ends it, and reaching the 10% profit
/// target after at least 4 trading days completes the challenge; ending or completing closes the position.
/// </summary>
public class FtmoRulesMonitorStrategy : Strategy
{
private const decimal _maxDailyLossPercent = 5m;
private const decimal _maxTotalLossPercent = 10m;
private const decimal _profitTargetPercent = 10m;
private const int _minTradingDays = 4;
private readonly StrategyParam<decimal> _accountSize;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<int> _atrPeriod;
private readonly StrategyParam<decimal> _atrMultiplier;
private readonly StrategyParam<DataType> _candleType;
private DateTime _currentDay;
private decimal _dayStartPnL;
private DateTime _lastTradingDay;
private int _tradingDays;
private bool _challengeOver;
private decimal _stopPrice;
/// <summary>
/// Challenge account size.
/// </summary>
public decimal AccountSize
{
get => _accountSize.Value;
set => _accountSize.Value = value;
}
/// <summary>
/// Percent of the account risked per trade.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// ATR period.
/// </summary>
public int AtrPeriod
{
get => _atrPeriod.Value;
set => _atrPeriod.Value = value;
}
/// <summary>
/// Stop distance in ATRs.
/// </summary>
public decimal AtrMultiplier
{
get => _atrMultiplier.Value;
set => _atrMultiplier.Value = value;
}
/// <summary>
/// Candle type.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Constructor.
/// </summary>
public FtmoRulesMonitorStrategy()
{
_accountSize = Param(nameof(AccountSize), 10000m)
.SetGreaterThanZero()
.SetDisplay("Account Size", "Challenge account size", "Challenge");
_riskPercent = Param(nameof(RiskPercent), 1m)
.SetGreaterThanZero()
.SetDisplay("Risk %", "Percent of the account risked per trade", "Risk");
_atrPeriod = Param(nameof(AtrPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("ATR Period", "ATR period", "Risk");
_atrMultiplier = Param(nameof(AtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Multiplier", "Stop distance in ATRs", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Type of candles to use", "General");
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
ResetState();
}
private void ResetState()
{
_currentDay = default;
_dayStartPnL = 0m;
_lastTradingDay = default;
_tradingDays = 0;
_challengeOver = false;
_stopPrice = 0m;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
ResetState();
var atr = new AverageTrueRange { Length = AtrPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal atr)
{
if (candle.State != CandleStates.Finished)
return;
if (!IsFormedAndOnlineAndAllowTrading())
return;
if (_challengeOver)
return;
var day = candle.OpenTime.Date;
if (_currentDay != day)
{
_currentDay = day;
_dayStartPnL = PnL;
}
var totalLossHit = PnL <= -AccountSize * _maxTotalLossPercent / 100m;
var targetReached = PnL >= AccountSize * _profitTargetPercent / 100m && _tradingDays >= _minTradingDays;
if (totalLossHit || targetReached)
{
_challengeOver = true;
ClosePosition();
return;
}
if (PnL - _dayStartPnL <= -AccountSize * _maxDailyLossPercent / 100m)
{
ClosePosition();
return;
}
if (Position > 0 && candle.LowPrice <= _stopPrice)
{
SellMarket(Position);
return;
}
if (Position < 0 && candle.HighPrice >= _stopPrice)
{
BuyMarket(-Position);
return;
}
var stopDistance = atr * AtrMultiplier;
if (stopDistance <= 0)
return;
var bullish = candle.ClosePrice > candle.OpenPrice;
var bearish = candle.ClosePrice < candle.OpenPrice;
if (!bullish && !bearish)
return;
if ((bullish && Position > 0) || (bearish && Position < 0))
return;
var volume = RoundVolume(AccountSize * RiskPercent / 100m / stopDistance);
if (volume <= 0)
return;
if (bullish)
{
BuyMarket(volume + Math.Abs(Position));
_stopPrice = candle.ClosePrice - stopDistance;
}
else
{
SellMarket(volume + Math.Abs(Position));
_stopPrice = candle.ClosePrice + stopDistance;
}
if (_lastTradingDay != day)
{
_lastTradingDay = day;
_tradingDays++;
}
}
private void ClosePosition()
{
if (Position > 0)
SellMarket(Position);
else if (Position < 0)
BuyMarket(-Position);
}
private decimal RoundVolume(decimal volume)
{
var step = Security?.VolumeStep ?? 0m;
return step > 0 ? Math.Floor(volume / step) * step : volume;
}
}
import clr
import math
clr.AddReference("StockSharp.Messages")
clr.AddReference("StockSharp.Algo")
clr.AddReference("StockSharp.Algo.Indicators")
clr.AddReference("StockSharp.Algo.Strategies")
from System import TimeSpan, Decimal
from StockSharp.Messages import DataType, CandleStates
from StockSharp.Algo.Indicators import AverageTrueRange
from StockSharp.Algo.Strategies import Strategy
MAX_DAILY_LOSS_PERCENT = 5.0
MAX_TOTAL_LOSS_PERCENT = 10.0
PROFIT_TARGET_PERCENT = 10.0
MIN_TRADING_DAYS = 4
class ftmo_rules_monitor_strategy(Strategy):
"""
FTMO Rules Monitor strategy.
A bullish candle goes long and a bearish candle goes short, reversing an opposite position. The volume risks RiskPercent of
AccountSize over a stop AtrMultiplier ATRs from the entry, and that stop closes the position. The FTMO challenge rules are
watched against AccountSize: a 5% daily loss halts trading for the day, a 10% total loss ends it, and reaching the 10% profit
target after at least 4 trading days completes the challenge; ending or completing closes the position.
"""
def __init__(self):
super(ftmo_rules_monitor_strategy, self).__init__()
self._account_size = self.Param("AccountSize", 10000.0).SetGreaterThanZero().SetDisplay("Account Size", "Challenge account size", "Challenge")
self._risk_percent = self.Param("RiskPercent", 1.0).SetGreaterThanZero().SetDisplay("Risk %", "Percent of the account risked per trade", "Risk")
self._atr_period = self.Param("AtrPeriod", 14).SetGreaterThanZero().SetDisplay("ATR Period", "ATR period", "Risk")
self._atr_multiplier = self.Param("AtrMultiplier", 2.0).SetGreaterThanZero().SetDisplay("ATR Multiplier", "Stop distance in ATRs", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))).SetDisplay("Candle Type", "Type of candles to use", "General")
self._reset_state()
@property
def candle_type(self):
return self._candle_type.Value
def _reset_state(self):
self._current_day = None
self._day_start_pnl = 0.0
self._last_trading_day = None
self._trading_days = 0
self._challenge_over = False
self._stop_price = 0.0
def OnReseted(self):
super(ftmo_rules_monitor_strategy, self).OnReseted()
self._reset_state()
def OnStarted2(self, time):
super(ftmo_rules_monitor_strategy, self).OnStarted2(time)
self._reset_state()
atr = AverageTrueRange()
atr.Length = self._atr_period.Value
subscription = self.SubscribeCandles(self.candle_type)
subscription.Bind(atr, self._process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawOwnTrades(area)
def _process_candle(self, candle, atr_value):
if candle.State != CandleStates.Finished:
return
if not self.IsFormedAndOnlineAndAllowTrading():
return
if self._challenge_over:
return
pnl = float(self.PnL)
account = float(self._account_size.Value)
day = candle.OpenTime.Date
if self._current_day != day:
self._current_day = day
self._day_start_pnl = pnl
total_loss_hit = pnl <= -account * MAX_TOTAL_LOSS_PERCENT / 100.0
target_reached = pnl >= account * PROFIT_TARGET_PERCENT / 100.0 and self._trading_days >= MIN_TRADING_DAYS
if total_loss_hit or target_reached:
self._challenge_over = True
self._close_position()
return
if pnl - self._day_start_pnl <= -account * MAX_DAILY_LOSS_PERCENT / 100.0:
self._close_position()
return
if self.Position > 0 and float(candle.LowPrice) <= self._stop_price:
self.SellMarket(self.Position)
return
if self.Position < 0 and float(candle.HighPrice) >= self._stop_price:
self.BuyMarket(-self.Position)
return
stop_distance = float(atr_value) * float(self._atr_multiplier.Value)
if stop_distance <= 0:
return
open_price = float(candle.OpenPrice)
close = float(candle.ClosePrice)
bullish = close > open_price
bearish = close < open_price
if not bullish and not bearish:
return
if (bullish and self.Position > 0) or (bearish and self.Position < 0):
return
volume = self._round_volume(account * float(self._risk_percent.Value) / 100.0 / stop_distance)
if volume <= 0:
return
if bullish:
self.BuyMarket(Decimal(volume) + abs(self.Position))
self._stop_price = close - stop_distance
else:
self.SellMarket(Decimal(volume) + abs(self.Position))
self._stop_price = close + stop_distance
if self._last_trading_day != day:
self._last_trading_day = day
self._trading_days += 1
def _close_position(self):
if self.Position > 0:
self.SellMarket(self.Position)
elif self.Position < 0:
self.BuyMarket(-self.Position)
def _round_volume(self, volume):
step = float(self.Security.VolumeStep) if self.Security is not None and self.Security.VolumeStep is not None else 0.0
return math.floor(volume / step) * step if step > 0 else volume
def CreateClone(self):
return ftmo_rules_monitor_strategy()