自己最適化RSIまたはMFIトレーダーv3
概要
この戦略は、MetaTraderエキスパートアドバイザー「Self Optimizing RSI or MFI Trader」をStockSharpの高レベルAPIにポートしたものです。完成した各キャンドルで、アルゴリズムは履歴バーのスライディングウィンドウをバックテストし、選択したオシレーターにとって最も収益性の高い買われすぎと売られすぎの閾値を見つけます。ライブ取引は、現在のオシレーター値が歴史的なエッジと同じ方向に最高パフォーマンスの閾値をクロスしたときにのみ行われ、「アグレッシブ」モードではクロスを必要としないオプションがあります。ポジションの出口はATRベースまたは固定距離のストップとターゲットに依存し、オプションのブレイクイーブンステップがあります。
市場データ
- OHLCキャンドルとボリュームを提供するあらゆるインストゥルメントで動作します(MFIにはボリュームが必要)。
CandleTypeパラメーターで指定された時間軸を使用します。デフォルトは15分キャンドルですが、会場アダプターでサポートされる任意の時間軸を使用できます。
インジケーター
IndicatorChoiceパラメーターに応じてRelative Strength Index (RSI)またはMoney Flow Index (MFI)。両方とも同じ平均化長さを共有します。UseDynamicTargetsが有効な場合のATRベースのストップロス/テイクプロフィットサイジング用のAverage True Range (ATR)。
取引ロジック
- オシレーター値と終値を持つ
OptimizingPeriods+ 1の完成したキャンドルの継続的な履歴を維持します。 IndicatorBottomValueとIndicatorTopValueの間の各整数レベルについて、戦略は履歴ウィンドウで取引をシミュレートします:- ショートシミュレーション:オシレーターがレベルを下回ってクロスした回数と、ショートのストップロスまたはテイクプロフィットのどちらが先にヒットしたかを数えます。
- ロングシミュレーション:オシレーターがレベルを上回ってクロスした回数と、取引がどれだけ収益性があったかを数えます。
- 各方向で最高のシミュレート利益を提供した閾値を選択します。
TradeReverseが有効な場合、収益性スコアが交換され、反対方向が有利になります。 - ライブオシレーターが収益性のある方向のベストレベルをクロスするとき(または
UseAggressiveEntriesがtrueの場合は即座に)、戦略はOneOrderAtATimeを尊重しながらポジションを開きます。 - エグジット管理:
- ストップロスとテイクプロフィットレベルはATR倍数(
StopLossAtrMultiplier、TakeProfitAtrMultiplier)または固定ポイント距離(StaticStopLossPoints、StaticTakeProfitPoints)から計算されます。 UseBreakEvenは、未実現利益がBreakEvenTriggerPointsに達すると、ストップをエントリー価格プラスBreakEvenPaddingPointsに移動します。- ストップロスまたはテイクプロフィット価格がクロスされると、ポジションは閉じられます。
- ストップロスとテイクプロフィットレベルはATR倍数(
リスク管理
- 動的サイジング:
UseDynamicVolumeがtrueの場合、戦略は現在のポートフォリオ価値のRiskPercentをリスクにさらします。計算は、インストゥルメントのPriceStepとStepPriceを使用して、ストップ距離を金銭的リスクに変換します。 - **静的サイジング:**無効の場合、各エントリーで
BaseVolumeロットが取引されます。 - **ブレイクイーブンガード:**十分な利益が積み上がったときに勝利取引が保護されることを保証します。
パラメーター
| パラメーター | 説明 |
|---|---|
OptimizingPeriods |
継続的なインサンプル最適化に使用するバーの数(デフォルト144)。 |
IndicatorChoice |
RSIまたはMFIを駆動オシレーターとして選択します。 |
IndicatorPeriod |
オシレーターとATRの平均化期間。 |
IndicatorTopValue / IndicatorBottomValue |
閾値レベルの検索境界(通常0–100)。 |
UseAggressiveEntries |
trueの場合、確認されたクロスなしにエントリーを許可します。 |
TradeReverse |
収益性スコアを交換して歴史的に負けている側を取引します。 |
OneOrderAtATime |
別のポジションがアクティブな間は新しいポジションの開設を防ぎます。 |
UseDynamicTargets |
ATRベースと固定ポイントのストップ/ターゲットを切り替えます。 |
StopLossAtrMultiplier、TakeProfitAtrMultiplier |
動的エグジット用のATR乗数。 |
StaticStopLossPoints、StaticTakeProfitPoints |
固定エグジット用のポイント距離。 |
UseBreakEven、BreakEvenTriggerPoints、BreakEvenPaddingPoints |
ブレイクイーブンストップ動作を設定します。 |
UseDynamicVolume、RiskPercent、BaseVolume |
ポジションサイジングロジックを制御します。 |
CandleType |
最適化と取引の時間軸。 |
実装上の注意
- 戦略はStockSharpの
SubscribeCandles().Bind(...)パイプラインを使用するため、完成したキャンドルでのみ実行されます。 OneOrderAtATimeはネッティング口座での取引時は有効にしたままにする必要があります。実装が単一の集約ポジションを追跡するためです。- ATRベースのエグジットには有効なATR値が必要です;インジケーターが完全に形成されるまで戦略は取引をスキップします。
- MFIを使用する場合は、データフィードがボリュームを提供することを確認してください。そうでなければインジケーターはゼロを返し、取引は生成されません。
最適化のヒント
OptimizingPeriods、オシレーター期間、ATR乗数を一緒に最適化してインストゥルメントのボラティリティ体制に合わせます。- 異なる資産はノイズを減らすために狭いレベル範囲(例:20–80)から恩恵を受ける可能性があります。
- 戦略が閾値を継続的に適応させるため、ウォークフォワード分析による先行テストを検討してください。
使用方法
- Designerでコネクターに戦略を追加するか、プログラムで実行します。
- 希望するインストゥルメント、ポートフォリオ、パラメーター値を設定します。
- 戦略を開始します;最適化に十分なキャンドルが蓄積されると取引を開始します。
制限事項
- 履歴の最適化は各バーで発生し、非常に大きな
OptimizingPeriodsや広いレベル範囲ではCPU集約的になる可能性があります。 - レベルは整数であるため、細かい閾値(例:70.5)はテストされません。
- このアプローチは最近の過去が予測的であり続けることを前提としています;突然の体制変化はパフォーマンスを低下させる可能性があるため、ライブ結果を監視し、必要に応じて構成を調整してください。
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;
using StockSharp.Algo.Candles;
namespace StockSharp.Samples.Strategies;
/// <summary>
/// Strategy that dynamically optimizes RSI or MFI threshold levels over a rolling history window.
/// Chooses the most profitable overbought/oversold levels and executes trades with ATR or point based risk control.
/// </summary>
public class SelfOptimizingRsiOrMfiTraderV3Strategy : Strategy
{
private readonly StrategyParam<int> _optimizingPeriods;
private readonly StrategyParam<bool> _useAggressiveEntries;
private readonly StrategyParam<bool> _tradeReverse;
private readonly StrategyParam<bool> _oneOrderAtATime;
private readonly StrategyParam<decimal> _baseVolume;
private readonly StrategyParam<bool> _useDynamicVolume;
private readonly StrategyParam<decimal> _riskPercent;
private readonly StrategyParam<IndicatorSources> _indicatorChoice;
private readonly StrategyParam<int> _indicatorTopValue;
private readonly StrategyParam<int> _indicatorBottomValue;
private readonly StrategyParam<int> _indicatorPeriod;
private readonly StrategyParam<bool> _useDynamicTargets;
private readonly StrategyParam<int> _staticStopLossPoints;
private readonly StrategyParam<int> _staticTakeProfitPoints;
private readonly StrategyParam<decimal> _stopLossAtrMultiplier;
private readonly StrategyParam<decimal> _takeProfitAtrMultiplier;
private readonly StrategyParam<bool> _useBreakEven;
private readonly StrategyParam<int> _breakEvenTriggerPoints;
private readonly StrategyParam<int> _breakEvenPaddingPoints;
private readonly StrategyParam<DataType> _candleType;
private readonly List<(decimal indicator, decimal close)> _history = new();
private decimal? _entryPrice;
private decimal? _stopPrice;
private decimal? _takeProfitPrice;
private IIndicator _indicator;
private AverageTrueRange _atr;
/// <summary>
/// Indicator source used for optimization.
/// </summary>
public enum IndicatorSources
{
/// <summary>
/// Use Relative Strength Index values.
/// </summary>
RelativeStrengthIndex,
/// <summary>
/// Use Money Flow Index values.
/// </summary>
MoneyFlowIndex,
}
/// <summary>
/// Number of bars evaluated when searching for best thresholds.
/// </summary>
public int OptimizingPeriods
{
get => _optimizingPeriods.Value;
set => _optimizingPeriods.Value = value;
}
/// <summary>
/// Allow entries without waiting for indicator crosses.
/// </summary>
public bool UseAggressiveEntries
{
get => _useAggressiveEntries.Value;
set => _useAggressiveEntries.Value = value;
}
/// <summary>
/// Invert profitability preference to trade opposite direction.
/// </summary>
public bool TradeReverse
{
get => _tradeReverse.Value;
set => _tradeReverse.Value = value;
}
/// <summary>
/// Restrict strategy to a single open position at a time.
/// </summary>
public bool OneOrderAtATime
{
get => _oneOrderAtATime.Value;
set => _oneOrderAtATime.Value = value;
}
/// <summary>
/// Static volume used when dynamic sizing is disabled.
/// </summary>
public decimal BaseVolume
{
get => _baseVolume.Value;
set => _baseVolume.Value = value;
}
/// <summary>
/// Enable risk based position sizing.
/// </summary>
public bool UseDynamicVolume
{
get => _useDynamicVolume.Value;
set => _useDynamicVolume.Value = value;
}
/// <summary>
/// Percentage of portfolio risked per trade when sizing dynamically.
/// </summary>
public decimal RiskPercent
{
get => _riskPercent.Value;
set => _riskPercent.Value = value;
}
/// <summary>
/// Oscillator used for optimization.
/// </summary>
public IndicatorSources IndicatorChoice
{
get => _indicatorChoice.Value;
set => _indicatorChoice.Value = value;
}
/// <summary>
/// Highest threshold tested when searching for overbought levels.
/// </summary>
public int IndicatorTopValue
{
get => _indicatorTopValue.Value;
set => _indicatorTopValue.Value = value;
}
/// <summary>
/// Lowest threshold tested when searching for oversold levels.
/// </summary>
public int IndicatorBottomValue
{
get => _indicatorBottomValue.Value;
set => _indicatorBottomValue.Value = value;
}
/// <summary>
/// Period used for the selected indicator.
/// </summary>
public int IndicatorPeriod
{
get => _indicatorPeriod.Value;
set => _indicatorPeriod.Value = value;
}
/// <summary>
/// Enable ATR based stop-loss and take-profit levels.
/// </summary>
public bool UseDynamicTargets
{
get => _useDynamicTargets.Value;
set => _useDynamicTargets.Value = value;
}
/// <summary>
/// Static stop-loss distance expressed in points when dynamic targets are disabled.
/// </summary>
public int StaticStopLossPoints
{
get => _staticStopLossPoints.Value;
set => _staticStopLossPoints.Value = value;
}
/// <summary>
/// Static take-profit distance expressed in points when dynamic targets are disabled.
/// </summary>
public int StaticTakeProfitPoints
{
get => _staticTakeProfitPoints.Value;
set => _staticTakeProfitPoints.Value = value;
}
/// <summary>
/// ATR multiplier applied to stop-loss when dynamic targets are enabled.
/// </summary>
public decimal StopLossAtrMultiplier
{
get => _stopLossAtrMultiplier.Value;
set => _stopLossAtrMultiplier.Value = value;
}
/// <summary>
/// ATR multiplier applied to take-profit when dynamic targets are enabled.
/// </summary>
public decimal TakeProfitAtrMultiplier
{
get => _takeProfitAtrMultiplier.Value;
set => _takeProfitAtrMultiplier.Value = value;
}
/// <summary>
/// Enable stop adjustment to breakeven once profit target is reached.
/// </summary>
public bool UseBreakEven
{
get => _useBreakEven.Value;
set => _useBreakEven.Value = value;
}
/// <summary>
/// Profit threshold in points required to arm the breakeven stop.
/// </summary>
public int BreakEvenTriggerPoints
{
get => _breakEvenTriggerPoints.Value;
set => _breakEvenTriggerPoints.Value = value;
}
/// <summary>
/// Additional padding in points applied once breakeven triggers.
/// </summary>
public int BreakEvenPaddingPoints
{
get => _breakEvenPaddingPoints.Value;
set => _breakEvenPaddingPoints.Value = value;
}
/// <summary>
/// Candle type processed by the strategy.
/// </summary>
public DataType CandleType
{
get => _candleType.Value;
set => _candleType.Value = value;
}
/// <summary>
/// Initialize <see cref="SelfOptimizingRsiOrMfiTraderV3Strategy"/>.
/// </summary>
public SelfOptimizingRsiOrMfiTraderV3Strategy()
{
_optimizingPeriods = Param(nameof(OptimizingPeriods), 30)
.SetGreaterThanZero()
.SetDisplay("Optimization Bars", "Number of bars used for optimization", "General")
.SetOptimize(20, 100, 10);
_useAggressiveEntries = Param(nameof(UseAggressiveEntries), false)
.SetDisplay("Aggressive Entries", "Allow entries without indicator crosses", "Trading");
_tradeReverse = Param(nameof(TradeReverse), false)
.SetDisplay("Reverse Trading", "Swap profitability preference for opposite trades", "Trading");
_oneOrderAtATime = Param(nameof(OneOrderAtATime), true)
.SetDisplay("One Position", "Permit only one open position", "Trading");
_baseVolume = Param(nameof(BaseVolume), 1m)
.SetGreaterThanZero()
.SetDisplay("Base Volume", "Static order volume when sizing manually", "Risk");
_useDynamicVolume = Param(nameof(UseDynamicVolume), true)
.SetDisplay("Dynamic Volume", "Use risk percentage for position sizing", "Risk");
_riskPercent = Param(nameof(RiskPercent), 2m)
.SetRange(0.1m, 10m)
.SetDisplay("Risk %", "Percent of capital risked per trade", "Risk");
_indicatorChoice = Param(nameof(IndicatorChoice), IndicatorSources.RelativeStrengthIndex)
.SetDisplay("Indicator", "Oscillator optimized by the strategy", "Indicator");
_indicatorTopValue = Param(nameof(IndicatorTopValue), 100)
.SetDisplay("Top Level", "Upper bound for level search", "Indicator");
_indicatorBottomValue = Param(nameof(IndicatorBottomValue), 0)
.SetDisplay("Bottom Level", "Lower bound for level search", "Indicator");
_indicatorPeriod = Param(nameof(IndicatorPeriod), 14)
.SetGreaterThanZero()
.SetDisplay("Indicator Period", "Averaging period for RSI or MFI", "Indicator");
_useDynamicTargets = Param(nameof(UseDynamicTargets), true)
.SetDisplay("Dynamic Targets", "Use ATR based stop-loss and take-profit", "Risk");
_staticStopLossPoints = Param(nameof(StaticStopLossPoints), 1000)
.SetGreaterThanZero()
.SetDisplay("Static Stop", "Stop-loss in points when dynamic targets disabled", "Risk");
_staticTakeProfitPoints = Param(nameof(StaticTakeProfitPoints), 2000)
.SetGreaterThanZero()
.SetDisplay("Static Take", "Take-profit in points when dynamic targets disabled", "Risk");
_stopLossAtrMultiplier = Param(nameof(StopLossAtrMultiplier), 2m)
.SetGreaterThanZero()
.SetDisplay("ATR Stop Mult", "Stop-loss multiplier applied to ATR", "Risk");
_takeProfitAtrMultiplier = Param(nameof(TakeProfitAtrMultiplier), 7m)
.SetGreaterThanZero()
.SetDisplay("ATR Take Mult", "Take-profit multiplier applied to ATR", "Risk");
_useBreakEven = Param(nameof(UseBreakEven), true)
.SetDisplay("Use Breakeven", "Move stop to breakeven after trigger", "Risk");
_breakEvenTriggerPoints = Param(nameof(BreakEvenTriggerPoints), 200)
.SetGreaterThanZero()
.SetDisplay("Breakeven Trigger", "Profit in points required to arm breakeven", "Risk");
_breakEvenPaddingPoints = Param(nameof(BreakEvenPaddingPoints), 100)
.SetGreaterThanZero()
.SetDisplay("Breakeven Padding", "Padding in points applied after trigger", "Risk");
_candleType = Param(nameof(CandleType), TimeSpan.FromMinutes(5).TimeFrame())
.SetDisplay("Candle Type", "Timeframe used for analysis", "General");
Volume = 1m;
}
/// <inheritdoc />
public override IEnumerable<(Security sec, DataType dt)> GetWorkingSecurities()
{
return [(Security, CandleType)];
}
/// <inheritdoc />
protected override void OnReseted()
{
base.OnReseted();
_history.Clear();
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
_indicator = null;
_atr = null;
}
/// <inheritdoc />
protected override void OnStarted2(DateTime time)
{
base.OnStarted2(time);
_indicator = IndicatorChoice switch
{
IndicatorSources.MoneyFlowIndex => new MoneyFlowIndex { Length = IndicatorPeriod },
_ => new RelativeStrengthIndex { Length = IndicatorPeriod }
};
_atr = new AverageTrueRange { Length = IndicatorPeriod };
var subscription = SubscribeCandles(CandleType);
subscription
.Bind(_indicator, _atr, ProcessCandle)
.Start();
var area = CreateChartArea();
if (area != null)
{
DrawCandles(area, subscription);
DrawIndicator(area, _indicator);
DrawIndicator(area, _atr);
DrawOwnTrades(area);
}
}
private void ProcessCandle(ICandleMessage candle, decimal indicatorValue, decimal atrValue)
{
if (candle.State != CandleStates.Finished)
return;
_history.Add((indicatorValue, candle.ClosePrice));
var maxNeeded = Math.Max(OptimizingPeriods + 1, 3);
while (_history.Count > maxNeeded)
{
_history.RemoveAt(0);
}
var priceStep = Security?.PriceStep ?? 1m;
if (priceStep <= 0m)
priceStep = 1m;
var stepPrice = priceStep;
var triggerDiff = UseBreakEven ? BreakEvenTriggerPoints * priceStep : 0m;
var paddingPoints = BreakEvenPaddingPoints > BreakEvenTriggerPoints ? 0 : BreakEvenPaddingPoints;
var paddingDiff = UseBreakEven ? paddingPoints * priceStep : 0m;
ManageOpenPosition(candle, triggerDiff, paddingDiff);
if (_history.Count < maxNeeded)
return;
var indicatorValues = new decimal[_history.Count];
var closeValues = new decimal[_history.Count];
for (var i = 0; i < _history.Count; i++)
{
var source = _history[_history.Count - 1 - i];
indicatorValues[i] = source.indicator;
closeValues[i] = source.close;
}
decimal stopLossDiff;
decimal takeProfitDiff;
if (UseDynamicTargets)
{
if (atrValue <= 0m)
return;
stopLossDiff = atrValue * StopLossAtrMultiplier;
takeProfitDiff = atrValue * TakeProfitAtrMultiplier;
}
else
{
stopLossDiff = StaticStopLossPoints * priceStep;
takeProfitDiff = StaticTakeProfitPoints * priceStep;
}
if (stopLossDiff <= 0m || takeProfitDiff <= 0m)
return;
var volume = CalculateVolume(stopLossDiff);
if (volume <= 0m)
return;
var stepMultiplier = priceStep > 0m ? stepPrice / priceStep : 1m;
var (sellLevel, sellProfit) = CalculateBestSellLevel(indicatorValues, closeValues, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
var (buyLevel, buyProfit) = CalculateBestBuyLevel(indicatorValues, closeValues, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
var adjustedSellProfit = sellProfit;
var adjustedBuyProfit = buyProfit;
if (TradeReverse)
{
adjustedSellProfit = buyProfit;
adjustedBuyProfit = sellProfit;
}
var canEnter = !OneOrderAtATime || Position == 0m;
var currentIndicator = indicatorValues[0];
var previousIndicator = indicatorValues[1];
if (adjustedSellProfit > adjustedBuyProfit)
{
if (canEnter && ((currentIndicator < sellLevel && previousIndicator > sellLevel) || UseAggressiveEntries))
{
EnterShort(candle, volume, stopLossDiff, takeProfitDiff);
}
}
else if (adjustedSellProfit < adjustedBuyProfit)
{
if (canEnter && ((currentIndicator > buyLevel && previousIndicator < buyLevel) || UseAggressiveEntries))
{
EnterLong(candle, volume, stopLossDiff, takeProfitDiff);
}
}
}
private (int level, decimal profit) CalculateBestSellLevel(decimal[] indicatorValues, decimal[] closeValues, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var bottom = Math.Min(IndicatorBottomValue, IndicatorTopValue);
var top = Math.Max(IndicatorBottomValue, IndicatorTopValue);
var bestProfit = 0m;
var bestLevel = bottom;
var updated = false;
for (var level = bottom; level <= top; level++)
{
var profit = EvaluateSellLevel(indicatorValues, closeValues, level, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
if (profit > bestProfit)
{
bestProfit = profit;
bestLevel = level;
updated = true;
}
}
return (bestLevel, updated ? bestProfit : 0m);
}
private (int level, decimal profit) CalculateBestBuyLevel(decimal[] indicatorValues, decimal[] closeValues, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var bottom = Math.Min(IndicatorBottomValue, IndicatorTopValue);
var top = Math.Max(IndicatorBottomValue, IndicatorTopValue);
var bestProfit = 0m;
var bestLevel = top;
var updated = false;
for (var level = top; level >= bottom; level--)
{
var profit = EvaluateBuyLevel(indicatorValues, closeValues, level, stopLossDiff, takeProfitDiff, volume, stepMultiplier);
if (profit > bestProfit)
{
bestProfit = profit;
bestLevel = level;
updated = true;
}
}
return (bestLevel, updated ? bestProfit : 0m);
}
private decimal EvaluateSellLevel(decimal[] indicatorValues, decimal[] closeValues, int level, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var totalProfit = 0m;
if (indicatorValues.Length < 3)
return 0m;
var threshold = (decimal)level;
for (var i = indicatorValues.Length - 2; i >= 2; i--)
{
if (indicatorValues[i] < threshold && indicatorValues[i + 1] > threshold)
{
var entryPrice = closeValues[i];
for (var j = i - 1; j >= 1; j--)
{
var price = closeValues[j];
if (price >= entryPrice + stopLossDiff)
{
var loss = (price - entryPrice) * stepMultiplier * volume;
totalProfit -= loss;
i = j;
break;
}
if (price <= entryPrice - takeProfitDiff)
{
var gain = (entryPrice - price) * stepMultiplier * volume;
totalProfit += gain;
i = j;
break;
}
}
}
}
return totalProfit;
}
private decimal EvaluateBuyLevel(decimal[] indicatorValues, decimal[] closeValues, int level, decimal stopLossDiff, decimal takeProfitDiff, decimal volume, decimal stepMultiplier)
{
var totalProfit = 0m;
if (indicatorValues.Length < 3)
return 0m;
var threshold = (decimal)level;
for (var i = indicatorValues.Length - 2; i >= 2; i--)
{
if (indicatorValues[i] > threshold && indicatorValues[i + 1] < threshold)
{
var entryPrice = closeValues[i];
for (var j = i - 1; j >= 1; j--)
{
var price = closeValues[j];
if (price <= entryPrice - stopLossDiff)
{
var loss = (entryPrice - price) * stepMultiplier * volume;
totalProfit -= loss;
i = j;
break;
}
if (price >= entryPrice + takeProfitDiff)
{
var gain = (price - entryPrice) * stepMultiplier * volume;
totalProfit += gain;
i = j;
break;
}
}
}
}
return totalProfit;
}
private decimal CalculateVolume(decimal stopLossDiff)
{
var volume = BaseVolume;
if (UseDynamicVolume && stopLossDiff > 0m && Security != null)
{
var priceStep = Security.PriceStep ?? 0m;
var stepPrice = priceStep;
if (priceStep > 0m && stepPrice > 0m)
{
var stopPoints = stopLossDiff / priceStep;
var riskPerUnit = stopPoints * stepPrice;
var capital = Portfolio?.CurrentValue ?? 0m;
var riskBudget = capital * (RiskPercent / 100m);
if (riskPerUnit > 0m && riskBudget > 0m)
{
var rawVolume = riskBudget / riskPerUnit;
if (rawVolume > 0m)
volume = rawVolume;
}
}
}
return AdjustVolume(volume);
}
private decimal AdjustVolume(decimal volume)
{
if (Security == null)
return Math.Max(volume, 0.01m);
var step = Security.VolumeStep ?? 0m;
var min = Security.MinVolume ?? 0m;
var max = Security.MaxVolume ?? decimal.MaxValue;
if (step <= 0m)
step = 1m;
if (min <= 0m)
min = step;
if (volume < min)
volume = min;
if (volume > max)
volume = max;
volume = Math.Floor(volume / step) * step;
if (volume <= 0m)
volume = min;
return volume;
}
private void EnterLong(ICandleMessage candle, decimal volume, decimal stopLossDiff, decimal takeProfitDiff)
{
var orderVolume = volume;
if (Position < 0m)
orderVolume += Math.Abs(Position);
BuyMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice - stopLossDiff;
_takeProfitPrice = _entryPrice + takeProfitDiff;
}
private void EnterShort(ICandleMessage candle, decimal volume, decimal stopLossDiff, decimal takeProfitDiff)
{
var orderVolume = volume;
if (Position > 0m)
orderVolume += Position;
SellMarket();
_entryPrice = candle.ClosePrice;
_stopPrice = _entryPrice + stopLossDiff;
_takeProfitPrice = _entryPrice - takeProfitDiff;
}
private void ManageOpenPosition(ICandleMessage candle, decimal triggerDiff, decimal paddingDiff)
{
if (Position > 0m)
{
if (UseBreakEven && _entryPrice is decimal entry && _stopPrice is decimal currentStop)
{
var triggerPrice = entry + triggerDiff;
var targetStop = entry + paddingDiff;
if (triggerDiff > 0m && candle.HighPrice >= triggerPrice && currentStop < targetStop)
_stopPrice = targetStop;
}
if (_stopPrice is decimal stop && candle.LowPrice <= stop)
{
SellMarket();
ResetPositionState();
return;
}
if (_takeProfitPrice is decimal target && candle.HighPrice >= target)
{
SellMarket();
ResetPositionState();
return;
}
}
else if (Position < 0m)
{
if (UseBreakEven && _entryPrice is decimal entry && _stopPrice is decimal currentStop)
{
var triggerPrice = entry - triggerDiff;
var targetStop = entry - paddingDiff;
if (triggerDiff > 0m && candle.LowPrice <= triggerPrice && currentStop > targetStop)
_stopPrice = targetStop;
}
if (_stopPrice is decimal stop && candle.HighPrice >= stop)
{
BuyMarket();
ResetPositionState();
return;
}
if (_takeProfitPrice is decimal target && candle.LowPrice <= target)
{
BuyMarket();
ResetPositionState();
return;
}
}
else
{
ResetPositionState();
}
}
private void ResetPositionState()
{
_entryPrice = null;
_stopPrice = null;
_takeProfitPrice = null;
}
}
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
from StockSharp.Algo.Indicators import (
RelativeStrengthIndex,
MoneyFlowIndex,
AverageTrueRange,
)
class self_optimizing_rsi_or_mfi_trader_v3_strategy(Strategy):
"""Self-optimizing RSI/MFI: dynamically finds best overbought/oversold levels over rolling history."""
def __init__(self):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).__init__()
self._optimizing_periods = self.Param("OptimizingPeriods", 30) \
.SetGreaterThanZero() \
.SetDisplay("Optimization Bars", "Number of bars used for optimization", "General")
self._use_aggressive_entries = self.Param("UseAggressiveEntries", False) \
.SetDisplay("Aggressive Entries", "Allow entries without indicator crosses", "Trading")
self._trade_reverse = self.Param("TradeReverse", False) \
.SetDisplay("Reverse Trading", "Swap profitability preference for opposite trades", "Trading")
self._one_order_at_a_time = self.Param("OneOrderAtATime", True) \
.SetDisplay("One Position", "Permit only one open position", "Trading")
self._base_volume = self.Param("BaseVolume", 1.0) \
.SetGreaterThanZero() \
.SetDisplay("Base Volume", "Static order volume when sizing manually", "Risk")
self._use_dynamic_volume = self.Param("UseDynamicVolume", True) \
.SetDisplay("Dynamic Volume", "Use risk percentage for position sizing", "Risk")
self._risk_percent = self.Param("RiskPercent", 2.0) \
.SetDisplay("Risk %", "Percent of capital risked per trade", "Risk")
# 0=RSI, 1=MFI
self._indicator_choice = self.Param("IndicatorChoice", 0) \
.SetDisplay("Indicator", "0=RSI, 1=MFI", "Indicator")
self._indicator_top_value = self.Param("IndicatorTopValue", 100) \
.SetDisplay("Top Level", "Upper bound for level search", "Indicator")
self._indicator_bottom_value = self.Param("IndicatorBottomValue", 0) \
.SetDisplay("Bottom Level", "Lower bound for level search", "Indicator")
self._indicator_period = self.Param("IndicatorPeriod", 14) \
.SetGreaterThanZero() \
.SetDisplay("Indicator Period", "Averaging period for RSI or MFI", "Indicator")
self._use_dynamic_targets = self.Param("UseDynamicTargets", True) \
.SetDisplay("Dynamic Targets", "Use ATR based stop-loss and take-profit", "Risk")
self._static_stop_loss_points = self.Param("StaticStopLossPoints", 1000) \
.SetGreaterThanZero() \
.SetDisplay("Static Stop", "Stop-loss in points when dynamic targets disabled", "Risk")
self._static_take_profit_points = self.Param("StaticTakeProfitPoints", 2000) \
.SetGreaterThanZero() \
.SetDisplay("Static Take", "Take-profit in points when dynamic targets disabled", "Risk")
self._stop_loss_atr_multiplier = self.Param("StopLossAtrMultiplier", 2.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Stop Mult", "Stop-loss multiplier applied to ATR", "Risk")
self._take_profit_atr_multiplier = self.Param("TakeProfitAtrMultiplier", 7.0) \
.SetGreaterThanZero() \
.SetDisplay("ATR Take Mult", "Take-profit multiplier applied to ATR", "Risk")
self._use_break_even = self.Param("UseBreakEven", True) \
.SetDisplay("Use Breakeven", "Move stop to breakeven after trigger", "Risk")
self._break_even_trigger_points = self.Param("BreakEvenTriggerPoints", 200) \
.SetGreaterThanZero() \
.SetDisplay("Breakeven Trigger", "Profit in points required to arm breakeven", "Risk")
self._break_even_padding_points = self.Param("BreakEvenPaddingPoints", 100) \
.SetGreaterThanZero() \
.SetDisplay("Breakeven Padding", "Padding in points applied after trigger", "Risk")
self._candle_type = self.Param("CandleType", DataType.TimeFrame(TimeSpan.FromMinutes(5))) \
.SetDisplay("Candle Type", "Timeframe used for analysis", "General")
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
@property
def OptimizingPeriods(self):
return int(self._optimizing_periods.Value)
@property
def UseAggressiveEntries(self):
return self._use_aggressive_entries.Value
@property
def TradeReverse(self):
return self._trade_reverse.Value
@property
def OneOrderAtATime(self):
return self._one_order_at_a_time.Value
@property
def BaseVolume(self):
return float(self._base_volume.Value)
@property
def UseDynamicVolume(self):
return self._use_dynamic_volume.Value
@property
def RiskPercent(self):
return float(self._risk_percent.Value)
@property
def IndicatorChoice(self):
return int(self._indicator_choice.Value)
@property
def IndicatorTopValue(self):
return int(self._indicator_top_value.Value)
@property
def IndicatorBottomValue(self):
return int(self._indicator_bottom_value.Value)
@property
def IndicatorPeriod(self):
return int(self._indicator_period.Value)
@property
def UseDynamicTargets(self):
return self._use_dynamic_targets.Value
@property
def StaticStopLossPoints(self):
return int(self._static_stop_loss_points.Value)
@property
def StaticTakeProfitPoints(self):
return int(self._static_take_profit_points.Value)
@property
def StopLossAtrMultiplier(self):
return float(self._stop_loss_atr_multiplier.Value)
@property
def TakeProfitAtrMultiplier(self):
return float(self._take_profit_atr_multiplier.Value)
@property
def UseBreakEven(self):
return self._use_break_even.Value
@property
def BreakEvenTriggerPoints(self):
return int(self._break_even_trigger_points.Value)
@property
def BreakEvenPaddingPoints(self):
return int(self._break_even_padding_points.Value)
@property
def CandleType(self):
return self._candle_type.Value
def OnStarted2(self, time):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).OnStarted2(time)
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
if self.IndicatorChoice == 1:
self._indicator = MoneyFlowIndex()
else:
self._indicator = RelativeStrengthIndex()
self._indicator.Length = self.IndicatorPeriod
self._atr = AverageTrueRange()
self._atr.Length = self.IndicatorPeriod
subscription = self.SubscribeCandles(self.CandleType)
subscription.Bind(self._indicator, self._atr, self.process_candle).Start()
area = self.CreateChartArea()
if area is not None:
self.DrawCandles(area, subscription)
self.DrawIndicator(area, self._indicator)
self.DrawIndicator(area, self._atr)
self.DrawOwnTrades(area)
def process_candle(self, candle, indicator_value, atr_value):
if candle.State != CandleStates.Finished:
return
ind_val = float(indicator_value)
atr_val = float(atr_value)
close = float(candle.ClosePrice)
self._history.append((ind_val, close))
max_needed = max(self.OptimizingPeriods + 1, 3)
while len(self._history) > max_needed:
self._history.pop(0)
sec = self.Security
price_step = float(sec.PriceStep) if sec is not None and sec.PriceStep is not None and float(sec.PriceStep) > 0 else 1.0
if price_step <= 0:
price_step = 1.0
trigger_diff = self.BreakEvenTriggerPoints * price_step if self.UseBreakEven else 0.0
padding_pts = self.BreakEvenPaddingPoints if self.BreakEvenPaddingPoints <= self.BreakEvenTriggerPoints else 0
padding_diff = padding_pts * price_step if self.UseBreakEven else 0.0
self._manage_open_position(candle, trigger_diff, padding_diff)
if len(self._history) < max_needed:
return
# Build arrays (newest first)
indicator_values = []
close_values = []
for i in range(len(self._history) - 1, -1, -1):
indicator_values.append(self._history[i][0])
close_values.append(self._history[i][1])
if self.UseDynamicTargets:
if atr_val <= 0:
return
stop_loss_diff = atr_val * self.StopLossAtrMultiplier
take_profit_diff = atr_val * self.TakeProfitAtrMultiplier
else:
stop_loss_diff = self.StaticStopLossPoints * price_step
take_profit_diff = self.StaticTakeProfitPoints * price_step
if stop_loss_diff <= 0 or take_profit_diff <= 0:
return
volume = self.BaseVolume
step_multiplier = 1.0
sell_level, sell_profit = self._calc_best_sell_level(indicator_values, close_values, stop_loss_diff, take_profit_diff, volume, step_multiplier)
buy_level, buy_profit = self._calc_best_buy_level(indicator_values, close_values, stop_loss_diff, take_profit_diff, volume, step_multiplier)
adjusted_sell = sell_profit
adjusted_buy = buy_profit
if self.TradeReverse:
adjusted_sell = buy_profit
adjusted_buy = sell_profit
can_enter = not self.OneOrderAtATime or self.Position == 0
current_ind = indicator_values[0]
prev_ind = indicator_values[1]
if adjusted_sell > adjusted_buy:
if can_enter and ((current_ind < sell_level and prev_ind > sell_level) or self.UseAggressiveEntries):
self._enter_short(candle, stop_loss_diff, take_profit_diff)
elif adjusted_sell < adjusted_buy:
if can_enter and ((current_ind > buy_level and prev_ind < buy_level) or self.UseAggressiveEntries):
self._enter_long(candle, stop_loss_diff, take_profit_diff)
def _calc_best_sell_level(self, ind_vals, close_vals, sl_diff, tp_diff, volume, step_mult):
bottom = min(self.IndicatorBottomValue, self.IndicatorTopValue)
top = max(self.IndicatorBottomValue, self.IndicatorTopValue)
best_profit = 0.0
best_level = bottom
updated = False
for level in range(bottom, top + 1):
profit = self._eval_sell(ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult)
if profit > best_profit:
best_profit = profit
best_level = level
updated = True
return (best_level, best_profit if updated else 0.0)
def _calc_best_buy_level(self, ind_vals, close_vals, sl_diff, tp_diff, volume, step_mult):
bottom = min(self.IndicatorBottomValue, self.IndicatorTopValue)
top = max(self.IndicatorBottomValue, self.IndicatorTopValue)
best_profit = 0.0
best_level = top
updated = False
for level in range(top, bottom - 1, -1):
profit = self._eval_buy(ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult)
if profit > best_profit:
best_profit = profit
best_level = level
updated = True
return (best_level, best_profit if updated else 0.0)
def _eval_sell(self, ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult):
total = 0.0
n = len(ind_vals)
if n < 3:
return 0.0
threshold = float(level)
i = n - 2
while i >= 2:
if ind_vals[i] < threshold and ind_vals[i + 1] > threshold:
entry = close_vals[i]
j = i - 1
while j >= 1:
price = close_vals[j]
if price >= entry + sl_diff:
total -= (price - entry) * step_mult * volume
i = j
break
if price <= entry - tp_diff:
total += (entry - price) * step_mult * volume
i = j
break
j -= 1
i -= 1
return total
def _eval_buy(self, ind_vals, close_vals, level, sl_diff, tp_diff, volume, step_mult):
total = 0.0
n = len(ind_vals)
if n < 3:
return 0.0
threshold = float(level)
i = n - 2
while i >= 2:
if ind_vals[i] > threshold and ind_vals[i + 1] < threshold:
entry = close_vals[i]
j = i - 1
while j >= 1:
price = close_vals[j]
if price <= entry - sl_diff:
total -= (entry - price) * step_mult * volume
i = j
break
if price >= entry + tp_diff:
total += (price - entry) * step_mult * volume
i = j
break
j -= 1
i -= 1
return total
def _enter_long(self, candle, sl_diff, tp_diff):
self.BuyMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price - sl_diff
self._take_profit_price = self._entry_price + tp_diff
def _enter_short(self, candle, sl_diff, tp_diff):
self.SellMarket()
self._entry_price = float(candle.ClosePrice)
self._stop_price = self._entry_price + sl_diff
self._take_profit_price = self._entry_price - tp_diff
def _manage_open_position(self, candle, trigger_diff, padding_diff):
h = float(candle.HighPrice)
lo = float(candle.LowPrice)
if self.Position > 0:
if self.UseBreakEven and self._entry_price is not None and self._stop_price is not None:
trigger_price = self._entry_price + trigger_diff
target_stop = self._entry_price + padding_diff
if trigger_diff > 0 and h >= trigger_price and self._stop_price < target_stop:
self._stop_price = target_stop
if self._stop_price is not None and lo <= self._stop_price:
self.SellMarket()
self._reset_position_state()
return
if self._take_profit_price is not None and h >= self._take_profit_price:
self.SellMarket()
self._reset_position_state()
return
elif self.Position < 0:
if self.UseBreakEven and self._entry_price is not None and self._stop_price is not None:
trigger_price = self._entry_price - trigger_diff
target_stop = self._entry_price - padding_diff
if trigger_diff > 0 and lo <= trigger_price and self._stop_price > target_stop:
self._stop_price = target_stop
if self._stop_price is not None and h >= self._stop_price:
self.BuyMarket()
self._reset_position_state()
return
if self._take_profit_price is not None and lo <= self._take_profit_price:
self.BuyMarket()
self._reset_position_state()
return
else:
self._reset_position_state()
def _reset_position_state(self):
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
def OnReseted(self):
super(self_optimizing_rsi_or_mfi_trader_v3_strategy, self).OnReseted()
self._history = []
self._entry_price = None
self._stop_price = None
self._take_profit_price = None
def CreateClone(self):
return self_optimizing_rsi_or_mfi_trader_v3_strategy()