GitHub で見る

่‡ชๅทฑๆœ€้ฉๅŒ–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)ใ€‚

ๅ–ๅผ•ใƒญใ‚ธใƒƒใ‚ฏ

  1. ใ‚ชใ‚ทใƒฌใƒผใ‚ฟใƒผๅ€คใจ็ต‚ๅ€คใ‚’ๆŒใคOptimizingPeriods + 1ใฎๅฎŒๆˆใ—ใŸใ‚ญใƒฃใƒณใƒ‰ใƒซใฎ็ถ™็ถš็š„ใชๅฑฅๆญดใ‚’็ถญๆŒใ—ใพใ™ใ€‚
  2. IndicatorBottomValueใจIndicatorTopValueใฎ้–“ใฎๅ„ๆ•ดๆ•ฐใƒฌใƒ™ใƒซใซใคใ„ใฆใ€ๆˆฆ็•ฅใฏๅฑฅๆญดใ‚ฆใ‚ฃใƒณใƒ‰ใ‚ฆใงๅ–ๅผ•ใ‚’ใ‚ทใƒŸใƒฅใƒฌใƒผใƒˆใ—ใพใ™๏ผš
    • ใ‚ทใƒงใƒผใƒˆใ‚ทใƒŸใƒฅใƒฌใƒผใ‚ทใƒงใƒณ๏ผšใ‚ชใ‚ทใƒฌใƒผใ‚ฟใƒผใŒใƒฌใƒ™ใƒซใ‚’ไธ‹ๅ›žใฃใฆใ‚ฏใƒญใ‚นใ—ใŸๅ›žๆ•ฐใจใ€ใ‚ทใƒงใƒผใƒˆใฎใ‚นใƒˆใƒƒใƒ—ใƒญใ‚นใพใŸใฏใƒ†ใ‚คใ‚ฏใƒ—ใƒญใƒ•ใ‚ฃใƒƒใƒˆใฎใฉใกใ‚‰ใŒๅ…ˆใซใƒ’ใƒƒใƒˆใ—ใŸใ‹ใ‚’ๆ•ฐใˆใพใ™ใ€‚
    • ใƒญใƒณใ‚ฐใ‚ทใƒŸใƒฅใƒฌใƒผใ‚ทใƒงใƒณ๏ผšใ‚ชใ‚ทใƒฌใƒผใ‚ฟใƒผใŒใƒฌใƒ™ใƒซใ‚’ไธŠๅ›žใฃใฆใ‚ฏใƒญใ‚นใ—ใŸๅ›žๆ•ฐใจใ€ๅ–ๅผ•ใŒใฉใ‚Œใ ใ‘ๅŽ็›Šๆ€งใŒใ‚ใฃใŸใ‹ใ‚’ๆ•ฐใˆใพใ™ใ€‚
  3. ๅ„ๆ–นๅ‘ใงๆœ€้ซ˜ใฎใ‚ทใƒŸใƒฅใƒฌใƒผใƒˆๅˆฉ็›Šใ‚’ๆไพ›ใ—ใŸ้–พๅ€คใ‚’้ธๆŠžใ—ใพใ™ใ€‚TradeReverseใŒๆœ‰ๅŠนใชๅ ดๅˆใ€ๅŽ็›Šๆ€งใ‚นใ‚ณใ‚ขใŒไบคๆ›ใ•ใ‚Œใ€ๅๅฏพๆ–นๅ‘ใŒๆœ‰ๅˆฉใซใชใ‚Šใพใ™ใ€‚
  4. ใƒฉใ‚คใƒ–ใ‚ชใ‚ทใƒฌใƒผใ‚ฟใƒผใŒๅŽ็›Šๆ€งใฎใ‚ใ‚‹ๆ–นๅ‘ใฎใƒ™ใ‚นใƒˆใƒฌใƒ™ใƒซใ‚’ใ‚ฏใƒญใ‚นใ™ใ‚‹ใจใ๏ผˆใพใŸใฏUseAggressiveEntriesใŒtrueใฎๅ ดๅˆใฏๅณๅบงใซ๏ผ‰ใ€ๆˆฆ็•ฅใฏOneOrderAtATimeใ‚’ๅฐŠ้‡ใ—ใชใŒใ‚‰ใƒใ‚ธใ‚ทใƒงใƒณใ‚’้–‹ใใพใ™ใ€‚
  5. ใ‚จใ‚ฐใ‚ธใƒƒใƒˆ็ฎก็†๏ผš
    • ใ‚นใƒˆใƒƒใƒ—ใƒญใ‚นใจใƒ†ใ‚คใ‚ฏใƒ—ใƒญใƒ•ใ‚ฃใƒƒใƒˆใƒฌใƒ™ใƒซใฏATRๅ€ๆ•ฐ๏ผˆStopLossAtrMultiplierใ€TakeProfitAtrMultiplier๏ผ‰ใพใŸใฏๅ›บๅฎšใƒใ‚คใƒณใƒˆ่ท้›ข๏ผˆStaticStopLossPointsใ€StaticTakeProfitPoints๏ผ‰ใ‹ใ‚‰่จˆ็ฎ—ใ•ใ‚Œใพใ™ใ€‚
    • UseBreakEvenใฏใ€ๆœชๅฎŸ็พๅˆฉ็›ŠใŒBreakEvenTriggerPointsใซ้”ใ™ใ‚‹ใจใ€ใ‚นใƒˆใƒƒใƒ—ใ‚’ใ‚จใƒณใƒˆใƒชใƒผไพกๆ ผใƒ—ใƒฉใ‚นBreakEvenPaddingPointsใซ็งปๅ‹•ใ—ใพใ™ใ€‚
    • ใ‚นใƒˆใƒƒใƒ—ใƒญใ‚นใพใŸใฏใƒ†ใ‚คใ‚ฏใƒ—ใƒญใƒ•ใ‚ฃใƒƒใƒˆไพกๆ ผใŒใ‚ฏใƒญใ‚นใ•ใ‚Œใ‚‹ใจใ€ใƒใ‚ธใ‚ทใƒงใƒณใฏ้–‰ใ˜ใ‚‰ใ‚Œใพใ™ใ€‚

ใƒชใ‚นใ‚ฏ็ฎก็†

  • ๅ‹•็š„ใ‚ตใ‚คใ‚ธใƒณใ‚ฐ๏ผš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๏ผ‰ใ‹ใ‚‰ๆฉๆตใ‚’ๅ—ใ‘ใ‚‹ๅฏ่ƒฝๆ€งใŒใ‚ใ‚Šใพใ™ใ€‚
  • ๆˆฆ็•ฅใŒ้–พๅ€คใ‚’็ถ™็ถš็š„ใซ้ฉๅฟœใ•ใ›ใ‚‹ใŸใ‚ใ€ใ‚ฆใ‚ฉใƒผใ‚ฏใƒ•ใ‚ฉใƒฏใƒผใƒ‰ๅˆ†ๆžใซใ‚ˆใ‚‹ๅ…ˆ่กŒใƒ†ใ‚นใƒˆใ‚’ๆคœ่จŽใ—ใฆใใ ใ•ใ„ใ€‚

ไฝฟ็”จๆ–นๆณ•

  1. Designerใงใ‚ณใƒใ‚ฏใ‚ฟใƒผใซๆˆฆ็•ฅใ‚’่ฟฝๅŠ ใ™ใ‚‹ใ‹ใ€ใƒ—ใƒญใ‚ฐใƒฉใƒ ใงๅฎŸ่กŒใ—ใพใ™ใ€‚
  2. ๅธŒๆœ›ใ™ใ‚‹ใ‚คใƒณใ‚นใƒˆใ‚ฅใƒซใƒกใƒณใƒˆใ€ใƒใƒผใƒˆใƒ•ใ‚ฉใƒชใ‚ชใ€ใƒ‘ใƒฉใƒกใƒผใ‚ฟใƒผๅ€คใ‚’่จญๅฎšใ—ใพใ™ใ€‚
  3. ๆˆฆ็•ฅใ‚’้–‹ๅง‹ใ—ใพใ™๏ผ›ๆœ€้ฉๅŒ–ใซๅๅˆ†ใชใ‚ญใƒฃใƒณใƒ‰ใƒซใŒ่“„็ฉใ•ใ‚Œใ‚‹ใจๅ–ๅผ•ใ‚’้–‹ๅง‹ใ—ใพใ™ใ€‚

ๅˆถ้™ไบ‹้ …

  • ๅฑฅๆญดใฎๆœ€้ฉๅŒ–ใฏๅ„ใƒใƒผใง็™บ็”Ÿใ—ใ€้žๅธธใซๅคงใใช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 Order _protectiveExitOrder;

	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;
		_protectiveExitOrder = 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;

		if (ManageOpenPosition(candle, triggerDiff, paddingDiff))
			return;

		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 (volume <= 0m)
			return 0m;

		if (Security == null)
			return volume;

		var min = Security.MinVolume ?? 0m;
		var max = Security.MaxVolume is > 0m ? Security.MaxVolume.Value : decimal.MaxValue;
		if (max < min)
			return 0m;

		var step = Security.VolumeStep ?? 0m;
		if (step <= 0m)
			return Math.Clamp(volume, min, max);

		var first = min > 0m ? Math.Ceiling(min / step) * step : step;
		var last = max == decimal.MaxValue ? decimal.MaxValue : Math.Floor(max / step) * step;
		if (last < first)
			return 0m;

		var aligned = Math.Floor(volume / step) * step;
		return Math.Clamp(aligned, first, last);
	}

	private void EnterLong(ICandleMessage candle, decimal volume, decimal stopLossDiff, decimal takeProfitDiff)
	{
		var orderVolume = volume;
		if (Position < 0m)
			orderVolume += Math.Abs(Position);

		BuyMarket(orderVolume);

		_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(orderVolume);

		_entryPrice = candle.ClosePrice;
		_stopPrice = _entryPrice + stopLossDiff;
		_takeProfitPrice = _entryPrice - takeProfitDiff;
	}

	private bool ManageOpenPosition(ICandleMessage candle, decimal triggerDiff, decimal paddingDiff)
	{
		var exitAction = ResolveProtectiveExitAction(Position, _protectiveExitOrder?.State);
		if (exitAction == ProtectiveExitAction.Reset)
		{
			ResetPositionState();
			_protectiveExitOrder = null;
			return false;
		}

		if (exitAction == ProtectiveExitAction.Wait)
			return true;

		if (_protectiveExitOrder != null)
		{
			// A terminal partial/cancel leaves protection intact and permits a retry.
			_protectiveExitOrder = null;
		}

		// Existing protection is evaluated before this candle can arm a new breakeven level.
		if (Position > 0m)
		{
			if (_stopPrice is decimal stop && candle.LowPrice <= stop)
			{
				_protectiveExitOrder = SellMarket(Math.Abs(Position));
				return true;
			}

			if (_takeProfitPrice is decimal target && candle.HighPrice >= target)
			{
				_protectiveExitOrder = SellMarket(Math.Abs(Position));
				return true;
			}

			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;
			}
		}
		else if (Position < 0m)
		{
			if (_stopPrice is decimal stop && candle.HighPrice >= stop)
			{
				_protectiveExitOrder = BuyMarket(Math.Abs(Position));
				return true;
			}

			if (_takeProfitPrice is decimal target && candle.LowPrice <= target)
			{
				_protectiveExitOrder = BuyMarket(Math.Abs(Position));
				return true;
			}

			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;
			}
		}

		return false;
	}

	private void ResetPositionState()
	{
		_entryPrice = null;
		_stopPrice = null;
		_takeProfitPrice = null;
	}

	internal enum ProtectiveExitAction
	{
		Reset,
		Wait,
		Evaluate,
	}

	internal static ProtectiveExitAction ResolveProtectiveExitAction(decimal position, OrderStates? exitOrderState)
	{
		if (position == 0m)
			return ProtectiveExitAction.Reset;

		if (exitOrderState.HasValue && exitOrderState.Value is not (OrderStates.Done or OrderStates.Failed))
			return ProtectiveExitAction.Wait;

		return ProtectiveExitAction.Evaluate;
	}
}