From fa3394415d66cc168fa1e2168990168f0d7e1cb1 Mon Sep 17 00:00:00 2001 From: Hiroaki86 Date: Wed, 27 May 2026 23:01:28 +0900 Subject: [PATCH] Consolidate Python ignore rules into root gitignore --- .github/copilot-instructions.md | 94 + .gitignore | 176 + .vscode/settings.json | 27 + AGENTS.md | 94 + Experts/Comment_Test.mq5 | Bin 0 -> 10196 bytes Experts/Debug_Entry_Panel.mq5 | 1707 + Experts/MyProject/HIT-EA.mq5 | 935 + Experts/MyProject/HIT-EA_refactor.mq5 | 1496 + Experts/MyProject/HIT-EA_refactor_ver2.mq5 | 2122 + Experts/MyProject/HIT-EA_refactor_ver3.mq5 | 2377 + Experts/MyProject/HIT-EA_refactor_ver4.mq5 | 2377 + Experts/MyProject/HIT-EA_refactor_ver5.mq5 | 441 + ...efactor_ver5.mq5.bak_codex_20260513_002404 | 440 + Experts/MyProject/HIT-EA_refactor_ver6.mq5 | 462 + ...T-EA_refactor_ver6.mq5.bak_20260523_230523 | 441 + ...efactor_ver6.mq5.bak_codex_20260524_000548 | 441 + ...efactor_ver6.mq5.bak_codex_20260527_224059 | 462 + Experts/MyProject/TradingPanel.mq5 | 395 + Include/Arrays/Array.mqh | 182 + Include/Arrays/ArrayChar.mqh | 770 + Include/Arrays/ArrayColor.mqh | 770 + Include/Arrays/ArrayDatetime.mqh | 770 + Include/Arrays/ArrayDouble.mqh | 777 + Include/Arrays/ArrayFloat.mqh | 777 + Include/Arrays/ArrayInt.mqh | 769 + Include/Arrays/ArrayLong.mqh | 769 + Include/Arrays/ArrayObj.mqh | 759 + Include/Arrays/ArrayShort.mqh | 769 + Include/Arrays/ArrayString.mqh | 780 + Include/Arrays/ArrayUChar.mqh | 770 + Include/Arrays/ArrayUInt.mqh | 774 + Include/Arrays/ArrayULong.mqh | 769 + Include/Arrays/ArrayUShort.mqh | 769 + Include/Arrays/List.mqh | 657 + Include/Arrays/Tree.mqh | 415 + Include/Arrays/TreeNode.mqh | 174 + Include/Canvas/Canvas.mqh | 4861 + Include/Canvas/Canvas3D.mqh | Bin 0 -> 34652 bytes Include/Canvas/Charts/ChartCanvas.mqh | 1046 + Include/Canvas/Charts/HistogramChart.mqh | 347 + Include/Canvas/Charts/LineChart.mqh | 383 + Include/Canvas/Charts/PieChart.mqh | 414 + Include/Canvas/DX/DXBox.mqh | 75 + Include/Canvas/DX/DXBuffers.mqh | 92 + Include/Canvas/DX/DXData.mqh | Bin 0 -> 4048 bytes Include/Canvas/DX/DXDispatcher.mqh | 373 + Include/Canvas/DX/DXHandle.mqh | Bin 0 -> 8568 bytes Include/Canvas/DX/DXInput.mqh | Bin 0 -> 4968 bytes Include/Canvas/DX/DXMath.mqh | 3351 + Include/Canvas/DX/DXMesh.mqh | 374 + Include/Canvas/DX/DXObject.mqh | Bin 0 -> 1744 bytes Include/Canvas/DX/DXObjectBase.mqh | Bin 0 -> 2106 bytes Include/Canvas/DX/DXShader.mqh | Bin 0 -> 16984 bytes Include/Canvas/DX/DXSurface.mqh | 159 + Include/Canvas/DX/DXTexture.mqh | Bin 0 -> 6890 bytes Include/Canvas/DX/DXUtils.mqh | 942 + .../Canvas/DX/Shaders/DefaultShaderPixel.hlsl | 71 + .../DX/Shaders/DefaultShaderVertex.hlsl | 72 + Include/Canvas/FlameCanvas.mqh | 751 + Include/ChartObjects/ChartObject.mqh | 1000 + Include/ChartObjects/ChartObjectPanel.mqh | 211 + Include/ChartObjects/ChartObjectSubChart.mqh | 398 + Include/ChartObjects/ChartObjectsArrows.mqh | 479 + .../ChartObjects/ChartObjectsBmpControls.mqh | 512 + Include/ChartObjects/ChartObjectsChannels.mqh | 246 + Include/ChartObjects/ChartObjectsElliott.mqh | 201 + Include/ChartObjects/ChartObjectsFibo.mqh | 365 + Include/ChartObjects/ChartObjectsGann.mqh | 390 + Include/ChartObjects/ChartObjectsLines.mqh | 335 + Include/ChartObjects/ChartObjectsShapes.mqh | 142 + .../ChartObjects/ChartObjectsTxtControls.mqh | 895 + Include/Charts/Chart.mqh | 1549 + Include/Controls/BmpButton.mqh | 268 + Include/Controls/Button.mqh | 146 + Include/Controls/CheckBox.mqh | 183 + Include/Controls/CheckGroup.mqh | 377 + Include/Controls/ComboBox.mqh | 324 + Include/Controls/DateDropList.mqh | 413 + Include/Controls/DatePicker.mqh | 264 + Include/Controls/Defines.mqh | 190 + Include/Controls/Dialog.mqh | 971 + Include/Controls/Edit.mqh | 188 + Include/Controls/Label.mqh | 93 + Include/Controls/ListView.mqh | 544 + Include/Controls/Panel.mqh | 124 + Include/Controls/Picture.mqh | 125 + Include/Controls/RadioButton.mqh | 160 + Include/Controls/RadioGroup.mqh | 361 + Include/Controls/Rect.mqh | 279 + Include/Controls/Scrolls.mqh | 675 + Include/Controls/SpinEdit.mqh | 265 + Include/Controls/Wnd.mqh | 755 + Include/Controls/WndClient.mqh | 308 + Include/Controls/WndContainer.mqh | 473 + Include/Controls/WndObj.mqh | 262 + Include/Controls/res/CheckBoxOff.bmp | Bin 0 -> 576 bytes Include/Controls/res/CheckBoxOn.bmp | Bin 0 -> 576 bytes Include/Controls/res/Close.bmp | Bin 0 -> 1080 bytes Include/Controls/res/DateDropOff.bmp | Bin 0 -> 2104 bytes Include/Controls/res/DateDropOn.bmp | Bin 0 -> 2104 bytes Include/Controls/res/Down.bmp | Bin 0 -> 824 bytes Include/Controls/res/DownTransp.bmp | Bin 0 -> 1080 bytes Include/Controls/res/DropOff.bmp | Bin 0 -> 1080 bytes Include/Controls/res/DropOn.bmp | Bin 0 -> 1080 bytes Include/Controls/res/Left.bmp | Bin 0 -> 824 bytes Include/Controls/res/LeftTransp.bmp | Bin 0 -> 1080 bytes Include/Controls/res/RadioButtonOff.bmp | Bin 0 -> 632 bytes Include/Controls/res/RadioButtonOn.bmp | Bin 0 -> 632 bytes Include/Controls/res/Restore.bmp | Bin 0 -> 1080 bytes Include/Controls/res/Right.bmp | Bin 0 -> 824 bytes Include/Controls/res/RightTransp.bmp | Bin 0 -> 1080 bytes Include/Controls/res/SpinDec.bmp | Bin 0 -> 568 bytes Include/Controls/res/SpinInc.bmp | Bin 0 -> 568 bytes Include/Controls/res/ThumbHor.bmp | Bin 0 -> 1144 bytes Include/Controls/res/ThumbVert.bmp | Bin 0 -> 1112 bytes Include/Controls/res/Turn.bmp | Bin 0 -> 1080 bytes Include/Controls/res/Up.bmp | Bin 0 -> 824 bytes Include/Controls/res/UpTransp.bmp | Bin 0 -> 1080 bytes Include/Expert/Expert.mqh | Bin 0 -> 122604 bytes Include/Expert/ExpertBase.mqh | 715 + Include/Expert/ExpertMoney.mqh | 124 + Include/Expert/ExpertSignal.mqh | 464 + Include/Expert/ExpertTrade.mqh | 171 + Include/Expert/ExpertTrailing.mqh | 33 + Include/Expert/Money/MoneyFixedLot.mqh | 73 + Include/Expert/Money/MoneyFixedMargin.mqh | 76 + Include/Expert/Money/MoneyFixedRisk.mqh | 109 + Include/Expert/Money/MoneyNone.mqh | 71 + Include/Expert/Money/MoneySizeOptimized.mqh | 155 + Include/Expert/Signal/SignalAC.mqh | 173 + Include/Expert/Signal/SignalAMA.mqh | 265 + Include/Expert/Signal/SignalAO.mqh | 339 + Include/Expert/Signal/SignalBearsPower.mqh | 289 + Include/Expert/Signal/SignalBullsPower.mqh | 289 + Include/Expert/Signal/SignalCCI.mqh | 382 + Include/Expert/Signal/SignalDEMA.mqh | 257 + Include/Expert/Signal/SignalDeMarker.mqh | 378 + Include/Expert/Signal/SignalEnvelopes.mqh | 193 + Include/Expert/Signal/SignalFrAMA.mqh | 257 + Include/Expert/Signal/SignalITF.mqh | 91 + Include/Expert/Signal/SignalMA.mqh | 261 + Include/Expert/Signal/SignalMACD.mqh | 408 + Include/Expert/Signal/SignalRSI.mqh | 400 + Include/Expert/Signal/SignalRVI.mqh | 180 + Include/Expert/Signal/SignalSAR.mqh | 168 + Include/Expert/Signal/SignalStoch.mqh | 426 + Include/Expert/Signal/SignalTEMA.mqh | 257 + Include/Expert/Signal/SignalTRIX.mqh | 395 + Include/Expert/Signal/SignalWPR.mqh | 371 + Include/Expert/Trailing/TrailingFixedPips.mqh | 132 + Include/Expert/Trailing/TrailingMA.mqh | 157 + Include/Expert/Trailing/TrailingNone.mqh | 40 + .../Expert/Trailing/TrailingParabolicSAR.mqh | 123 + Include/Generic/ArrayList.mqh | Bin 0 -> 50384 bytes Include/Generic/HashMap.mqh | 622 + Include/Generic/HashSet.mqh | 972 + Include/Generic/Interfaces/ICollection.mqh | 24 + Include/Generic/Interfaces/IComparable.mqh | 19 + Include/Generic/Interfaces/IComparer.mqh | 17 + .../Interfaces/IEqualityComparable.mqh | 19 + .../Generic/Interfaces/IEqualityComparer.mqh | 19 + Include/Generic/Interfaces/IList.mqh | 26 + Include/Generic/Interfaces/IMap.mqh | 27 + Include/Generic/Interfaces/ISet.mqh | 37 + Include/Generic/Internal/ArrayFunction.mqh | 135 + Include/Generic/Internal/CompareFunction.mqh | 209 + Include/Generic/Internal/DefaultComparer.mqh | 22 + .../Internal/DefaultEqualityComparer.mqh | 25 + Include/Generic/Internal/EqualFunction.mqh | 26 + Include/Generic/Internal/HashFunction.mqh | 176 + Include/Generic/Internal/Introsort.mqh | 244 + Include/Generic/Internal/PrimeGenerator.mqh | 81 + Include/Generic/LinkedList.mqh | 563 + Include/Generic/Queue.mqh | Bin 0 -> 29814 bytes Include/Generic/RedBlackTree.mqh | Bin 0 -> 74814 bytes Include/Generic/SortedMap.mqh | 341 + Include/Generic/SortedSet.mqh | 673 + Include/Generic/Stack.mqh | 227 + Include/Graphics/Axis.mqh | 260 + Include/Graphics/ColorGenerator.mqh | 74 + Include/Graphics/Curve.mqh | 700 + Include/Graphics/Graphic.mqh | Bin 0 -> 173126 bytes Include/Indicators/BillWilliams.mqh | 761 + Include/Indicators/Custom.mqh | 225 + Include/Indicators/Indicator.mqh | 516 + Include/Indicators/Indicators.mqh | 385 + Include/Indicators/Oscilators.mqh | 1722 + Include/Indicators/Series.mqh | 299 + Include/Indicators/TimeSeries.mqh | 1552 + Include/Indicators/Trend.mqh | 1717 + Include/Indicators/Volumes.mqh | 424 + Include/MQLTA ErrorHandling.mqh | Bin 0 -> 33862 bytes Include/MQLTA Utils.mqh | 368 + Include/Math/Alglib/alglib.mqh | 38345 +++++++ Include/Math/Alglib/alglibinternal.mqh | 15934 +++ Include/Math/Alglib/alglibmisc.mqh | 2575 + Include/Math/Alglib/ap.mqh | 2549 + Include/Math/Alglib/arrayresize.mqh | 79 + Include/Math/Alglib/bitconvert.mqh | 411 + Include/Math/Alglib/dataanalysis.mqh | 26387 +++++ Include/Math/Alglib/delegatefunctions.mqh | 444 + Include/Math/Alglib/diffequations.mqh | 849 + Include/Math/Alglib/fasttransforms.mqh | 2657 + Include/Math/Alglib/integration.mqh | 2980 + Include/Math/Alglib/interpolation.mqh | 34261 ++++++ Include/Math/Alglib/linalg.mqh | 37629 +++++++ Include/Math/Alglib/matrix.mqh | 1026 + Include/Math/Alglib/optimization.mqh | 51766 +++++++++ Include/Math/Alglib/solvers.mqh | 7154 ++ Include/Math/Alglib/specialfunctions.mqh | 6534 ++ Include/Math/Alglib/statistics.mqh | 13650 +++ Include/Math/Fuzzy/dictionary.mqh | 197 + Include/Math/Fuzzy/fuzzyrule.mqh | 371 + Include/Math/Fuzzy/fuzzyterm.mqh | 69 + Include/Math/Fuzzy/fuzzyvariable.mqh | 113 + Include/Math/Fuzzy/genericfuzzysystem.mqh | 333 + Include/Math/Fuzzy/helper.mqh | 158 + Include/Math/Fuzzy/inferencemethod.mqh | 125 + Include/Math/Fuzzy/mamdanifuzzysystem.mqh | 556 + Include/Math/Fuzzy/membershipfunction.mqh | 1008 + Include/Math/Fuzzy/ruleparser.mqh | 1019 + Include/Math/Fuzzy/sugenofuzzysystem.mqh | 327 + Include/Math/Fuzzy/sugenovariable.mqh | 254 + Include/Math/Stat/Beta.mqh | 772 + Include/Math/Stat/Binomial.mqh | 874 + Include/Math/Stat/Cauchy.mqh | 539 + Include/Math/Stat/ChiSquare.mqh | 531 + Include/Math/Stat/Exponential.mqh | 520 + Include/Math/Stat/F.mqh | 563 + Include/Math/Stat/Gamma.mqh | 764 + Include/Math/Stat/Geometric.mqh | 567 + Include/Math/Stat/Hypergeometric.mqh | 754 + Include/Math/Stat/Logistic.mqh | 592 + Include/Math/Stat/Lognormal.mqh | 635 + Include/Math/Stat/Math.mqh | Bin 0 -> 434790 bytes Include/Math/Stat/NegativeBinomial.mqh | 643 + Include/Math/Stat/NoncentralBeta.mqh | 954 + Include/Math/Stat/NoncentralChiSquare.mqh | 912 + Include/Math/Stat/NoncentralF.mqh | 790 + Include/Math/Stat/NoncentralT.mqh | 1192 + Include/Math/Stat/Normal.mqh | 914 + Include/Math/Stat/Poisson.mqh | 791 + Include/Math/Stat/Stat.mqh | 27 + Include/Math/Stat/T.mqh | 654 + Include/Math/Stat/Uniform.mqh | 539 + Include/Math/Stat/Weibull.mqh | 574 + Include/MovingAverages.mqh | 317 + Include/MyLib/Common/HITExternalProcess.mqh | 528 + Include/MyLib/Common/HITRuntimeController.mqh | 289 + ...meController.mqh.bak_codex_20260524_000548 | 287 + .../MyLib/Common/TradingPanelSymbolUtils.mqh | 40 + Include/MyLib/Panel/SLTPManagerPanel.mqh | 543 + .../SLTPManagerPanel.mqh.bak_20260523_230523 | 494 + .../SLTPManagerPanel.mqh.bak_20260523_232830 | 543 + Include/MyLib/Panel/TradingPanelPanel.mqh | 546 + Include/MyLib/Signals/HITEntrySignal.mqh | 1046 + ...TEntrySignal.mqh.bak_codex_20260513_002404 | 619 + ...TEntrySignal.mqh.bak_codex_20260524_000548 | 685 + ...TEntrySignal.mqh.bak_codex_20260527_224059 | 1026 + .../MyLib/Signals/HITPythonSignalGateway.mqh | 514 + ...ignalGateway.mqh.bak_codex_20260524_000548 | 385 + Include/MyLib/Trading/HITTradeManager.mqh | 561 + ...TradeManager.mqh.bak_codex_20260524_000548 | 427 + Include/MyLib/Trading/SLTPManager.mqh | 760 + .../Trading/TradingPanelTradingManagers.mqh | 1186 + Include/Object.mqh | 33 + Include/OpenCL/OpenCL.mqh | 765 + Include/SmoothAlgorithms_.mqh | Bin 0 -> 250382 bytes Include/StdLibErr.mqh | 10 + Include/Strings/String.mqh | 377 + Include/Tools/DateTime.mqh | 581 + Include/Trade/AccountInfo.mqh | 397 + Include/Trade/DealInfo.mqh | 430 + Include/Trade/HistoryOrderInfo.mqh | 472 + Include/Trade/OrderInfo.mqh | 553 + Include/Trade/PositionInfo.mqh | 366 + Include/Trade/SymbolInfo.mqh | 789 + Include/Trade/TerminalInfo.mqh | 225 + Include/Trade/Trade.mqh | 1708 + Include/VirtualKeys.mqh | 200 + Include/WinAPI/errhandlingapi.mqh | 28 + Include/WinAPI/fileapi.mqh | 146 + Include/WinAPI/handleapi.mqh | 21 + Include/WinAPI/libloaderapi.mqh | 47 + Include/WinAPI/memoryapi.mqh | 85 + Include/WinAPI/processenv.mqh | 27 + Include/WinAPI/processthreadsapi.mqh | 202 + Include/WinAPI/securitybaseapi.mqh | 119 + Include/WinAPI/sysinfoapi.mqh | 95 + Include/WinAPI/winapi.mqh | 21 + Include/WinAPI/winbase.mqh | 814 + Include/WinAPI/windef.mqh | 325 + Include/WinAPI/wingdi.mqh | 2080 + Include/WinAPI/winnt.mqh | 3642 + Include/WinAPI/winreg.mqh | 77 + Include/WinAPI/winuser.mqh | 1825 + Include/smoothalgorithms.mqh | 3296 + Indicators/MyProject/eATR.mq5 | Bin 0 -> 4028 bytes Indicators/SuperTrend_Custom.mq5 | Bin 0 -> 17322 bytes Indicators/SuperTrend_CustomWithOffset.mq5 | Bin 0 -> 21460 bytes Indicators/SuperTrend_Custom_20251203.mq5 | Bin 0 -> 17990 bytes Indicators/eSuperTrend.mq5 | Bin 0 -> 18426 bytes Indicators/eSuperTrendWithOffset.mq5 | Bin 0 -> 22802 bytes Indicators/eSuperTrendWithOffset2.mq5 | Bin 0 -> 24432 bytes README.md | 3 + Scripts/UnitTests/Alglib/TestClasses.mq5 | 716 + Scripts/UnitTests/Alglib/TestClasses.mqh | 86597 ++++++++++++++++ Scripts/UnitTests/Alglib/TestInterfaces.mq5 | 365 + Scripts/UnitTests/Alglib/TestInterfaces.mqh | 17989 ++++ Scripts/UnitTests/Fuzzy/TestFuzzy.mq5 | 661 + Scripts/UnitTests/Generic/TestArrayList.mq5 | 1069 + Scripts/UnitTests/Generic/TestHashMap.mq5 | 186 + Scripts/UnitTests/Generic/TestHashSet.mq5 | 147 + Scripts/UnitTests/Generic/TestLinkedList.mq5 | 1991 + Scripts/UnitTests/Generic/TestQueue.mq5 | 259 + .../UnitTests/Generic/TestRedBlackTree.mq5 | 361 + Scripts/UnitTests/Generic/TestSortedMap.mq5 | 187 + Scripts/UnitTests/Generic/TestSortedSet.mq5 | 147 + Scripts/UnitTests/Generic/TestStack.mq5 | 259 + Scripts/UnitTests/Stat/TestStat.mq5 | 4855 + Scripts/UnitTests/Stat/TestStatBenchmark.mq5 | 1590 + Scripts/UnitTests/Stat/TestStatPrecision.mq5 | 288 + WorkSpace/MQL5.code-workspace | 37 + compile_commands.json | 7586 ++ docs/MyProject/HIT-EA_spec_en.md | 199 + ...IT-EA_spec_en.md.bak_codex_20260513_002404 | 180 + docs/MyProject/HIT-EA_spec_ja.md | 231 + ...IT-EA_spec_ja.md.bak_codex_20260513_002404 | 180 + docs/MyProject/SLTPManager_spec_en.md | 56 + docs/MyProject/SLTPManager_spec_ja.md | 56 + docs/MyProject/TradingPanel_spec_en.md | 55 + docs/MyProject/TradingPanel_spec_ja.md | 55 + .../codex_mql5_sltp_manager_instructions.md | 387 + .../.agents/skills/fastapi-api/SKILL.md | 197 + .../oracle-select/oracle-select_SKILL.md | 254 + .../.agents/skills/python-base/SKILL.md | 47 + .../.agents/skills/python-logging/SKILL.md | 62 + .../.agents/skills/python-settings/SKILL.md | 59 + .../.agents/skills/python-testing/SKILL.md | 74 + .../.agents/skills/sqlserver-ddl/SKILL.md | 48 + .../.agents/skills/streamlit-page/SKILL.md | 43 + python_for_ea/.cursor/rules/fastapi.mdc | 334 + python_for_ea/.cursor/rules/logging.mdc | 31 + python_for_ea/.cursor/rules/oracle.mdc | 17 + python_for_ea/.cursor/rules/pandas.mdc | 27 + python_for_ea/.cursor/rules/project.mdc | 31 + python_for_ea/.cursor/rules/pytest.mdc | 48 + python_for_ea/.cursor/rules/python.mdc | 40 + python_for_ea/.cursor/rules/settings.mdc | 26 + python_for_ea/.cursor/rules/sqlserver.mdc | 29 + python_for_ea/.cursor/rules/streamlit.mdc | 28 + python_for_ea/.github/copilot-instructions.md | 26 + .../instructions/fastapi.instructions.md | 332 + .../instructions/logging.instructions.md | 29 + .../instructions/oracle.instructions.md | 15 + .../instructions/pandas.instructions.md | 25 + .../instructions/pytest.instructions.md | 46 + .../instructions/python.instructions.md | 38 + .../instructions/settings.instructions.md | 24 + .../instructions/sqlserver.instructions.md | 27 + .../instructions/streamlit.instructions.md | 26 + python_for_ea/.python-version | 1 + python_for_ea/AGENTS.md | 123 + python_for_ea/README.md | 195 + python_for_ea/bat/get_entry_reply.bat | 27 + python_for_ea/bat/get_trend_reply.bat | 27 + .../docs/architecture/folder-structure.md | 229 + .../docs/refactor/gold_strategy_20260503.md | 347 + python_for_ea/get_entry_reply.py | 21 + python_for_ea/get_trend_reply.py | 21 + python_for_ea/pyproject.toml | 35 + python_for_ea/src/ea_py/__init__.py | 2 + python_for_ea/src/ea_py/charting/__init__.py | 2 + .../src/ea_py/charting/candlestick.py | 81 + python_for_ea/src/ea_py/config.py | 56 + python_for_ea/src/ea_py/constants.py | 54 + python_for_ea/src/ea_py/io/__init__.py | 2 + python_for_ea/src/ea_py/io/debug_logs.py | 105 + python_for_ea/src/ea_py/io/mt5_files.py | 66 + python_for_ea/src/ea_py/io/ohlc_csv.py | 48 + python_for_ea/src/ea_py/openai_client.py | 130 + python_for_ea/src/ea_py/paths.py | 86 + python_for_ea/src/ea_py/pipelines/__init__.py | 2 + .../src/ea_py/pipelines/entry_pipeline.py | 317 + .../src/ea_py/pipelines/trend_pipeline.py | 104 + python_for_ea/src/ea_py/prompts/__init__.py | 2 + .../src/ea_py/prompts/entry_prompt.py | 323 + .../src/ea_py/prompts/trend_prompt.py | 100 + python_for_ea/src/ea_py/types.py | 38 + python_for_ea/tests/unit/test_config.py | 29 + python_for_ea/tests/unit/test_imbalance.py | 118 + .../tests/unit/test_openai_client.py | 93 + .../tests/unit/test_target_prices.py | 131 + python_for_ea/tests/unit/test_trend_state.py | 160 + python_for_ea/tests/unit/test_volatility.py | 37 + python_for_ea/uv.lock | 2148 + .../work/HIT-EA_refactor_ver3_restore.mq5 | 2419 + python_for_ea/work/get_entry_reply.py | 630 + python_for_ea/work/get_trend_reply.py | 488 + 399 files changed, 509103 insertions(+) create mode 100644 .github/copilot-instructions.md create mode 100644 .gitignore create mode 100644 .vscode/settings.json create mode 100644 AGENTS.md create mode 100644 Experts/Comment_Test.mq5 create mode 100644 Experts/Debug_Entry_Panel.mq5 create mode 100644 Experts/MyProject/HIT-EA.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver2.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver3.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver4.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver5.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver6.mq5 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_20260523_230523 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260524_000548 create mode 100644 Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260527_224059 create mode 100644 Experts/MyProject/TradingPanel.mq5 create mode 100644 Include/Arrays/Array.mqh create mode 100644 Include/Arrays/ArrayChar.mqh create mode 100644 Include/Arrays/ArrayColor.mqh create mode 100644 Include/Arrays/ArrayDatetime.mqh create mode 100644 Include/Arrays/ArrayDouble.mqh create mode 100644 Include/Arrays/ArrayFloat.mqh create mode 100644 Include/Arrays/ArrayInt.mqh create mode 100644 Include/Arrays/ArrayLong.mqh create mode 100644 Include/Arrays/ArrayObj.mqh create mode 100644 Include/Arrays/ArrayShort.mqh create mode 100644 Include/Arrays/ArrayString.mqh create mode 100644 Include/Arrays/ArrayUChar.mqh create mode 100644 Include/Arrays/ArrayUInt.mqh create mode 100644 Include/Arrays/ArrayULong.mqh create mode 100644 Include/Arrays/ArrayUShort.mqh create mode 100644 Include/Arrays/List.mqh create mode 100644 Include/Arrays/Tree.mqh create mode 100644 Include/Arrays/TreeNode.mqh create mode 100644 Include/Canvas/Canvas.mqh create mode 100644 Include/Canvas/Canvas3D.mqh create mode 100644 Include/Canvas/Charts/ChartCanvas.mqh create mode 100644 Include/Canvas/Charts/HistogramChart.mqh create mode 100644 Include/Canvas/Charts/LineChart.mqh create mode 100644 Include/Canvas/Charts/PieChart.mqh create mode 100644 Include/Canvas/DX/DXBox.mqh create mode 100644 Include/Canvas/DX/DXBuffers.mqh create mode 100644 Include/Canvas/DX/DXData.mqh create mode 100644 Include/Canvas/DX/DXDispatcher.mqh create mode 100644 Include/Canvas/DX/DXHandle.mqh create mode 100644 Include/Canvas/DX/DXInput.mqh create mode 100644 Include/Canvas/DX/DXMath.mqh create mode 100644 Include/Canvas/DX/DXMesh.mqh create mode 100644 Include/Canvas/DX/DXObject.mqh create mode 100644 Include/Canvas/DX/DXObjectBase.mqh create mode 100644 Include/Canvas/DX/DXShader.mqh create mode 100644 Include/Canvas/DX/DXSurface.mqh create mode 100644 Include/Canvas/DX/DXTexture.mqh create mode 100644 Include/Canvas/DX/DXUtils.mqh create mode 100644 Include/Canvas/DX/Shaders/DefaultShaderPixel.hlsl create mode 100644 Include/Canvas/DX/Shaders/DefaultShaderVertex.hlsl create mode 100644 Include/Canvas/FlameCanvas.mqh create mode 100644 Include/ChartObjects/ChartObject.mqh create mode 100644 Include/ChartObjects/ChartObjectPanel.mqh create mode 100644 Include/ChartObjects/ChartObjectSubChart.mqh create mode 100644 Include/ChartObjects/ChartObjectsArrows.mqh create mode 100644 Include/ChartObjects/ChartObjectsBmpControls.mqh create mode 100644 Include/ChartObjects/ChartObjectsChannels.mqh create mode 100644 Include/ChartObjects/ChartObjectsElliott.mqh create mode 100644 Include/ChartObjects/ChartObjectsFibo.mqh create mode 100644 Include/ChartObjects/ChartObjectsGann.mqh create mode 100644 Include/ChartObjects/ChartObjectsLines.mqh create mode 100644 Include/ChartObjects/ChartObjectsShapes.mqh create mode 100644 Include/ChartObjects/ChartObjectsTxtControls.mqh create mode 100644 Include/Charts/Chart.mqh create mode 100644 Include/Controls/BmpButton.mqh create mode 100644 Include/Controls/Button.mqh create mode 100644 Include/Controls/CheckBox.mqh create mode 100644 Include/Controls/CheckGroup.mqh create mode 100644 Include/Controls/ComboBox.mqh create mode 100644 Include/Controls/DateDropList.mqh create mode 100644 Include/Controls/DatePicker.mqh create mode 100644 Include/Controls/Defines.mqh create mode 100644 Include/Controls/Dialog.mqh create mode 100644 Include/Controls/Edit.mqh create mode 100644 Include/Controls/Label.mqh create mode 100644 Include/Controls/ListView.mqh create mode 100644 Include/Controls/Panel.mqh create mode 100644 Include/Controls/Picture.mqh create mode 100644 Include/Controls/RadioButton.mqh create mode 100644 Include/Controls/RadioGroup.mqh create mode 100644 Include/Controls/Rect.mqh create mode 100644 Include/Controls/Scrolls.mqh create mode 100644 Include/Controls/SpinEdit.mqh create mode 100644 Include/Controls/Wnd.mqh create mode 100644 Include/Controls/WndClient.mqh create mode 100644 Include/Controls/WndContainer.mqh create mode 100644 Include/Controls/WndObj.mqh create mode 100644 Include/Controls/res/CheckBoxOff.bmp create mode 100644 Include/Controls/res/CheckBoxOn.bmp create mode 100644 Include/Controls/res/Close.bmp create mode 100644 Include/Controls/res/DateDropOff.bmp create mode 100644 Include/Controls/res/DateDropOn.bmp create mode 100644 Include/Controls/res/Down.bmp create mode 100644 Include/Controls/res/DownTransp.bmp create mode 100644 Include/Controls/res/DropOff.bmp create mode 100644 Include/Controls/res/DropOn.bmp create mode 100644 Include/Controls/res/Left.bmp create mode 100644 Include/Controls/res/LeftTransp.bmp create mode 100644 Include/Controls/res/RadioButtonOff.bmp create mode 100644 Include/Controls/res/RadioButtonOn.bmp create mode 100644 Include/Controls/res/Restore.bmp create mode 100644 Include/Controls/res/Right.bmp create mode 100644 Include/Controls/res/RightTransp.bmp create mode 100644 Include/Controls/res/SpinDec.bmp create mode 100644 Include/Controls/res/SpinInc.bmp create mode 100644 Include/Controls/res/ThumbHor.bmp create mode 100644 Include/Controls/res/ThumbVert.bmp create mode 100644 Include/Controls/res/Turn.bmp create mode 100644 Include/Controls/res/Up.bmp create mode 100644 Include/Controls/res/UpTransp.bmp create mode 100644 Include/Expert/Expert.mqh create mode 100644 Include/Expert/ExpertBase.mqh create mode 100644 Include/Expert/ExpertMoney.mqh create mode 100644 Include/Expert/ExpertSignal.mqh create mode 100644 Include/Expert/ExpertTrade.mqh create mode 100644 Include/Expert/ExpertTrailing.mqh create mode 100644 Include/Expert/Money/MoneyFixedLot.mqh create mode 100644 Include/Expert/Money/MoneyFixedMargin.mqh create mode 100644 Include/Expert/Money/MoneyFixedRisk.mqh create mode 100644 Include/Expert/Money/MoneyNone.mqh create mode 100644 Include/Expert/Money/MoneySizeOptimized.mqh create mode 100644 Include/Expert/Signal/SignalAC.mqh create mode 100644 Include/Expert/Signal/SignalAMA.mqh create mode 100644 Include/Expert/Signal/SignalAO.mqh create mode 100644 Include/Expert/Signal/SignalBearsPower.mqh create mode 100644 Include/Expert/Signal/SignalBullsPower.mqh create mode 100644 Include/Expert/Signal/SignalCCI.mqh create mode 100644 Include/Expert/Signal/SignalDEMA.mqh create mode 100644 Include/Expert/Signal/SignalDeMarker.mqh create mode 100644 Include/Expert/Signal/SignalEnvelopes.mqh create mode 100644 Include/Expert/Signal/SignalFrAMA.mqh create mode 100644 Include/Expert/Signal/SignalITF.mqh create mode 100644 Include/Expert/Signal/SignalMA.mqh create mode 100644 Include/Expert/Signal/SignalMACD.mqh create mode 100644 Include/Expert/Signal/SignalRSI.mqh create mode 100644 Include/Expert/Signal/SignalRVI.mqh create mode 100644 Include/Expert/Signal/SignalSAR.mqh create mode 100644 Include/Expert/Signal/SignalStoch.mqh create mode 100644 Include/Expert/Signal/SignalTEMA.mqh create mode 100644 Include/Expert/Signal/SignalTRIX.mqh create mode 100644 Include/Expert/Signal/SignalWPR.mqh create mode 100644 Include/Expert/Trailing/TrailingFixedPips.mqh create mode 100644 Include/Expert/Trailing/TrailingMA.mqh create mode 100644 Include/Expert/Trailing/TrailingNone.mqh create mode 100644 Include/Expert/Trailing/TrailingParabolicSAR.mqh create mode 100644 Include/Generic/ArrayList.mqh create mode 100644 Include/Generic/HashMap.mqh create mode 100644 Include/Generic/HashSet.mqh create mode 100644 Include/Generic/Interfaces/ICollection.mqh create mode 100644 Include/Generic/Interfaces/IComparable.mqh create mode 100644 Include/Generic/Interfaces/IComparer.mqh create mode 100644 Include/Generic/Interfaces/IEqualityComparable.mqh create mode 100644 Include/Generic/Interfaces/IEqualityComparer.mqh create mode 100644 Include/Generic/Interfaces/IList.mqh create mode 100644 Include/Generic/Interfaces/IMap.mqh create mode 100644 Include/Generic/Interfaces/ISet.mqh create mode 100644 Include/Generic/Internal/ArrayFunction.mqh create mode 100644 Include/Generic/Internal/CompareFunction.mqh create mode 100644 Include/Generic/Internal/DefaultComparer.mqh create mode 100644 Include/Generic/Internal/DefaultEqualityComparer.mqh create mode 100644 Include/Generic/Internal/EqualFunction.mqh create mode 100644 Include/Generic/Internal/HashFunction.mqh create mode 100644 Include/Generic/Internal/Introsort.mqh create mode 100644 Include/Generic/Internal/PrimeGenerator.mqh create mode 100644 Include/Generic/LinkedList.mqh create mode 100644 Include/Generic/Queue.mqh create mode 100644 Include/Generic/RedBlackTree.mqh create mode 100644 Include/Generic/SortedMap.mqh create mode 100644 Include/Generic/SortedSet.mqh create mode 100644 Include/Generic/Stack.mqh create mode 100644 Include/Graphics/Axis.mqh create mode 100644 Include/Graphics/ColorGenerator.mqh create mode 100644 Include/Graphics/Curve.mqh create mode 100644 Include/Graphics/Graphic.mqh create mode 100644 Include/Indicators/BillWilliams.mqh create mode 100644 Include/Indicators/Custom.mqh create mode 100644 Include/Indicators/Indicator.mqh create mode 100644 Include/Indicators/Indicators.mqh create mode 100644 Include/Indicators/Oscilators.mqh create mode 100644 Include/Indicators/Series.mqh create mode 100644 Include/Indicators/TimeSeries.mqh create mode 100644 Include/Indicators/Trend.mqh create mode 100644 Include/Indicators/Volumes.mqh create mode 100644 Include/MQLTA ErrorHandling.mqh create mode 100644 Include/MQLTA Utils.mqh create mode 100644 Include/Math/Alglib/alglib.mqh create mode 100644 Include/Math/Alglib/alglibinternal.mqh create mode 100644 Include/Math/Alglib/alglibmisc.mqh create mode 100644 Include/Math/Alglib/ap.mqh create mode 100644 Include/Math/Alglib/arrayresize.mqh create mode 100644 Include/Math/Alglib/bitconvert.mqh create mode 100644 Include/Math/Alglib/dataanalysis.mqh create mode 100644 Include/Math/Alglib/delegatefunctions.mqh create mode 100644 Include/Math/Alglib/diffequations.mqh create mode 100644 Include/Math/Alglib/fasttransforms.mqh create mode 100644 Include/Math/Alglib/integration.mqh create mode 100644 Include/Math/Alglib/interpolation.mqh create mode 100644 Include/Math/Alglib/linalg.mqh create mode 100644 Include/Math/Alglib/matrix.mqh create mode 100644 Include/Math/Alglib/optimization.mqh create mode 100644 Include/Math/Alglib/solvers.mqh create mode 100644 Include/Math/Alglib/specialfunctions.mqh create mode 100644 Include/Math/Alglib/statistics.mqh create mode 100644 Include/Math/Fuzzy/dictionary.mqh create mode 100644 Include/Math/Fuzzy/fuzzyrule.mqh create mode 100644 Include/Math/Fuzzy/fuzzyterm.mqh create mode 100644 Include/Math/Fuzzy/fuzzyvariable.mqh create mode 100644 Include/Math/Fuzzy/genericfuzzysystem.mqh create mode 100644 Include/Math/Fuzzy/helper.mqh create mode 100644 Include/Math/Fuzzy/inferencemethod.mqh create mode 100644 Include/Math/Fuzzy/mamdanifuzzysystem.mqh create mode 100644 Include/Math/Fuzzy/membershipfunction.mqh create mode 100644 Include/Math/Fuzzy/ruleparser.mqh create mode 100644 Include/Math/Fuzzy/sugenofuzzysystem.mqh create mode 100644 Include/Math/Fuzzy/sugenovariable.mqh create mode 100644 Include/Math/Stat/Beta.mqh create mode 100644 Include/Math/Stat/Binomial.mqh create mode 100644 Include/Math/Stat/Cauchy.mqh create mode 100644 Include/Math/Stat/ChiSquare.mqh create mode 100644 Include/Math/Stat/Exponential.mqh create mode 100644 Include/Math/Stat/F.mqh create mode 100644 Include/Math/Stat/Gamma.mqh create mode 100644 Include/Math/Stat/Geometric.mqh create mode 100644 Include/Math/Stat/Hypergeometric.mqh create mode 100644 Include/Math/Stat/Logistic.mqh create mode 100644 Include/Math/Stat/Lognormal.mqh create mode 100644 Include/Math/Stat/Math.mqh create mode 100644 Include/Math/Stat/NegativeBinomial.mqh create mode 100644 Include/Math/Stat/NoncentralBeta.mqh create mode 100644 Include/Math/Stat/NoncentralChiSquare.mqh create mode 100644 Include/Math/Stat/NoncentralF.mqh create mode 100644 Include/Math/Stat/NoncentralT.mqh create mode 100644 Include/Math/Stat/Normal.mqh create mode 100644 Include/Math/Stat/Poisson.mqh create mode 100644 Include/Math/Stat/Stat.mqh create mode 100644 Include/Math/Stat/T.mqh create mode 100644 Include/Math/Stat/Uniform.mqh create mode 100644 Include/Math/Stat/Weibull.mqh create mode 100644 Include/MovingAverages.mqh create mode 100644 Include/MyLib/Common/HITExternalProcess.mqh create mode 100644 Include/MyLib/Common/HITRuntimeController.mqh create mode 100644 Include/MyLib/Common/HITRuntimeController.mqh.bak_codex_20260524_000548 create mode 100644 Include/MyLib/Common/TradingPanelSymbolUtils.mqh create mode 100644 Include/MyLib/Panel/SLTPManagerPanel.mqh create mode 100644 Include/MyLib/Panel/SLTPManagerPanel.mqh.bak_20260523_230523 create mode 100644 Include/MyLib/Panel/SLTPManagerPanel.mqh.bak_20260523_232830 create mode 100644 Include/MyLib/Panel/TradingPanelPanel.mqh create mode 100644 Include/MyLib/Signals/HITEntrySignal.mqh create mode 100644 Include/MyLib/Signals/HITEntrySignal.mqh.bak_codex_20260513_002404 create mode 100644 Include/MyLib/Signals/HITEntrySignal.mqh.bak_codex_20260524_000548 create mode 100644 Include/MyLib/Signals/HITEntrySignal.mqh.bak_codex_20260527_224059 create mode 100644 Include/MyLib/Signals/HITPythonSignalGateway.mqh create mode 100644 Include/MyLib/Signals/HITPythonSignalGateway.mqh.bak_codex_20260524_000548 create mode 100644 Include/MyLib/Trading/HITTradeManager.mqh create mode 100644 Include/MyLib/Trading/HITTradeManager.mqh.bak_codex_20260524_000548 create mode 100644 Include/MyLib/Trading/SLTPManager.mqh create mode 100644 Include/MyLib/Trading/TradingPanelTradingManagers.mqh create mode 100644 Include/Object.mqh create mode 100644 Include/OpenCL/OpenCL.mqh create mode 100644 Include/SmoothAlgorithms_.mqh create mode 100644 Include/StdLibErr.mqh create mode 100644 Include/Strings/String.mqh create mode 100644 Include/Tools/DateTime.mqh create mode 100644 Include/Trade/AccountInfo.mqh create mode 100644 Include/Trade/DealInfo.mqh create mode 100644 Include/Trade/HistoryOrderInfo.mqh create mode 100644 Include/Trade/OrderInfo.mqh create mode 100644 Include/Trade/PositionInfo.mqh create mode 100644 Include/Trade/SymbolInfo.mqh create mode 100644 Include/Trade/TerminalInfo.mqh create mode 100644 Include/Trade/Trade.mqh create mode 100644 Include/VirtualKeys.mqh create mode 100644 Include/WinAPI/errhandlingapi.mqh create mode 100644 Include/WinAPI/fileapi.mqh create mode 100644 Include/WinAPI/handleapi.mqh create mode 100644 Include/WinAPI/libloaderapi.mqh create mode 100644 Include/WinAPI/memoryapi.mqh create mode 100644 Include/WinAPI/processenv.mqh create mode 100644 Include/WinAPI/processthreadsapi.mqh create mode 100644 Include/WinAPI/securitybaseapi.mqh create mode 100644 Include/WinAPI/sysinfoapi.mqh create mode 100644 Include/WinAPI/winapi.mqh create mode 100644 Include/WinAPI/winbase.mqh create mode 100644 Include/WinAPI/windef.mqh create mode 100644 Include/WinAPI/wingdi.mqh create mode 100644 Include/WinAPI/winnt.mqh create mode 100644 Include/WinAPI/winreg.mqh create mode 100644 Include/WinAPI/winuser.mqh create mode 100644 Include/smoothalgorithms.mqh create mode 100644 Indicators/MyProject/eATR.mq5 create mode 100644 Indicators/SuperTrend_Custom.mq5 create mode 100644 Indicators/SuperTrend_CustomWithOffset.mq5 create mode 100644 Indicators/SuperTrend_Custom_20251203.mq5 create mode 100644 Indicators/eSuperTrend.mq5 create mode 100644 Indicators/eSuperTrendWithOffset.mq5 create mode 100644 Indicators/eSuperTrendWithOffset2.mq5 create mode 100644 README.md create mode 100644 Scripts/UnitTests/Alglib/TestClasses.mq5 create mode 100644 Scripts/UnitTests/Alglib/TestClasses.mqh create mode 100644 Scripts/UnitTests/Alglib/TestInterfaces.mq5 create mode 100644 Scripts/UnitTests/Alglib/TestInterfaces.mqh create mode 100644 Scripts/UnitTests/Fuzzy/TestFuzzy.mq5 create mode 100644 Scripts/UnitTests/Generic/TestArrayList.mq5 create mode 100644 Scripts/UnitTests/Generic/TestHashMap.mq5 create mode 100644 Scripts/UnitTests/Generic/TestHashSet.mq5 create mode 100644 Scripts/UnitTests/Generic/TestLinkedList.mq5 create mode 100644 Scripts/UnitTests/Generic/TestQueue.mq5 create mode 100644 Scripts/UnitTests/Generic/TestRedBlackTree.mq5 create mode 100644 Scripts/UnitTests/Generic/TestSortedMap.mq5 create mode 100644 Scripts/UnitTests/Generic/TestSortedSet.mq5 create mode 100644 Scripts/UnitTests/Generic/TestStack.mq5 create mode 100644 Scripts/UnitTests/Stat/TestStat.mq5 create mode 100644 Scripts/UnitTests/Stat/TestStatBenchmark.mq5 create mode 100644 Scripts/UnitTests/Stat/TestStatPrecision.mq5 create mode 100644 WorkSpace/MQL5.code-workspace create mode 100644 compile_commands.json create mode 100644 docs/MyProject/HIT-EA_spec_en.md create mode 100644 docs/MyProject/HIT-EA_spec_en.md.bak_codex_20260513_002404 create mode 100644 docs/MyProject/HIT-EA_spec_ja.md create mode 100644 docs/MyProject/HIT-EA_spec_ja.md.bak_codex_20260513_002404 create mode 100644 docs/MyProject/SLTPManager_spec_en.md create mode 100644 docs/MyProject/SLTPManager_spec_ja.md create mode 100644 docs/MyProject/TradingPanel_spec_en.md create mode 100644 docs/MyProject/TradingPanel_spec_ja.md create mode 100644 docs/MyProject/refactor/codex_mql5_sltp_manager_instructions.md create mode 100644 python_for_ea/.agents/skills/fastapi-api/SKILL.md create mode 100644 python_for_ea/.agents/skills/oracle-select/oracle-select_SKILL.md create mode 100644 python_for_ea/.agents/skills/python-base/SKILL.md create mode 100644 python_for_ea/.agents/skills/python-logging/SKILL.md create mode 100644 python_for_ea/.agents/skills/python-settings/SKILL.md create mode 100644 python_for_ea/.agents/skills/python-testing/SKILL.md create mode 100644 python_for_ea/.agents/skills/sqlserver-ddl/SKILL.md create mode 100644 python_for_ea/.agents/skills/streamlit-page/SKILL.md create mode 100644 python_for_ea/.cursor/rules/fastapi.mdc create mode 100644 python_for_ea/.cursor/rules/logging.mdc create mode 100644 python_for_ea/.cursor/rules/oracle.mdc create mode 100644 python_for_ea/.cursor/rules/pandas.mdc create mode 100644 python_for_ea/.cursor/rules/project.mdc create mode 100644 python_for_ea/.cursor/rules/pytest.mdc create mode 100644 python_for_ea/.cursor/rules/python.mdc create mode 100644 python_for_ea/.cursor/rules/settings.mdc create mode 100644 python_for_ea/.cursor/rules/sqlserver.mdc create mode 100644 python_for_ea/.cursor/rules/streamlit.mdc create mode 100644 python_for_ea/.github/copilot-instructions.md create mode 100644 python_for_ea/.github/instructions/fastapi.instructions.md create mode 100644 python_for_ea/.github/instructions/logging.instructions.md create mode 100644 python_for_ea/.github/instructions/oracle.instructions.md create mode 100644 python_for_ea/.github/instructions/pandas.instructions.md create mode 100644 python_for_ea/.github/instructions/pytest.instructions.md create mode 100644 python_for_ea/.github/instructions/python.instructions.md create mode 100644 python_for_ea/.github/instructions/settings.instructions.md create mode 100644 python_for_ea/.github/instructions/sqlserver.instructions.md create mode 100644 python_for_ea/.github/instructions/streamlit.instructions.md create mode 100644 python_for_ea/.python-version create mode 100644 python_for_ea/AGENTS.md create mode 100644 python_for_ea/README.md create mode 100644 python_for_ea/bat/get_entry_reply.bat create mode 100644 python_for_ea/bat/get_trend_reply.bat create mode 100644 python_for_ea/docs/architecture/folder-structure.md create mode 100644 python_for_ea/docs/refactor/gold_strategy_20260503.md create mode 100644 python_for_ea/get_entry_reply.py create mode 100644 python_for_ea/get_trend_reply.py create mode 100644 python_for_ea/pyproject.toml create mode 100644 python_for_ea/src/ea_py/__init__.py create mode 100644 python_for_ea/src/ea_py/charting/__init__.py create mode 100644 python_for_ea/src/ea_py/charting/candlestick.py create mode 100644 python_for_ea/src/ea_py/config.py create mode 100644 python_for_ea/src/ea_py/constants.py create mode 100644 python_for_ea/src/ea_py/io/__init__.py create mode 100644 python_for_ea/src/ea_py/io/debug_logs.py create mode 100644 python_for_ea/src/ea_py/io/mt5_files.py create mode 100644 python_for_ea/src/ea_py/io/ohlc_csv.py create mode 100644 python_for_ea/src/ea_py/openai_client.py create mode 100644 python_for_ea/src/ea_py/paths.py create mode 100644 python_for_ea/src/ea_py/pipelines/__init__.py create mode 100644 python_for_ea/src/ea_py/pipelines/entry_pipeline.py create mode 100644 python_for_ea/src/ea_py/pipelines/trend_pipeline.py create mode 100644 python_for_ea/src/ea_py/prompts/__init__.py create mode 100644 python_for_ea/src/ea_py/prompts/entry_prompt.py create mode 100644 python_for_ea/src/ea_py/prompts/trend_prompt.py create mode 100644 python_for_ea/src/ea_py/types.py create mode 100644 python_for_ea/tests/unit/test_config.py create mode 100644 python_for_ea/tests/unit/test_imbalance.py create mode 100644 python_for_ea/tests/unit/test_openai_client.py create mode 100644 python_for_ea/tests/unit/test_target_prices.py create mode 100644 python_for_ea/tests/unit/test_trend_state.py create mode 100644 python_for_ea/tests/unit/test_volatility.py create mode 100644 python_for_ea/uv.lock create mode 100644 python_for_ea/work/HIT-EA_refactor_ver3_restore.mq5 create mode 100644 python_for_ea/work/get_entry_reply.py create mode 100644 python_for_ea/work/get_trend_reply.py diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md new file mode 100644 index 0000000..8ba1778 --- /dev/null +++ b/.github/copilot-instructions.md @@ -0,0 +1,94 @@ +# MQL5 Expert Advisor 開発指示書 + +あなたはMQL5のシニアエンジニアであり、同時に定量的リスク管理の専門家です。常に**『バックテストの再現性』と『ライブ実行時のエラー耐性』**を最優先に考えた回答をしてください。 + +## 1. 基本設計方針 +- **言語:** MQL5 (C++ベース) +- **標準ライブラリの優先:** `Trade\Trade.mqh` の `CTrade` クラスを積極的に使用し、注文処理(OrderSend)を簡略化・安全化すること。 +- **イベント駆動:** `OnInit`, `OnDeinit`, `OnTick` などのイベントハンドラを適切に使用すること。 + +## 2. コーディングスタイル +- **命名規則:** + - 外部パラメータ(input)は `input_` または `inp` を接頭辞につける。 + - グローバル変数は `g_`、メンバ変数は `m_` をつける。 +- **エラーハンドリング:** - 注文実行時やインジケータハンドル作成時は、必ず戻り値をチェックし、失敗した場合は `GetLastError()` を用いてログ出力(Print)すること。 +- **最適化:** - 重い処理は `OnTick` ではなく `OnInit` や特定の足が確定したタイミングで行うように工夫すること。 + +## 3. 重要事項 +- **マジックナンバー:** EAごとに固有の `input int MagicNumber` を定義し、注文時に必ず使用すること。 +- **シンボル/期間:** `_Symbol` および `_Period` を使用し、汎用性を持たせること。 +- **コメント:** コードの重要セクションには、ロジックを説明する英語のコメントを付与すること。 + +## 4. 禁止事項 +- MQL4の古い関数(OrderSendの旧形式など)は絶対に使用しないこと。 +- バックテストのパフォーマンスを著しく低下させる無駄なループ処理を避けること。 + +## 5. ドキュメント管理規則 (Documentation & Specifications) +- **フォルダ構成:** プロジェクトルートに `docs` フォルダを維持し、各EA/インジケータに対応する仕様書(Markdown形式)を格納すること。 +- **ファイル命名:** 仕様書は英語版と日本語版を両方作成し、以下の形式で区別すること: + - 日本語版:`{プログラム名}_spec_ja.md` + - 英語版:`{プログラム名}_spec_en.md` +- **新規作成時のルール:** - 新しいEAまたはインジケータを作成する際は、コードを書く前、あるいは同時に `docs/` 内に日本語版と英語版の両方の仕様書を生成すること。 +- **修正時の同期ルール:** - プログラムのロジック、外部パラメータ(input)、または重要な関数を変更した際は、必ず対応する `docs/{プログラム名}_spec_ja.md` および `docs/{プログラム名}_spec_en.md` を読み込み、修正内容を反映させること。 + - 修正の際は「変更履歴(Changelog)」セクションを仕様書末尾に設け、日付と修正概要を記録すること。 + +## 6. 仕様書の標準構成案 +仕様書を作成する際は、以下の項目を含めること: +1. **概要**: プログラムの目的と動作概要。 +2. **使用インジケータ**: 依存している標準・カスタムインジケータ。 +3. **パラメータ設定**: `input` 変数の説明、デフォルト値、設定の意味。 +4. **エントリー/エグジット条件**: ロジックの詳細(文章および箇条書き)。 +5. **リスク管理**: マジックナンバー、ロット計算、ストップロス等の仕様。 +6. **変更履歴**: 日付、修正箇所、修正理由。 + +## 7. フォルダ構成と再利用性のルール +### 7.1 プロジェクト単位のフォルダ管理 +- **プロジェクトフォルダ:** 各EA/インジケータは `Experts/MyProject/`、`Indicators/MyProject/` のようにプロジェクト名でフォルダを作成し、その中に関連ファイルを配置すること。 +- **仕様書の配置:** 対応する仕様書は `docs/MyProject/` 内に格納すること。 + +### 7.2 標準フォルダ構成 +``` +MQL5/ +├── Experts/ +│ └── MyProject/ # プロジェクトごとのフォルダ +│ ├── MyEA.mq5 # メインソース +│ └── MyEA.mqh # (オプション) EA固有のヘッダー +├── Indicators/ +│ └── MyProject/ # 関連するカスタムインジケータ +│ └── MySignal.mq5 +├── Include/ +│ └── MyLib/ # 【重要】自作ライブラリの本拠地 +│ ├── Common/ # 汎用ツール(時間操作、ログ、配列操作など) +│ │ └── TimeUtils.mqh +│ ├── Trading/ # トレードロジック(資金管理、注文処理) +│ │ └── RiskManager.mqh +│ ├── Signals/ # 売買サイン判定ロジック +│ │ └── CrossLogic.mqh +│ └── Wrapper/ # 標準ライブラリの拡張 +│ └── MyTrade.mqh +├── Scripts/ +│ └── Utilities/ # 一括決済などの便利ツール +├── docs/ # 仕様書フォルダ +│ └── MyProject/ +│ ├── MyEA_spec_ja.md # 日本語仕様書 +│ └── MyEA_spec_en.md # 英語仕様書 +└── .github/ # Copilot指示書など + └── copilot-instructions.md +``` + +### 7.3 再利用性のルール +- **共通ロジックの配置:** 複数のプロジェクトで利用可能なロジックは、必ず `Include/MyLib/` 内の適切なサブフォルダにクラスまたは関数として定義すること。 + - `Common/`: 汎用ユーティリティ(時間、ログ、配列操作など) + - `Trading/`: トレード関連(資金管理、注文処理、ポジション管理など) + - `Signals/`: 売買サイン判定ロジック + - `Wrapper/`: 標準ライブラリの拡張クラス +- **EAの構成:** `Experts/` 内の `.mq5` ファイルはエントリーポイントとしての役割に専念させ、具体的なロジックは `Include/MyLib/` 側のヘッダーファイルをインクルードして呼び出す形式をとること。 +- **依存関係の解決:** 新しいファイルを作成する際は、既存の `Include/MyLib/` 内にある自作ライブラリを優先的に再利用するよう提案すること。 + +## 8. 自動コンパイルとバグ修正フロー +- **対象:** 新規作成または修正した `.mq5` / `.mqh` ファイル。 +- **実行手段:** まず VSCode の MQL Tools のコンパイル機能を優先して使用し、環境要件や自動化の都合で難しい場合は `metaeditor64.exe /compile` を使用すること。 +- **必須ループ:** 「実装 → コンパイル → ログ確認 → 修正」を繰り返し、`0 errors, 0 warnings` を満たすまで作業を継続すること。 +- **ログ管理:** コンパイルログは `MQL5/Logs/compile_{ProgramName}.log` に保存し、修正時は直近ログを根拠に対応すること。 +- **修正優先度:** コンパイルエラーを最優先で解消し、その後に警告を解消すること。 +- **完了条件:** ①コンパイル成功、②主要ロジックの整合性確認、③仕様書(`_spec_ja.md` / `_spec_en.md`)への反映。 \ No newline at end of file diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..d41da8a --- /dev/null +++ b/.gitignore @@ -0,0 +1,176 @@ +# ============================================================================== +# MQL5 Project .gitignore +# ============================================================================== + +# ------------------------------------------------------------------------------ +# Compiled Files (コンパイル済みファイル) +# ------------------------------------------------------------------------------ +*.ex4 +*.ex5 + +# ------------------------------------------------------------------------------ +# Logs and Temporary Files (ログと一時ファイル) +# ------------------------------------------------------------------------------ +*.log +Logs/ +*/Logs/ + +# ------------------------------------------------------------------------------ +# Tester and Profiles (テスターとプロファイル) +# ------------------------------------------------------------------------------ +Tester/ +*/Tester/ +Profiles/ + +# ------------------------------------------------------------------------------ +# Runtime Generated Files (実行時生成ファイル) +# ------------------------------------------------------------------------------ +Files/ +Images/ + +# ------------------------------------------------------------------------------ +# Standard MT5 Folders (標準MT5フォルダ - 除外) +# ------------------------------------------------------------------------------ +# Market downloads +Market/ +*/Market/ + +# Standard examples +Examples/ +Experts/Examples/ +Indicators/Examples/ +Scripts/Examples/ + +# Standard advisors +Experts/Advisors/ + +# Free robots and indicators +Experts/Free Robots/ +Indicators/Free Indicators/ + +# Standard libraries (Include配下の標準ライブラリは含めるが、サブフォルダのMarket等は除外) +Include/*/Market/ +Include/*/Examples/ + +# Services +Services/ + +# Shared Projects +Shared Projects/ + +# ------------------------------------------------------------------------------ +# Third-party Packages (サードパーティパッケージ) +# ------------------------------------------------------------------------------ +# OANDA packages +Indicators/oanda_basic_package/ +Indicators/oanda_lab/ +Indicators/oanda_lab_new/ + +# Market indicators/experts (root level ex5 files) +Indicators/*.ex5 +Experts/*.ex5 + +# ------------------------------------------------------------------------------ +# Editor and IDE (エディタとIDE設定) +# ------------------------------------------------------------------------------ +.history/ +.vscode/ipch/ +.vscode/msvc.json + +# ------------------------------------------------------------------------------ +# OS Specific (OS固有ファイル) +# ------------------------------------------------------------------------------ +# macOS +.DS_Store + +# Windows +Thumbs.db +desktop.ini +experts.dat +*.lnk + +# ------------------------------------------------------------------------------ +# Python Project (python_for_ea) +# ------------------------------------------------------------------------------ +# Python bytecode and C extensions +python_for_ea/**/__pycache__/ +python_for_ea/**/*.py[cod] +python_for_ea/**/*$py.class +python_for_ea/**/*.so + +# Virtual environments +python_for_ea/**/.venv/ +python_for_ea/**/venv/ +python_for_ea/**/env/ +python_for_ea/**/ENV/ + +# Environment variables +python_for_ea/**/.env +python_for_ea/**/.env.* +!python_for_ea/**/.env.example + +# Python package and build outputs +python_for_ea/**/build/ +python_for_ea/**/dist/ +python_for_ea/**/*.egg-info/ +python_for_ea/**/.eggs/ +python_for_ea/**/*.egg + +# Test, coverage, type-checker, and linter caches +python_for_ea/**/.pytest_cache/ +python_for_ea/**/.coverage +python_for_ea/**/.coverage.* +python_for_ea/**/htmlcov/ +python_for_ea/**/.tox/ +python_for_ea/**/.nox/ +python_for_ea/**/.mypy_cache/ +python_for_ea/**/.pyright/ +python_for_ea/**/.ruff_cache/ +python_for_ea/**/.ty/ + +# Jupyter, editor, local output, and database files +python_for_ea/**/.ipynb_checkpoints/ +python_for_ea/**/.vscode/ +python_for_ea/**/.idea/ +python_for_ea/**/*.code-workspace +python_for_ea/**/logs/ +python_for_ea/**/data/ +python_for_ea/**/output/ +python_for_ea/**/outputs/ +python_for_ea/**/tmp/ +python_for_ea/**/temp/ +python_for_ea/**/.cache/ +python_for_ea/**/*.db +python_for_ea/**/*.sqlite +python_for_ea/**/*.sqlite3 +python_for_ea/**/.streamlit/secrets.toml + +# Excel / Office temporary files +python_for_ea/**/~$*.xlsx +python_for_ea/**/~$*.xls +python_for_ea/**/~$*.csv + +# uv.lock is intentionally not ignored so reproducible environments can be tracked. + +# ------------------------------------------------------------------------------ +# VS Code AI Rules (VSCode AI設定) +# ------------------------------------------------------------------------------ +.github/instructions/codacy.instructions.md + +# ------------------------------------------------------------------------------ +# MQL Clangd Settings +# ------------------------------------------------------------------------------ +.clangd +.clang-format +.cache/ + +# ============================================================================== +# Keep Custom Projects (自作プロジェクトは含める) +# ============================================================================== +# !Experts/YourProjectName/ +# !Indicators/YourProjectName/ +# !Include/MyLib/ +# !Scripts/Utilities/ +# !docs/ +# !README.md +# !.github/copilot-instructions.md diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..4deb14e --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,27 @@ +{ + "C_Cpp.default.includePath": [ + "${workspaceFolder}/**", + "${workspaceFolder}/Include" + ], + "C_Cpp.default.compilerPath": "", + "C_Cpp.default.intelliSenseMode": "gcc-x64", + "C_Cpp.errorSquiggles": "disabled", + "C_Cpp.autocompleteAddParentheses": true, + "files.exclude": { + "**/*.ex4": true, + "**/*.ex5": true, + "**/*_@!!@.mq4": true, + "**/*_@!!@.mq5": true, + "**/*_@!!@.mqh": true, + "**/*_@!!@.log": true + }, + "files.associations": { + "*.mqh": "cpp", + "*.mq4": "cpp", + "*.mq5": "cpp" + }, + "editor.tabSize": 3, + "C_Cpp.default.forcedInclude": [ + "c:\\Users\\new\\.vscode\\extensions\\l-i-v.mql-tools-2.2.0\\data\\mql5_en.mqh" + ] +} \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 0000000..8ba1778 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,94 @@ +# MQL5 Expert Advisor 開発指示書 + +あなたはMQL5のシニアエンジニアであり、同時に定量的リスク管理の専門家です。常に**『バックテストの再現性』と『ライブ実行時のエラー耐性』**を最優先に考えた回答をしてください。 + +## 1. 基本設計方針 +- **言語:** MQL5 (C++ベース) +- **標準ライブラリの優先:** `Trade\Trade.mqh` の `CTrade` クラスを積極的に使用し、注文処理(OrderSend)を簡略化・安全化すること。 +- **イベント駆動:** `OnInit`, `OnDeinit`, `OnTick` などのイベントハンドラを適切に使用すること。 + +## 2. コーディングスタイル +- **命名規則:** + - 外部パラメータ(input)は `input_` または `inp` を接頭辞につける。 + - グローバル変数は `g_`、メンバ変数は `m_` をつける。 +- **エラーハンドリング:** - 注文実行時やインジケータハンドル作成時は、必ず戻り値をチェックし、失敗した場合は `GetLastError()` を用いてログ出力(Print)すること。 +- **最適化:** - 重い処理は `OnTick` ではなく `OnInit` や特定の足が確定したタイミングで行うように工夫すること。 + +## 3. 重要事項 +- **マジックナンバー:** EAごとに固有の `input int MagicNumber` を定義し、注文時に必ず使用すること。 +- **シンボル/期間:** `_Symbol` および `_Period` を使用し、汎用性を持たせること。 +- **コメント:** コードの重要セクションには、ロジックを説明する英語のコメントを付与すること。 + +## 4. 禁止事項 +- MQL4の古い関数(OrderSendの旧形式など)は絶対に使用しないこと。 +- バックテストのパフォーマンスを著しく低下させる無駄なループ処理を避けること。 + +## 5. ドキュメント管理規則 (Documentation & Specifications) +- **フォルダ構成:** プロジェクトルートに `docs` フォルダを維持し、各EA/インジケータに対応する仕様書(Markdown形式)を格納すること。 +- **ファイル命名:** 仕様書は英語版と日本語版を両方作成し、以下の形式で区別すること: + - 日本語版:`{プログラム名}_spec_ja.md` + - 英語版:`{プログラム名}_spec_en.md` +- **新規作成時のルール:** - 新しいEAまたはインジケータを作成する際は、コードを書く前、あるいは同時に `docs/` 内に日本語版と英語版の両方の仕様書を生成すること。 +- **修正時の同期ルール:** - プログラムのロジック、外部パラメータ(input)、または重要な関数を変更した際は、必ず対応する `docs/{プログラム名}_spec_ja.md` および `docs/{プログラム名}_spec_en.md` を読み込み、修正内容を反映させること。 + - 修正の際は「変更履歴(Changelog)」セクションを仕様書末尾に設け、日付と修正概要を記録すること。 + +## 6. 仕様書の標準構成案 +仕様書を作成する際は、以下の項目を含めること: +1. **概要**: プログラムの目的と動作概要。 +2. **使用インジケータ**: 依存している標準・カスタムインジケータ。 +3. **パラメータ設定**: `input` 変数の説明、デフォルト値、設定の意味。 +4. **エントリー/エグジット条件**: ロジックの詳細(文章および箇条書き)。 +5. **リスク管理**: マジックナンバー、ロット計算、ストップロス等の仕様。 +6. **変更履歴**: 日付、修正箇所、修正理由。 + +## 7. フォルダ構成と再利用性のルール +### 7.1 プロジェクト単位のフォルダ管理 +- **プロジェクトフォルダ:** 各EA/インジケータは `Experts/MyProject/`、`Indicators/MyProject/` のようにプロジェクト名でフォルダを作成し、その中に関連ファイルを配置すること。 +- **仕様書の配置:** 対応する仕様書は `docs/MyProject/` 内に格納すること。 + +### 7.2 標準フォルダ構成 +``` +MQL5/ +├── Experts/ +│ └── MyProject/ # プロジェクトごとのフォルダ +│ ├── MyEA.mq5 # メインソース +│ └── MyEA.mqh # (オプション) EA固有のヘッダー +├── Indicators/ +│ └── MyProject/ # 関連するカスタムインジケータ +│ └── MySignal.mq5 +├── Include/ +│ └── MyLib/ # 【重要】自作ライブラリの本拠地 +│ ├── Common/ # 汎用ツール(時間操作、ログ、配列操作など) +│ │ └── TimeUtils.mqh +│ ├── Trading/ # トレードロジック(資金管理、注文処理) +│ │ └── RiskManager.mqh +│ ├── Signals/ # 売買サイン判定ロジック +│ │ └── CrossLogic.mqh +│ └── Wrapper/ # 標準ライブラリの拡張 +│ └── MyTrade.mqh +├── Scripts/ +│ └── Utilities/ # 一括決済などの便利ツール +├── docs/ # 仕様書フォルダ +│ └── MyProject/ +│ ├── MyEA_spec_ja.md # 日本語仕様書 +│ └── MyEA_spec_en.md # 英語仕様書 +└── .github/ # Copilot指示書など + └── copilot-instructions.md +``` + +### 7.3 再利用性のルール +- **共通ロジックの配置:** 複数のプロジェクトで利用可能なロジックは、必ず `Include/MyLib/` 内の適切なサブフォルダにクラスまたは関数として定義すること。 + - `Common/`: 汎用ユーティリティ(時間、ログ、配列操作など) + - `Trading/`: トレード関連(資金管理、注文処理、ポジション管理など) + - `Signals/`: 売買サイン判定ロジック + - `Wrapper/`: 標準ライブラリの拡張クラス +- **EAの構成:** `Experts/` 内の `.mq5` ファイルはエントリーポイントとしての役割に専念させ、具体的なロジックは `Include/MyLib/` 側のヘッダーファイルをインクルードして呼び出す形式をとること。 +- **依存関係の解決:** 新しいファイルを作成する際は、既存の `Include/MyLib/` 内にある自作ライブラリを優先的に再利用するよう提案すること。 + +## 8. 自動コンパイルとバグ修正フロー +- **対象:** 新規作成または修正した `.mq5` / `.mqh` ファイル。 +- **実行手段:** まず VSCode の MQL Tools のコンパイル機能を優先して使用し、環境要件や自動化の都合で難しい場合は `metaeditor64.exe /compile` を使用すること。 +- **必須ループ:** 「実装 → コンパイル → ログ確認 → 修正」を繰り返し、`0 errors, 0 warnings` を満たすまで作業を継続すること。 +- **ログ管理:** コンパイルログは `MQL5/Logs/compile_{ProgramName}.log` に保存し、修正時は直近ログを根拠に対応すること。 +- **修正優先度:** コンパイルエラーを最優先で解消し、その後に警告を解消すること。 +- **完了条件:** ①コンパイル成功、②主要ロジックの整合性確認、③仕様書(`_spec_ja.md` / `_spec_en.md`)への反映。 \ No newline at end of file diff --git a/Experts/Comment_Test.mq5 b/Experts/Comment_Test.mq5 new file mode 100644 index 0000000000000000000000000000000000000000..a5c5d032c638382930fa328f21dec26807fd05e1 GIT binary patch literal 10196 zcmds7OKclO7@iw1A#vukbuWoxlns;vt%BrN(oiQ(9hcfr5t7Dn-MVgY9yGL35khK9 zPaF^urwVc5fP{oNz@;Y;LT~`7QUynjJ;D(YzHfd{X4c;IBe6?_RS{y%F}J(=?C$Tc{(AeV-S7P`{BQgp z{EGkGn-j%f{Ga`YzK0S0Uf1{N9j5gkRcoq&wXb324Xk_{>srTeLsivz{E|n8PoCN- z-2fl%eXvnxTNS)oQ=91Jsf?OaXVk1(R7sUk3wT}xPjf1d_6)wstr^{)oNnNb^n{0# z_;vd(sw~D#sRHIWqvmmOh7;&TX?be)!}^V{{J;Gl@oB|DOvKaa5*)?0KEyGhd`+p- zc!Q&q+u16r5%A!u8r58KInq0lQYez=`$SS20Ftwi=?eIFVGj{`@`CCGi^xXoBlE_= zxpER3QdJdTG4d4o^o!-eCsA0=LlbLS8>6Kyx{_4WdmNFz#Bv^^8_@TfTEhLM2)z-$ z{PgaX)y8|R+S(5STFpv})d+$#_;eWv8^Ez0fV(YZmR{o&KhVfsCP6C(aK{XG=m5X7w-9=0l`C`-1r*z-yQ%>602T;%ncS# z37UzX(Znk^%RwaM%~7pst5_@jvnl!xR)EvfBMl{`8dWV{X71+y6C47 z<<8}vS!OSe6OGSch3klS4e%t7594DN>t4ov4c?d5%pUYtL9MQ%7jp}V64x|uL_4Z# z6(e()<6?8hDb3XtwE8NJ`OL_dAzL3ac$)r(#!<%;WA!G+o^WO*LfgZgJ*7u4;cdBv zLL4ZP>J`W@k9eDcbSU#_9g7)}6L_1!Z(QRdHlh)#JDPSlr~Z!|>V+HBoh@htBP#P7 z%0&21Z$_(`$20AVy3?*1qMeKzoXOReBalO9O>DK-(dPUzi>rUqG7og(14m##E>;gn zm++1rZ$;5YVvv06{Bg8;=+VG6se4T0NvwmGkkM}#!MIYRH9}jyU5#}$Su{)fxOz;V zzo9cqYH~%>7K>@eCVD*&z0Lu7FfWZaIZK(W>DXp+(;&)0(JsPE&A?xab)~?Wcr!09 zw(w<86ibm5OGBbq#QJ8`YkF-)#=>8lUk-=6rAW$dHaQ&H%n`YZ$>NiTRJn3lHQx+} zW-C3Hrp*^gTjU@<kF*a8 z4fmhiLrN4&>if9%WERihd2kZ)PUGTU*!UF_osIl0)n@k5%m!_{WCTGU@5WHqzex@? zOz$of4y%7R=z8(+#u~YQ8I+HYq95kv4gDhOUlZ6-8;#hn=10*E^Y?~!Nz=A@+n{NO z`6EL+xPKhh>Zjm+=MY_k5lH-SaPQU)!2(f4e{Hw%YybTLJN= z2#pWQJciZ;q`+CZ>)1pbj_vsz8*L;Cu?q0`?qj#t%Gp~vL~-W%=b(e0s`xLV?TKAG zqM!I=cICs2P=>}~jZqfge-)*o5j=`G6-~Wcq&ntQ820P4qNzW+SQU-I#EP(J<<|BN z>N~n42zrmfMyqJ*eA6DUg8K&RdG@nAkgA}mZP^-t8=dUh-PYm6tY}^gJ8DH!?k#Rv zIiyhSUqWuQp{t-PxLZkP^0sZ?z^!dAefVq!(N4tHvXf1c$ z%3MEMP}TV?c~aLk5uO!=)jZwGBYjYNl~fQ|U6lOg!mZ=mcXGCM9rN4V-Qb^eD&)d% zo*iwYGS410XqePD$5115+sBYOF&m0u_hf09845V5mV17i8Cap+#LNZE9MqT*T_2FM z@&dRqi{~j=QbOM?AV-_m)yyhJ@)Ur%Q)dJVs9Z{QnU!XPU?gw`F&bEr;VpmNeMWHI zaf~Se6}5yXKMPu$nOCN}V#=tuuzt#!|0pn+(pb|bxXo&EJ7*HGaN78$MrAO%V@5zb zg)4`1KJM}s!MoG?%&!2XqsW0<_tnu}Cx(^r+?FSotiosUp;xQxXYTp9QdYY?tsgvR zGuCRgz^z-4K$>&-5Z3}w5orb&D?;R$=x&&Aq+O&zpGd?uTwljlo+*ip*TJDWW+R4c zjy{SdOgA3PosOuekpekSiMM+$;|mN<(P`JCbRI9eqWz9`Yot*e;{KiI>|vfmR)0VL z{?=csUzY2Bt2f{th@(B&l$^rwnP+kyc(SBzh39LohZAp1s_d77Uh;fH{vkvRM{w3* z#)>?IXRcJF#q^qlZbZ%IQkGG@dNJu8+nZaSjm;T~?e=C+#W7O+&?2-jfu{`q6J^_& znb79u@Rt6mlQ%M0l33>oPFNG*?4=&<-}SlS*5BDbC9w+HSzNuSQCce%R0bQX&pgK( zL+?)h=%9G`Kd4gLI9iCKf8+{{qBS##{gZ literal 0 HcmV?d00001 diff --git a/Experts/Debug_Entry_Panel.mq5 b/Experts/Debug_Entry_Panel.mq5 new file mode 100644 index 0000000..75d2b5f --- /dev/null +++ b/Experts/Debug_Entry_Panel.mq5 @@ -0,0 +1,1707 @@ +//+------------------------------------------------------------------+ +//| SimpleOrderPanel.mq5 | +//| Copyright 2022, Manuel Alejandro Cercós Pérez | +//| https://www.mql5.com/en/users/alexcercos | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2022, Manuel Alejandro Cercós Pérez" +#property link "https://www.mql5.com/en/users/alexcercos" +// #property icon "\\Images\\ProgramIcons\\simplePanelLogo.ico" //Not included +#property version "2.08" + +//+------------------------------------------------------------------+ +//| CHANGES NEEDED TO FIX ISSUES | +//+------------------------------------------------------------------+ + +// To avoid some errors, it's necessary to change code in Dialog.mqh +// file (to modify variables that are defined as private). +// Take into consideration that changes made in standard library +// files are reverted with each terminal update. To avoid errors +// when recompiling you can: +// - Track with git the changes made in the file to revert the deletion +// - Copy the file "Dialog.mqh" into another folder and change +// dependencies as needed, so it doesn't change later +// +// If you are using MT4, I recommend using the Controls classes from MT5 +// To open the file, press "Alt + G" with the cursor set over the line +// "#include " below. +// +// The errors would appear when FIRST COMPILING or after RECOMPILING +// +//+------------------------------------------------------------------+ +//| ISSUE 1: DUPLICATED OBJECTS WHEN USING TEMPLATES | +//+------------------------------------------------------------------+ +#define M_NAME "12345" // Override in panel identifier +// +// Change the function "CreateInstanceId" to the one below: +//+-------------------------------------------------------------------+ +// string CAppDialog::CreateInstanceId(void) +// { +// #ifdef M_NAME +// return M_NAME; +// #else +// return(IntegerToString(rand(),5,'0')); +// #endif +// } +//+-------------------------------------------------------------------+ +// +// If you don't use templates and want to remove the macro M_NAME, +// delete also the following line in this file, at the beginning of +// OnInitEvent (line 1303): +//+-------------------------------------------------------------------+ +// ObjectsDeleteAll(0, M_NAME); +//+-------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| ISSUE 2: HEADER FONT SIZE | +//+------------------------------------------------------------------+ +// This issue is not as common (only if your PC has a different text +// size setting than default). You can modify manually the font sizes +// with the input "fontSize". If you want the header text size to +// change too: +// +// Uncomment this line (465) in CControlsDialog::Create +//+-------------------------------------------------------------------+ +// CaptionFontSize(fontSize); +//+-------------------------------------------------------------------+ +// +// Add the function definition to the class CDialog (in public or +// protected sections, between lines ~45-85) +//+-------------------------------------------------------------------+ +// void CaptionFontSize(const int size) { m_caption.FontSize(size); } +//+-------------------------------------------------------------------+ + +#include +#include +#include +#include + +#ifdef __MQL5__ + +#include +#include +#include + +#define POS_TOTAL PositionsTotal() +#define ORD_TOTAL OrdersTotal() +#define ASK_PRICE SymbolInfoDouble(_Symbol, SYMBOL_ASK) +#define BID_PRICE SymbolInfoDouble(_Symbol, SYMBOL_BID) + +#define POS_SELECT_BY_INDEX(i) \ + if (!m_position.SelectByIndex(i)) \ + continue; +#define POS_SYMBOL m_position.Symbol() +#define POS_MAGIC m_position.Magic() +#define POS_TYPE m_position.PositionType() +#define POS_OPEN m_position.PriceOpen() +#define POS_STOP m_position.StopLoss() +#define POS_TAKE_PROFIT m_position.TakeProfit() +#define POS_TICKET m_position.Ticket() + +#define ORD_SELECT_BY_INDEX(i) \ + if (!m_order.SelectByIndex(i)) \ + continue; +#define ORD_SYMBOL m_order.Symbol() +#define ORD_MAGIC m_order.Magic() +#define ORD_TYPE m_order.OrderType() +#define ORD_OPEN m_order.PriceOpen() +#define ORD_STOP m_order.StopLoss() +#define ORD_TAKE_PROFIT m_order.TakeProfit() +#define ORD_TICKET m_order.Ticket() + +#define POS_BUY(lots, price, sl, tp) m_trade.Buy(lots, _Symbol, price, sl, tp, trade_comment); +#define POS_SELL(lots, price, sl, tp) m_trade.Sell(lots, _Symbol, price, sl, tp, trade_comment); +#define ORDER_BUY_LIMIT(lots, price, sl, tp) m_trade.BuyLimit(lots, price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, trade_comment); +#define ORDER_BUY_STOP(lots, price, sl, tp) m_trade.BuyStop(lots, price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, trade_comment); +#define ORDER_SELL_LIMIT(lots, price, sl, tp) m_trade.SellLimit(lots, price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, trade_comment); +#define ORDER_SELL_STOP(lots, price, sl, tp) m_trade.SellStop(lots, price, _Symbol, sl, tp, ORDER_TIME_GTC, 0, trade_comment); +#define POS_MODIFY(ticket, stop, take) \ + if (!m_trade.PositionModify(ticket, stop, take)) \ + Print("Error modyfing position: ", GetLastError()); +#define POS_CLOSE(ticket) \ + if (!m_trade.PositionClose(ticket)) \ + Print("Error closing position, ", GetLastError()); +#define POS_CLOSE_PARTIAL(ticket) \ + if (!m_trade.PositionClosePartial(ticket, partialLots)) \ + Print("Error closing partial position, ", GetLastError()); + +#define ORD_DELETE(ticket) \ + if (!m_trade.OrderDelete(ticket)) \ + Print("Error deleting order, ", GetLastError()); +#define ORD_MODIFY(ticket, stop, take) \ + if (!m_trade.OrderModify(ticket, ORD_OPEN, stop, take, ORDER_TIME_GTC, 0)) \ + Print("Error modyfing order: ", GetLastError()); + +#define TRIM_STRING_LEFT(param) StringTrimLeft(param) +#define TRIM_STRING_RIGHT(param) StringTrimRight(param) + +CTrade m_trade; +CPositionInfo m_position; +COrderInfo m_order; + +#else //__MQL4__ + +#define POSITION_TYPE_BUY OP_BUY +#define POSITION_TYPE_SELL OP_SELL + +#define POS_TOTAL OrdersTotal() +#define ORD_TOTAL OrdersTotal() +#define ASK_PRICE Ask +#define BID_PRICE Bid + +#define POS_SELECT_BY_INDEX(i) \ + if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) \ + continue; +#define POS_SYMBOL OrderSymbol() +#define POS_MAGIC OrderMagicNumber() +#define POS_TYPE OrderType() +#define POS_OPEN OrderOpenPrice() +#define POS_STOP OrderStopLoss() +#define POS_TAKE_PROFIT OrderTakeProfit() +#define POS_TICKET OrderTicket() + +#define ORD_SELECT_BY_INDEX(i) \ + if (!OrderSelect(i, SELECT_BY_POS, MODE_TRADES)) \ + continue; +#define ORD_SYMBOL OrderSymbol() +#define ORD_MAGIC OrderMagicNumber() +#define ORD_TYPE OrderType() +#define ORD_OPEN OrderOpenPrice() +#define ORD_STOP OrderStopLoss() +#define ORD_TAKE_PROFIT OrderTakeProfit() +#define ORD_TICKET OrderTicket() + +#define POS_BUY(lots, price, sl, tp) OrderSend(_Symbol, OP_BUY, lots, price, expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define POS_SELL(lots, price, sl, tp) OrderSend(_Symbol, OP_SELL, lots, price, expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define ORDER_BUY_LIMIT(lots, price, sl, tp) OrderSend(_Symbol, OP_BUYLIMIT, lots, NormalizeDouble(price, _Digits), expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define ORDER_BUY_STOP(lots, price, sl, tp) OrderSend(_Symbol, OP_BUYSTOP, lots, NormalizeDouble(price, _Digits), expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define ORDER_SELL_LIMIT(lots, price, sl, tp) OrderSend(_Symbol, OP_SELLLIMIT, lots, NormalizeDouble(price, _Digits), expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define ORDER_SELL_STOP(lots, price, sl, tp) OrderSend(_Symbol, OP_SELLSTOP, lots, NormalizeDouble(price, _Digits), expertDeviation, sl, tp, trade_comment, expertMagic, 0, clrNONE); +#define POS_MODIFY(ticket, stop, take) \ + if (!OrderModify(ticket, POS_OPEN, stop, take, OrderExpiration(), clrNONE)) \ + Print("Error modyfing position: ", GetLastError()); +#define POS_CLOSE(ticket) \ + if (POS_TYPE == POSITION_TYPE_BUY) \ + { \ + if (!OrderClose(ticket, OrderLots(), BID_PRICE, expertDeviation)) \ + Print("Error closing position, ", GetLastError()); \ + } \ + else if (POS_TYPE == POSITION_TYPE_SELL) \ + { \ + if (!OrderClose(ticket, OrderLots(), ASK_PRICE, expertDeviation)) \ + Print("Error closing position, ", GetLastError()); \ + } +#define POS_CLOSE_PARTIAL(ticket) \ + if (POS_TYPE == POSITION_TYPE_BUY) \ + { \ + if (!OrderClose(ticket, MathMin(partialLots, OrderLots()), BID_PRICE, expertDeviation)) \ + Print("Error closing partial position, ", GetLastError()); \ + } \ + else if (POS_TYPE == POSITION_TYPE_SELL) \ + { \ + if (!OrderClose(ticket, MathMin(partialLots, OrderLots()), ASK_PRICE, expertDeviation)) \ + Print("Error closing partial position, ", GetLastError()); \ + } + +#define ORD_DELETE(ticket) \ + if (!OrderDelete(ticket)) \ + Print("Error deleting order, ", GetLastError()); +#define ORD_MODIFY(ticket, stop, take) \ + if (!OrderModify(ticket, ORD_OPEN, stop, take, OrderExpiration(), clrNONE)) \ + Print("Error modyfing order: ", GetLastError()); + +#define TRIM_STRING_LEFT(param) param = StringTrimLeft(param) +#define TRIM_STRING_RIGHT(param) param = StringTrimRight(param) + +#endif + +//+------------------------------------------------------------------+ +//| defines | +//+------------------------------------------------------------------+ +//--- indents and gaps +#define INDENT_LEFT (11) // indent from left (with allowance for border width) +#define INDENT_TOP (11) // indent from top (with allowance for border width) +#define INDENT_RIGHT (11) // indent from right (with allowance for border width) +#define INDENT_BOTTOM (11) // indent from bottom (with allowance for border width) +#define CONTROLS_GAP_X (10) // gap by X coordinate +#define CONTROLS_GAP_Y (10) // gap by Y coordinate +//--- for buttons +#define BUTTON_WIDTH (100) // size by X coordinate +#define BUTTON_HEIGHT (25) // size by Y coordinate + +#define RISK_LABEL "RISK" +#define STOP_EDIT "SL_EDIT" +#define TAKE_EDIT "TP_EDIT" +#define RISK_EDIT "R_EDIT" +#define BUY_BUTTON "BUY" +#define SELL_BUTTON "SELL" +#define BREAKEVEN_BUTTON "SL BREAKEVEN" +#define CLOSE_BUTTON "CLOSE ALL" +#define CLOSE_BUY_BUTTON "CLOSE BUYS" +#define CLOSE_SELL_BUTTON "CLOSE SELLS" +#define STOP_BUTTON "MODIFY SL" +#define TAKE_BUTTON "MODIFY TP" +#define PARTIAL_EDIT "PARTIAL_EDIT" +#define PARTIAL_BUTTON "CLOSE PARTIAL" +#define COMMENT_EDIT "COMM_EDIT" +#define BUY_PEND_BUTTON "PENDING BUY" +#define SELL_PEND_BUTTON "PENDING SELL" +#define DELETE_BUY_PEND_BUTTON "DEL BUY ORD." +#define DELETE_SELL_PEND_BUTTON "DEL SELL ORD." + +#define GVAR_POSITION_X "SimplePanel_positionX" +#define GVAR_POSITION_Y "SimplePanel_positionY" +#define GVAR_RISK_NAME "SimplePanel_risk_percent" +#define GVAR_CALC_TYPE "SimplePanel_calc_mode" +#define GVAR_STOP_LOSS "SimplePanel_stop_loss" +#define GVAR_TAKE_PROFIT "SimplePanel_take_profit" +#define GVAR_PARTIAL_LOTS "SimplePanel_partial_close" + +//+------------------------------------------------------------------+ +//| Enumerators (for inputs) | +//+------------------------------------------------------------------+ +enum RiskMode +{ + BALANCE_PERCENT, // Percentage of balance + FIXED_LOTS // Fixed lots +}; + +enum SLTPMode +{ + ST_PRICE, // Price + ST_DISTANCE // Distance to open price (>0) +}; + +enum BidAskMode +{ + BA_NORMAL, // Bid for sell/Ask for buy + BA_INVERT, // Ask for sell/Bid for buy (inv) + BA_ONLY_ASK, // Only Ask price + BA_ONLY_BID // Only Bid price +}; + +//+------------------------------------------------------------------+ +//| Inputs | +//+------------------------------------------------------------------+ +#ifdef __MQL5__ +input group "Trade Settings" +#endif + input RiskMode riskCalculation = FIXED_LOTS; // Risk calculation mode +input SLTPMode stop_takeMode = ST_DISTANCE; // SL/TP Mode +input BidAskMode bidAskReference = BA_NORMAL; // Price reference for SL and TP +input int expertDeviation = 20; // Maximum slippage +input int expertMagic = 10001; // Expert Magic number +input bool affectOtherTrades = false; // Modify external trades (not opened by the panel) + +#ifdef __MQL5__ +input group "Buttons" +#endif + input bool showBuySellButtons = true; // Show Buy/Sell buttons +input bool showCloseSepButtons = false; // Show Close Buy/Sell buttons +input bool showSLBECloseAllButtons = true; // Show SL to BE and Close All buttons +input bool showModifyButtons = false; // Show Modify SL/TP buttons +input bool showPartialClose = false; // Show Partial Close +input bool showPendingOrder = false; // Show Pending Orders +input bool showDeleteOrder = false; // Show Delete Pending Orders +input bool showCommentEdit = false; // Show Edit Comment + +#ifdef __MQL5__ +input group "Other settings" +#endif + input bool grabSLwithDrag = false; // Pick SL values with crosshair drag (Dist. mode) +input int fontSize = 10; // Font size +input double autoSetTP = 0.0; // Auto-set TP to SL ratio (0=don't use) +input bool autoSetSL = true; // Auto-set SL to ratio from TP change +#ifdef __MQL5__ +input bool asyncOperations = false; // Use asynchronous orders +#endif + +int lot_digits = 2; +double riskAmount = 2.0; +double stopLoss = 0.0; +double takeProfit = 0.0; +double partialLots = 1.0; + +string trade_comment = NULL; + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +class CControlsDialog : public CAppDialog +{ +private: + CLabel m_label, m_stoploss_label, m_takeprofit_label; + CEdit m_edit, m_stoploss_edit, m_takeprofit_edit; + CButton m_buy_button, m_sell_button, m_breakeven_button, m_close_button, m_modify_sl_button, m_modify_tp_button; + CButton m_close_buys_button, m_close_sell_button, m_buy_order_button, m_sell_order_button, m_delete_buy_button, m_delete_sell_button; + CEdit m_partial_edit; + CButton m_partial_button; + CEdit m_comment_edit; + +public: + //--- create + virtual bool Create(const string name); + //--- chart event handler + virtual bool OnEvent(const int id, const long &lparam, const double &dparam, const string &sparam); + + double GetRiskPercent(); + double GetStopLoss(); + double GetTakeProfit(); + double GetPartialLots(); + + void SetStopLoss(int stop); + +protected: + //--- create dependent controls + bool CreateButton(CButton &button, string name, int x1, int y1, int x2, int y2, color clr_back = clrDarkCyan); + bool CreateEdit(CEdit &edit, string name, string editText, int x1, int y1, int x2, int y2); + bool CreateLabel(CLabel &label, string name, int x1, int y1, int x2, int y2); + //--- handlers of the dependent controls events + void OnEndEditRisk(void); + void OnEndEditSL(void); + void OnEndEditTP(void); + void OnClickBuyButton(void); + void OnClickSellButton(void); + void OnClickBreakevenButton(void); + void OnClickCloseButton(void); + void OnClickCloseBuyButton(void); + void OnClickCloseSellButton(void); + void OnClickModifySLButton(void); + void OnClickModifyTPButton(void); + void OnEditPartialClose(void); + void OnClickPartialCloseButton(void); + void OnEditComment(void); + void OnClickBuyOrderButton(void); + void OnClickSellOrderButton(void); + void OnClickDeleteBuyOrders(void); + void OnClickDeleteSellOrders(void); + + void WriteExpertSettings(); + void RestoreExpertSettings(); + void RestoreStopTakeValues(); + + void RestoreComment(); + void SaveComment(); + + double NormalizeLots(double lots); +}; + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +EVENT_MAP_BEGIN(CControlsDialog) +ON_EVENT(ON_END_EDIT, m_edit, OnEndEditRisk) +ON_EVENT(ON_END_EDIT, m_stoploss_edit, OnEndEditSL) +ON_EVENT(ON_END_EDIT, m_takeprofit_edit, OnEndEditTP) +if (showBuySellButtons) +{ + ON_EVENT(ON_CLICK, m_buy_button, OnClickBuyButton) + ON_EVENT(ON_CLICK, m_sell_button, OnClickSellButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showCloseSepButtons) +{ + ON_EVENT(ON_CLICK, m_close_buys_button, OnClickCloseBuyButton) + ON_EVENT(ON_CLICK, m_close_sell_button, OnClickCloseSellButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showSLBECloseAllButtons) +{ + ON_EVENT(ON_CLICK, m_breakeven_button, OnClickBreakevenButton) + ON_EVENT(ON_CLICK, m_close_button, OnClickCloseButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showModifyButtons) +{ + ON_EVENT(ON_CLICK, m_modify_sl_button, OnClickModifySLButton) + ON_EVENT(ON_CLICK, m_modify_tp_button, OnClickModifyTPButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showPartialClose) +{ + ON_EVENT(ON_END_EDIT, m_partial_edit, OnEditPartialClose) + ON_EVENT(ON_CLICK, m_partial_button, OnClickPartialCloseButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showCommentEdit) +{ + ON_EVENT(ON_END_EDIT, m_comment_edit, OnEditComment) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showPendingOrder) +{ + ON_EVENT(ON_CLICK, m_buy_order_button, OnClickBuyOrderButton) + ON_EVENT(ON_CLICK, m_sell_order_button, OnClickSellOrderButton) +} +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +if (showDeleteOrder) +{ + ON_EVENT(ON_CLICK, m_delete_buy_button, OnClickDeleteBuyOrders) + ON_EVENT(ON_CLICK, m_delete_sell_button, OnClickDeleteSellOrders) +} +EVENT_MAP_END(CAppDialog) + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CControlsDialog::Create(const string name) +{ + stopLoss = 0.0; + takeProfit = 0.0; + partialLots = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN); + + RestoreExpertSettings(); + + int rows = 3; + + if (showBuySellButtons) + rows++; + if (showCloseSepButtons) + rows++; + if (showSLBECloseAllButtons) + rows++; + if (showModifyButtons) + rows++; + if (showPartialClose) + rows++; + if (showCommentEdit) + rows++; + if (showPendingOrder) + rows++; + if (showDeleteOrder) + rows++; + + if (rows == 3) + { + Print("Error: no buttons are being shown"); + return false; + } + + // Get position + int x1 = 100; + int y1 = 40; + if (GlobalVariableCheck(GVAR_POSITION_X)) + x1 = (int)GlobalVariableGet(GVAR_POSITION_X); + else + GlobalVariableSet(GVAR_POSITION_X, x1); + + if (GlobalVariableCheck(GVAR_POSITION_Y)) + y1 = (int)GlobalVariableGet(GVAR_POSITION_Y); + else + GlobalVariableSet(GVAR_POSITION_Y, y1); + + int x2 = 8 + x1 + INDENT_LEFT + 2 * BUTTON_WIDTH + CONTROLS_GAP_X + INDENT_RIGHT; + int y2 = 30 + y1 + INDENT_TOP + rows * BUTTON_HEIGHT + (rows - 1) * CONTROLS_GAP_Y + INDENT_BOTTOM; + + if (!CAppDialog::Create(0, name, 1, x1, y1, x2, y2)) + return (false); + // CaptionFontSize(fontSize); //CUSTOM function defined in CDialog + + int bx1 = INDENT_LEFT; + int by1 = INDENT_TOP; + int bx2 = bx1 + BUTTON_WIDTH; + int by2 = by1 + BUTTON_HEIGHT; + +#define NEXT_COLUMN \ + bx1 += (BUTTON_WIDTH + CONTROLS_GAP_X); \ + bx2 += (BUTTON_WIDTH + CONTROLS_GAP_X); + +#define PREV_COLUMN \ + bx1 -= (BUTTON_WIDTH + CONTROLS_GAP_X); \ + bx2 -= (BUTTON_WIDTH + CONTROLS_GAP_X); + +#define NEXT_ROW \ + by1 += (BUTTON_HEIGHT + CONTROLS_GAP_Y); \ + by2 += (BUTTON_HEIGHT + CONTROLS_GAP_Y); + + if (!CreateLabel(m_label, riskCalculation == BALANCE_PERCENT ? "RISK %" : "LOTS", bx1 + BUTTON_WIDTH / 2, by1, bx2, by2)) + return (false); + + NEXT_COLUMN + + string riskText; + if (riskCalculation == BALANCE_PERCENT) + riskText = DoubleToString(riskAmount, 2) + " %"; + else + riskText = DoubleToString(riskAmount, lot_digits); + + if (!CreateEdit(m_edit, RISK_EDIT, riskText, bx1, by1, bx2, by2)) + return (false); + + PREV_COLUMN + NEXT_ROW + + if (!CreateLabel(m_stoploss_label, stop_takeMode == ST_DISTANCE ? "SL (points)" : "SL (price)", bx1 + BUTTON_WIDTH / 3, by1, bx2, by2)) + return (false); + + NEXT_COLUMN + + string sl_edit_str = stop_takeMode == ST_DISTANCE ? DoubleToString(stopLoss, 0) : DoubleToString(stopLoss, _Digits); + + if (!CreateEdit(m_stoploss_edit, STOP_EDIT, sl_edit_str, bx1, by1, bx2, by2)) + return (false); + + PREV_COLUMN + NEXT_ROW + + if (!CreateLabel(m_takeprofit_label, stop_takeMode == ST_DISTANCE ? "TP (points)" : "TP (price)", bx1 + BUTTON_WIDTH / 3, by1, bx2, by2)) + return (false); + + NEXT_COLUMN + + string tp_edit_str = stop_takeMode == ST_DISTANCE ? DoubleToString(takeProfit, 0) : DoubleToString(takeProfit, _Digits); + + if (!CreateEdit(m_takeprofit_edit, TAKE_EDIT, tp_edit_str, bx1, by1, bx2, by2)) + return (false); + + if (showBuySellButtons) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_buy_button, BUY_BUTTON, bx1, by1, bx2, by2, clrGreen)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_sell_button, SELL_BUTTON, bx1, by1, bx2, by2, clrRed)) + return (false); + } + + if (showCloseSepButtons) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_close_buys_button, CLOSE_BUY_BUTTON, bx1, by1, bx2, by2, clrDarkSlateGray)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_close_sell_button, CLOSE_SELL_BUTTON, bx1, by1, bx2, by2, clrDarkRed)) + return (false); + } + + if (showSLBECloseAllButtons) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_breakeven_button, BREAKEVEN_BUTTON, bx1, by1, bx2, by2, clrGray)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_close_button, CLOSE_BUTTON, bx1, by1, bx2, by2, clrBlue)) + return (false); + } + + if (showModifyButtons) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_modify_sl_button, STOP_BUTTON, bx1, by1, bx2, by2, clrIndianRed)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_modify_tp_button, TAKE_BUTTON, bx1, by1, bx2, by2, clrDarkCyan)) + return (false); + } + + if (showPartialClose) + { + PREV_COLUMN + NEXT_ROW + + string partialEdit = DoubleToString(partialLots, lot_digits); + if (!CreateEdit(m_partial_edit, PARTIAL_EDIT, partialEdit, bx1, by1, bx2, by2)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_partial_button, PARTIAL_BUTTON, bx1, by1, bx2, by2, clrSlateBlue)) + return (false); + } + + if (showPendingOrder) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_buy_order_button, BUY_PEND_BUTTON, bx1, by1, bx2, by2, clrLimeGreen)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_sell_order_button, SELL_PEND_BUTTON, bx1, by1, bx2, by2, clrOrangeRed)) + return (false); + } + + if (showDeleteOrder) + { + PREV_COLUMN + NEXT_ROW + + if (!CreateButton(m_delete_buy_button, DELETE_BUY_PEND_BUTTON, bx1, by1, bx2, by2, clrDarkGreen)) + return (false); + + NEXT_COLUMN + + if (!CreateButton(m_delete_sell_button, DELETE_SELL_PEND_BUTTON, bx1, by1, bx2, by2, clrMaroon)) + return (false); + } + + if (showCommentEdit) + { + bx1 -= (BUTTON_WIDTH + CONTROLS_GAP_X); // Keep bx2 at the end + NEXT_ROW + + if (!CreateEdit(m_comment_edit, COMMENT_EDIT, trade_comment, bx1, by1, bx2, by2)) + return (false); + } + + //--- succeed + return (true); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CControlsDialog::CreateButton(CButton &button, string name, int x1, int y1, int x2, int y2, color clr_back = clrDarkCyan) +{ + //--- create + if (!button.Create(m_chart_id, m_name + name, m_subwin, x1, y1, x2, y2)) + return (false); + if (!button.Text(name)) + return (false); + if (!button.FontSize(fontSize)) + return (false); + button.ColorBorder(clrBlack); + button.Color(clrWhite); + button.ColorBackground(clr_back); + + if (!Add(button)) + return (false); + //--- succeed + return (true); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double CControlsDialog::GetRiskPercent(void) +{ + if (riskCalculation == FIXED_LOTS) + return NormalizeLots(StringToDouble(m_edit.Text())); + return MathMin(MathMax(StringToDouble(m_edit.Text()), 0.0), 100.0); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double CControlsDialog::GetStopLoss(void) +{ + return MathMax(StringToDouble(m_stoploss_edit.Text()), 0.0); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double CControlsDialog::GetTakeProfit(void) +{ + return MathMax(StringToDouble(m_takeprofit_edit.Text()), 0.0); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double CControlsDialog::GetPartialLots(void) +{ + return NormalizeLots(StringToDouble(m_partial_edit.Text())); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CControlsDialog::CreateEdit(CEdit &edit, string name, string editText, int x1, int y1, int x2, int y2) +{ + //--- create + if (!edit.Create(m_chart_id, m_name + name, m_subwin, x1, y1, x2, y2)) + return (false); + + if (!edit.ReadOnly(false)) + return (false); + + if (!edit.Text(editText)) + return (false); + if (!edit.FontSize(fontSize)) + return (false); + if (!edit.TextAlign(ALIGN_CENTER)) + return (false); + if (!Add(edit)) + return (false); + //--- succeed + return (true); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CControlsDialog::CreateLabel(CLabel &label, string name, int x1, int y1, int x2, int y2) +{ + //--- create + if (!label.Create(m_chart_id, m_name + name, m_subwin, x1, y1, x2, y2)) + return (false); + if (!label.Text(name)) + return (false); + if (!label.FontSize(fontSize)) + return (false); + if (!Add(label)) + return (false); + //--- succeed + return (true); +} + +#define PENDING_SETLINE "SOP_setline" + +//+------------------------------------------------------------------+ +//| Event handler | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickBuyButton(void) +{ + Print("Buy pressed"); + + RecalculateValues(POSITION_TYPE_BUY); + + if (!CheckLots()) + return; + + double ask = ASK_PRICE; + + double sl = 0.0; + double tp = 0.0; + + if (stop_takeMode == ST_PRICE) + { + sl = stopLoss; + tp = takeProfit; + } + else + { + double ref = (bidAskReference == BA_NORMAL || bidAskReference == BA_ONLY_ASK) ? ask : BID_PRICE; + + if (stopLoss != 0.0) + sl = ref - stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if (takeProfit != 0.0) + tp = ref + takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + } + + POS_BUY(totalLots, ask, sl, tp) +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickBuyOrderButton(void) +{ + Print("Pending Buy pressed"); + + setting_buy = true; + setting_sell = false; + + CreatePendingSetLine(clrGreen, ASK_PRICE); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickSellButton(void) +{ + Print("Sell pressed"); + + RecalculateValues(POSITION_TYPE_SELL); + + if (!CheckLots()) + return; + + double bid = BID_PRICE; + + double sl = 0.0; + double tp = 0.0; + + if (stop_takeMode == ST_PRICE) + { + sl = stopLoss; + tp = takeProfit; + } + else + { + double ref = (bidAskReference == BA_NORMAL || bidAskReference == BA_ONLY_BID) ? bid : ASK_PRICE; + + if (stopLoss != 0.0) + sl = ref + stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if (takeProfit != 0.0) + tp = ref - takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + } + + POS_SELL(totalLots, bid, sl, tp) +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickSellOrderButton(void) +{ + Print("Pending Sell pressed"); + + setting_sell = true; + setting_buy = false; + + CreatePendingSetLine(clrRed, BID_PRICE); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickBreakevenButton(void) +{ + Print("Breakeven pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + if (POS_TYPE == POSITION_TYPE_BUY) + { + double bid = BID_PRICE; + if (bid <= POS_OPEN) + { + continue; + } + } + else if (POS_TYPE == POSITION_TYPE_SELL) + { + double ask = ASK_PRICE; + if (ask >= POS_OPEN) + { + continue; + } + } + else + continue; // Pending orders + + if (POS_OPEN == POS_STOP) + { + continue; + } + + POS_MODIFY(POS_TICKET, POS_OPEN, POS_TAKE_PROFIT) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickCloseButton(void) +{ + Print("Close pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + POS_CLOSE(POS_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickCloseBuyButton(void) +{ + Print("Close Buy pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_TYPE == POSITION_TYPE_SELL) + continue; + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + POS_CLOSE(POS_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickDeleteBuyOrders(void) +{ + Print("Delete Pending Buys pressed"); + + for (int j = ORD_TOTAL - 1; j >= 0; j--) + { + ORD_SELECT_BY_INDEX(j) + + if (ORD_TYPE != ORDER_TYPE_BUY_LIMIT && ORD_TYPE != ORDER_TYPE_BUY_STOP) + continue; + + if (ORD_SYMBOL == _Symbol && (affectOtherTrades || ORD_MAGIC == expertMagic)) + { + ORD_DELETE(ORD_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickCloseSellButton(void) +{ + Print("Close Sell pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_TYPE == POSITION_TYPE_BUY) + continue; + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + POS_CLOSE(POS_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickDeleteSellOrders(void) +{ + Print("Delete Pending Sell pressed"); + + for (int j = ORD_TOTAL - 1; j >= 0; j--) + { + ORD_SELECT_BY_INDEX(j) + + if (ORD_TYPE != ORDER_TYPE_SELL_LIMIT && ORD_TYPE != ORDER_TYPE_SELL_STOP) + continue; + + if (ORD_SYMBOL == _Symbol && (affectOtherTrades || ORD_MAGIC == expertMagic)) + { + ORD_DELETE(ORD_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickModifySLButton(void) +{ + Print("Modify Stop Loss pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + double sl = 0.0; + + if (stop_takeMode == ST_PRICE) + { + sl = stopLoss; + } + else + { + + if (stopLoss != 0.0) + { + if (POS_TYPE == POSITION_TYPE_BUY) + sl = BID_PRICE - stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + else if (POS_TYPE == POSITION_TYPE_SELL) + sl = ASK_PRICE + stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + else + continue; // Pending orders + } + } + + if (sl != POS_STOP) + { + POS_MODIFY(POS_TICKET, sl, POS_TAKE_PROFIT) + } + else + { + Print("Stop Loss is already " + (string)sl); + } + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickModifyTPButton(void) +{ + Print("Modify Take Profit pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + double tp = 0.0; + + if (stop_takeMode == ST_PRICE) + { + tp = takeProfit; + } + else + { + + if (takeProfit != 0.0) + { + if (POS_TYPE == POSITION_TYPE_BUY) + tp = ASK_PRICE + takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + else if (POS_TYPE == POSITION_TYPE_SELL) + tp = BID_PRICE - takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + else + continue; + } + } + + if (tp != POS_TAKE_PROFIT) + { + POS_MODIFY(POS_TICKET, POS_STOP, tp) + } + else + { + Print("Take Profit is already " + (string)tp); + } + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnClickPartialCloseButton(void) +{ + Print("Close Partial pressed"); + + for (int i = POS_TOTAL - 1; i >= 0; i--) + { + POS_SELECT_BY_INDEX(i) + + if (POS_SYMBOL == _Symbol && (affectOtherTrades || POS_MAGIC == expertMagic)) + { + POS_CLOSE_PARTIAL(POS_TICKET) + } + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnEndEditRisk(void) +{ + riskAmount = GetRiskPercent(); + if (riskCalculation == BALANCE_PERCENT) + m_edit.Text(DoubleToString(riskAmount, 2) + " %"); + else + m_edit.Text(DoubleToString(riskAmount, lot_digits)); + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnEndEditSL(void) +{ + stopLoss = GetStopLoss(); + + if (stop_takeMode == ST_PRICE) + m_stoploss_edit.Text(DoubleToString(stopLoss, _Digits)); + else + m_stoploss_edit.Text(DoubleToString(stopLoss, 0)); + + if (autoSetTP > 0.0 && stop_takeMode == ST_DISTANCE) + { + takeProfit = MathRound(stopLoss * autoSetTP); + m_takeprofit_edit.Text(DoubleToString(takeProfit, 0)); + } + + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnEndEditTP(void) +{ + takeProfit = GetTakeProfit(); + + if (stop_takeMode == ST_PRICE) + m_takeprofit_edit.Text(DoubleToString(takeProfit, _Digits)); + else + m_takeprofit_edit.Text(DoubleToString(takeProfit, 0)); + + if (autoSetSL && autoSetTP > 0.0 && stop_takeMode == ST_DISTANCE) + { + stopLoss = MathRound(takeProfit / autoSetTP); + m_stoploss_edit.Text(DoubleToString(stopLoss, 0)); + } + + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnEditPartialClose(void) +{ + partialLots = GetPartialLots(); + + m_partial_edit.Text(DoubleToString(partialLots, lot_digits)); + + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::OnEditComment(void) +{ + trade_comment = m_comment_edit.Text(); + SaveComment(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::RestoreExpertSettings(void) +{ + if (showCommentEdit) + RestoreComment(); + else + trade_comment = NULL; + + if (GlobalVariableCheck(GVAR_RISK_NAME)) + { + riskAmount = GlobalVariableGet(GVAR_RISK_NAME); + if (riskCalculation == FIXED_LOTS) + riskAmount = NormalizeLots(riskAmount); + partialLots = NormalizeLots(GlobalVariableGet(GVAR_PARTIAL_LOTS)); + + RestoreStopTakeValues(); + } + else + { + WriteExpertSettings(); + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::WriteExpertSettings(void) +{ + GlobalVariableSet(GVAR_RISK_NAME, riskAmount); + GlobalVariableSet(GVAR_CALC_TYPE, stop_takeMode); + GlobalVariableSet(GVAR_STOP_LOSS, stopLoss); + GlobalVariableSet(GVAR_TAKE_PROFIT, takeProfit); + GlobalVariableSet(GVAR_PARTIAL_LOTS, partialLots); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::RestoreStopTakeValues(void) +{ + if (GlobalVariableCheck(GVAR_CALC_TYPE)) + { + int typeCalc = (int)GlobalVariableGet(GVAR_CALC_TYPE); + + if (typeCalc == stop_takeMode) + { + stopLoss = GlobalVariableGet(GVAR_STOP_LOSS); + takeProfit = GlobalVariableGet(GVAR_TAKE_PROFIT); + + return; + } + } + + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::RestoreComment(void) +{ + if (!FileIsExist("SOP_SavedComment.txt")) + return; + + int file = FileOpen("SOP_SavedComment.txt", FILE_READ | FILE_TXT | FILE_SHARE_READ); + + if (file == INVALID_HANDLE) + { + Print("Error reading comment in file"); + return; + } + trade_comment = FileReadString(file); + FileClose(file); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::SaveComment(void) +{ + int file = FileOpen("SOP_SavedComment.txt", FILE_WRITE | FILE_TXT); + + if (file == INVALID_HANDLE) + { + Print("Error saving comment in file"); + return; + } + + FileWrite(file, trade_comment); + FileClose(file); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +double CControlsDialog::NormalizeLots(double lots) +{ + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + return MathMax(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN), + MathMin(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX), + MathFloor(lots / lotStep) * lotStep)); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CControlsDialog::SetStopLoss(int stop) +{ + stopLoss = stop; + m_stoploss_edit.Text(IntegerToString(stop)); + + if (autoSetTP > 0.0 && stop_takeMode == ST_DISTANCE) + { + takeProfit = MathRound(stopLoss * autoSetTP); + m_takeprofit_edit.Text(DoubleToString(takeProfit, 0)); + } + + ChartRedraw(); + WriteExpertSettings(); +} + +//+------------------------------------------------------------------+ +//| Global Variables | +//+------------------------------------------------------------------+ +CControlsDialog ExtDialog; + +double totalLots; +MqlRates currentRates[2]; + +bool is_tracking = false; +double recorded_price = 0.0; + +bool setting_buy = false; +bool setting_sell = false; +int xysub; +double xyprice; +datetime xytime; + +//+------------------------------------------------------------------+ +//| Event handlers | +//+------------------------------------------------------------------+ +int OnInitEvent() +{ + ObjectsDeleteAll(0, M_NAME); // Remove elements in chart (for template issues) + lot_digits = int(-MathLog10(SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP))); + + if (!ExtDialog.Create("Simple Panel")) + return (INIT_FAILED); + + ExtDialog.Run(); + +#ifdef __MQL5__ + m_trade.SetExpertMagicNumber(expertMagic); + m_trade.SetDeviationInPoints(expertDeviation); + if (asyncOperations) + m_trade.SetAsyncMode(true); +#endif + + setting_buy = false; + setting_sell = false; + + return (INIT_SUCCEEDED); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void OnDeinitEvent(const int reason) +{ + GlobalVariableSet(GVAR_POSITION_X, ExtDialog.Left()); + GlobalVariableSet(GVAR_POSITION_Y, ExtDialog.Top()); + + DeletePendingSetLine(); + Comment(""); + ExtDialog.Destroy(reason); +} + +#define MASK_RIGHT_CLICK 2 +#define MASK_RIGHT_MIDDLE 16 + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CommonChartEvent(const int id, // event ID + const long &lparam, // event parameter of the long type + const double &dparam, // event parameter of the double type + const string &sparam) // event parameter of the string type +{ + if (setting_buy || setting_sell) + { + int smask = (int)StringToInteger(sparam); + + if ((id == CHARTEVENT_KEYDOWN && lparam == 27) || // ESC + (id == CHARTEVENT_MOUSE_MOVE && + ((smask & MASK_RIGHT_CLICK) > 0 || + (smask & MASK_RIGHT_MIDDLE) > 0))) + { + DeletePendingSetLine(); + + setting_buy = false; + setting_sell = false; + return; + } + } + if (setting_buy) + { + if (id == CHARTEVENT_CLICK) + { + DeletePendingSetLine(); + + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); + OpenBuyPendingOrder(xyprice); + + setting_buy = false; + return; + } + if (id == CHARTEVENT_MOUSE_MOVE) + { + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); + MovePendingSetLine(xyprice); + return; + } + } + if (setting_sell) + { + if (id == CHARTEVENT_CLICK) + { + DeletePendingSetLine(); + + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); + OpenSellPendingOrder(xyprice); + + setting_sell = false; + return; + } + if (id == CHARTEVENT_MOUSE_MOVE) + { + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); + MovePendingSetLine(xyprice); + return; + } + } + + if (!grabSLwithDrag || stop_takeMode == ST_PRICE) + return; + + if (id == CHARTEVENT_CLICK) + { + if (!is_tracking) + return; + + if (!(recorded_price > 0.0)) + { + is_tracking = false; + return; + } + + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); +#ifdef __MQL5__ + int pips = (int)MathRound(MathAbs(xyprice - recorded_price) / SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)); +#else + int pips = (int)(MathAbs(xyprice - recorded_price) / SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_SIZE)); +#endif + + if (pips > 0) + ExtDialog.SetStopLoss(pips); + + is_tracking = false; + recorded_price = 0.0; + } + + if (id == CHARTEVENT_MOUSE_MOVE) + { + if (sparam != "1") + return; + + if (is_tracking) + return; + + ChartXYToTimePrice(ChartID(), (int)lparam, (int)dparam, xysub, xytime, xyprice); + + if (xysub > 0) + return; + + recorded_price = xyprice; + is_tracking = true; + } + + if (id == CHARTEVENT_CHART_CHANGE || id == CHARTEVENT_CUSTOM + ON_DRAG_PROCESS) // Stop on drag panel + { + if (recorded_price > 0) + recorded_price = 0.0; + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CreatePendingSetLine(color clr, double line_price) +{ + if (ObjectFind(ChartID(), PENDING_SETLINE) < 0) + ObjectCreate(ChartID(), PENDING_SETLINE, OBJ_HLINE, 0, 0, line_price); + ObjectSetInteger(ChartID(), PENDING_SETLINE, OBJPROP_COLOR, clr); + ObjectSetInteger(ChartID(), PENDING_SETLINE, OBJPROP_SELECTABLE, false); + ObjectSetInteger(ChartID(), PENDING_SETLINE, OBJPROP_HIDDEN, false); + ObjectSetString(ChartID(), PENDING_SETLINE, OBJPROP_TOOLTIP, "\n"); + + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void MovePendingSetLine(double line_price) +{ + ObjectSetDouble(ChartID(), PENDING_SETLINE, OBJPROP_PRICE, line_price); + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void DeletePendingSetLine() +{ + ObjectDelete(ChartID(), PENDING_SETLINE); + ChartRedraw(); +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void RecalculateValues(int position, double price_ref = 0.0) +{ + if (riskCalculation == FIXED_LOTS) + { + totalLots = riskAmount; + return; + } + + double riskTotal = AccountInfoDouble(ACCOUNT_BALANCE) * riskAmount / 100.0; + // Si risk es 0, puede ser por cadena invalida + + double sl = 0.0; + if (stop_takeMode == ST_PRICE) + { + if (stopLoss != 0.0) + { + if (price_ref > 0.0) // pending + sl = MathAbs(price_ref - stopLoss); + else + { + if (position == POSITION_TYPE_BUY) + sl = ASK_PRICE - stopLoss; + else if (position == POSITION_TYPE_SELL) + sl = stopLoss - BID_PRICE; + } + } + } + else + { + sl = stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + } + + if (sl == 0.0) + { + totalLots = 0.0; + Print("Stop Loss Cannot be 0 with risk percent"); + return; + } + + sl /= SymbolInfoDouble(_Symbol, SYMBOL_POINT); + + double lotValue = SymbolInfoDouble(_Symbol, SYMBOL_TRADE_TICK_VALUE_LOSS); + double pointValue = riskTotal / sl; + + if (pointValue < 0) + { + totalLots = 0.0; + Print("Invalid stop"); + return; + } + + double lotStep = SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_STEP); + + totalLots = MathFloor((pointValue / lotValue) / lotStep) * lotStep; +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CheckLots() +{ + if (totalLots < SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MIN)) + { + Print("Risk is too small to Open a trade"); + return false; + } + if (totalLots > SymbolInfoDouble(_Symbol, SYMBOL_VOLUME_MAX)) + { + Print("Lots are too big to Open a trade"); + return false; + } + return true; +} + +//+------------------------------------------------------------------+ +//| Pending orders | +//+------------------------------------------------------------------+ +void OpenBuyPendingOrder(double order_price) +{ + Print("Open Buy pending"); + + RecalculateValues(POSITION_TYPE_BUY, order_price); + + if (!CheckLots()) + return; + + double sl = 0.0; + double tp = 0.0; + + if (stop_takeMode == ST_PRICE) + { + sl = stopLoss; + tp = takeProfit; + } + else + { + if (stopLoss != 0.0) + sl = order_price - stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if (takeProfit != 0.0) + tp = order_price + takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + } + + if (order_price > ASK_PRICE) + { + ORDER_BUY_STOP(totalLots, order_price, sl, tp) + } + else + { + ORDER_BUY_LIMIT(totalLots, order_price, sl, tp) + } +} + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void OpenSellPendingOrder(double order_price) +{ + Print("Open Sell pending"); + + RecalculateValues(POSITION_TYPE_SELL, order_price); + + if (!CheckLots()) + return; + + double sl = 0.0; + double tp = 0.0; + + if (stop_takeMode == ST_PRICE) + { + sl = stopLoss; + tp = takeProfit; + } + else + { + if (stopLoss != 0.0) + sl = order_price + stopLoss * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + if (takeProfit != 0.0) + tp = order_price - takeProfit * SymbolInfoDouble(_Symbol, SYMBOL_POINT); + } + + if (order_price < BID_PRICE) + { + ORDER_SELL_STOP(totalLots, order_price, sl, tp) + } + else + { + ORDER_SELL_LIMIT(totalLots, order_price, sl, tp) + } +} +//+------------------------------------------------------------------+ + +int OnInit() +{ + return OnInitEvent(); +} + +void OnDeinit(const int reason) +{ + OnDeinitEvent(reason); +} + +void OnChartEvent(const int id, // event ID + const long &lparam, // event parameter of the long type + const double &dparam, // event parameter of the double type + const string &sparam) // event parameter of the string type +{ + ExtDialog.ChartEvent(id, lparam, dparam, sparam); + CommonChartEvent(id, lparam, dparam, sparam); +} diff --git a/Experts/MyProject/HIT-EA.mq5 b/Experts/MyProject/HIT-EA.mq5 new file mode 100644 index 0000000..2a9b77f --- /dev/null +++ b/Experts/MyProject/HIT-EA.mq5 @@ -0,0 +1,935 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.00" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +#import "Shell32.dll" +int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 20; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // ★追加:トレンドを格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 必要な関数プロトタイプ宣言 +//| プロセス完了ファイル("process_done.txt")関連 +//+------------------------------------------------------------------+ + +// "process_done.txt"を作成する関数 +void CreateDoneFile(const string name); + +// "process_done.txt"を削除する関数 +void DeleteDoneFile(const string name); + +// "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +bool CheckDoneFile(const string name); + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 必要な関数プロトタイプ宣言 +//| OHLCデータの取得からターゲット価格の取得まで +//+------------------------------------------------------------------+ + +//| 最新のOHLCデータを取得する関数 +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count); + +//| "ohlc.csv"を出力する関数 +void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]); + +//| バッチファイル(Pythonスクリプト)を実行する関数 +void ExecuteBatchTrend(); +void ExecuteBatchEntry(); + +// | "ohlc.csv"を出力後、バッチファイルの実行する関数 +bool RecordOHLCAndExecuteBatch_Trend(EAState &state); +bool RecordOHLCAndExecuteBatch_Entry(EAState &state); + +//| ターゲット価格を取得する関数 +bool GetTargetPrices(EAState &state); + +//| "target_prices.txt"を読み込む関数 +void LoadTargetPrices(double &target_prices[]); + +//| "xxx"を読み込む関数 +bool GetTrendState(EAState &state); +void LoadTrendState(int &trend_state); + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 必要な関数プロトタイプ宣言 +//| エントリー注文関連 +//+------------------------------------------------------------------+ + +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +bool SendOrder(int orderType, double price, double tp, double sl); + +//| 注文の執行ポリシーを取得する関数 +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol); + +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +string RequestComment(int orderType); + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 必要な関数プロトタイプ宣言 +//| 時間制限処理関連 +//+------------------------------------------------------------------+ + +//| 時間が経過したエントリー注文をキャンセルする関数 +bool CancelExpiredOrders(); + +//| 時間が経過したポジションをクローズする関数 +bool CloseExpiredPositions(); + +//| 指定された時間に最も近いバーのインデックスを返す関数 +// int GetBarIndexByTime(datetime time, int default_index = 0); +int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1); + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +void OnInit() +// プロセス完了ファイル("process_done.txt)"を作成 + { + CreateDoneFile(done_trend_file); + CreateDoneFile(done_entry_file); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +void OnTick() + { + static int init_order_flg = initial_order; + +// ───────────────────────────────────────────── +// (1) H4更新検知:トレンド用 +// ───────────────────────────────────────────── + int current_bars_H4 = iBars(NULL, PERIOD_H4); + static int pre_bars_H4 = current_bars_H4; + int bars_H4_change = current_bars_H4 - pre_bars_H4; + + if(bars_H4_change > 0 || init_order_flg == 1) + { + RecordOHLCAndExecuteBatch_Trend(g_ea); + } + pre_bars_H4 = current_bars_H4; + +// trend done待ち(なければ以降をスキップ) + if(!CheckDoneFile(done_trend_file)) + return; + +// トレンド取得 + GetTrendState(g_ea); + +// ───────────────────────────────────────────── +// (2) H1更新検知:エントリー用 +// ───────────────────────────────────────────── + static bool bars_H1_check = false; + + int current_bars_H1 = iBars(NULL, PERIOD_H1); + static int pre_bars_H1 = current_bars_H1; + int bars_H1_change = current_bars_H1 - pre_bars_H1; + + if(bars_H1_change > 0 || init_order_flg == 1) + { + init_order_flg = 0; + bars_H1_check = true; + g_ea.chk_cnt = 0; + g_ea.last_chk = 0; + RecordOHLCAndExecuteBatch_Entry(g_ea); + } + pre_bars_H1 = current_bars_H1; + +// ── ティック情報・コメント表示 ── + MqlTick last_tick; + if(!SymbolInfoTick(_Symbol, last_tick)) + { + return; + } + double Ask = last_tick.ask; + double Bid = last_tick.bid; + double Spread = MathRound((Ask - Bid) / Point()) * Point(); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + string message = StringFormat( + " \nAsk: %." + IntegerToString(digits) + "f" + "\nBid: %." + IntegerToString(digits) + "f" + "\nSpread: %." + IntegerToString(digits) + "f" + "\n\nLast Trend Update:\n %s" + "\n\nTrend State: %.0f" // ★追加 + "\n\nLast Target Update:\n %s" + "\n\n[T1 Buy Stop ] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" + "\n[T2 Buy Limit] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" + "\n[T3 Sell Stop] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f" + "\n[T4 SellLimit] en: %." + IntegerToString(digits) + "f tp: %." + IntegerToString(digits) + "f sl: %." + IntegerToString(digits) + "f\n", + Ask, Bid, Spread, + TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), + g_ea.trend_state, // ★追加 + TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), + g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], + g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], + g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], + g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] + ); + Comment(message); + +// スプレッド制限 + if(Spread > spread_limit * Point()) + return; + +// entry done待ち + if(!CheckDoneFile(done_entry_file)) + return; + +// ターゲット取得 + GetTargetPrices(g_ea); + +// 期限切れ注文キャンセル・ポジクローズ + if(OrdersTotal() > 0) + CancelExpiredOrders(); + if(PositionsTotal() > 0) + CloseExpiredPositions(); + +// ───────────────────────────────────────────── +// エントリー条件判定&注文送信(H1更新タイミングで1回) +// ───────────────────────────────────────────── +// if(bars_H4_check && TimeCurrent() - g_ea.last_chk >= 60) +// { +// bars_H4_check = false; +// g_ea.last_chk = TimeCurrent(); + if(bars_H1_check && TimeCurrent() - g_ea.last_chk >= 60) + { + bars_H1_check = false; + g_ea.last_chk = TimeCurrent(); + + if(g_ea.res_chk == 0) + { + Print("[Entry Skip] res_chk=0 (range). No entry orders are sent."); + g_ea.chk_cnt = 0; + } + else + { + int used = OrdersTotal() + PositionsTotal(); + if(used >= POSITION_LIMIT) + { + Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); + } + else + { + int sent_success = 0; // ★ 送信成功数(= SendOrder true の回数) + + // ★ 1..4 を順に判定して、条件OKなら送る + for(int t=1; t<=4; t++) + { + // ポジション上限チェック(途中で到達したら打ち切り) + used = OrdersTotal() + PositionsTotal(); + if(used >= POSITION_LIMIT) + { + Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); + break; + } + + string entry_type; + double cur_price; // ログ用(Ask/Bid) + + if(t==1) + { + entry_type="Buy Stop"; + cur_price=Ask; + } + else + if(t==2) + { + entry_type="Buy Limit"; + cur_price=Ask; + } + else + if(t==3) + { + entry_type="Sell Stop"; + cur_price=Bid; + } + else + { + entry_type="Sell Limit"; // t==4 + cur_price=Bid; + } + + double en = g_ea.en_price[t]; + double tp = g_ea.tp_price[t]; + double sl = g_ea.sl_price[t]; + + bool ok=false; + + if(t==1) + ok = (Ask < en && tp > en && sl < en); // buy-stop + else + if(t==2) + ok = (Ask > en && tp > en && sl < en); // buy-limit + else + if(t==3) + ok = (Bid > en && tp < en && sl > en); // sell-stop + else + ok = (Bid < en && tp < en && sl > en); // sell-limit + + if(ok) + { + Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); + + if(SendOrder(t, en, tp, sl)) + { + sent_success++; + Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); + } + else + { + Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); + } + } + else + { + Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); + } + } + + if(sent_success > 0) + { + g_ea.chk_cnt = 0; // 1件でも通ったら終了 + } + else + { + g_ea.chk_cnt += 1; + Print("[Retry] no order sent. chk_cnt=", g_ea.chk_cnt, "/10"); + + if(g_ea.chk_cnt < 10) + { + // bars_H4_check = true; // 60秒後に再度このブロックに入る + bars_H1_check = true; // ★ GPT-EAの bars_H4_check → bars_H1_check に変更 + } + else + { + Print("[Retry End] reached max tries. reset chk_cnt."); + g_ea.chk_cnt = 0; + } + } + } + } + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を作成する関数 +//+------------------------------------------------------------------+ +void CreateDoneFile(const string name) + { + if(!FileIsExist(name)) + { + int h = FileOpen(name, FILE_WRITE|FILE_TXT); + if(h != INVALID_HANDLE) + FileClose(h); + else + Print("Failed to create file: ", name, " err=", GetLastError()); + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を削除する関数 +//+------------------------------------------------------------------+ +void DeleteDoneFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + + +//+------------------------------------------------------------------+ +//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +//+------------------------------------------------------------------+ +bool CheckDoneFile(const string name) + { + return FileIsExist(name); + } + +//+------------------------------------------------------------------+ +//| 最新のOHLCデータを取得する関数 +//+------------------------------------------------------------------+ +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) + { + ArrayResize(times, 0); + ArrayResize(open_prices, 0); + ArrayResize(high_prices, 0); + ArrayResize(low_prices, 0); + ArrayResize(close_prices, 0); + + if(bars_count < 2) + return false; + +// 時間データを取得 + if(CopyTime(_Symbol, tf, 0, bars_count, times) <= 0) + { + Print(__FUNCTION__, ": Failed to copy time data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + +// OHLCデータを取得 + MqlRates rates[]; + if(CopyRates(_Symbol, tf, 0, bars_count, rates) <= 0) + { + Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + +// 必要なデータを配列に格納 + for(int i = 0; i < ArraySize(rates); i++) + { + ArrayResize(open_prices, i + 1); + ArrayResize(high_prices, i + 1); + ArrayResize(low_prices, i + 1); + ArrayResize(close_prices, i + 1); + + open_prices[i] = rates[i].open; + high_prices[i] = rates[i].high; + low_prices[i] = rates[i].low; + close_prices[i] = rates[i].close; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力する関数 +//+------------------------------------------------------------------+ +void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) + { +// 出力先ファイル名 +// string filename = "ohlc.csv"; + +// ファイルを "書き込みモード" でオープン (テキスト/ANSI) + int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); + +// ファイルが開けたかチェック + if(fileHandle == INVALID_HANDLE) + { + Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); + return; + } + +// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) + int size = ArraySize(times); + +// ヘッダー行を追加 + FileWrite(fileHandle, "Time,Open,High,Low,Close"); + +// 小数点の桁数を取得 + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + +// フォーマット文字列を動的に生成 + string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); + +// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み + for(int i = 0; i < size; i++) + { + // 時間をフォーマット + string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); + + // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) + string line = StringFormat(formatString, + timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); + + // ファイルに書き込み (改行付き) + FileWrite(fileHandle, line); + } + +// 書き込み終了後、ファイルを閉じる + FileClose(fileHandle); + } + +//+------------------------------------------------------------------+ +//| バッチファイル(Pythonスクリプト)を実行する関数 +//+------------------------------------------------------------------+ +void ExecuteBatchTrend() + { + if(CheckDoneFile(done_trend_file)) + { + DeleteDoneFile(done_trend_file); + ShellExecuteW(0, "open", get_trend_reply_bat, "", "", 1); + } + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void ExecuteBatchEntry() + { + if(CheckDoneFile(done_entry_file)) + { + DeleteDoneFile(done_entry_file); + ShellExecuteW(0, "open", get_entry_reply_bat, "", "", 1); + } + } + + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力後、バッチファイルの実行する関数 +//+------------------------------------------------------------------+ +bool RecordOHLCAndExecuteBatch_Trend(EAState &state) + { + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H4, HISTORY_BARS)) + { Print("GetLatestOHLC(H4) failed."); return false; } + + RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices); + ExecuteBatchTrend(); + + state.load_trend_flg = true; + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool RecordOHLCAndExecuteBatch_Entry(EAState &state) + { + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H1, HISTORY_BARS)) + { Print("GetLatestOHLC(H1) failed."); return false; } + + RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices); + ExecuteBatchEntry(); + + state.load_target_flg = true; + return true; + } + + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool GetTrendState(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_trend_file); + if(ProcessDone && g_ea.load_trend_flg) + { + int trend_state; + LoadTrendState(trend_state); + state.trend_state = trend_state; + Print("trend_state: ", state.trend_state); + g_ea.load_trend_flg = false; + g_ea.last_trend_update = TimeLocal(); + } + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void LoadTrendState(int &trend_state) + { + string filename = "trend_state.txt"; + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + string line = FileReadString(filehandle); + trend_state = (int)StringToInteger(line); + FileClose(filehandle); + } + else + { Print("Failed to open trend_state.txt"); trend_state = 0; } + } + else + { Print("trend_state.txt not found"); trend_state = 0; } + } + +//+------------------------------------------------------------------+ +//| ターゲット価格を取得する関数 +//+------------------------------------------------------------------+ +bool GetTargetPrices(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 + if(ProcessDone && g_ea.load_target_flg) // ←変更 + { + double target_prices[]; + LoadTargetPrices(target_prices); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // 先頭は res_chk + state.res_chk = (int)target_prices[0]; + + // ★ 1..4 の (en,tp,sl) を読む:合計12個 + for(int t=1; t<=4; t++) + { + int base = 1 + (t-1)*3; // 1,4,7,10 + state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); + state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); + state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); + } + + // ログ(任意) + Print("target_prices: res=", state.res_chk, + " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], + " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], + " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], + " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); + + g_ea.load_target_flg = false; // ←変更 + g_ea.last_target_update = TimeLocal(); // ←変更 + } + return true; + } +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| "target_prices.txt"を読み込む関数 +//+------------------------------------------------------------------+ +void LoadTargetPrices(double &target_prices[]) + { + ArrayResize(target_prices, TARGET_SIZE); + string filename = "target_prices.txt"; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + int i = 0; + while(!FileIsEnding(filehandle) && i < TARGET_SIZE) + { + string line = FileReadString(filehandle); + target_prices[i] = StringToDouble(line); + i++; + } + FileClose(filehandle); + } + else + { + Print("Failed to open file. Error code: ", GetLastError()); + for(int i = 0; i < TARGET_SIZE; i++) + { + target_prices[i] = DEFAULT_TARGET_PRICE; + } + } + } + else + { + Print("File does not exist: ", filename); + for(int i = 0; i < TARGET_SIZE; i++) + { + target_prices[i] = DEFAULT_TARGET_PRICE; + } + } + } + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +//+------------------------------------------------------------------+ +bool SendOrder(int orderType, double price, double tp, double sl) + { + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.magic = magic_number; + request.symbol = _Symbol; + request.volume = lot_size; + request.deviation = slippage; + request.type_filling = GetOrderFillingPolicy(_Symbol); + request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.comment = RequestComment(orderType); + + string orderTypeStr = ""; + +// 指値または逆指値注文 + request.action = TRADE_ACTION_PENDING; + + switch(orderType) + { + case 1: // 順張り買い (buy-stop) + request.type = ORDER_TYPE_BUY_STOP; + orderTypeStr = "buy-stop"; + break; + + case 2: // 逆張り買い (buy-limit) + request.type = ORDER_TYPE_BUY_LIMIT; + orderTypeStr = "buy-limit"; + break; + + case 3: // 順張り売り (sell-stop) + request.type = ORDER_TYPE_SELL_STOP; + orderTypeStr = "sell-stop"; + break; + + case 4: // 逆張り売り (sell-limit) + request.type = ORDER_TYPE_SELL_LIMIT; + orderTypeStr = "sell-limit"; + break; + + default: // 不正なorderType + Print(__FUNCTION__, ": invalid orderType=", orderType); + return false; + } + +// 注文送信 + if(!OrderSend(request, result)) + { + Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); + return false; + } + +// 注文成功確認 + if(result.retcode == TRADE_RETCODE_PLACED || + result.retcode == TRADE_RETCODE_DONE || + result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); + return true; + } + else + { + Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文の執行ポリシーを取得する関数 +//+------------------------------------------------------------------+ +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) + { + long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); + if((fill_mode & SYMBOL_FILLING_IOC) != 0) + return ORDER_FILLING_IOC; + if((fill_mode & SYMBOL_FILLING_FOK) != 0) + return ORDER_FILLING_FOK; + return ORDER_FILLING_RETURN; + } + +//+------------------------------------------------------------------+ +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +//+------------------------------------------------------------------+ +string RequestComment(int orderType) + { + datetime localTime = TimeLocal(); + string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); + +// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") + return IntegerToString(orderType) + " | PC Time: " + timeStr; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したエントリー注文をキャンセルする関数 +//+------------------------------------------------------------------+ +bool CancelExpiredOrders() + { + bool result = false; + +// 未決済注文の数を取得 + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(OrderSelect(ticket)) // 注文を選択 + { + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if(OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); + + int open_shift = GetBarShiftByTime(open_time, PERIOD_H1, -1); // H1 + if(open_shift < 0) + continue; // 時刻解決できないなら触らない + + if(open_shift >= ENTRY_H1_LIMIT) // H1本数(=1本 ≒ 1時間) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ + request.order = ticket; // 注文チケット + if(!OrderSend(request, trade_result)) // 削除リクエスト送信 + { + Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); + } + else + { + if(trade_result.retcode == TRADE_RETCODE_DONE) + { + Print("Order canceled successfully due to a time limit. Ticket: ", ticket); + result = true; // 削除成功フラグ + } + else + { + Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); + } + } + } + } + } + return result; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したポジションをクローズする関数 +//+------------------------------------------------------------------+ +bool CloseExpiredPositions() + { + bool result = false; + +// ポジションを逆順でループ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string position_symbol = PositionGetSymbol(i); + if(position_symbol!="") + { + // Magic Number とシンボルでフィルタリング + if(position_symbol != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) == magic_number) + { + // ポジションのエントリー時刻を取得 + datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + + int entry_shift = GetBarShiftByTime(entry_time, PERIOD_H1, -1); + if(entry_shift < 0) + continue; + + if(entry_shift >= CLOSE_H1_LIMIT) // H1本数(=12本 ≒ 12時間) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + + // ポジションタイプに応じてリクエストを設定 + int position_type = (int)PositionGetInteger(POSITION_TYPE); + request.action = TRADE_ACTION_DEAL; + request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 + request.symbol = position_symbol; // シンボル + request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ + request.price = (position_type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ + : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ + request.deviation = slippage; + request.type = (position_type == POSITION_TYPE_BUY) + ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ + : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ + request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー + request.magic = magic_number; + + // 注文送信 + if(!OrderSend(request, trade_result)) + { + Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); + continue; + } + + // 結果の確認 + if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print("Position closed successfully due to a time limit. Ticket: ", request.position); + result = true; // 少なくとも1つ成功した場合 + } + else + { + Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); + } + } + } + } + else + { + Print("Failed to select position at index ", i, ". Error: ", GetLastError()); + } + } + return result; + } + + +//+------------------------------------------------------------------+ +//| 指定された時間に最も近いバーのインデックスを返す関数 +//+------------------------------------------------------------------+ +int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) + { + if(time <= 0) + return default_shift; + int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する + if(shift < 0) + return default_shift; + return shift; + } +//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor.mq5 b/Experts/MyProject/HIT-EA_refactor.mq5 new file mode 100644 index 0000000..7c1daae --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor.mq5 @@ -0,0 +1,1496 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +#import "Shell32.dll" +int ShellExecuteW(int hwnd, string lpOperation, string lpFile, string lpParameters, string lpDirectory, int nShowCmd); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // ★追加:トレンドを格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +bool g_bars_H1_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdoneファイルを作成し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +void OnInit() +// プロセス完了ファイル("process_done_trend.txt" または "process_done_entry.txt")を作成 + { + CreateDoneFile(done_trend_file); + CreateDoneFile(done_entry_file); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| ティック情報を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 + * + * @param ctx 取得したティック情報を格納する構造体参照。 + * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 + */ +bool GetTickContext(TickContext &ctx) + { + MqlTick last_tick; + if(!SymbolInfoTick(_Symbol, last_tick)) + return false; + + ctx.ask = last_tick.ask; + ctx.bid = last_tick.bid; + ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); + ctx.spread = ctx.spread_points * Point(); + ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + return true; + } + +//+------------------------------------------------------------------+ +//| 期限切れ注文・期限切れポジションを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief 期限切れの未約定注文と保有ポジションを処理します。 + * + * 未約定注文はENTRY_H1_LIMIT、保有ポジションはCLOSE_H1_LIMITを基準に判定します。 + * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 + */ +void ManageExpiredTrades() + { + if(OrdersTotal() > 0) + CancelExpiredOrders(); + + if(PositionsTotal() > 0) + CloseExpiredPositions(); + } + +//+------------------------------------------------------------------+ +//| H4更新検知とトレンド判定Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド更新開始フラグを更新します。 + * + * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 + */ +void ProcessTrendUpdate(EAState &state) + { + int current_bars_H4 = iBars(NULL, PERIOD_H4); + if(g_pre_bars_H4 == 0) + g_pre_bars_H4 = current_bars_H4; + + int bars_H4_change = current_bars_H4 - g_pre_bars_H4; + bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); + + if(!trend_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Trend(state)) + { + g_init_trend_pending = false; + g_pre_bars_H4 = current_bars_H4; + } + } + +//+------------------------------------------------------------------+ +//| トレンド判定Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定Pythonの完了状態を確認します。 + * + * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsTrendResultReady() + { + return CheckDoneFile(done_trend_file); + } + +//+------------------------------------------------------------------+ +//| トレンド判定結果をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 + */ +void RefreshTrendState(EAState &state) + { + GetTrendState(state); + } + +//+------------------------------------------------------------------+ +//| H1更新検知とエントリー価格生成Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 + * + * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 + */ +void ProcessEntryUpdate(EAState &state) + { + int current_bars_H1 = iBars(NULL, PERIOD_H1); + if(g_pre_bars_H1 == 0) + g_pre_bars_H1 = current_bars_H1; + + int bars_H1_change = current_bars_H1 - g_pre_bars_H1; + bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); + + if(!entry_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Entry(state)) + { + g_init_entry_pending = false; + g_bars_H1_check = true; + state.chk_cnt = 0; + state.last_chk = 0; + g_pre_bars_H1 = current_bars_H1; + } + } + +//+------------------------------------------------------------------+ +//| チャートコメント文字列を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート左上に表示するステータスメッセージを組み立てます。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return Comment()へ渡す表示用文字列。 + */ +string BuildStatusMessage(TickContext &ctx) + { + string message = StringFormat( + " \nAsk: %." + IntegerToString(ctx.digits) + "f" + "\nBid: %." + IntegerToString(ctx.digits) + "f" + "\nSpread: %." + IntegerToString(ctx.digits) + "f" + "\nSpread Points: %.0f" + "\n\nLast Trend Update:\n %s" + "\n\nTrend State: %d" + "\n\nLast Target Update:\n %s" + "\n\nMy Used Count: %d / %d" + "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", + ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, + TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), + g_ea.trend_state, + TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), + CountMyUsed(), POSITION_LIMIT, + g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], + g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], + g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], + g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] + ); + return message; + } + +//+------------------------------------------------------------------+ +//| チャートコメントを更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート上のステータスコメントを更新します。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + */ +void UpdateStatusComment(TickContext &ctx) + { + Comment(BuildStatusMessage(ctx)); + } + +//+------------------------------------------------------------------+ +//| スプレッドが許容範囲内か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 + * + * @param ctx 現在のスプレッド情報。 + * @return 許容範囲内ならtrue、超過している場合はfalse。 + */ +bool IsSpreadAllowed(TickContext &ctx) + { + return (ctx.spread <= spread_limit * Point()); + } + +//+------------------------------------------------------------------+ +//| エントリー価格生成Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成Pythonの完了状態を確認します。 + * + * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsEntryResultReady() + { + return CheckDoneFile(done_entry_file); + } + +//+------------------------------------------------------------------+ +//| ターゲット価格をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 + */ +void RefreshTargetPrices(EAState &state) + { + GetTargetPrices(state); + } + +//+------------------------------------------------------------------+ +//| エントリー判定が必要な場合のみ注文処理を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 + * + * @param state EA全体の状態。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * + * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 + */ +void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) + { + if(!ShouldRunEntryDecision(state)) + return; + + g_bars_H1_check = false; + state.last_chk = TimeCurrent(); + + if(!ValidateEntryPreconditions(state)) + return; + + int sent_success = SendAllowedEntryOrders(state, ctx); + UpdateEntryRetryState(state, sent_success); + } + +//+------------------------------------------------------------------+ +//| エントリー判定を実行するタイミングか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定を実行するタイミングか確認します。 + * + * @param state EA全体の状態。前回判定時刻を参照します。 + * @return H1更新後、かつ前回判定から60秒以上経過していればtrue。 + */ +bool ShouldRunEntryDecision(EAState &state) + { + return (g_bars_H1_check && TimeCurrent() - state.last_chk >= 60); + } + +//+------------------------------------------------------------------+ +//| エントリー判定前の共通チェックを行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 新規注文前の共通条件を検証します。 + * + * @param state EA全体の状態。 + * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 + * + * `res_chk`、`trend_state`、対象EAの注文/ポジション数上限を確認します。 + */ +bool ValidateEntryPreconditions(EAState &state) + { + if(state.res_chk != 1) + { + Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state == 0) + { + Print("[Entry Skip] trend_state=0 (range). No entry orders are sent."); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state != 1 && state.trend_state != 2) + { + Print("[Entry Skip] invalid trend_state=", state.trend_state); + state.chk_cnt = 0; + return false; + } + + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| 許可された注文タイプだけを順番に送信する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド方向で許可された注文タイプだけを順番に送信します。 + * + * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return 送信成功した注文数。 + */ +int SendAllowedEntryOrders(EAState &state, TickContext &ctx) + { + int sent_success = 0; + + for(int t = 1; t <= 4; t++) + { + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); + break; + } + + if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) + { + Print("[Skip] orderType=", t, " not allowed by trend_state=", state.trend_state); + continue; + } + + if(TrySendEntryOrder(t, state, ctx)) + sent_success++; + } + + return sent_success; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ1件分の価格検証と注文送信を行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ1件分の価格検証と注文送信を行います。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid情報。 + * @return 注文送信に成功した場合はtrue、それ以外はfalse。 + */ +bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) + { + string entry_type = EntryTypeName(orderType); + double cur_price = CurrentPriceForOrderType(orderType, ctx); + + double en = state.en_price[orderType]; + double tp = state.tp_price[orderType]; + double sl = state.sl_price[orderType]; + + if(!HasValidTargetPrices(en, tp, sl)) + { + Print("[Skip] invalid target prices. orderType=", orderType, + " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); + if(!ok) + { + Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); + + if(SendOrder(orderType, en, tp, sl)) + { + Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); + return true; + } + + Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); + return false; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ番号に対応する表示名を返します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプの表示名。不正値の場合は"Unknown"。 + */ +string EntryTypeName(const int orderType) + { + switch(orderType) + { + case 1: + return "Buy Stop"; + case 2: + return "Buy Limit"; + case 3: + return "Sell Stop"; + case 4: + return "Sell Limit"; + default: + return "Unknown"; + } + } + +//+------------------------------------------------------------------+ +//| 注文タイプに応じた現在価格を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @return 買い系注文ではAsk、売り系注文ではBid。 + */ +double CurrentPriceForOrderType(const int orderType, TickContext &ctx) + { + if(orderType == 1 || orderType == 2) + return ctx.ask; + + return ctx.bid; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとの価格整合条件を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文タイプの価格条件を満たす場合はtrue。 + */ +bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + switch(orderType) + { + case 1: // Buy Stop + return (ctx.ask < en && tp > en && sl < en); + + case 2: // Buy Limit + return (ctx.ask > en && tp > en && sl < en); + + case 3: // Sell Stop + return (ctx.bid > en && tp < en && sl > en); + + case 4: // Sell Limit + return (ctx.bid < en && tp < en && sl > en); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文送信結果に応じてリトライ状態を更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 + * + * @param state EA全体の状態。`chk_cnt` を更新します。 + * @param sent_success 今回送信に成功した注文数。 + * + * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 + */ +void UpdateEntryRetryState(EAState &state, const int sent_success) + { + if(sent_success > 0) + { + state.chk_cnt = 0; + return; + } + + state.chk_cnt += 1; + Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/10"); + + if(state.chk_cnt < 10) + { + g_bars_H1_check = true; // 60秒後に再度エントリー判定を実行する + return; + } + + Print("[Retry End] reached max tries. reset chk_cnt."); + state.chk_cnt = 0; + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを作成します。 + * + * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 + */ +void CreateDoneFile(const string name) + { + if(!FileIsExist(name)) + { + int h = FileOpen(name, FILE_WRITE|FILE_TXT); + if(h != INVALID_HANDLE) + FileClose(h); + else + Print("Failed to create file: ", name, " err=", GetLastError()); + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを削除します。 + * + * @param name 削除するファイル名。存在しない場合は何もしません。 + */ +void DeleteDoneFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + + +//+------------------------------------------------------------------+ +//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルの存在有無を確認します。 + * + * @param name 確認するファイル名。 + * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 + */ +bool CheckDoneFile(const string name) + { + return FileIsExist(name); + } + +//+------------------------------------------------------------------+ +//| 最新のOHLCデータを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時間足のOHLCデータを取得します。 + * + * @param times 取得したバー時刻を格納する配列。 + * @param open_prices 取得したOpen価格を格納する配列。 + * @param high_prices 取得したHigh価格を格納する配列。 + * @param low_prices 取得したLow価格を格納する配列。 + * @param close_prices 取得したClose価格を格納する配列。 + * @param tf 取得対象の時間足。 + * @param bars_count 取得するバー本数。 + * @return 取得に成功した場合はtrue、失敗した場合はfalse。 + * + * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 + */ +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) + { + ArrayResize(times, 0); + ArrayResize(open_prices, 0); + ArrayResize(high_prices, 0); + ArrayResize(low_prices, 0); + ArrayResize(close_prices, 0); + + if(bars_count < 2) + return false; + +// OHLCデータを取得 +// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 +// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 + MqlRates rates[]; + int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); + if(copied <= 0) + { + Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + + ArrayResize(times, copied); + ArrayResize(open_prices, copied); + ArrayResize(high_prices, copied); + ArrayResize(low_prices, copied); + ArrayResize(close_prices, copied); + +// 必要なデータを配列に格納 + for(int i = 0; i < copied; i++) + { + times[i] = rates[i].time; + open_prices[i] = rates[i].open; + high_prices[i] = rates[i].high; + low_prices[i] = rates[i].low; + close_prices[i] = rates[i].close; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力する関数 +//+------------------------------------------------------------------+ +/** + * @brief OHLC配列をPython入力用CSVとして出力します。 + * + * @param filename 出力するCSVファイル名。 + * @param times バー時刻配列。 + * @param open_prices Open価格配列。 + * @param high_prices High価格配列。 + * @param low_prices Low価格配列。 + * @param close_prices Close価格配列。 + */ +void RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) + { +// 出力先ファイル名 +// string filename = "ohlc.csv"; + +// ファイルを "書き込みモード" でオープン (テキスト/ANSI) + int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); + +// ファイルが開けたかチェック + if(fileHandle == INVALID_HANDLE) + { + Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); + return; + } + +// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) + int size = ArraySize(times); + +// ヘッダー行を追加 + FileWrite(fileHandle, "Time,Open,High,Low,Close"); + +// 小数点の桁数を取得 + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + +// フォーマット文字列を動的に生成 + string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); + +// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み + for(int i = 0; i < size; i++) + { + // 時間をフォーマット + string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); + + // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) + string line = StringFormat(formatString, + timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); + + // ファイルに書き込み (改行付き) + FileWrite(fileHandle, line); + } + +// 書き込み終了後、ファイルを閉じる + FileClose(fileHandle); + } + +//+------------------------------------------------------------------+ +//| バッチファイル(Pythonスクリプト)を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_trend.txt`を削除し、ShellExecuteW失敗時はdoneファイルを戻します。 + */ +bool ExecuteBatchTrend() + { + if(!CheckDoneFile(done_trend_file)) + { + Print("Trend batch is still running. Skip execute."); + return false; + } + + DeleteDoneFile(done_trend_file); + int ret = ShellExecuteW(0, "open", get_trend_reply_bat, "", "", 1); + if(ret <= 32) + { + Print("ShellExecuteW trend failed. ret=", ret, " file=", get_trend_reply_bat); + CreateDoneFile(done_trend_file); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_entry.txt`を削除し、ShellExecuteW失敗時はdoneファイルを戻します。 + */ +bool ExecuteBatchEntry() + { + if(!CheckDoneFile(done_entry_file)) + { + Print("Entry batch is still running. Skip execute."); + return false; + } + + DeleteDoneFile(done_entry_file); + int ret = ShellExecuteW(0, "open", get_entry_reply_bat, "", "", 1); + if(ret <= 32) + { + Print("ShellExecuteW entry failed. ret=", ret, " file=", get_entry_reply_bat); + CreateDoneFile(done_entry_file); + return false; + } + + return true; + } + + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力後、バッチファイルの実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Trend(EAState &state) + { + if(!CheckDoneFile(done_trend_file)) + { + Print("Trend Python is still running. Skip H4 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H4, HISTORY_BARS)) + { Print("GetLatestOHLC(H4) failed."); return false; } + + RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices); + + if(!ExecuteBatchTrend()) + return false; + + state.load_trend_flg = true; + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Entry(EAState &state) + { + if(!CheckDoneFile(done_entry_file)) + { + Print("Entry Python is still running. Skip H1 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H1, HISTORY_BARS)) + { Print("GetLatestOHLC(H1) failed."); return false; } + + RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices); + + if(!ExecuteBatchEntry()) + return false; + + state.load_target_flg = true; + return true; + } + + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`trend_state` と更新フラグを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTrendState(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_trend_file); + if(ProcessDone && g_ea.load_trend_flg) + { + int trend_state; + LoadTrendState(trend_state); + state.trend_state = trend_state; + Print("trend_state: ", state.trend_state); + g_ea.load_trend_flg = false; + g_ea.last_trend_update = TimeLocal(); + } + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief `trend_state.txt` からH4トレンド判定値を読み込みます。 + * + * @param trend_state 読み込んだ値を格納する参照。0=RANGE、1=UP、2=DOWN。 + * + * ファイル未存在、読込失敗、異常値の場合は安全側として0を設定します。 + */ +void LoadTrendState(int &trend_state) + { + string filename = "trend_state.txt"; + trend_state = 0; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + string line = FileReadString(filehandle); + int value = (int)StringToInteger(line); + if(value == 0 || value == 1 || value == 2) + trend_state = value; + else + Print("Invalid trend_state value: ", line, ". Use 0."); + FileClose(filehandle); + } + else + { Print("Failed to open trend_state.txt"); trend_state = 0; } + } + else + { Print("trend_state.txt not found"); trend_state = 0; } + } + +//+------------------------------------------------------------------+ +//| ターゲット価格を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTargetPrices(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 + if(ProcessDone && g_ea.load_target_flg) // ←変更 + { + double target_prices[]; + LoadTargetPrices(target_prices); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // 先頭は res_chk + state.res_chk = (int)target_prices[0]; + + // ★ 1..4 の (en,tp,sl) を読む:合計12個 + for(int t=1; t<=4; t++) + { + int base = 1 + (t-1)*3; // 1,4,7,10 + state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); + state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); + state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); + } + + // ログ(任意) + Print("target_prices: res=", state.res_chk, + " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], + " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], + " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], + " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); + + g_ea.load_target_flg = false; // ←変更 + g_ea.last_target_update = TimeLocal(); // ←変更 + } + return true; + } +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| "target_prices.txt"を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief `target_prices.txt` から13個の数値を読み込みます。 + * + * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 + * + * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 + */ +void LoadTargetPrices(double &target_prices[]) + { + ArrayResize(target_prices, TARGET_SIZE); + for(int i = 0; i < TARGET_SIZE; i++) + target_prices[i] = DEFAULT_TARGET_PRICE; + + string filename = "target_prices.txt"; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + int i = 0; + while(!FileIsEnding(filehandle) && i < TARGET_SIZE) + { + string line = FileReadString(filehandle); + target_prices[i] = StringToDouble(line); + i++; + } + FileClose(filehandle); + + if(i < TARGET_SIZE) + { + Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); + target_prices[0] = 0; // 不完全なファイルは無効扱い + } + } + else + { + Print("Failed to open file. Error code: ", GetLastError()); + } + } + else + { + Print("File does not exist: ", filename); + } + } + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +//+------------------------------------------------------------------+ +/** + * @brief 指定された注文タイプでペンディング注文を送信します。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param price エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 + */ +bool SendOrder(int orderType, double price, double tp, double sl) + { + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.magic = magic_number; + request.symbol = _Symbol; + request.volume = lot_size; + request.deviation = slippage; + request.type_filling = GetOrderFillingPolicy(_Symbol); + request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.comment = RequestComment(orderType); + + string orderTypeStr = ""; + +// 指値または逆指値注文 + request.action = TRADE_ACTION_PENDING; + + switch(orderType) + { + case 1: // 順張り買い (buy-stop) + request.type = ORDER_TYPE_BUY_STOP; + orderTypeStr = "buy-stop"; + break; + + case 2: // 逆張り買い (buy-limit) + request.type = ORDER_TYPE_BUY_LIMIT; + orderTypeStr = "buy-limit"; + break; + + case 3: // 順張り売り (sell-stop) + request.type = ORDER_TYPE_SELL_STOP; + orderTypeStr = "sell-stop"; + break; + + case 4: // 逆張り売り (sell-limit) + request.type = ORDER_TYPE_SELL_LIMIT; + orderTypeStr = "sell-limit"; + break; + + default: // 不正なorderType + Print(__FUNCTION__, ": invalid orderType=", orderType); + return false; + } + +// 注文送信 + if(!OrderSend(request, result)) + { + Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); + return false; + } + +// 注文成功確認 + if(result.retcode == TRADE_RETCODE_PLACED || + result.retcode == TRADE_RETCODE_DONE || + result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); + return true; + } + else + { + Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文の執行ポリシーを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief シンボルに対応する注文執行ポリシーを取得します。 + * + * @param symbol 対象シンボル。 + * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 + */ +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) + { + long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); + if((fill_mode & SYMBOL_FILLING_IOC) != 0) + return ORDER_FILLING_IOC; + if((fill_mode & SYMBOL_FILLING_FOK) != 0) + return ORDER_FILLING_FOK; + return ORDER_FILLING_RETURN; + } + +//+------------------------------------------------------------------+ +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +//+------------------------------------------------------------------+ +/** + * @brief 注文コメント文字列を生成します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプとPCローカル時刻を含むコメント文字列。 + */ +string RequestComment(int orderType) + { + datetime localTime = TimeLocal(); + string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); + +// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") + return IntegerToString(orderType) + " | PC Time: " + timeStr; + } + +//+------------------------------------------------------------------+ +//| H4トレンド方向に対して許可された注文タイプか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド方向に対して指定注文タイプが許可されるか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param trend_state H4トレンド判定。0=RANGE、1=UP、2=DOWN。 + * @return 許可される注文タイプならtrue。レンジまたは異常値ではfalse。 + */ +bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) + { + if(trend_state == 1) + return (orderType == 1 || orderType == 2); // UP: Buy Stop / Buy Limit のみ + + if(trend_state == 2) + return (orderType == 3 || orderType == 4); // DOWN: Sell Stop / Sell Limit のみ + + return false; // RANGEまたは異常値は新規注文しない + } + +//+------------------------------------------------------------------+ +//| target_prices.txt の価格が有効か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 + * + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return すべて0より大きい場合はtrue。 + */ +bool HasValidTargetPrices(const double en, const double tp, const double sl) + { + return (en > 0.0 && tp > 0.0 && sl > 0.0); + } + +//+------------------------------------------------------------------+ +//| 自EAの未約定注文数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする未約定注文数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致する未約定注文数。 + */ +int CountMyPendingOrders() + { + int count = 0; + + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(ticket == 0) + continue; + + if(!OrderSelect(ticket)) + continue; + + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + + if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAのポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする保有ポジション数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致するポジション数。 + */ +int CountMyPositions() + { + int count = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string symbol = PositionGetSymbol(i); + if(symbol == "") + continue; + + if(symbol != _Symbol) + continue; + + if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAの注文+ポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが使用中の注文数とポジション数の合計を返します。 + * + * @return 未約定注文数 + 保有ポジション数。 + */ +int CountMyUsed() + { + return CountMyPendingOrders() + CountMyPositions(); + } + + +//+------------------------------------------------------------------+ +//| 時間が経過したエントリー注文をキャンセルする関数 +//+------------------------------------------------------------------+ +/** + * @brief H1経過本数が上限を超えた未約定注文をキャンセルします。 + * + * @return 1件以上キャンセルに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 + */ +bool CancelExpiredOrders() + { + bool result = false; + +// 未決済注文の数を取得 + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(OrderSelect(ticket)) // 注文を選択 + { + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if(OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); + + int open_shift = GetBarShiftByTime(open_time, PERIOD_H1, -1); // H1 + if(open_shift < 0) + continue; // 時刻解決できないなら触らない + + if(open_shift >= ENTRY_H1_LIMIT) // H1本数(=1本 ≒ 1時間) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ + request.order = ticket; // 注文チケット + if(!OrderSend(request, trade_result)) // 削除リクエスト送信 + { + Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); + } + else + { + if(trade_result.retcode == TRADE_RETCODE_DONE) + { + Print("Order canceled successfully due to a time limit. Ticket: ", ticket); + result = true; // 削除成功フラグ + } + else + { + Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); + } + } + } + } + } + return result; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したポジションをクローズする関数 +//+------------------------------------------------------------------+ +/** + * @brief H1経過本数が上限を超えた保有ポジションをクローズします。 + * + * @return 1件以上クローズに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 + */ +bool CloseExpiredPositions() + { + bool result = false; + +// ポジションを逆順でループ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string position_symbol = PositionGetSymbol(i); + if(position_symbol!="") + { + // Magic Number とシンボルでフィルタリング + if(position_symbol != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) == magic_number) + { + // ポジションのエントリー時刻を取得 + datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + + int entry_shift = GetBarShiftByTime(entry_time, PERIOD_H1, -1); + if(entry_shift < 0) + continue; + + if(entry_shift >= CLOSE_H1_LIMIT) // H1本数(=12本 ≒ 12時間) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + + // ポジションタイプに応じてリクエストを設定 + int position_type = (int)PositionGetInteger(POSITION_TYPE); + request.action = TRADE_ACTION_DEAL; + request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 + request.symbol = position_symbol; // シンボル + request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ + request.price = (position_type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ + : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ + request.deviation = slippage; + request.type = (position_type == POSITION_TYPE_BUY) + ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ + : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ + request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー + request.magic = magic_number; + + // 注文送信 + if(!OrderSend(request, trade_result)) + { + Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); + continue; + } + + // 結果の確認 + if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print("Position closed successfully due to a time limit. Ticket: ", request.position); + result = true; // 少なくとも1つ成功した場合 + } + else + { + Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); + } + } + } + } + else + { + Print("Failed to select position at index ", i, ". Error: ", GetLastError()); + } + } + return result; + } + + +//+------------------------------------------------------------------+ +//| 指定された時間に最も近いバーのインデックスを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時刻に最も近いバーシフトを取得します。 + * + * @param time 判定対象の時刻。 + * @param tf 判定対象の時間足。 + * @param default_shift 取得失敗時に返す値。 + * @return 取得できたバーシフト。失敗時はdefault_shift。 + * + * `iBarShift(..., false)` を使い、厳密一致ではなく近傍バーで解決します。 + */ +int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) + { + if(time <= 0) + return default_shift; + int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する + if(shift < 0) + return default_shift; + return shift; + } +//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor_ver2.mq5 b/Experts/MyProject/HIT-EA_refactor_ver2.mq5 new file mode 100644 index 0000000..00430eb --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver2.mq5 @@ -0,0 +1,2122 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // ★追加:トレンドを格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +bool g_bars_H1_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +void OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| ティック情報を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 + * + * @param ctx 取得したティック情報を格納する構造体参照。 + * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 + */ +bool GetTickContext(TickContext &ctx) + { + MqlTick last_tick; + if(!SymbolInfoTick(_Symbol, last_tick)) + return false; + + ctx.ask = last_tick.ask; + ctx.bid = last_tick.bid; + ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); + ctx.spread = ctx.spread_points * Point(); + ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + return true; + } + +//+------------------------------------------------------------------+ +//| 期限切れ注文・期限切れポジションを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief 期限切れの未約定注文と保有ポジションを処理します。 + * + * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 + * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 + */ +void ManageExpiredTrades() + { + if(OrdersTotal() > 0) + CancelExpiredOrders(); + + if(PositionsTotal() > 0) + CloseExpiredPositions(); + } + +//+------------------------------------------------------------------+ +//| H4更新検知とトレンド判定Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド更新開始フラグを更新します。 + * + * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 + */ +void ProcessTrendUpdate(EAState &state) + { + int current_bars_H4 = iBars(NULL, PERIOD_H4); + if(g_pre_bars_H4 == 0) + g_pre_bars_H4 = current_bars_H4; + + int bars_H4_change = current_bars_H4 - g_pre_bars_H4; + bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); + + if(!trend_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Trend(state)) + { + g_init_trend_pending = false; + g_pre_bars_H4 = current_bars_H4; + } + } + +//+------------------------------------------------------------------+ +//| トレンド判定Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定Pythonの完了状態を確認します。 + * + * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsTrendResultReady() + { + return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| トレンド判定結果をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 + */ +void RefreshTrendState(EAState &state) + { + GetTrendState(state); + } + +//+------------------------------------------------------------------+ +//| H1更新検知とエントリー価格生成Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 + * + * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 + */ +void ProcessEntryUpdate(EAState &state) + { + int current_bars_H1 = iBars(NULL, PERIOD_H1); + if(g_pre_bars_H1 == 0) + g_pre_bars_H1 = current_bars_H1; + + int bars_H1_change = current_bars_H1 - g_pre_bars_H1; + bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); + + if(!entry_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Entry(state)) + { + g_init_entry_pending = false; + g_bars_H1_check = true; + state.chk_cnt = 0; + state.last_chk = 0; + g_pre_bars_H1 = current_bars_H1; + } + } + +//+------------------------------------------------------------------+ +//| チャートコメント文字列を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート左上に表示するステータスメッセージを組み立てます。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return Comment()へ渡す表示用文字列。 + */ +string BuildStatusMessage(TickContext &ctx) + { + string message = StringFormat( + " \nAsk: %." + IntegerToString(ctx.digits) + "f" + "\nBid: %." + IntegerToString(ctx.digits) + "f" + "\nSpread: %." + IntegerToString(ctx.digits) + "f" + "\nSpread Points: %.0f" + "\n\nLast Trend Update:\n %s" + "\n\nTrend State: %d" + "\n\nLast Target Update:\n %s" + "\n\nMy Used Count: %d / %d" + "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", + ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, + TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), + g_ea.trend_state, + TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), + CountMyUsed(), POSITION_LIMIT, + g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], + g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], + g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], + g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] + ); + return message; + } + +//+------------------------------------------------------------------+ +//| チャートコメントを更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート上のステータスコメントを更新します。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + */ +void UpdateStatusComment(TickContext &ctx) + { + Comment(BuildStatusMessage(ctx)); + } + +//+------------------------------------------------------------------+ +//| スプレッドが許容範囲内か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 + * + * @param ctx 現在のスプレッド情報。 + * @return 許容範囲内ならtrue、超過している場合はfalse。 + */ +bool IsSpreadAllowed(TickContext &ctx) + { + return (ctx.spread <= spread_limit * Point()); + } + +//+------------------------------------------------------------------+ +//| エントリー価格生成Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成Pythonの完了状態を確認します。 + * + * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsEntryResultReady() + { + return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ターゲット価格をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 + */ +void RefreshTargetPrices(EAState &state) + { + GetTargetPrices(state); + } + +//+------------------------------------------------------------------+ +//| エントリー判定が必要な場合のみ注文処理を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 + * + * @param state EA全体の状態。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * + * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 + */ +void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) + { + if(!ShouldRunEntryDecision(state)) + return; + + g_bars_H1_check = false; + state.last_chk = TimeCurrent(); + + if(!ValidateEntryPreconditions(state)) + return; + + int sent_success = SendAllowedEntryOrders(state, ctx); + UpdateEntryRetryState(state, sent_success); + } + +//+------------------------------------------------------------------+ +//| エントリー判定を実行するタイミングか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定を実行するタイミングか確認します。 + * + * @param state EA全体の状態。前回判定時刻を参照します。 + * @return H1更新後、かつ前回判定から60秒以上経過していればtrue。 + */ +bool ShouldRunEntryDecision(EAState &state) + { + return (g_bars_H1_check && TimeCurrent() - state.last_chk >= 60); + } + +//+------------------------------------------------------------------+ +//| エントリー判定前の共通チェックを行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 新規注文前の共通条件を検証します。 + * + * @param state EA全体の状態。 + * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 + * + * `res_chk`、`trend_state`、対象EAの注文/ポジション数上限を確認します。 + */ +bool ValidateEntryPreconditions(EAState &state) + { + if(state.res_chk != 1) + { + Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state == 0) + { + Print("[Entry Skip] trend_state=0 (range). No entry orders are sent."); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state != 1 && state.trend_state != 2) + { + Print("[Entry Skip] invalid trend_state=", state.trend_state); + state.chk_cnt = 0; + return false; + } + + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| 許可された注文タイプだけを順番に送信する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド方向で許可された注文タイプだけを順番に送信します。 + * + * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return 送信成功した注文数。 + */ +int SendAllowedEntryOrders(EAState &state, TickContext &ctx) + { + int sent_success = 0; + + for(int t = 1; t <= 4; t++) + { + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); + break; + } + + if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) + { + Print("[Skip] orderType=", t, " not allowed by trend_state=", state.trend_state); + continue; + } + + if(TrySendEntryOrder(t, state, ctx)) + sent_success++; + } + + return sent_success; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ1件分の価格検証と注文送信を行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ1件分の価格検証と注文送信を行います。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid情報。 + * @return 注文送信に成功した場合はtrue、それ以外はfalse。 + */ +bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) + { + string entry_type = EntryTypeName(orderType); + double cur_price = CurrentPriceForOrderType(orderType, ctx); + + double en = state.en_price[orderType]; + double tp = state.tp_price[orderType]; + double sl = state.sl_price[orderType]; + + if(!HasValidTargetPrices(en, tp, sl)) + { + Print("[Skip] invalid target prices. orderType=", orderType, + " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); + if(!ok) + { + Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) + return false; + + Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); + + if(SendOrder(orderType, en, tp, sl)) + { + Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); + return true; + } + + Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); + return false; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ番号に対応する表示名を返します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプの表示名。不正値の場合は"Unknown"。 + */ +string EntryTypeName(const int orderType) + { + switch(orderType) + { + case 1: + return "Buy Stop"; + case 2: + return "Buy Limit"; + case 3: + return "Sell Stop"; + case 4: + return "Sell Limit"; + default: + return "Unknown"; + } + } + +//+------------------------------------------------------------------+ +//| 注文タイプに応じた現在価格を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @return 買い系注文ではAsk、売り系注文ではBid。 + */ +double CurrentPriceForOrderType(const int orderType, TickContext &ctx) + { + if(orderType == 1 || orderType == 2) + return ctx.ask; + + return ctx.bid; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとの価格整合条件を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文タイプの価格条件を満たす場合はtrue。 + */ +bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + switch(orderType) + { + case 1: // Buy Stop + return (ctx.ask < en && tp > en && sl < en); + + case 2: // Buy Limit + return (ctx.ask > en && tp > en && sl < en); + + case 3: // Sell Stop + return (ctx.bid > en && tp < en && sl > en); + + case 4: // Sell Limit + return (ctx.bid < en && tp < en && sl > en); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| brokerの最小距離制約を満たすか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 最小距離を満たす場合はtrue。 + */ +bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); + int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); + int min_level = stop_level; + if(freeze_level > min_level) + min_level = freeze_level; + double min_distance = min_level * Point(); + + if(min_distance <= 0.0) + return true; + + double cur_price = CurrentPriceForOrderType(orderType, ctx); + string entry_type = EntryTypeName(orderType); + + if(MathAbs(en - cur_price) < min_distance) + { + Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, + " en=", en, " min_distance=", min_distance); + return false; + } + + if(MathAbs(tp - en) < min_distance) + { + Print("[Skip] ", entry_type, " TP is too close. en=", en, + " tp=", tp, " min_distance=", min_distance); + return false; + } + + if(MathAbs(en - sl) < min_distance) + { + Print("[Skip] ", entry_type, " SL is too close. en=", en, + " sl=", sl, " min_distance=", min_distance); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| 注文送信結果に応じてリトライ状態を更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 + * + * @param state EA全体の状態。`chk_cnt` を更新します。 + * @param sent_success 今回送信に成功した注文数。 + * + * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 + */ +void UpdateEntryRetryState(EAState &state, const int sent_success) + { + if(sent_success > 0) + { + state.chk_cnt = 0; + return; + } + + state.chk_cnt += 1; + Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/10"); + + if(state.chk_cnt < 10) + { + g_bars_H1_check = true; // 60秒後に再度エントリー判定を実行する + return; + } + + Print("[Retry End] reached max tries. reset chk_cnt."); + state.chk_cnt = 0; + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを作成します。 + * + * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 + */ +void CreateDoneFile(const string name) + { + if(!FileIsExist(name)) + { + int h = FileOpen(name, FILE_WRITE|FILE_TXT); + if(h != INVALID_HANDLE) + FileClose(h); + else + Print("Failed to create file: ", name, " err=", GetLastError()); + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを削除します。 + * + * @param name 削除するファイル名。存在しない場合は何もしません。 + */ +void DeleteDoneFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + + +//+------------------------------------------------------------------+ +//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルの存在有無を確認します。 + * + * @param name 確認するファイル名。 + * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 + */ +bool CheckDoneFile(const string name) + { + return FileIsExist(name); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief Python起動時刻を実行中ファイルへ記録します。 + * + * @param name 作成するrunningファイル名。 + */ +void CreateRunningFile(const string name, const uint process_id) + { + int h = FileOpen(name, FILE_WRITE | FILE_TXT); + if(h != INVALID_HANDLE) + { + FileWrite(h, IntegerToString((long)TimeCurrent())); + FileWrite(h, IntegerToString((long)process_id)); + FileClose(h); + } + else + Print("Failed to create running file: ", name, " err=", GetLastError()); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したrunningファイルを削除します。 + * + * @param name 削除するrunningファイル名。 + */ +void DeleteRunningFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + +//+------------------------------------------------------------------+ +//| Python実行開始時刻を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたPython起動時刻を読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めた起動時刻。失敗時は0。 + */ +datetime LoadRunningStartedAt(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (datetime)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行プロセスIDを読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたプロセスIDを読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めたプロセスID。旧形式または失敗時は0。 + */ +uint LoadRunningProcessId(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + FileReadString(h); // started_at + if(FileIsEnding(h)) + { + FileClose(h); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (uint)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 + * + * @param name 判定するrunningファイル名。 + * @return タイムアウトしている場合はtrue。 + */ +bool IsRunningFileTimedOut(const string name) + { + datetime started_at = LoadRunningStartedAt(name); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| 外部プロセス状態を初期値に戻す関数 +//+------------------------------------------------------------------+ +void ResetExternalProcessState(ExternalProcessState &process) + { + if(process.handle != 0) + CloseHandle(process.handle); + + process.handle = 0; + process.process_id = 0; + process.active = false; + process.exit_code_ready = false; + process.exit_code = 0; + process.started_at = 0; + } + +//+------------------------------------------------------------------+ +//| runningファイルのPIDからプロセスハンドルを復元する関数 +//+------------------------------------------------------------------+ +bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) + { + if(process.active && process.handle != 0) + return true; + + uint process_id = LoadRunningProcessId(running_file); + if(process_id == 0) + return false; + + long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); + if(handle == 0) + { + Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); + return false; + } + + process.handle = handle; + process.process_id = process_id; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = LoadRunningStartedAt(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスの終了状態を更新する関数 +//+------------------------------------------------------------------+ +bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) + { + if(!process.active || process.handle == 0) + return true; + + int wait_result = WaitForSingleObject(process.handle, 0); + if(wait_result == WAIT_TIMEOUT) + return false; + + uint exit_code = 1; + if(wait_result == WAIT_OBJECT_0) + { + if(GetExitCodeProcess(process.handle, exit_code) == 0) + { + Print("[", label, "] failed to get process exit code. err=", GetLastError()); + exit_code = 1; + } + } + else + { + Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); + exit_code = 1; + } + + CloseHandle(process.handle); + process.handle = 0; + process.active = false; + process.exit_code_ready = true; + process.exit_code = exit_code; + Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) + { + datetime started_at = process.started_at; + if(started_at <= 0) + started_at = LoadRunningStartedAt(running_file); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| バッチファイルをプロセスハンドル付きで起動する関数 +//+------------------------------------------------------------------+ +bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) + { + STARTUPINFO_W startup_info = {}; + PROCESS_INFORMATION process_info = {}; + + startup_info.cb = (uint)sizeof(startup_info); + + string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; + string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; + + int created = CreateProcessW( + cmd_exe, + command_line, + 0, + 0, + 0, + CREATE_NO_WINDOW, + 0, + "C:\\ea_py", + startup_info, + process_info + ); + + if(created == 0 || process_info.hProcess == 0) + { + Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); + ResetExternalProcessState(process); + DeleteRunningFile(running_file); + return false; + } + + if(process_info.hThread != 0) + CloseHandle(process_info.hThread); + + process.handle = process_info.hProcess; + process.process_id = process_info.dwProcessId; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = TimeCurrent(); + + CreateRunningFile(running_file, process.process_id); + Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); + return true; + } + +//+------------------------------------------------------------------+ +//| EA起動時のdoneファイル状態を整える関数 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + */ +void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return; + } + + if(FileIsExist(running_file)) + { + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running file found on init. Remove marker without creating done."); + DeleteRunningFile(running_file); + } + else + Print("[", label, "] Python seems to be running on init. Keep waiting."); + return; + } + + Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); + } + +//+------------------------------------------------------------------+ +//| Python完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @return 完了済みの場合はtrue。 + */ +bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + return false; + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] process failed. exit_code=", process.exit_code); + return false; + } + } + else if(!CheckDoneFile(done_file)) + { + return false; + } + } + + if(!CheckDoneFile(done_file)) + return false; + + if(!FileIsExist(result_file)) + { + Print("[", label, "] done exists but result file is missing: ", result_file); + return false; + } + + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| Python開始可能状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return 起動可能な場合はtrue。 + */ +bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); + else + Print("[", label, "] Python is still running. Skip execute."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); + } + else + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running marker without live process. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); + return false; + } + } + + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] done file missing without running marker. Start new process."); + return true; + } + +//+------------------------------------------------------------------+ +//| タイムアウトしたPython待ちを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 + */ +void RecoverTimedOutPythonProcesses() + { + if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + g_ea.load_trend_flg = false; + g_init_trend_pending = true; + } + + if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + g_ea.load_target_flg = false; + g_bars_H1_check = false; + g_ea.chk_cnt = 0; + g_init_entry_pending = true; + } + } + +//+------------------------------------------------------------------+ +//| 個別Python処理のタイムアウトを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return タイムアウト復旧を行った場合はtrue。 + */ +bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(CheckDoneFile(done_file) && !process.active) + { + DeleteRunningFile(running_file); + return false; + } + + if(!process.active && !FileIsExist(running_file)) + return false; + + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, + " seconds, but the process is still running. Keep waiting."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] Python process failed. Retry on next trigger."); + return true; + } + + if(!CheckDoneFile(done_file)) + { + Print("[", label, "] Python process ended without done file. Retry on next trigger."); + return true; + } + + return false; + } + + if(!IsRunningFileTimedOut(running_file)) + return false; + + Print("[", label, "] Python did not finish within ", + PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 最新のOHLCデータを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時間足のOHLCデータを取得します。 + * + * @param times 取得したバー時刻を格納する配列。 + * @param open_prices 取得したOpen価格を格納する配列。 + * @param high_prices 取得したHigh価格を格納する配列。 + * @param low_prices 取得したLow価格を格納する配列。 + * @param close_prices 取得したClose価格を格納する配列。 + * @param tf 取得対象の時間足。 + * @param bars_count 取得するバー本数。 + * @return 取得に成功した場合はtrue、失敗した場合はfalse。 + * + * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 + */ +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) + { + ArrayResize(times, 0); + ArrayResize(open_prices, 0); + ArrayResize(high_prices, 0); + ArrayResize(low_prices, 0); + ArrayResize(close_prices, 0); + + if(bars_count < 2) + return false; + +// OHLCデータを取得 +// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 +// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 + MqlRates rates[]; + int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); + if(copied <= 0) + { + Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + + ArrayResize(times, copied); + ArrayResize(open_prices, copied); + ArrayResize(high_prices, copied); + ArrayResize(low_prices, copied); + ArrayResize(close_prices, copied); + +// 必要なデータを配列に格納 + for(int i = 0; i < copied; i++) + { + times[i] = rates[i].time; + open_prices[i] = rates[i].open; + high_prices[i] = rates[i].high; + low_prices[i] = rates[i].low; + close_prices[i] = rates[i].close; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力する関数 +//+------------------------------------------------------------------+ +/** + * @brief OHLC配列をPython入力用CSVとして出力します。 + * + * @param filename 出力するCSVファイル名。 + * @param times バー時刻配列。 + * @param open_prices Open価格配列。 + * @param high_prices High価格配列。 + * @param low_prices Low価格配列。 + * @param close_prices Close価格配列。 + * @return CSV出力に成功した場合はtrue。 + */ +bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) + { +// 出力先ファイル名 +// string filename = "ohlc.csv"; + +// ファイルを "書き込みモード" でオープン (テキスト/ANSI) + int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); + +// ファイルが開けたかチェック + if(fileHandle == INVALID_HANDLE) + { + Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); + return false; + } + +// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) + int size = ArraySize(times); + +// ヘッダー行を追加 + FileWrite(fileHandle, "Time,Open,High,Low,Close"); + +// 小数点の桁数を取得 + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + +// フォーマット文字列を動的に生成 + string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); + +// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み + for(int i = 0; i < size; i++) + { + // 時間をフォーマット + string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); + + // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) + string line = StringFormat(formatString, + timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); + + // ファイルに書き込み (改行付き) + FileWrite(fileHandle, line); + } + +// 書き込み終了後、ファイルを閉じる + FileClose(fileHandle); + return true; + } + +//+------------------------------------------------------------------+ +//| バッチファイル(Pythonスクリプト)を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchTrend() + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + return false; + + DeleteDoneFile(done_trend_file); + return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchEntry() + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + return false; + + DeleteDoneFile(done_entry_file); + return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); + } + + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力後、バッチファイルの実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Trend(EAState &state) + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + Print("Trend Python is still running. Skip H4 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H4, HISTORY_BARS)) + { Print("GetLatestOHLC(H4) failed."); return false; } + + if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H4) failed."); return false; } + + if(!ExecuteBatchTrend()) + return false; + + state.load_trend_flg = true; + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Entry(EAState &state) + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + Print("Entry Python is still running. Skip H1 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H1, HISTORY_BARS)) + { Print("GetLatestOHLC(H1) failed."); return false; } + + if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H1) failed."); return false; } + + if(!ExecuteBatchEntry()) + return false; + + state.load_target_flg = true; + return true; + } + + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`trend_state` と更新フラグを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTrendState(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_trend_file); + if(ProcessDone && g_ea.load_trend_flg) + { + int trend_state; + LoadTrendState(trend_state); + state.trend_state = trend_state; + Print("trend_state: ", state.trend_state); + g_ea.load_trend_flg = false; + g_ea.last_trend_update = TimeLocal(); + } + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief `trend_state.txt` からH4トレンド判定値を読み込みます。 + * + * @param trend_state 読み込んだ値を格納する参照。0=RANGE、1=UP、2=DOWN。 + * + * ファイル未存在、読込失敗、異常値の場合は安全側として0を設定します。 + */ +void LoadTrendState(int &trend_state) + { + string filename = trend_state_file; + trend_state = 0; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + string line = FileReadString(filehandle); + int value = (int)StringToInteger(line); + if(value == 0 || value == 1 || value == 2) + trend_state = value; + else + Print("Invalid trend_state value: ", line, ". Use 0."); + FileClose(filehandle); + } + else + { Print("Failed to open trend_state.txt"); trend_state = 0; } + } + else + { Print("trend_state.txt not found"); trend_state = 0; } + } + +//+------------------------------------------------------------------+ +//| ターゲット価格を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTargetPrices(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 + if(ProcessDone && g_ea.load_target_flg) // ←変更 + { + double target_prices[]; + LoadTargetPrices(target_prices); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // 先頭は res_chk + state.res_chk = (int)target_prices[0]; + + // ★ 1..4 の (en,tp,sl) を読む:合計12個 + for(int t=1; t<=4; t++) + { + int base = 1 + (t-1)*3; // 1,4,7,10 + state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); + state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); + state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); + } + + // ログ(任意) + Print("target_prices: res=", state.res_chk, + " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], + " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], + " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], + " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); + + g_ea.load_target_flg = false; // ←変更 + g_ea.last_target_update = TimeLocal(); // ←変更 + } + return true; + } +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| "target_prices.txt"を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief `target_prices.txt` から13個の数値を読み込みます。 + * + * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 + * + * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 + */ +void LoadTargetPrices(double &target_prices[]) + { + ArrayResize(target_prices, TARGET_SIZE); + for(int i = 0; i < TARGET_SIZE; i++) + target_prices[i] = DEFAULT_TARGET_PRICE; + + string filename = target_prices_file; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + int i = 0; + while(!FileIsEnding(filehandle) && i < TARGET_SIZE) + { + string line = FileReadString(filehandle); + target_prices[i] = StringToDouble(line); + i++; + } + FileClose(filehandle); + + if(i < TARGET_SIZE) + { + Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); + target_prices[0] = 0; // 不完全なファイルは無効扱い + } + } + else + { + Print("Failed to open file. Error code: ", GetLastError()); + } + } + else + { + Print("File does not exist: ", filename); + } + } + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +//+------------------------------------------------------------------+ +/** + * @brief 指定された注文タイプでペンディング注文を送信します。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param price エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 + */ +bool SendOrder(int orderType, double price, double tp, double sl) + { + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.magic = magic_number; + request.symbol = _Symbol; + request.volume = lot_size; + request.deviation = slippage; + request.type_filling = GetOrderFillingPolicy(_Symbol); + request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.comment = RequestComment(orderType); + + string orderTypeStr = ""; + +// 指値または逆指値注文 + request.action = TRADE_ACTION_PENDING; + ApplyPendingOrderExpiration(request); + + switch(orderType) + { + case 1: // 順張り買い (buy-stop) + request.type = ORDER_TYPE_BUY_STOP; + orderTypeStr = "buy-stop"; + break; + + case 2: // 逆張り買い (buy-limit) + request.type = ORDER_TYPE_BUY_LIMIT; + orderTypeStr = "buy-limit"; + break; + + case 3: // 順張り売り (sell-stop) + request.type = ORDER_TYPE_SELL_STOP; + orderTypeStr = "sell-stop"; + break; + + case 4: // 逆張り売り (sell-limit) + request.type = ORDER_TYPE_SELL_LIMIT; + orderTypeStr = "sell-limit"; + break; + + default: // 不正なorderType + Print(__FUNCTION__, ": invalid orderType=", orderType); + return false; + } + +// 注文送信 + if(!OrderSend(request, result)) + { + Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); + return false; + } + +// 注文成功確認 + if(result.retcode == TRADE_RETCODE_PLACED || + result.retcode == TRADE_RETCODE_DONE || + result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); + return true; + } + else + { + Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文の執行ポリシーを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief シンボルに対応する注文執行ポリシーを取得します。 + * + * @param symbol 対象シンボル。 + * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 + */ +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) + { + long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); + if((fill_mode & SYMBOL_FILLING_IOC) != 0) + return ORDER_FILLING_IOC; + if((fill_mode & SYMBOL_FILLING_FOK) != 0) + return ORDER_FILLING_FOK; + return ORDER_FILLING_RETURN; + } + +//+------------------------------------------------------------------+ +//| ペンディング注文の有効期限を設定する関数 +//+------------------------------------------------------------------+ +/** + * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 + * + * @param request 更新対象の取引リクエスト。 + */ +void ApplyPendingOrderExpiration(MqlTradeRequest &request) + { + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED_DAY; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) + { + request.type_time = ORDER_TIME_DAY; + return; + } + + request.type_time = ORDER_TIME_GTC; + } + +//+------------------------------------------------------------------+ +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +//+------------------------------------------------------------------+ +/** + * @brief 注文コメント文字列を生成します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプとPCローカル時刻を含むコメント文字列。 + */ +string RequestComment(int orderType) + { + datetime localTime = TimeLocal(); + string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); + +// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") + return IntegerToString(orderType) + " | PC Time: " + timeStr; + } + +//+------------------------------------------------------------------+ +//| H4トレンド方向に対して許可された注文タイプか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド方向に対して指定注文タイプが許可されるか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param trend_state H4トレンド判定。0=RANGE、1=UP、2=DOWN。 + * @return 許可される注文タイプならtrue。レンジまたは異常値ではfalse。 + */ +bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) + { + if(trend_state == 1) + return (orderType == 1 || orderType == 2); // UP: Buy Stop / Buy Limit のみ + + if(trend_state == 2) + return (orderType == 3 || orderType == 4); // DOWN: Sell Stop / Sell Limit のみ + + return false; // RANGEまたは異常値は新規注文しない + } + +//+------------------------------------------------------------------+ +//| target_prices.txt の価格が有効か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 + * + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return すべて0より大きい場合はtrue。 + */ +bool HasValidTargetPrices(const double en, const double tp, const double sl) + { + return (en > 0.0 && tp > 0.0 && sl > 0.0); + } + +//+------------------------------------------------------------------+ +//| 自EAの未約定注文数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする未約定注文数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致する未約定注文数。 + */ +int CountMyPendingOrders() + { + int count = 0; + + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(ticket == 0) + continue; + + if(!OrderSelect(ticket)) + continue; + + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + + if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAのポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする保有ポジション数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致するポジション数。 + */ +int CountMyPositions() + { + int count = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string symbol = PositionGetSymbol(i); + if(symbol == "") + continue; + + if(symbol != _Symbol) + continue; + + if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAの注文+ポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが使用中の注文数とポジション数の合計を返します。 + * + * @return 未約定注文数 + 保有ポジション数。 + */ +int CountMyUsed() + { + return CountMyPendingOrders() + CountMyPositions(); + } + + +//+------------------------------------------------------------------+ +//| 時間が経過したエントリー注文をキャンセルする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 + * + * @return 1件以上キャンセルに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 + */ +bool CancelExpiredOrders() + { + bool result = false; + +// 未決済注文の数を取得 + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(OrderSelect(ticket)) // 注文を選択 + { + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if(OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); + + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(open_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - open_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ + request.order = ticket; // 注文チケット + if(!OrderSend(request, trade_result)) // 削除リクエスト送信 + { + Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); + } + else + { + if(trade_result.retcode == TRADE_RETCODE_DONE) + { + Print("Order canceled successfully due to a time limit. Ticket: ", ticket); + result = true; // 削除成功フラグ + } + else + { + Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); + } + } + } + } + } + return result; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したポジションをクローズする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 + * + * @return 1件以上クローズに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 + */ +bool CloseExpiredPositions() + { + bool result = false; + +// ポジションを逆順でループ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string position_symbol = PositionGetSymbol(i); + if(position_symbol!="") + { + // Magic Number とシンボルでフィルタリング + if(position_symbol != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) == magic_number) + { + // ポジションのエントリー時刻を取得 + datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + + int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(entry_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - entry_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + + // ポジションタイプに応じてリクエストを設定 + int position_type = (int)PositionGetInteger(POSITION_TYPE); + request.action = TRADE_ACTION_DEAL; + request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 + request.symbol = position_symbol; // シンボル + request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ + request.price = (position_type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ + : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ + request.deviation = slippage; + request.type = (position_type == POSITION_TYPE_BUY) + ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ + : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ + request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー + request.magic = magic_number; + + // 注文送信 + if(!OrderSend(request, trade_result)) + { + Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); + continue; + } + + // 結果の確認 + if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print("Position closed successfully due to a time limit. Ticket: ", request.position); + result = true; // 少なくとも1つ成功した場合 + } + else + { + Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); + } + } + } + } + else + { + Print("Failed to select position at index ", i, ". Error: ", GetLastError()); + } + } + return result; + } + + +//+------------------------------------------------------------------+ +//| 指定された時間に最も近いバーのインデックスを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時刻に最も近いバーシフトを取得します。 + * + * @param time 判定対象の時刻。 + * @param tf 判定対象の時間足。 + * @param default_shift 取得失敗時に返す値。 + * @return 取得できたバーシフト。失敗時はdefault_shift。 + * + * `iBarShift(..., false)` を使い、厳密一致ではなく近傍バーで解決します。 + */ +int GetBarShiftByTime(datetime time, ENUM_TIMEFRAMES tf, int default_shift = -1) + { + if(time <= 0) + return default_shift; + int shift = iBarShift(_Symbol, tf, time, false); // 近傍バーで解決する + if(shift < 0) + return default_shift; + return shift; + } +//+------------------------------------------------------------------+ diff --git a/Experts/MyProject/HIT-EA_refactor_ver3.mq5 b/Experts/MyProject/HIT-EA_refactor_ver3.mq5 new file mode 100644 index 0000000..958a263 --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver3.mq5 @@ -0,0 +1,2377 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 20 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_ABNORMAL_VOL_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..6)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +void OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| ティック情報を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 + * + * @param ctx 取得したティック情報を格納する構造体参照。 + * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 + */ +bool GetTickContext(TickContext &ctx) + { + MqlTick last_tick; + if(!SymbolInfoTick(_Symbol, last_tick)) + return false; + + ctx.ask = last_tick.ask; + ctx.bid = last_tick.bid; + ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); + ctx.spread = ctx.spread_points * Point(); + ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + return true; + } + +//+------------------------------------------------------------------+ +//| 期限切れ注文・期限切れポジションを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief 期限切れの未約定注文と保有ポジションを処理します。 + * + * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 + * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 + */ +void ManageExpiredTrades() + { + if(OrdersTotal() > 0) + CancelExpiredOrders(); + + if(PositionsTotal() > 0) + CloseExpiredPositions(); + } + +//+------------------------------------------------------------------+ +//| H4更新検知とトレンド判定Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド更新開始フラグを更新します。 + * + * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 + */ +void ProcessTrendUpdate(EAState &state) + { + int current_bars_H4 = iBars(NULL, PERIOD_H4); + if(g_pre_bars_H4 == 0) + g_pre_bars_H4 = current_bars_H4; + + int bars_H4_change = current_bars_H4 - g_pre_bars_H4; + bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); + + if(!trend_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Trend(state)) + { + g_init_trend_pending = false; + g_pre_bars_H4 = current_bars_H4; + } + } + +//+------------------------------------------------------------------+ +//| トレンド判定Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定Pythonの完了状態を確認します。 + * + * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsTrendResultReady() + { + return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| トレンド判定結果をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 + */ +void RefreshTrendState(EAState &state) + { + GetTrendState(state); + } + +//+------------------------------------------------------------------+ +//| H1更新検知とエントリー価格生成Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 + * + * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 + */ +void ProcessEntryUpdate(EAState &state) + { + int current_bars_H1 = iBars(NULL, PERIOD_H1); + if(g_pre_bars_H1 == 0) + g_pre_bars_H1 = current_bars_H1; + + int bars_H1_change = current_bars_H1 - g_pre_bars_H1; + bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); + + if(!entry_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Entry(state)) + { + g_init_entry_pending = false; + g_bars_H1_check = true; + state.chk_cnt = 0; + state.last_chk = 0; + g_pre_bars_H1 = current_bars_H1; + } + } + +//+------------------------------------------------------------------+ +//| M15更新検知とエントリータイミング判定トリガーを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief M15新バーを検知し、H1候補価格の発注判定を実行できる状態にします。 + * + * M15は方向判定を上書きする足ではなく、H4/H1で決めた候補価格を発注する + * タイミング確認として使います。初回は直近の確定M15足で一度だけ判定可能にします。 + */ +void ProcessM15EntryTimingUpdate() + { + int current_bars_M15 = iBars(NULL, PERIOD_M15); + if(current_bars_M15 <= 0) + return; + + if(g_pre_bars_M15 == 0) + { + g_pre_bars_M15 = current_bars_M15; + g_bars_M15_check = true; + return; + } + + int bars_M15_change = current_bars_M15 - g_pre_bars_M15; + if(bars_M15_change <= 0) + return; + + g_bars_M15_check = true; + g_pre_bars_M15 = current_bars_M15; + } + +//+------------------------------------------------------------------+ +//| チャートコメント文字列を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート左上に表示するステータスメッセージを組み立てます。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return Comment()へ渡す表示用文字列。 + */ +string BuildStatusMessage(TickContext &ctx) + { + string message = StringFormat( + " \nAsk: %." + IntegerToString(ctx.digits) + "f" + "\nBid: %." + IntegerToString(ctx.digits) + "f" + "\nSpread: %." + IntegerToString(ctx.digits) + "f" + "\nSpread Points: %.0f" + "\n\nLast Trend Update:\n %s" + "\n\nMarket State: %d (%s)" + "\n\nLast Target Update:\n %s" + "\n\nMy Used Count: %d / %d" + "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", + ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, + TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), + g_ea.trend_state, MarketStateName(g_ea.trend_state), + TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), + CountMyUsed(), POSITION_LIMIT, + g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], + g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], + g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], + g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] + ); + return message; + } + +//+------------------------------------------------------------------+ +//| チャートコメントを更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート上のステータスコメントを更新します。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + */ +void UpdateStatusComment(TickContext &ctx) + { + Comment(BuildStatusMessage(ctx)); + } + +//+------------------------------------------------------------------+ +//| market_stateの表示名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateの人間可読名を返します。 + * + * @param market_state Pythonが出力したH4環境分類(0..6)。 + * @return 表示用ラベル。 + */ +string MarketStateName(const int market_state) + { + switch(market_state) + { + case MARKET_LOW_VOL_RANGE: + return "LOW_VOL_RANGE"; + case MARKET_HIGH_VOL_RANGE: + return "HIGH_VOL_RANGE"; + case MARKET_LOW_VOL_UP: + return "LOW_VOL_UP"; + case MARKET_HIGH_VOL_UP: + return "HIGH_VOL_UP"; + case MARKET_LOW_VOL_DOWN: + return "LOW_VOL_DOWN"; + case MARKET_HIGH_VOL_DOWN: + return "HIGH_VOL_DOWN"; + case MARKET_ABNORMAL_VOL_STOP: + return "ABNORMAL_VOL_STOP"; + default: + return "UNKNOWN"; + } + } + +//+------------------------------------------------------------------+ +//| スプレッドが許容範囲内か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 + * + * @param ctx 現在のスプレッド情報。 + * @return 許容範囲内ならtrue、超過している場合はfalse。 + */ +bool IsSpreadAllowed(TickContext &ctx) + { + return (ctx.spread <= spread_limit * Point()); + } + +//+------------------------------------------------------------------+ +//| エントリー価格生成Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成Pythonの完了状態を確認します。 + * + * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsEntryResultReady() + { + return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ターゲット価格をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 + */ +void RefreshTargetPrices(EAState &state) + { + GetTargetPrices(state); + } + +//+------------------------------------------------------------------+ +//| エントリー判定が必要な場合のみ注文処理を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 + * + * @param state EA全体の状態。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * + * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 + */ +void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) + { + if(!ShouldRunEntryDecision(state)) + return; + + g_bars_H1_check = false; + g_bars_M15_check = false; + state.last_chk = TimeCurrent(); + + if(!ValidateEntryPreconditions(state)) + return; + + int sent_success = SendAllowedEntryOrders(state, ctx); + UpdateEntryRetryState(state, sent_success); + } + +//+------------------------------------------------------------------+ +//| エントリー判定を実行するタイミングか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定を実行するタイミングか確認します。 + * + * @param state EA全体の状態。前回判定時刻を参照します。 + * @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。 + */ +bool ShouldRunEntryDecision(EAState &state) + { + return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS); + } + +//+------------------------------------------------------------------+ +//| エントリー判定前の共通チェックを行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 新規注文前の共通条件を検証します。 + * + * @param state EA全体の状態。 + * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 + * + * `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。 + */ +bool ValidateEntryPreconditions(EAState &state) + { + if(state.res_chk != 1) + { + Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state == MARKET_ABNORMAL_VOL_STOP) + { + Print("[Entry Skip] market_state=6 (abnormal volatility). No new entry orders are sent."); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_ABNORMAL_VOL_STOP) + { + Print("[Entry Skip] invalid market_state=", state.trend_state); + state.chk_cnt = 0; + return false; + } + + if(IsTargetCandidateExpired(state)) + { + Print("[Entry Skip] H1 target candidate expired. loaded_at=", + TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS)); + state.chk_cnt = 0; + return false; + } + + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| H1候補価格の有効期限を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。 + * + * @param state EA全体の状態。H1候補価格の読込時刻を参照します。 + * @return 候補価格が期限切れの場合はtrue。 + */ +bool IsTargetCandidateExpired(EAState &state) + { + if(state.target_loaded_at <= 0) + return false; + + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(expiration_seconds <= 0) + return false; + + return (TimeCurrent() - state.target_loaded_at >= expiration_seconds); + } + +//+------------------------------------------------------------------+ +//| 許可された注文タイプだけを順番に送信する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateで許可された注文タイプだけを順番に送信します。 + * + * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return 送信成功した注文数。 + */ +int SendAllowedEntryOrders(EAState &state, TickContext &ctx) + { + int sent_success = 0; + + for(int t = 1; t <= 4; t++) + { + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); + break; + } + + if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) + { + Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state, + " (", MarketStateName(state.trend_state), ")"); + continue; + } + + if(TrySendEntryOrder(t, state, ctx)) + sent_success++; + } + + return sent_success; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ1件分の価格検証と注文送信を行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ1件分の価格検証と注文送信を行います。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid情報。 + * @return 注文送信に成功した場合はtrue、それ以外はfalse。 + */ +bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) + { + string entry_type = EntryTypeName(orderType); + double cur_price = CurrentPriceForOrderType(orderType, ctx); + + double en = state.en_price[orderType]; + double tp = state.tp_price[orderType]; + double sl = state.sl_price[orderType]; + + if(!HasValidTargetPrices(en, tp, sl)) + { + Print("[Skip] invalid target prices. orderType=", orderType, + " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); + if(!ok) + { + Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + if(!IsM15EntryTimingConfirmed(orderType, ctx, en)) + { + Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en); + return false; + } + + if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) + return false; + + Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); + + if(SendOrder(orderType, en, tp, sl)) + { + Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); + return true; + } + + Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); + return false; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ番号に対応する表示名を返します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプの表示名。不正値の場合は"Unknown"。 + */ +string EntryTypeName(const int orderType) + { + switch(orderType) + { + case 1: + return "Buy Stop"; + case 2: + return "Buy Limit"; + case 3: + return "Sell Stop"; + case 4: + return "Sell Limit"; + default: + return "Unknown"; + } + } + +//+------------------------------------------------------------------+ +//| 注文タイプに応じた現在価格を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @return 買い系注文ではAsk、売り系注文ではBid。 + */ +double CurrentPriceForOrderType(const int orderType, TickContext &ctx) + { + if(orderType == 1 || orderType == 2) + return ctx.ask; + + return ctx.bid; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとの価格整合条件を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文タイプの価格条件を満たす場合はtrue。 + */ +bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + switch(orderType) + { + case 1: // Buy Stop + return (ctx.ask < en && tp > en && sl < en); + + case 2: // Buy Limit + return (ctx.ask > en && tp > en && sl < en); + + case 3: // Sell Stop + return (ctx.bid > en && tp < en && sl > en); + + case 4: // Sell Limit + return (ctx.bid < en && tp < en && sl > en); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| M15確定足によるエントリータイミング確認 +//+------------------------------------------------------------------+ +/** + * @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en H1で決めたエントリー候補価格。 + * @return M15確認条件を満たす場合はtrue。 + * + * H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が + * 順張り/反転の根拠を持つ」ことだけを確認します。 + */ +bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en) + { + if(!use_m15_entry_filter) + return true; + + MqlRates rates[]; + int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates); + if(copied < 3) + { + Print("[M15 Filter] insufficient M15 bars. copied=", copied); + return false; + } + + int last_index = copied - 1; + int prev_index = copied - 2; + + double avg_range = AverageM15Range(rates, copied); + double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point(); + double entry_zone = avg_range * m15_entry_zone_atr_multiplier; + if(entry_zone < min_zone) + entry_zone = min_zone; + + double cur_price = CurrentPriceForOrderType(orderType, ctx); + if(MathAbs(cur_price - en) > entry_zone) + { + Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=", + cur_price, " en=", en, " zone=", entry_zone); + return false; + } + + return IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone); + } + +//+------------------------------------------------------------------+ +//| M15平均レンジを計算する関数 +//+------------------------------------------------------------------+ +/** + * @brief M15確定足の平均レンジを計算します。 + * + * @param rates M15のMqlRates配列。 + * @param count 使用する本数。 + * @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。 + */ +double AverageM15Range(const MqlRates &rates[], const int count) + { + double total_range = 0.0; + int used = 0; + + for(int i = 0; i < count; i++) + { + double range = rates[i].high - rates[i].low; + if(range <= 0.0) + continue; + + total_range += range; + used++; + } + + if(used <= 0) + return M15_MIN_ENTRY_ZONE_POINTS * Point(); + + return total_range / used; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとのM15シグナル方向を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。 + * + * @param orderType 注文タイプ番号。 + * @param prev_bar 1本前のM15確定足。 + * @param last_bar 直近のM15確定足。 + * @param en H1で決めたエントリー候補価格。 + * @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。 + * @return 注文タイプに沿ったM15根拠があればtrue。 + */ +bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone) + { + double range = SafeBarRange(last_bar); + double body_ratio = MathAbs(last_bar.close - last_bar.open) / range; + double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range; + double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range; + + bool bullish = (last_bar.close > last_bar.open); + bool bearish = (last_bar.close < last_bar.open); + bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO); + bool bullish_break = (last_bar.close > prev_bar.high); + bool bearish_break = (last_bar.close < prev_bar.low); + bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO); + bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO); + + switch(orderType) + { + case 1: // Buy Stop: M15の上方向モメンタムを確認 + return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone); + + case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認 + return (last_bar.low <= en + entry_zone && bullish && + (lower_rejection || bullish_break || strong_body)); + + case 3: // Sell Stop: M15の下方向モメンタムを確認 + return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone); + + case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認 + return (last_bar.high >= en - entry_zone && bearish && + (upper_rejection || bearish_break || strong_body)); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| ローソク足レンジを安全に取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。 + * + * @param bar 対象ローソク足。 + * @return high-low。0以下の場合はPoint()を返します。 + */ +double SafeBarRange(const MqlRates &bar) + { + double range = bar.high - bar.low; + if(range <= 0.0) + return Point(); + + return range; + } + +//+------------------------------------------------------------------+ +//| brokerの最小距離制約を満たすか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 最小距離を満たす場合はtrue。 + */ +bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); + int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); + int min_level = stop_level; + if(freeze_level > min_level) + min_level = freeze_level; + double min_distance = min_level * Point(); + + if(min_distance <= 0.0) + return true; + + double cur_price = CurrentPriceForOrderType(orderType, ctx); + string entry_type = EntryTypeName(orderType); + + if(MathAbs(en - cur_price) < min_distance) + { + Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, + " en=", en, " min_distance=", min_distance); + return false; + } + + if(MathAbs(tp - en) < min_distance) + { + Print("[Skip] ", entry_type, " TP is too close. en=", en, + " tp=", tp, " min_distance=", min_distance); + return false; + } + + if(MathAbs(en - sl) < min_distance) + { + Print("[Skip] ", entry_type, " SL is too close. en=", en, + " sl=", sl, " min_distance=", min_distance); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| 注文送信結果に応じてリトライ状態を更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 + * + * @param state EA全体の状態。`chk_cnt` を更新します。 + * @param sent_success 今回送信に成功した注文数。 + * + * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 + */ +void UpdateEntryRetryState(EAState &state, const int sent_success) + { + if(sent_success > 0) + { + state.chk_cnt = 0; + return; + } + + state.chk_cnt += 1; + Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT, + " (wait next M15 bar)"); + + if(state.chk_cnt < ENTRY_RETRY_LIMIT) + { + g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する + return; + } + + Print("[Retry End] reached max tries. reset chk_cnt."); + state.chk_cnt = 0; + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを作成します。 + * + * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 + */ +void CreateDoneFile(const string name) + { + if(!FileIsExist(name)) + { + int h = FileOpen(name, FILE_WRITE|FILE_TXT); + if(h != INVALID_HANDLE) + FileClose(h); + else + Print("Failed to create file: ", name, " err=", GetLastError()); + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを削除します。 + * + * @param name 削除するファイル名。存在しない場合は何もしません。 + */ +void DeleteDoneFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + + +//+------------------------------------------------------------------+ +//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルの存在有無を確認します。 + * + * @param name 確認するファイル名。 + * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 + */ +bool CheckDoneFile(const string name) + { + return FileIsExist(name); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief Python起動時刻を実行中ファイルへ記録します。 + * + * @param name 作成するrunningファイル名。 + */ +void CreateRunningFile(const string name, const uint process_id) + { + int h = FileOpen(name, FILE_WRITE | FILE_TXT); + if(h != INVALID_HANDLE) + { + FileWrite(h, IntegerToString((long)TimeCurrent())); + FileWrite(h, IntegerToString((long)process_id)); + FileClose(h); + } + else + Print("Failed to create running file: ", name, " err=", GetLastError()); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したrunningファイルを削除します。 + * + * @param name 削除するrunningファイル名。 + */ +void DeleteRunningFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + +//+------------------------------------------------------------------+ +//| Python実行開始時刻を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたPython起動時刻を読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めた起動時刻。失敗時は0。 + */ +datetime LoadRunningStartedAt(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (datetime)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行プロセスIDを読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたプロセスIDを読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めたプロセスID。旧形式または失敗時は0。 + */ +uint LoadRunningProcessId(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + FileReadString(h); // started_at + if(FileIsEnding(h)) + { + FileClose(h); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (uint)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 + * + * @param name 判定するrunningファイル名。 + * @return タイムアウトしている場合はtrue。 + */ +bool IsRunningFileTimedOut(const string name) + { + datetime started_at = LoadRunningStartedAt(name); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| 外部プロセス状態を初期値に戻す関数 +//+------------------------------------------------------------------+ +void ResetExternalProcessState(ExternalProcessState &process) + { + if(process.handle != 0) + CloseHandle(process.handle); + + process.handle = 0; + process.process_id = 0; + process.active = false; + process.exit_code_ready = false; + process.exit_code = 0; + process.started_at = 0; + } + +//+------------------------------------------------------------------+ +//| runningファイルのPIDからプロセスハンドルを復元する関数 +//+------------------------------------------------------------------+ +bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) + { + if(process.active && process.handle != 0) + return true; + + uint process_id = LoadRunningProcessId(running_file); + if(process_id == 0) + return false; + + long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); + if(handle == 0) + { + Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); + return false; + } + + process.handle = handle; + process.process_id = process_id; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = LoadRunningStartedAt(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスの終了状態を更新する関数 +//+------------------------------------------------------------------+ +bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) + { + if(!process.active || process.handle == 0) + return true; + + int wait_result = WaitForSingleObject(process.handle, 0); + if(wait_result == WAIT_TIMEOUT) + return false; + + uint exit_code = 1; + if(wait_result == WAIT_OBJECT_0) + { + if(GetExitCodeProcess(process.handle, exit_code) == 0) + { + Print("[", label, "] failed to get process exit code. err=", GetLastError()); + exit_code = 1; + } + } + else + { + Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); + exit_code = 1; + } + + CloseHandle(process.handle); + process.handle = 0; + process.active = false; + process.exit_code_ready = true; + process.exit_code = exit_code; + Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) + { + datetime started_at = process.started_at; + if(started_at <= 0) + started_at = LoadRunningStartedAt(running_file); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| バッチファイルをプロセスハンドル付きで起動する関数 +//+------------------------------------------------------------------+ +bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) + { + STARTUPINFO_W startup_info = {}; + PROCESS_INFORMATION process_info = {}; + + startup_info.cb = (uint)sizeof(startup_info); + + string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; + string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; + + int created = CreateProcessW( + cmd_exe, + command_line, + 0, + 0, + 0, + CREATE_NO_WINDOW, + 0, + "C:\\ea_py", + startup_info, + process_info + ); + + if(created == 0 || process_info.hProcess == 0) + { + Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); + ResetExternalProcessState(process); + DeleteRunningFile(running_file); + return false; + } + + if(process_info.hThread != 0) + CloseHandle(process_info.hThread); + + process.handle = process_info.hProcess; + process.process_id = process_info.dwProcessId; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = TimeCurrent(); + + CreateRunningFile(running_file, process.process_id); + Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); + return true; + } + +//+------------------------------------------------------------------+ +//| EA起動時のdoneファイル状態を整える関数 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + */ +void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return; + } + + if(FileIsExist(running_file)) + { + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running file found on init. Remove marker without creating done."); + DeleteRunningFile(running_file); + } + else + Print("[", label, "] Python seems to be running on init. Keep waiting."); + return; + } + + Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); + } + +//+------------------------------------------------------------------+ +//| Python完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @return 完了済みの場合はtrue。 + */ +bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + return false; + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] process failed. exit_code=", process.exit_code); + return false; + } + } + else if(!CheckDoneFile(done_file)) + { + return false; + } + } + + if(!CheckDoneFile(done_file)) + return false; + + if(!FileIsExist(result_file)) + { + Print("[", label, "] done exists but result file is missing: ", result_file); + return false; + } + + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| Python開始可能状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return 起動可能な場合はtrue。 + */ +bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); + else + Print("[", label, "] Python is still running. Skip execute."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); + } + else + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running marker without live process. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); + return false; + } + } + + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] done file missing without running marker. Start new process."); + return true; + } + +//+------------------------------------------------------------------+ +//| タイムアウトしたPython待ちを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 + */ +void RecoverTimedOutPythonProcesses() + { + if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + g_ea.load_trend_flg = false; + g_init_trend_pending = true; + } + + if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + g_ea.load_target_flg = false; + g_bars_H1_check = false; + g_bars_M15_check = false; + g_ea.chk_cnt = 0; + g_init_entry_pending = true; + } + } + +//+------------------------------------------------------------------+ +//| 個別Python処理のタイムアウトを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return タイムアウト復旧を行った場合はtrue。 + */ +bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(CheckDoneFile(done_file) && !process.active) + { + DeleteRunningFile(running_file); + return false; + } + + if(!process.active && !FileIsExist(running_file)) + return false; + + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, + " seconds, but the process is still running. Keep waiting."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] Python process failed. Retry on next trigger."); + return true; + } + + if(!CheckDoneFile(done_file)) + { + Print("[", label, "] Python process ended without done file. Retry on next trigger."); + return true; + } + + return false; + } + + if(!IsRunningFileTimedOut(running_file)) + return false; + + Print("[", label, "] Python did not finish within ", + PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 最新のOHLCデータを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時間足のOHLCデータを取得します。 + * + * @param times 取得したバー時刻を格納する配列。 + * @param open_prices 取得したOpen価格を格納する配列。 + * @param high_prices 取得したHigh価格を格納する配列。 + * @param low_prices 取得したLow価格を格納する配列。 + * @param close_prices 取得したClose価格を格納する配列。 + * @param tf 取得対象の時間足。 + * @param bars_count 取得するバー本数。 + * @return 取得に成功した場合はtrue、失敗した場合はfalse。 + * + * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 + */ +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) + { + ArrayResize(times, 0); + ArrayResize(open_prices, 0); + ArrayResize(high_prices, 0); + ArrayResize(low_prices, 0); + ArrayResize(close_prices, 0); + + if(bars_count < 2) + return false; + +// OHLCデータを取得 +// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 +// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 + MqlRates rates[]; + int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); + if(copied <= 0) + { + Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + + ArrayResize(times, copied); + ArrayResize(open_prices, copied); + ArrayResize(high_prices, copied); + ArrayResize(low_prices, copied); + ArrayResize(close_prices, copied); + +// 必要なデータを配列に格納 + for(int i = 0; i < copied; i++) + { + times[i] = rates[i].time; + open_prices[i] = rates[i].open; + high_prices[i] = rates[i].high; + low_prices[i] = rates[i].low; + close_prices[i] = rates[i].close; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力する関数 +//+------------------------------------------------------------------+ +/** + * @brief OHLC配列をPython入力用CSVとして出力します。 + * + * @param filename 出力するCSVファイル名。 + * @param times バー時刻配列。 + * @param open_prices Open価格配列。 + * @param high_prices High価格配列。 + * @param low_prices Low価格配列。 + * @param close_prices Close価格配列。 + * @return CSV出力に成功した場合はtrue。 + */ +bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) + { +// 出力先ファイル名 +// string filename = "ohlc.csv"; + +// ファイルを "書き込みモード" でオープン (テキスト/ANSI) + int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); + +// ファイルが開けたかチェック + if(fileHandle == INVALID_HANDLE) + { + Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); + return false; + } + +// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) + int size = ArraySize(times); + +// ヘッダー行を追加 + FileWrite(fileHandle, "Time,Open,High,Low,Close"); + +// 小数点の桁数を取得 + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + +// フォーマット文字列を動的に生成 + string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); + +// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み + for(int i = 0; i < size; i++) + { + // 時間をフォーマット + string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); + + // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) + string line = StringFormat(formatString, + timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); + + // ファイルに書き込み (改行付き) + FileWrite(fileHandle, line); + } + +// 書き込み終了後、ファイルを閉じる + FileClose(fileHandle); + return true; + } + +//+------------------------------------------------------------------+ +//| バッチファイル(Pythonスクリプト)を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchTrend() + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + return false; + + DeleteDoneFile(done_trend_file); + return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchEntry() + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + return false; + + DeleteDoneFile(done_entry_file); + return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); + } + + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力後、バッチファイルの実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Trend(EAState &state) + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + Print("Trend Python is still running. Skip H4 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H4, HISTORY_BARS)) + { Print("GetLatestOHLC(H4) failed."); return false; } + + if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H4) failed."); return false; } + + if(!ExecuteBatchTrend()) + return false; + + state.load_trend_flg = true; + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Entry(EAState &state) + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + Print("Entry Python is still running. Skip H1 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H1, HISTORY_BARS)) + { Print("GetLatestOHLC(H1) failed."); return false; } + + if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H1) failed."); return false; } + + if(!ExecuteBatchEntry()) + return false; + + state.load_target_flg = true; + return true; + } + + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。H4 market_stateと更新フラグを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTrendState(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_trend_file); + if(ProcessDone && g_ea.load_trend_flg) + { + int trend_state; + LoadTrendState(trend_state); + state.trend_state = trend_state; + Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")"); + g_ea.load_trend_flg = false; + g_ea.last_trend_update = TimeLocal(); + } + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief `trend_state.txt` からH4 market_state判定値を読み込みます。 + * + * @param trend_state 読み込んだ値を格納する参照。0..6のmarket_state。 + * + * ファイル未存在、読込失敗、異常値の場合は安全側として6(新規停止)を設定します。 + */ +void LoadTrendState(int &trend_state) + { + string filename = trend_state_file; + trend_state = MARKET_ABNORMAL_VOL_STOP; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + string line = FileReadString(filehandle); + int value = (int)StringToInteger(line); + if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_ABNORMAL_VOL_STOP) + trend_state = value; + else + Print("Invalid market_state value: ", line, ". Use 6."); + FileClose(filehandle); + } + else + { Print("Failed to open trend_state.txt"); trend_state = MARKET_ABNORMAL_VOL_STOP; } + } + else + { Print("trend_state.txt not found"); trend_state = MARKET_ABNORMAL_VOL_STOP; } + } + +//+------------------------------------------------------------------+ +//| ターゲット価格を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTargetPrices(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 + if(ProcessDone && g_ea.load_target_flg) // ←変更 + { + double target_prices[]; + LoadTargetPrices(target_prices); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // 先頭は res_chk + state.res_chk = (int)target_prices[0]; + + // ★ 1..4 の (en,tp,sl) を読む:合計12個 + for(int t=1; t<=4; t++) + { + int base = 1 + (t-1)*3; // 1,4,7,10 + state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); + state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); + state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); + } + + // ログ(任意) + Print("target_prices: res=", state.res_chk, + " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], + " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], + " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], + " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); + + g_ea.load_target_flg = false; // ←変更 + g_ea.last_target_update = TimeLocal(); // ←変更 + g_ea.target_loaded_at = TimeCurrent(); + } + return true; + } +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| "target_prices.txt"を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief `target_prices.txt` から13個の数値を読み込みます。 + * + * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 + * + * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 + */ +void LoadTargetPrices(double &target_prices[]) + { + ArrayResize(target_prices, TARGET_SIZE); + for(int i = 0; i < TARGET_SIZE; i++) + target_prices[i] = DEFAULT_TARGET_PRICE; + + string filename = target_prices_file; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + int i = 0; + while(!FileIsEnding(filehandle) && i < TARGET_SIZE) + { + string line = FileReadString(filehandle); + target_prices[i] = StringToDouble(line); + i++; + } + FileClose(filehandle); + + if(i < TARGET_SIZE) + { + Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); + target_prices[0] = 0; // 不完全なファイルは無効扱い + } + } + else + { + Print("Failed to open file. Error code: ", GetLastError()); + } + } + else + { + Print("File does not exist: ", filename); + } + } + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +//+------------------------------------------------------------------+ +/** + * @brief 指定された注文タイプでペンディング注文を送信します。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param price エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 + */ +bool SendOrder(int orderType, double price, double tp, double sl) + { + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.magic = magic_number; + request.symbol = _Symbol; + request.volume = lot_size; + request.deviation = slippage; + request.type_filling = GetOrderFillingPolicy(_Symbol); + request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.comment = RequestComment(orderType); + + string orderTypeStr = ""; + +// 指値または逆指値注文 + request.action = TRADE_ACTION_PENDING; + ApplyPendingOrderExpiration(request); + + switch(orderType) + { + case 1: // 順張り買い (buy-stop) + request.type = ORDER_TYPE_BUY_STOP; + orderTypeStr = "buy-stop"; + break; + + case 2: // 逆張り買い (buy-limit) + request.type = ORDER_TYPE_BUY_LIMIT; + orderTypeStr = "buy-limit"; + break; + + case 3: // 順張り売り (sell-stop) + request.type = ORDER_TYPE_SELL_STOP; + orderTypeStr = "sell-stop"; + break; + + case 4: // 逆張り売り (sell-limit) + request.type = ORDER_TYPE_SELL_LIMIT; + orderTypeStr = "sell-limit"; + break; + + default: // 不正なorderType + Print(__FUNCTION__, ": invalid orderType=", orderType); + return false; + } + +// 注文送信 + if(!OrderSend(request, result)) + { + Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); + return false; + } + +// 注文成功確認 + if(result.retcode == TRADE_RETCODE_PLACED || + result.retcode == TRADE_RETCODE_DONE || + result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); + return true; + } + else + { + Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文の執行ポリシーを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief シンボルに対応する注文執行ポリシーを取得します。 + * + * @param symbol 対象シンボル。 + * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 + */ +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) + { + long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); + if((fill_mode & SYMBOL_FILLING_IOC) != 0) + return ORDER_FILLING_IOC; + if((fill_mode & SYMBOL_FILLING_FOK) != 0) + return ORDER_FILLING_FOK; + return ORDER_FILLING_RETURN; + } + +//+------------------------------------------------------------------+ +//| ペンディング注文の有効期限を設定する関数 +//+------------------------------------------------------------------+ +/** + * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 + * + * @param request 更新対象の取引リクエスト。 + */ +void ApplyPendingOrderExpiration(MqlTradeRequest &request) + { + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED_DAY; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) + { + request.type_time = ORDER_TIME_DAY; + return; + } + + request.type_time = ORDER_TIME_GTC; + } + +//+------------------------------------------------------------------+ +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +//+------------------------------------------------------------------+ +/** + * @brief 注文コメント文字列を生成します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプとPCローカル時刻を含むコメント文字列。 + */ +string RequestComment(int orderType) + { + datetime localTime = TimeLocal(); + string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); + +// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") + return IntegerToString(orderType) + " | PC Time: " + timeStr; + } + +//+------------------------------------------------------------------+ +//| H4 market_stateに対して許可された注文タイプか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param trend_state H4 market_state(0..6)。 + * @return 許可される注文タイプならtrue。異常ボラまたは異常値ではfalse。 + */ +bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) + { + switch(trend_state) + { + case MARKET_LOW_VOL_RANGE: + case MARKET_HIGH_VOL_RANGE: + return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ + + case MARKET_LOW_VOL_UP: + case MARKET_HIGH_VOL_UP: + return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ + + case MARKET_LOW_VOL_DOWN: + case MARKET_HIGH_VOL_DOWN: + return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ + + case MARKET_ABNORMAL_VOL_STOP: + default: + return false; // 異常ボラまたは異常値は新規注文しない + } + } + +//+------------------------------------------------------------------+ +//| target_prices.txt の価格が有効か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 + * + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return すべて0より大きい場合はtrue。 + */ +bool HasValidTargetPrices(const double en, const double tp, const double sl) + { + return (en > 0.0 && tp > 0.0 && sl > 0.0); + } + +//+------------------------------------------------------------------+ +//| 自EAの未約定注文数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする未約定注文数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致する未約定注文数。 + */ +int CountMyPendingOrders() + { + int count = 0; + + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(ticket == 0) + continue; + + if(!OrderSelect(ticket)) + continue; + + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + + if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAのポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする保有ポジション数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致するポジション数。 + */ +int CountMyPositions() + { + int count = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string symbol = PositionGetSymbol(i); + if(symbol == "") + continue; + + if(symbol != _Symbol) + continue; + + if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAの注文+ポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが使用中の注文数とポジション数の合計を返します。 + * + * @return 未約定注文数 + 保有ポジション数。 + */ +int CountMyUsed() + { + return CountMyPendingOrders() + CountMyPositions(); + } + + +//+------------------------------------------------------------------+ +//| 時間が経過したエントリー注文をキャンセルする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 + * + * @return 1件以上キャンセルに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 + */ +bool CancelExpiredOrders() + { + bool result = false; + +// 未決済注文の数を取得 + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(OrderSelect(ticket)) // 注文を選択 + { + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if(OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); + + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(open_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - open_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ + request.order = ticket; // 注文チケット + if(!OrderSend(request, trade_result)) // 削除リクエスト送信 + { + Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); + } + else + { + if(trade_result.retcode == TRADE_RETCODE_DONE) + { + Print("Order canceled successfully due to a time limit. Ticket: ", ticket); + result = true; // 削除成功フラグ + } + else + { + Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); + } + } + } + } + } + return result; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したポジションをクローズする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 + * + * @return 1件以上クローズに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 + */ +bool CloseExpiredPositions() + { + bool result = false; + +// ポジションを逆順でループ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string position_symbol = PositionGetSymbol(i); + if(position_symbol!="") + { + // Magic Number とシンボルでフィルタリング + if(position_symbol != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) == magic_number) + { + // ポジションのエントリー時刻を取得 + datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + + int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(entry_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - entry_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + + // ポジションタイプに応じてリクエストを設定 + int position_type = (int)PositionGetInteger(POSITION_TYPE); + request.action = TRADE_ACTION_DEAL; + request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 + request.symbol = position_symbol; // シンボル + request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ + request.price = (position_type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ + : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ + request.deviation = slippage; + request.type = (position_type == POSITION_TYPE_BUY) + ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ + : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ + request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー + request.magic = magic_number; + + // 注文送信 + if(!OrderSend(request, trade_result)) + { + Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); + continue; + } + + // 結果の確認 + if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print("Position closed successfully due to a time limit. Ticket: ", request.position); + result = true; // 少なくとも1つ成功した場合 + } + else + { + Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); + } + } + } + } + else + { + Print("Failed to select position at index ", i, ". Error: ", GetLastError()); + } + } + return result; + } diff --git a/Experts/MyProject/HIT-EA_refactor_ver4.mq5 b/Experts/MyProject/HIT-EA_refactor_ver4.mq5 new file mode 100644 index 0000000..958a263 --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver4.mq5 @@ -0,0 +1,2377 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 20 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_ABNORMAL_VOL_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..6)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +void OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| ティック情報を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在のAsk/Bid/スプレッド情報を取得してTickContextへ格納します。 + * + * @param ctx 取得したティック情報を格納する構造体参照。 + * @return 取得成功時はtrue、ティック情報を取得できない場合はfalse。 + */ +bool GetTickContext(TickContext &ctx) + { + MqlTick last_tick; + if(!SymbolInfoTick(_Symbol, last_tick)) + return false; + + ctx.ask = last_tick.ask; + ctx.bid = last_tick.bid; + ctx.spread_points = MathRound((ctx.ask - ctx.bid) / Point()); + ctx.spread = ctx.spread_points * Point(); + ctx.digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + return true; + } + +//+------------------------------------------------------------------+ +//| 期限切れ注文・期限切れポジションを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief 期限切れの未約定注文と保有ポジションを処理します。 + * + * 未約定注文はENTRY_H1_LIMIT時間、保有ポジションはCLOSE_H1_LIMIT時間を基準に判定します。 + * 新規注文条件とは独立して、OnTickの早い段階で実行される想定です。 + */ +void ManageExpiredTrades() + { + if(OrdersTotal() > 0) + CancelExpiredOrders(); + + if(PositionsTotal() > 0) + CloseExpiredPositions(); + } + +//+------------------------------------------------------------------+ +//| H4更新検知とトレンド判定Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4新バーまたは初回起動を検知し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド更新開始フラグを更新します。 + * + * Python処理中はdoneファイルが存在しないため、CSVの上書きと二重起動を抑止します。 + */ +void ProcessTrendUpdate(EAState &state) + { + int current_bars_H4 = iBars(NULL, PERIOD_H4); + if(g_pre_bars_H4 == 0) + g_pre_bars_H4 = current_bars_H4; + + int bars_H4_change = current_bars_H4 - g_pre_bars_H4; + bool trend_trigger = (bars_H4_change > 0 || g_init_trend_pending); + + if(!trend_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Trend(state)) + { + g_init_trend_pending = false; + g_pre_bars_H4 = current_bars_H4; + } + } + +//+------------------------------------------------------------------+ +//| トレンド判定Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定Pythonの完了状態を確認します。 + * + * @return `process_done_trend.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsTrendResultReady() + { + return IsProcessResultReady(done_trend_file, running_trend_file, trend_state_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| トレンド判定結果をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH4トレンド判定結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`trend_state` と最終更新時刻が更新されます。 + */ +void RefreshTrendState(EAState &state) + { + GetTrendState(state); + } + +//+------------------------------------------------------------------+ +//| H1更新検知とエントリー価格生成Python起動を処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1新バーまたは初回起動を検知し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格更新開始フラグや判定リトライ状態を更新します。 + * + * Python処理中はdoneファイルが存在しないため、H1 CSVの上書きと二重起動を抑止します。 + */ +void ProcessEntryUpdate(EAState &state) + { + int current_bars_H1 = iBars(NULL, PERIOD_H1); + if(g_pre_bars_H1 == 0) + g_pre_bars_H1 = current_bars_H1; + + int bars_H1_change = current_bars_H1 - g_pre_bars_H1; + bool entry_trigger = (bars_H1_change > 0 || g_init_entry_pending); + + if(!entry_trigger) + return; + + if(RecordOHLCAndExecuteBatch_Entry(state)) + { + g_init_entry_pending = false; + g_bars_H1_check = true; + state.chk_cnt = 0; + state.last_chk = 0; + g_pre_bars_H1 = current_bars_H1; + } + } + +//+------------------------------------------------------------------+ +//| M15更新検知とエントリータイミング判定トリガーを処理する関数 +//+------------------------------------------------------------------+ +/** + * @brief M15新バーを検知し、H1候補価格の発注判定を実行できる状態にします。 + * + * M15は方向判定を上書きする足ではなく、H4/H1で決めた候補価格を発注する + * タイミング確認として使います。初回は直近の確定M15足で一度だけ判定可能にします。 + */ +void ProcessM15EntryTimingUpdate() + { + int current_bars_M15 = iBars(NULL, PERIOD_M15); + if(current_bars_M15 <= 0) + return; + + if(g_pre_bars_M15 == 0) + { + g_pre_bars_M15 = current_bars_M15; + g_bars_M15_check = true; + return; + } + + int bars_M15_change = current_bars_M15 - g_pre_bars_M15; + if(bars_M15_change <= 0) + return; + + g_bars_M15_check = true; + g_pre_bars_M15 = current_bars_M15; + } + +//+------------------------------------------------------------------+ +//| チャートコメント文字列を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート左上に表示するステータスメッセージを組み立てます。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return Comment()へ渡す表示用文字列。 + */ +string BuildStatusMessage(TickContext &ctx) + { + string message = StringFormat( + " \nAsk: %." + IntegerToString(ctx.digits) + "f" + "\nBid: %." + IntegerToString(ctx.digits) + "f" + "\nSpread: %." + IntegerToString(ctx.digits) + "f" + "\nSpread Points: %.0f" + "\n\nLast Trend Update:\n %s" + "\n\nMarket State: %d (%s)" + "\n\nLast Target Update:\n %s" + "\n\nMy Used Count: %d / %d" + "\n\n[T1 Buy Stop ] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T2 Buy Limit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T3 Sell Stop] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f" + "\n[T4 SellLimit] en: %." + IntegerToString(ctx.digits) + "f tp: %." + IntegerToString(ctx.digits) + "f sl: %." + IntegerToString(ctx.digits) + "f\n", + ctx.ask, ctx.bid, ctx.spread, ctx.spread_points, + TimeToString(g_ea.last_trend_update, TIME_DATE | TIME_MINUTES), + g_ea.trend_state, MarketStateName(g_ea.trend_state), + TimeToString(g_ea.last_target_update, TIME_DATE | TIME_MINUTES), + CountMyUsed(), POSITION_LIMIT, + g_ea.en_price[1], g_ea.tp_price[1], g_ea.sl_price[1], + g_ea.en_price[2], g_ea.tp_price[2], g_ea.sl_price[2], + g_ea.en_price[3], g_ea.tp_price[3], g_ea.sl_price[3], + g_ea.en_price[4], g_ea.tp_price[4], g_ea.sl_price[4] + ); + return message; + } + +//+------------------------------------------------------------------+ +//| チャートコメントを更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief チャート上のステータスコメントを更新します。 + * + * @param ctx 現在のAsk/Bid/スプレッド情報。 + */ +void UpdateStatusComment(TickContext &ctx) + { + Comment(BuildStatusMessage(ctx)); + } + +//+------------------------------------------------------------------+ +//| market_stateの表示名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateの人間可読名を返します。 + * + * @param market_state Pythonが出力したH4環境分類(0..6)。 + * @return 表示用ラベル。 + */ +string MarketStateName(const int market_state) + { + switch(market_state) + { + case MARKET_LOW_VOL_RANGE: + return "LOW_VOL_RANGE"; + case MARKET_HIGH_VOL_RANGE: + return "HIGH_VOL_RANGE"; + case MARKET_LOW_VOL_UP: + return "LOW_VOL_UP"; + case MARKET_HIGH_VOL_UP: + return "HIGH_VOL_UP"; + case MARKET_LOW_VOL_DOWN: + return "LOW_VOL_DOWN"; + case MARKET_HIGH_VOL_DOWN: + return "HIGH_VOL_DOWN"; + case MARKET_ABNORMAL_VOL_STOP: + return "ABNORMAL_VOL_STOP"; + default: + return "UNKNOWN"; + } + } + +//+------------------------------------------------------------------+ +//| スプレッドが許容範囲内か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 現在スプレッドが入力パラメータの許容範囲内か判定します。 + * + * @param ctx 現在のスプレッド情報。 + * @return 許容範囲内ならtrue、超過している場合はfalse。 + */ +bool IsSpreadAllowed(TickContext &ctx) + { + return (ctx.spread <= spread_limit * Point()); + } + +//+------------------------------------------------------------------+ +//| エントリー価格生成Pythonの完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成Pythonの完了状態を確認します。 + * + * @return `process_done_entry.txt` が存在する場合はtrue、未完了の場合はfalse。 + */ +bool IsEntryResultReady() + { + return IsProcessResultReady(done_entry_file, running_entry_file, target_prices_file, "entry", g_entry_process); + } + +//+------------------------------------------------------------------+ +//| ターゲット価格をEA状態へ反映する関数 +//+------------------------------------------------------------------+ +/** + * @brief 完了済みのH1エントリー価格生成結果をEA状態へ反映します。 + * + * @param state EA全体の状態。`res_chk` と各注文タイプのen/tp/slが更新されます。 + */ +void RefreshTargetPrices(EAState &state) + { + GetTargetPrices(state); + } + +//+------------------------------------------------------------------+ +//| エントリー判定が必要な場合のみ注文処理を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定タイミングに到達している場合のみ注文処理を実行します。 + * + * @param state EA全体の状態。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * + * 判定前チェック、許可注文の送信、リトライ状態更新をまとめて制御します。 + */ +void ProcessEntryDecisionIfNeeded(EAState &state, TickContext &ctx) + { + if(!ShouldRunEntryDecision(state)) + return; + + g_bars_H1_check = false; + g_bars_M15_check = false; + state.last_chk = TimeCurrent(); + + if(!ValidateEntryPreconditions(state)) + return; + + int sent_success = SendAllowedEntryOrders(state, ctx); + UpdateEntryRetryState(state, sent_success); + } + +//+------------------------------------------------------------------+ +//| エントリー判定を実行するタイミングか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー判定を実行するタイミングか確認します。 + * + * @param state EA全体の状態。前回判定時刻を参照します。 + * @return H1候補が有効で、M15確定足が更新され、前回判定から一定秒数以上経過していればtrue。 + */ +bool ShouldRunEntryDecision(EAState &state) + { + return (g_bars_H1_check && g_bars_M15_check && TimeCurrent() - state.last_chk >= ENTRY_RETRY_SECONDS); + } + +//+------------------------------------------------------------------+ +//| エントリー判定前の共通チェックを行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 新規注文前の共通条件を検証します。 + * + * @param state EA全体の状態。 + * @return 注文判定を続行できる場合はtrue、停止すべき場合はfalse。 + * + * `res_chk`、market_state、対象EAの注文/ポジション数上限を確認します。 + */ +bool ValidateEntryPreconditions(EAState &state) + { + if(state.res_chk != 1) + { + Print("[Entry Skip] target_prices invalid. res_chk=", state.res_chk); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state == MARKET_ABNORMAL_VOL_STOP) + { + Print("[Entry Skip] market_state=6 (abnormal volatility). No new entry orders are sent."); + state.chk_cnt = 0; + return false; + } + + if(state.trend_state < MARKET_LOW_VOL_RANGE || state.trend_state > MARKET_ABNORMAL_VOL_STOP) + { + Print("[Entry Skip] invalid market_state=", state.trend_state); + state.chk_cnt = 0; + return false; + } + + if(IsTargetCandidateExpired(state)) + { + Print("[Entry Skip] H1 target candidate expired. loaded_at=", + TimeToString(state.target_loaded_at, TIME_DATE | TIME_SECONDS)); + state.chk_cnt = 0; + return false; + } + + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit exceeded: used=", used, " limit=", POSITION_LIMIT); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| H1候補価格の有効期限を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H1候補価格がENTRY_H1_LIMIT時間を超えて古くなっていないか判定します。 + * + * @param state EA全体の状態。H1候補価格の読込時刻を参照します。 + * @return 候補価格が期限切れの場合はtrue。 + */ +bool IsTargetCandidateExpired(EAState &state) + { + if(state.target_loaded_at <= 0) + return false; + + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(expiration_seconds <= 0) + return false; + + return (TimeCurrent() - state.target_loaded_at >= expiration_seconds); + } + +//+------------------------------------------------------------------+ +//| 許可された注文タイプだけを順番に送信する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateで許可された注文タイプだけを順番に送信します。 + * + * @param state EA全体の状態。各注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid/スプレッド情報。 + * @return 送信成功した注文数。 + */ +int SendAllowedEntryOrders(EAState &state, TickContext &ctx) + { + int sent_success = 0; + + for(int t = 1; t <= 4; t++) + { + int used = CountMyUsed(); + if(used >= POSITION_LIMIT) + { + Print("Position limit reached while sending. used=", used, " limit=", POSITION_LIMIT); + break; + } + + if(!IsOrderTypeAllowedByTrend(t, state.trend_state)) + { + Print("[Skip] orderType=", t, " not allowed by market_state=", state.trend_state, + " (", MarketStateName(state.trend_state), ")"); + continue; + } + + if(TrySendEntryOrder(t, state, ctx)) + sent_success++; + } + + return sent_success; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ1件分の価格検証と注文送信を行う関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ1件分の価格検証と注文送信を行います。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param state EA全体の状態。対象注文タイプのen/tp/slを参照します。 + * @param ctx 現在のAsk/Bid情報。 + * @return 注文送信に成功した場合はtrue、それ以外はfalse。 + */ +bool TrySendEntryOrder(const int orderType, EAState &state, TickContext &ctx) + { + string entry_type = EntryTypeName(orderType); + double cur_price = CurrentPriceForOrderType(orderType, ctx); + + double en = state.en_price[orderType]; + double tp = state.tp_price[orderType]; + double sl = state.sl_price[orderType]; + + if(!HasValidTargetPrices(en, tp, sl)) + { + Print("[Skip] invalid target prices. orderType=", orderType, + " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + bool ok = IsTargetPriceOrderConditionMatched(orderType, ctx, en, tp, sl); + if(!ok) + { + Print("[No ", entry_type, "] cur=", cur_price, " en=", en, " tp=", tp, " sl=", sl); + return false; + } + + if(!IsM15EntryTimingConfirmed(orderType, ctx, en)) + { + Print("[No ", entry_type, "] M15 timing not confirmed. cur=", cur_price, " en=", en); + return false; + } + + if(!MeetsTradeDistanceRules(orderType, ctx, en, tp, sl)) + return false; + + Print("[", entry_type, " Order Try at ", cur_price, "] en=", en, " tp=", tp, " sl=", sl); + + if(SendOrder(orderType, en, tp, sl)) + { + Print("[", entry_type, " Order Sent] ticket ok. en=", en, " tp=", tp, " sl=", sl); + return true; + } + + Print("[", entry_type, " Order Failed] en=", en, " tp=", tp, " sl=", sl); + return false; + } + +//+------------------------------------------------------------------+ +//| 注文タイプ名を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプ番号に対応する表示名を返します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプの表示名。不正値の場合は"Unknown"。 + */ +string EntryTypeName(const int orderType) + { + switch(orderType) + { + case 1: + return "Buy Stop"; + case 2: + return "Buy Limit"; + case 3: + return "Sell Stop"; + case 4: + return "Sell Limit"; + default: + return "Unknown"; + } + } + +//+------------------------------------------------------------------+ +//| 注文タイプに応じた現在価格を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプに応じて価格比較に使う現在価格を返します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @return 買い系注文ではAsk、売り系注文ではBid。 + */ +double CurrentPriceForOrderType(const int orderType, TickContext &ctx) + { + if(orderType == 1 || orderType == 2) + return ctx.ask; + + return ctx.bid; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとの価格整合条件を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとの現在価格・エントリー・TP・SLの大小関係を検証します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文タイプの価格条件を満たす場合はtrue。 + */ +bool IsTargetPriceOrderConditionMatched(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + switch(orderType) + { + case 1: // Buy Stop + return (ctx.ask < en && tp > en && sl < en); + + case 2: // Buy Limit + return (ctx.ask > en && tp > en && sl < en); + + case 3: // Sell Stop + return (ctx.bid > en && tp < en && sl > en); + + case 4: // Sell Limit + return (ctx.bid < en && tp < en && sl > en); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| M15確定足によるエントリータイミング確認 +//+------------------------------------------------------------------+ +/** + * @brief H1候補価格に対してM15の発注タイミングが整っているか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en H1で決めたエントリー候補価格。 + * @return M15確認条件を満たす場合はtrue。 + * + * H4/H1の方向判断は維持し、M15では「候補価格に近い」「直近確定足が + * 順張り/反転の根拠を持つ」ことだけを確認します。 + */ +bool IsM15EntryTimingConfirmed(const int orderType, TickContext &ctx, const double en) + { + if(!use_m15_entry_filter) + return true; + + MqlRates rates[]; + int copied = CopyRates(_Symbol, PERIOD_M15, OHLC_START_SHIFT, M15_CONFIRM_BARS, rates); + if(copied < 3) + { + Print("[M15 Filter] insufficient M15 bars. copied=", copied); + return false; + } + + int last_index = copied - 1; + int prev_index = copied - 2; + + double avg_range = AverageM15Range(rates, copied); + double min_zone = M15_MIN_ENTRY_ZONE_POINTS * Point(); + double entry_zone = avg_range * m15_entry_zone_atr_multiplier; + if(entry_zone < min_zone) + entry_zone = min_zone; + + double cur_price = CurrentPriceForOrderType(orderType, ctx); + if(MathAbs(cur_price - en) > entry_zone) + { + Print("[M15 Filter] ", EntryTypeName(orderType), " is not near entry zone. cur=", + cur_price, " en=", en, " zone=", entry_zone); + return false; + } + + return IsM15SignalAligned(orderType, rates[prev_index], rates[last_index], en, entry_zone); + } + +//+------------------------------------------------------------------+ +//| M15平均レンジを計算する関数 +//+------------------------------------------------------------------+ +/** + * @brief M15確定足の平均レンジを計算します。 + * + * @param rates M15のMqlRates配列。 + * @param count 使用する本数。 + * @return 平均レンジ。算出不能な場合は最小ゾーン幅を返します。 + */ +double AverageM15Range(const MqlRates &rates[], const int count) + { + double total_range = 0.0; + int used = 0; + + for(int i = 0; i < count; i++) + { + double range = rates[i].high - rates[i].low; + if(range <= 0.0) + continue; + + total_range += range; + used++; + } + + if(used <= 0) + return M15_MIN_ENTRY_ZONE_POINTS * Point(); + + return total_range / used; + } + +//+------------------------------------------------------------------+ +//| 注文タイプごとのM15シグナル方向を判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文タイプごとにM15確定足の勢い・反転根拠を確認します。 + * + * @param orderType 注文タイプ番号。 + * @param prev_bar 1本前のM15確定足。 + * @param last_bar 直近のM15確定足。 + * @param en H1で決めたエントリー候補価格。 + * @param entry_zone M15平均レンジから算出した候補価格付近の許容幅。 + * @return 注文タイプに沿ったM15根拠があればtrue。 + */ +bool IsM15SignalAligned(const int orderType, const MqlRates &prev_bar, const MqlRates &last_bar, const double en, const double entry_zone) + { + double range = SafeBarRange(last_bar); + double body_ratio = MathAbs(last_bar.close - last_bar.open) / range; + double upper_wick_ratio = (last_bar.high - MathMax(last_bar.open, last_bar.close)) / range; + double lower_wick_ratio = (MathMin(last_bar.open, last_bar.close) - last_bar.low) / range; + + bool bullish = (last_bar.close > last_bar.open); + bool bearish = (last_bar.close < last_bar.open); + bool strong_body = (body_ratio >= M15_MIN_BODY_RATIO); + bool bullish_break = (last_bar.close > prev_bar.high); + bool bearish_break = (last_bar.close < prev_bar.low); + bool lower_rejection = (lower_wick_ratio >= M15_REJECTION_WICK_RATIO); + bool upper_rejection = (upper_wick_ratio >= M15_REJECTION_WICK_RATIO); + + switch(orderType) + { + case 1: // Buy Stop: M15の上方向モメンタムを確認 + return (bullish && (bullish_break || strong_body) && last_bar.close <= en + entry_zone); + + case 2: // Buy Limit: 候補価格付近で下ヒゲ反転または買い戻しを確認 + return (last_bar.low <= en + entry_zone && bullish && + (lower_rejection || bullish_break || strong_body)); + + case 3: // Sell Stop: M15の下方向モメンタムを確認 + return (bearish && (bearish_break || strong_body) && last_bar.close >= en - entry_zone); + + case 4: // Sell Limit: 候補価格付近で上ヒゲ反転または売り戻しを確認 + return (last_bar.high >= en - entry_zone && bearish && + (upper_rejection || bearish_break || strong_body)); + + default: + return false; + } + } + +//+------------------------------------------------------------------+ +//| ローソク足レンジを安全に取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief ゼロ除算を避けるため、最小値を持つローソク足レンジを返します。 + * + * @param bar 対象ローソク足。 + * @return high-low。0以下の場合はPoint()を返します。 + */ +double SafeBarRange(const MqlRates &bar) + { + double range = bar.high - bar.low; + if(range <= 0.0) + return Point(); + + return range; + } + +//+------------------------------------------------------------------+ +//| brokerの最小距離制約を満たすか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief pending価格、TP、SLがstop level / freeze levelの最小距離を満たすか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param ctx 現在のAsk/Bid情報。 + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 最小距離を満たす場合はtrue。 + */ +bool MeetsTradeDistanceRules(const int orderType, TickContext &ctx, const double en, const double tp, const double sl) + { + int stop_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_STOPS_LEVEL); + int freeze_level = (int)SymbolInfoInteger(_Symbol, SYMBOL_TRADE_FREEZE_LEVEL); + int min_level = stop_level; + if(freeze_level > min_level) + min_level = freeze_level; + double min_distance = min_level * Point(); + + if(min_distance <= 0.0) + return true; + + double cur_price = CurrentPriceForOrderType(orderType, ctx); + string entry_type = EntryTypeName(orderType); + + if(MathAbs(en - cur_price) < min_distance) + { + Print("[Skip] ", entry_type, " entry is too close. cur=", cur_price, + " en=", en, " min_distance=", min_distance); + return false; + } + + if(MathAbs(tp - en) < min_distance) + { + Print("[Skip] ", entry_type, " TP is too close. en=", en, + " tp=", tp, " min_distance=", min_distance); + return false; + } + + if(MathAbs(en - sl) < min_distance) + { + Print("[Skip] ", entry_type, " SL is too close. en=", en, + " sl=", sl, " min_distance=", min_distance); + return false; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| 注文送信結果に応じてリトライ状態を更新する関数 +//+------------------------------------------------------------------+ +/** + * @brief 注文送信結果に応じてエントリー判定のリトライ状態を更新します。 + * + * @param state EA全体の状態。`chk_cnt` を更新します。 + * @param sent_success 今回送信に成功した注文数。 + * + * 注文が1件も送信されなかった場合、最大10回まで60秒間隔で再判定します。 + */ +void UpdateEntryRetryState(EAState &state, const int sent_success) + { + if(sent_success > 0) + { + state.chk_cnt = 0; + return; + } + + state.chk_cnt += 1; + Print("[Retry] no order sent. chk_cnt=", state.chk_cnt, "/", ENTRY_RETRY_LIMIT, + " (wait next M15 bar)"); + + if(state.chk_cnt < ENTRY_RETRY_LIMIT) + { + g_bars_H1_check = true; // 次のM15確定足で再度エントリー判定を実行する + return; + } + + Print("[Retry End] reached max tries. reset chk_cnt."); + state.chk_cnt = 0; + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを作成します。 + * + * @param name 作成するファイル名。MT5のMQL5\Files配下を基準に扱います。 + */ +void CreateDoneFile(const string name) + { + if(!FileIsExist(name)) + { + int h = FileOpen(name, FILE_WRITE|FILE_TXT); + if(h != INVALID_HANDLE) + FileClose(h); + else + Print("Failed to create file: ", name, " err=", GetLastError()); + } + } + +//+------------------------------------------------------------------+ +//| "process_done.txt"を削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルを削除します。 + * + * @param name 削除するファイル名。存在しない場合は何もしません。 + */ +void DeleteDoneFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + + +//+------------------------------------------------------------------+ +//| "process_done.txt"の存在を確認し、存在する場合はtrueを返す関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したdoneファイルの存在有無を確認します。 + * + * @param name 確認するファイル名。 + * @return ファイルが存在する場合はtrue、存在しない場合はfalse。 + */ +bool CheckDoneFile(const string name) + { + return FileIsExist(name); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを作成する関数 +//+------------------------------------------------------------------+ +/** + * @brief Python起動時刻を実行中ファイルへ記録します。 + * + * @param name 作成するrunningファイル名。 + */ +void CreateRunningFile(const string name, const uint process_id) + { + int h = FileOpen(name, FILE_WRITE | FILE_TXT); + if(h != INVALID_HANDLE) + { + FileWrite(h, IntegerToString((long)TimeCurrent())); + FileWrite(h, IntegerToString((long)process_id)); + FileClose(h); + } + else + Print("Failed to create running file: ", name, " err=", GetLastError()); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルを削除する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定したrunningファイルを削除します。 + * + * @param name 削除するrunningファイル名。 + */ +void DeleteRunningFile(const string name) + { + if(FileIsExist(name)) + FileDelete(name); + } + +//+------------------------------------------------------------------+ +//| Python実行開始時刻を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたPython起動時刻を読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めた起動時刻。失敗時は0。 + */ +datetime LoadRunningStartedAt(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (datetime)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行プロセスIDを読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルに記録されたプロセスIDを読み込みます。 + * + * @param name 読み込むrunningファイル名。 + * @return 読み込めたプロセスID。旧形式または失敗時は0。 + */ +uint LoadRunningProcessId(const string name) + { + if(!FileIsExist(name)) + return 0; + + int h = FileOpen(name, FILE_READ | FILE_TXT); + if(h == INVALID_HANDLE) + { + Print("Failed to open running file: ", name, " err=", GetLastError()); + return 0; + } + + FileReadString(h); // started_at + if(FileIsEnding(h)) + { + FileClose(h); + return 0; + } + + string line = FileReadString(h); + FileClose(h); + return (uint)StringToInteger(line); + } + +//+------------------------------------------------------------------+ +//| Python実行中ファイルがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief runningファイルの起動時刻からPython待ち上限を超えているか判定します。 + * + * @param name 判定するrunningファイル名。 + * @return タイムアウトしている場合はtrue。 + */ +bool IsRunningFileTimedOut(const string name) + { + datetime started_at = LoadRunningStartedAt(name); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| 外部プロセス状態を初期値に戻す関数 +//+------------------------------------------------------------------+ +void ResetExternalProcessState(ExternalProcessState &process) + { + if(process.handle != 0) + CloseHandle(process.handle); + + process.handle = 0; + process.process_id = 0; + process.active = false; + process.exit_code_ready = false; + process.exit_code = 0; + process.started_at = 0; + } + +//+------------------------------------------------------------------+ +//| runningファイルのPIDからプロセスハンドルを復元する関数 +//+------------------------------------------------------------------+ +bool AttachRunningProcess(const string running_file, const string label, ExternalProcessState &process) + { + if(process.active && process.handle != 0) + return true; + + uint process_id = LoadRunningProcessId(running_file); + if(process_id == 0) + return false; + + long handle = OpenProcess(PROCESS_QUERY_LIMITED_INFORMATION | SYNCHRONIZE, 0, process_id); + if(handle == 0) + { + Print("[", label, "] failed to open running process. pid=", process_id, " err=", GetLastError()); + return false; + } + + process.handle = handle; + process.process_id = process_id; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = LoadRunningStartedAt(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスの終了状態を更新する関数 +//+------------------------------------------------------------------+ +bool UpdateExternalProcessStatus(ExternalProcessState &process, const string label) + { + if(!process.active || process.handle == 0) + return true; + + int wait_result = WaitForSingleObject(process.handle, 0); + if(wait_result == WAIT_TIMEOUT) + return false; + + uint exit_code = 1; + if(wait_result == WAIT_OBJECT_0) + { + if(GetExitCodeProcess(process.handle, exit_code) == 0) + { + Print("[", label, "] failed to get process exit code. err=", GetLastError()); + exit_code = 1; + } + } + else + { + Print("[", label, "] WaitForSingleObject failed. result=", wait_result, " err=", GetLastError()); + exit_code = 1; + } + + CloseHandle(process.handle); + process.handle = 0; + process.active = false; + process.exit_code_ready = true; + process.exit_code = exit_code; + Print("[", label, "] process finished. pid=", process.process_id, " exit_code=", exit_code); + return true; + } + +//+------------------------------------------------------------------+ +//| 外部プロセスがタイムアウトしているか判定する関数 +//+------------------------------------------------------------------+ +bool IsExternalProcessTimedOut(ExternalProcessState &process, const string running_file) + { + datetime started_at = process.started_at; + if(started_at <= 0) + started_at = LoadRunningStartedAt(running_file); + if(started_at <= 0) + return true; + + return (TimeCurrent() - started_at >= PYTHON_TIMEOUT_SECONDS); + } + +//+------------------------------------------------------------------+ +//| バッチファイルをプロセスハンドル付きで起動する関数 +//+------------------------------------------------------------------+ +bool StartBatchProcess(const string bat_file, const string running_file, const string label, ExternalProcessState &process) + { + STARTUPINFO_W startup_info = {}; + PROCESS_INFORMATION process_info = {}; + + startup_info.cb = (uint)sizeof(startup_info); + + string cmd_exe = "C:\\Windows\\System32\\cmd.exe"; + string command_line = "\"" + cmd_exe + "\" /c \"\"" + bat_file + "\"\""; + + int created = CreateProcessW( + cmd_exe, + command_line, + 0, + 0, + 0, + CREATE_NO_WINDOW, + 0, + "C:\\ea_py", + startup_info, + process_info + ); + + if(created == 0 || process_info.hProcess == 0) + { + Print("[", label, "] CreateProcessW failed. file=", bat_file, " err=", GetLastError()); + ResetExternalProcessState(process); + DeleteRunningFile(running_file); + return false; + } + + if(process_info.hThread != 0) + CloseHandle(process_info.hThread); + + process.handle = process_info.hProcess; + process.process_id = process_info.dwProcessId; + process.active = true; + process.exit_code_ready = false; + process.exit_code = STILL_ACTIVE; + process.started_at = TimeCurrent(); + + CreateRunningFile(running_file, process.process_id); + Print("[", label, "] process started. pid=", process.process_id, " file=", bat_file); + return true; + } + +//+------------------------------------------------------------------+ +//| EA起動時のdoneファイル状態を整える関数 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時にPython実行中状態を壊さない範囲でdoneファイルを準備します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + */ +void PrepareDoneFileOnInit(const string done_file, const string running_file, const string label) + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return; + } + + if(FileIsExist(running_file)) + { + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running file found on init. Remove marker without creating done."); + DeleteRunningFile(running_file); + } + else + Print("[", label, "] Python seems to be running on init. Keep waiting."); + return; + } + + Print("[", label, "] done file not found on init. Keep as not-ready until the next trigger."); + } + +//+------------------------------------------------------------------+ +//| Python完了状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief doneファイルがあれば完了扱いにし、runningファイルを片付けます。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @return 完了済みの場合はtrue。 + */ +bool IsProcessResultReady(const string done_file, const string running_file, const string result_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + return false; + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] process failed. exit_code=", process.exit_code); + return false; + } + } + else if(!CheckDoneFile(done_file)) + { + return false; + } + } + + if(!CheckDoneFile(done_file)) + return false; + + if(!FileIsExist(result_file)) + { + Print("[", label, "] done exists but result file is missing: ", result_file); + return false; + } + + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| Python開始可能状態を返す関数 +//+------------------------------------------------------------------+ +/** + * @brief done/runningファイル状態からPythonを新規起動できるか判定します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return 起動可能な場合はtrue。 + */ +bool IsProcessStartAllowed(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(process.active || FileIsExist(running_file)) + { + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python timed out, but the process is still running. Keep waiting."); + else + Print("[", label, "] Python is still running. Skip execute."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + Print("[", label, "] previous process failed. exit_code=", process.exit_code, ". Retry is allowed."); + } + else + { + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + if(IsRunningFileTimedOut(running_file)) + { + Print("[", label, "] stale running marker without live process. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] running marker exists, but process cannot be verified yet. Skip execute."); + return false; + } + } + + if(CheckDoneFile(done_file)) + { + DeleteRunningFile(running_file); + return true; + } + + Print("[", label, "] done file missing without running marker. Start new process."); + return true; + } + +//+------------------------------------------------------------------+ +//| タイムアウトしたPython待ちを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonがdoneファイルを返さない状態を検知し、再実行できる状態へ戻します。 + */ +void RecoverTimedOutPythonProcesses() + { + if(RecoverTimedOutProcess(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + g_ea.load_trend_flg = false; + g_init_trend_pending = true; + } + + if(RecoverTimedOutProcess(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + g_ea.load_target_flg = false; + g_bars_H1_check = false; + g_bars_M15_check = false; + g_ea.chk_cnt = 0; + g_init_entry_pending = true; + } + } + +//+------------------------------------------------------------------+ +//| 個別Python処理のタイムアウトを復旧する関数 +//+------------------------------------------------------------------+ +/** + * @brief 1種類のPython処理について、実行中ファイルのタイムアウトを検知します。 + * + * @param done_file 完了判定ファイル名。 + * @param running_file 実行中判定ファイル名。 + * @param label ログ表示用ラベル。 + * @return タイムアウト復旧を行った場合はtrue。 + */ +bool RecoverTimedOutProcess(const string done_file, const string running_file, const string label, ExternalProcessState &process) + { + if(CheckDoneFile(done_file) && !process.active) + { + DeleteRunningFile(running_file); + return false; + } + + if(!process.active && !FileIsExist(running_file)) + return false; + + if(process.active || AttachRunningProcess(running_file, label, process)) + { + if(!UpdateExternalProcessStatus(process, label)) + { + if(IsExternalProcessTimedOut(process, running_file)) + Print("[", label, "] Python exceeded ", PYTHON_TIMEOUT_SECONDS, + " seconds, but the process is still running. Keep waiting."); + return false; + } + + DeleteRunningFile(running_file); + if(process.exit_code_ready && process.exit_code != 0) + { + Print("[", label, "] Python process failed. Retry on next trigger."); + return true; + } + + if(!CheckDoneFile(done_file)) + { + Print("[", label, "] Python process ended without done file. Retry on next trigger."); + return true; + } + + return false; + } + + if(!IsRunningFileTimedOut(running_file)) + return false; + + Print("[", label, "] Python did not finish within ", + PYTHON_TIMEOUT_SECONDS, " seconds, and no live process was found. Retry is allowed."); + DeleteRunningFile(running_file); + return true; + } + +//+------------------------------------------------------------------+ +//| 最新のOHLCデータを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief 指定時間足のOHLCデータを取得します。 + * + * @param times 取得したバー時刻を格納する配列。 + * @param open_prices 取得したOpen価格を格納する配列。 + * @param high_prices 取得したHigh価格を格納する配列。 + * @param low_prices 取得したLow価格を格納する配列。 + * @param close_prices 取得したClose価格を格納する配列。 + * @param tf 取得対象の時間足。 + * @param bars_count 取得するバー本数。 + * @return 取得に成功した場合はtrue、失敗した場合はfalse。 + * + * `OHLC_START_SHIFT=1` のため、形成中のバーではなく確定足から取得します。 + */ +bool GetLatestOHLC(datetime ×[], double &open_prices[], double &high_prices[], double &low_prices[], double &close_prices[], ENUM_TIMEFRAMES tf, int bars_count) + { + ArrayResize(times, 0); + ArrayResize(open_prices, 0); + ArrayResize(high_prices, 0); + ArrayResize(low_prices, 0); + ArrayResize(close_prices, 0); + + if(bars_count < 2) + return false; + +// OHLCデータを取得 +// CopyTime と CopyRates を別々に呼ばず、MqlRates の time/open/high/low/close を同一配列から取得する。 +// OHLC_START_SHIFT=1 のため、形成中の0本目ではなく確定足からPythonへ渡す。 + MqlRates rates[]; + int copied = CopyRates(_Symbol, tf, OHLC_START_SHIFT, bars_count, rates); + if(copied <= 0) + { + Print(__FUNCTION__, ": Failed to copy rates data (bars=", bars_count, ") err=", GetLastError()); + return false; + } + + ArrayResize(times, copied); + ArrayResize(open_prices, copied); + ArrayResize(high_prices, copied); + ArrayResize(low_prices, copied); + ArrayResize(close_prices, copied); + +// 必要なデータを配列に格納 + for(int i = 0; i < copied; i++) + { + times[i] = rates[i].time; + open_prices[i] = rates[i].open; + high_prices[i] = rates[i].high; + low_prices[i] = rates[i].low; + close_prices[i] = rates[i].close; + } + + return true; + } + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力する関数 +//+------------------------------------------------------------------+ +/** + * @brief OHLC配列をPython入力用CSVとして出力します。 + * + * @param filename 出力するCSVファイル名。 + * @param times バー時刻配列。 + * @param open_prices Open価格配列。 + * @param high_prices High価格配列。 + * @param low_prices Low価格配列。 + * @param close_prices Close価格配列。 + * @return CSV出力に成功した場合はtrue。 + */ +bool RecordOHLC(const string filename, const datetime ×[], const double &open_prices[], const double &high_prices[], const double &low_prices[], const double &close_prices[]) + { +// 出力先ファイル名 +// string filename = "ohlc.csv"; + +// ファイルを "書き込みモード" でオープン (テキスト/ANSI) + int fileHandle = FileOpen(filename, FILE_WRITE | FILE_TXT | FILE_ANSI); + +// ファイルが開けたかチェック + if(fileHandle == INVALID_HANDLE) + { + Print(__FUNCTION__, " : Failed to open file: ", GetLastError()); + return false; + } + +// 配列サイズを取得 (times, open, high, low, close の要素数は同じ前提) + int size = ArraySize(times); + +// ヘッダー行を追加 + FileWrite(fileHandle, "Time,Open,High,Low,Close"); + +// 小数点の桁数を取得 + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + +// フォーマット文字列を動的に生成 + string formatString = StringFormat("%%s,%%.%df,%%.%df,%%.%df,%%.%df", digits, digits, digits, digits); + +// 1行ずつ "時刻, Open, High, Low, Close" の形式で書き込み + for(int i = 0; i < size; i++) + { + // 時間をフォーマット + string timeStr = TimeToString(times[i], TIME_DATE | TIME_MINUTES); + + // 1行分の文字列を生成 (小数点以下の桁数を `digits` に調整) + string line = StringFormat(formatString, + timeStr, open_prices[i], high_prices[i], low_prices[i], close_prices[i]); + + // ファイルに書き込み (改行付き) + FileWrite(fileHandle, line); + } + +// 書き込み終了後、ファイルを閉じる + FileClose(fileHandle); + return true; + } + +//+------------------------------------------------------------------+ +//| バッチファイル(Pythonスクリプト)を実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4トレンド判定用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_trend.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchTrend() + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + return false; + + DeleteDoneFile(done_trend_file); + return StartBatchProcess(get_trend_reply_bat, running_trend_file, "trend", g_trend_process); + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1エントリー価格生成用バッチファイルを起動します。 + * + * @return 起動に成功した場合はtrue、Python実行中または起動失敗時はfalse。 + * + * 起動直前に`process_done_entry.txt`を削除し、プロセスハンドルを保持して終了確認できる状態にします。 + */ +bool ExecuteBatchEntry() + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + return false; + + DeleteDoneFile(done_entry_file); + return StartBatchProcess(get_entry_reply_bat, running_entry_file, "entry", g_entry_process); + } + + +//+------------------------------------------------------------------+ +//| "ohlc.csv"を出力後、バッチファイルの実行する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 OHLCをCSV出力し、トレンド判定Pythonを起動します。 + * + * @param state EA全体の状態。トレンド読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Trend(EAState &state) + { + if(!IsProcessStartAllowed(done_trend_file, running_trend_file, "trend", g_trend_process)) + { + Print("Trend Python is still running. Skip H4 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H4, HISTORY_BARS)) + { Print("GetLatestOHLC(H4) failed."); return false; } + + if(!RecordOHLC("ohlc_H4.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H4) failed."); return false; } + + if(!ExecuteBatchTrend()) + return false; + + state.load_trend_flg = true; + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief H1 OHLCをCSV出力し、エントリー価格生成Pythonを起動します。 + * + * @param state EA全体の状態。ターゲット価格読込待ちフラグを更新します。 + * @return CSV出力とバッチ起動が開始できた場合はtrue。 + */ +bool RecordOHLCAndExecuteBatch_Entry(EAState &state) + { + if(!IsProcessStartAllowed(done_entry_file, running_entry_file, "entry", g_entry_process)) + { + Print("Entry Python is still running. Skip H1 CSV update."); + return false; + } + + datetime times[]; + double open_prices[], high_prices[], low_prices[], close_prices[]; + + if(!GetLatestOHLC(times, open_prices, high_prices, low_prices, close_prices, + PERIOD_H1, HISTORY_BARS)) + { Print("GetLatestOHLC(H1) failed."); return false; } + + if(!RecordOHLC("ohlc_H1.csv", times, open_prices, high_prices, low_prices, close_prices)) + { Print("RecordOHLC(H1) failed."); return false; } + + if(!ExecuteBatchEntry()) + return false; + + state.load_target_flg = true; + return true; + } + + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したトレンド判定結果を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。H4 market_stateと更新フラグを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTrendState(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_trend_file); + if(ProcessDone && g_ea.load_trend_flg) + { + int trend_state; + LoadTrendState(trend_state); + state.trend_state = trend_state; + Print("market_state: ", state.trend_state, " (", MarketStateName(state.trend_state), ")"); + g_ea.load_trend_flg = false; + g_ea.last_trend_update = TimeLocal(); + } + return true; + } + +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +/** + * @brief `trend_state.txt` からH4 market_state判定値を読み込みます。 + * + * @param trend_state 読み込んだ値を格納する参照。0..6のmarket_state。 + * + * ファイル未存在、読込失敗、異常値の場合は安全側として6(新規停止)を設定します。 + */ +void LoadTrendState(int &trend_state) + { + string filename = trend_state_file; + trend_state = MARKET_ABNORMAL_VOL_STOP; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + string line = FileReadString(filehandle); + int value = (int)StringToInteger(line); + if(value >= MARKET_LOW_VOL_RANGE && value <= MARKET_ABNORMAL_VOL_STOP) + trend_state = value; + else + Print("Invalid market_state value: ", line, ". Use 6."); + FileClose(filehandle); + } + else + { Print("Failed to open trend_state.txt"); trend_state = MARKET_ABNORMAL_VOL_STOP; } + } + else + { Print("trend_state.txt not found"); trend_state = MARKET_ABNORMAL_VOL_STOP; } + } + +//+------------------------------------------------------------------+ +//| ターゲット価格を取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief Pythonが出力したエントリー価格群を必要なタイミングで読み込みます。 + * + * @param state EA全体の状態。`res_chk` と4タイプ分のen/tp/slを更新します。 + * @return 現状は常にtrue。 + */ +bool GetTargetPrices(EAState &state) + { + bool ProcessDone = CheckDoneFile(done_entry_file); // ←変更 + if(ProcessDone && g_ea.load_target_flg) // ←変更 + { + double target_prices[]; + LoadTargetPrices(target_prices); + + int digits = (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS); + + // 先頭は res_chk + state.res_chk = (int)target_prices[0]; + + // ★ 1..4 の (en,tp,sl) を読む:合計12個 + for(int t=1; t<=4; t++) + { + int base = 1 + (t-1)*3; // 1,4,7,10 + state.en_price[t] = NormalizeDouble(target_prices[base + 0], digits); + state.tp_price[t] = NormalizeDouble(target_prices[base + 1], digits); + state.sl_price[t] = NormalizeDouble(target_prices[base + 2], digits); + } + + // ログ(任意) + Print("target_prices: res=", state.res_chk, + " | T1 en=", state.en_price[1], " tp=", state.tp_price[1], " sl=", state.sl_price[1], + " | T2 en=", state.en_price[2], " tp=", state.tp_price[2], " sl=", state.sl_price[2], + " | T3 en=", state.en_price[3], " tp=", state.tp_price[3], " sl=", state.sl_price[3], + " | T4 en=", state.en_price[4], " tp=", state.tp_price[4], " sl=", state.sl_price[4]); + + g_ea.load_target_flg = false; // ←変更 + g_ea.last_target_update = TimeLocal(); // ←変更 + g_ea.target_loaded_at = TimeCurrent(); + } + return true; + } +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| "target_prices.txt"を読み込む関数 +//+------------------------------------------------------------------+ +/** + * @brief `target_prices.txt` から13個の数値を読み込みます。 + * + * @param target_prices 読み込んだ数値を格納する配列。先頭がres_chk、以降は4タイプ分のen/tp/sl。 + * + * 行数不足の場合は`res_chk=0`として、ターゲット価格を無効扱いにします。 + */ +void LoadTargetPrices(double &target_prices[]) + { + ArrayResize(target_prices, TARGET_SIZE); + for(int i = 0; i < TARGET_SIZE; i++) + target_prices[i] = DEFAULT_TARGET_PRICE; + + string filename = target_prices_file; + + if(FileIsExist(filename)) + { + int filehandle = FileOpen(filename, FILE_READ | FILE_TXT); + if(filehandle != INVALID_HANDLE) + { + int i = 0; + while(!FileIsEnding(filehandle) && i < TARGET_SIZE) + { + string line = FileReadString(filehandle); + target_prices[i] = StringToDouble(line); + i++; + } + FileClose(filehandle); + + if(i < TARGET_SIZE) + { + Print("target_prices.txt line count is short. loaded=", i, " required=", TARGET_SIZE); + target_prices[0] = 0; // 不完全なファイルは無効扱い + } + } + else + { + Print("Failed to open file. Error code: ", GetLastError()); + } + } + else + { + Print("File does not exist: ", filename); + } + } + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| エントリー注文を送信する関数 (1:buy-stop, 2:buy-limit, 3:sell-stop, 4:sell-limit) +//+------------------------------------------------------------------+ +/** + * @brief 指定された注文タイプでペンディング注文を送信します。 + * + * @param orderType 注文タイプ。1=Buy Stop、2=Buy Limit、3=Sell Stop、4=Sell Limit。 + * @param price エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return 注文が正常に受理された場合はtrue、それ以外はfalse。 + */ +bool SendOrder(int orderType, double price, double tp, double sl) + { + MqlTradeRequest request = {}; + MqlTradeResult result = {}; + + request.magic = magic_number; + request.symbol = _Symbol; + request.volume = lot_size; + request.deviation = slippage; + request.type_filling = GetOrderFillingPolicy(_Symbol); + request.price = NormalizeDouble(price, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.tp = NormalizeDouble(tp, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.sl = NormalizeDouble(sl, (int)SymbolInfoInteger(_Symbol, SYMBOL_DIGITS)); + request.comment = RequestComment(orderType); + + string orderTypeStr = ""; + +// 指値または逆指値注文 + request.action = TRADE_ACTION_PENDING; + ApplyPendingOrderExpiration(request); + + switch(orderType) + { + case 1: // 順張り買い (buy-stop) + request.type = ORDER_TYPE_BUY_STOP; + orderTypeStr = "buy-stop"; + break; + + case 2: // 逆張り買い (buy-limit) + request.type = ORDER_TYPE_BUY_LIMIT; + orderTypeStr = "buy-limit"; + break; + + case 3: // 順張り売り (sell-stop) + request.type = ORDER_TYPE_SELL_STOP; + orderTypeStr = "sell-stop"; + break; + + case 4: // 逆張り売り (sell-limit) + request.type = ORDER_TYPE_SELL_LIMIT; + orderTypeStr = "sell-limit"; + break; + + default: // 不正なorderType + Print(__FUNCTION__, ": invalid orderType=", orderType); + return false; + } + +// 注文送信 + if(!OrderSend(request, result)) + { + Print(__FUNCTION__, ": OrderSend failed. err=", GetLastError()); + return false; + } + +// 注文成功確認 + if(result.retcode == TRADE_RETCODE_PLACED || + result.retcode == TRADE_RETCODE_DONE || + result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print(request.comment + " - " + orderTypeStr + " order success (ticket=", result.order, ")"); + return true; + } + else + { + Print(__FUNCTION__, ": OrderSend retcode=", result.retcode); + return false; + } + } + +//+------------------------------------------------------------------+ +//| 注文の執行ポリシーを取得する関数 +//+------------------------------------------------------------------+ +/** + * @brief シンボルに対応する注文執行ポリシーを取得します。 + * + * @param symbol 対象シンボル。 + * @return 利用可能な執行ポリシー。IOC、FOK、RETURNの順で選択します。 + */ +ENUM_ORDER_TYPE_FILLING GetOrderFillingPolicy(string symbol) + { + long fill_mode = SymbolInfoInteger(symbol, SYMBOL_FILLING_MODE); + if((fill_mode & SYMBOL_FILLING_IOC) != 0) + return ORDER_FILLING_IOC; + if((fill_mode & SYMBOL_FILLING_FOK) != 0) + return ORDER_FILLING_FOK; + return ORDER_FILLING_RETURN; + } + +//+------------------------------------------------------------------+ +//| ペンディング注文の有効期限を設定する関数 +//+------------------------------------------------------------------+ +/** + * @brief brokerが対応している範囲でpending orderにサーバー側の期限を設定します。 + * + * @param request 更新対象の取引リクエスト。 + */ +void ApplyPendingOrderExpiration(MqlTradeRequest &request) + { + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + long expiration_mode = SymbolInfoInteger(request.symbol, SYMBOL_EXPIRATION_MODE); + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if(expiration_seconds > 0 && + (expiration_mode & SYMBOL_EXPIRATION_SPECIFIED_DAY) != 0) + { + request.type_time = ORDER_TIME_SPECIFIED_DAY; + request.expiration = TimeCurrent() + expiration_seconds; + return; + } + + if((expiration_mode & SYMBOL_EXPIRATION_DAY) != 0) + { + request.type_time = ORDER_TIME_DAY; + return; + } + + request.type_time = ORDER_TIME_GTC; + } + +//+------------------------------------------------------------------+ +//| 注文コメント(ローカル時刻)を生成する関数(先頭に orderType を付与) +//+------------------------------------------------------------------+ +/** + * @brief 注文コメント文字列を生成します。 + * + * @param orderType 注文タイプ番号。 + * @return 注文タイプとPCローカル時刻を含むコメント文字列。 + */ +string RequestComment(int orderType) + { + datetime localTime = TimeLocal(); + string timeStr = TimeToString(localTime, TIME_DATE|TIME_MINUTES); + +// 先頭に 1,2,3,4 を付ける(例: "1 | PC Time: 2026.02.14 19:05") + return IntegerToString(orderType) + " | PC Time: " + timeStr; + } + +//+------------------------------------------------------------------+ +//| H4 market_stateに対して許可された注文タイプか判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief H4 market_stateに対して指定注文タイプが許可されるか判定します。 + * + * @param orderType 注文タイプ番号。 + * @param trend_state H4 market_state(0..6)。 + * @return 許可される注文タイプならtrue。異常ボラまたは異常値ではfalse。 + */ +bool IsOrderTypeAllowedByTrend(const int orderType, const int trend_state) + { + switch(trend_state) + { + case MARKET_LOW_VOL_RANGE: + case MARKET_HIGH_VOL_RANGE: + return (orderType == 2 || orderType == 4); // Range: Buy Limit / Sell Limitのみ + + case MARKET_LOW_VOL_UP: + case MARKET_HIGH_VOL_UP: + return (orderType == 1 || orderType == 2); // Up: Buy Stop / Buy Limitのみ + + case MARKET_LOW_VOL_DOWN: + case MARKET_HIGH_VOL_DOWN: + return (orderType == 3 || orderType == 4); // Down: Sell Stop / Sell Limitのみ + + case MARKET_ABNORMAL_VOL_STOP: + default: + return false; // 異常ボラまたは異常値は新規注文しない + } + } + +//+------------------------------------------------------------------+ +//| target_prices.txt の価格が有効か判定する関数 +//+------------------------------------------------------------------+ +/** + * @brief エントリー価格、利確価格、損切価格が有効値か判定します。 + * + * @param en エントリー価格。 + * @param tp 利確価格。 + * @param sl 損切価格。 + * @return すべて0より大きい場合はtrue。 + */ +bool HasValidTargetPrices(const double en, const double tp, const double sl) + { + return (en > 0.0 && tp > 0.0 && sl > 0.0); + } + +//+------------------------------------------------------------------+ +//| 自EAの未約定注文数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする未約定注文数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致する未約定注文数。 + */ +int CountMyPendingOrders() + { + int count = 0; + + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(ticket == 0) + continue; + + if(!OrderSelect(ticket)) + continue; + + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + + if((int)OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAのポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが管理対象とする保有ポジション数を数えます。 + * + * @return `_Symbol` と `magic_number` が一致するポジション数。 + */ +int CountMyPositions() + { + int count = 0; + + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string symbol = PositionGetSymbol(i); + if(symbol == "") + continue; + + if(symbol != _Symbol) + continue; + + if((int)PositionGetInteger(POSITION_MAGIC) != magic_number) + continue; + + count++; + } + + return count; + } + +//+------------------------------------------------------------------+ +//| 自EAの注文+ポジション数を数える関数 +//+------------------------------------------------------------------+ +/** + * @brief このEAが使用中の注文数とポジション数の合計を返します。 + * + * @return 未約定注文数 + 保有ポジション数。 + */ +int CountMyUsed() + { + return CountMyPendingOrders() + CountMyPositions(); + } + + +//+------------------------------------------------------------------+ +//| 時間が経過したエントリー注文をキャンセルする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でENTRY_H1_LIMIT時間を超えた未約定注文をキャンセルします。 + * + * @return 1件以上キャンセルに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致する未約定注文のみです。 + */ +bool CancelExpiredOrders() + { + bool result = false; + +// 未決済注文の数を取得 + for(int i = OrdersTotal() - 1; i >= 0; i--) + { + ulong ticket = OrderGetTicket(i); + if(OrderSelect(ticket)) // 注文を選択 + { + if(OrderGetString(ORDER_SYMBOL) != _Symbol) + continue; + if(OrderGetInteger(ORDER_MAGIC) != magic_number) + continue; + + datetime open_time = (datetime)OrderGetInteger(ORDER_TIME_SETUP); + + int expiration_seconds = ENTRY_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(open_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - open_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + request.action=TRADE_ACTION_REMOVE; // 取引操作タイプ + request.order = ticket; // 注文チケット + if(!OrderSend(request, trade_result)) // 削除リクエスト送信 + { + Print("Failed to delete order. Ticket: ", ticket, " Error: ", GetLastError()); + } + else + { + if(trade_result.retcode == TRADE_RETCODE_DONE) + { + Print("Order canceled successfully due to a time limit. Ticket: ", ticket); + result = true; // 削除成功フラグ + } + else + { + Print("Order cancellation failed. Ticket: ", ticket, " Retcode: ", trade_result.retcode); + } + } + } + } + } + return result; + } + +//+------------------------------------------------------------------+ +//| 時間が経過したポジションをクローズする関数 +//+------------------------------------------------------------------+ +/** + * @brief 実時間でCLOSE_H1_LIMIT時間を超えた保有ポジションをクローズします。 + * + * @return 1件以上クローズに成功した場合はtrue。 + * + * 対象は`_Symbol` と `magic_number` が一致するポジションのみです。 + */ +bool CloseExpiredPositions() + { + bool result = false; + +// ポジションを逆順でループ + for(int i = PositionsTotal() - 1; i >= 0; i--) + { + string position_symbol = PositionGetSymbol(i); + if(position_symbol!="") + { + // Magic Number とシンボルでフィルタリング + if(position_symbol != _Symbol) + continue; + if(PositionGetInteger(POSITION_MAGIC) == magic_number) + { + // ポジションのエントリー時刻を取得 + datetime entry_time = (datetime)PositionGetInteger(POSITION_TIME); + + int expiration_seconds = CLOSE_H1_LIMIT * PeriodSeconds(PERIOD_H1); + if(entry_time <= 0 || expiration_seconds <= 0) + continue; + + if(TimeCurrent() - entry_time >= expiration_seconds) + { + MqlTradeRequest request = {}; + MqlTradeResult trade_result = {}; + + // ポジションタイプに応じてリクエストを設定 + int position_type = (int)PositionGetInteger(POSITION_TYPE); + request.action = TRADE_ACTION_DEAL; + request.position = PositionGetInteger(POSITION_TICKET); // ポジションのチケット番号 + request.symbol = position_symbol; // シンボル + request.volume = PositionGetDouble(POSITION_VOLUME); // ポジションサイズ + request.price = (position_type == POSITION_TYPE_BUY) + ? SymbolInfoDouble(position_symbol, SYMBOL_BID) // BUYの場合はBIDでクローズ + : SymbolInfoDouble(position_symbol, SYMBOL_ASK); // SELLの場合はASKでクローズ + request.deviation = slippage; + request.type = (position_type == POSITION_TYPE_BUY) + ? ORDER_TYPE_SELL // BUYポジションをSELLでクローズ + : ORDER_TYPE_BUY; // SELLポジションをBUYでクローズ + request.type_filling = GetOrderFillingPolicy(position_symbol); // 注文執行ポリシー + request.magic = magic_number; + + // 注文送信 + if(!OrderSend(request, trade_result)) + { + Print("Failed to close position. Ticket: ", request.position, " Error: ", GetLastError()); + continue; + } + + // 結果の確認 + if(trade_result.retcode == TRADE_RETCODE_DONE || trade_result.retcode == TRADE_RETCODE_DONE_PARTIAL) + { + Print("Position closed successfully due to a time limit. Ticket: ", request.position); + result = true; // 少なくとも1つ成功した場合 + } + else + { + Print("Failed to close position. Ticket: ", request.position, " Retcode: ", trade_result.retcode); + } + } + } + } + else + { + Print("Failed to select position at index ", i, ". Error: ", GetLastError()); + } + } + return result; + } diff --git a/Experts/MyProject/HIT-EA_refactor_ver5.mq5 b/Experts/MyProject/HIT-EA_refactor_ver5.mq5 new file mode 100644 index 0000000..724829b --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver5.mq5 @@ -0,0 +1,441 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input int input_entry_max_candidate_age_minutes = 45; // H1候補価格を新規発注に使う最大経過分数 +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 b/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 new file mode 100644 index 0000000..042e45b --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver5.mq5.bak_codex_20260513_002404 @@ -0,0 +1,440 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver6.mq5 b/Experts/MyProject/HIT-EA_refactor_ver6.mq5 new file mode 100644 index 0000000..653da17 --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver6.mq5 @@ -0,0 +1,462 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; +string target_zones_file = "target_zones.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +enum SplitLotMode + { + SPLIT_LOT_TOTAL = 0, // 総ロットをN分割 + SPLIT_LOT_FIXED = 1 // 1注文あたり固定ロット + }; + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input int input_position_limit = 10; // 自EAの未約定注文+ポジション上限 +input bool use_split_entry_zone = false; // H1予測ゾーンで分割エントリーする +input int split_entry_count = 3; // 分割エントリー本数(1..10) +input SplitLotMode split_lot_mode = SPLIT_LOT_TOTAL; // 分割ロット配分モード +input double split_total_lot_size = 0.09; // 総量分割モードの合計ロット +input double split_fixed_lot_size = 0.01; // 固定ロットモードの1注文ロット +input bool cancel_old_split_pending_on_new_zone = true; // 新ゾーン読込時に旧分割pendingを取消 +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input int input_entry_max_candidate_age_minutes = 120; // H1候補価格を新規発注に使う最大経過分数 +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 2 // H1本数(=2時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define TARGET_ZONE_SCHEMA_VERSION 2 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + int zone_res_chk; // 予測ゾーンが正しく取得できたか + string zone_candidate_id; // H1確定足由来の候補ID + double zone_low[5]; // 分割エントリー用ゾーン下限 + double zone_high[5]; // 分割エントリー用ゾーン上限 + double zone_tp[5]; // 分割エントリー用TP + double zone_sl[5]; // 分割エントリー用SL + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_20260523_230523 b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_20260523_230523 new file mode 100644 index 0000000..724829b --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_20260523_230523 @@ -0,0 +1,441 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input int input_entry_max_candidate_age_minutes = 45; // H1候補価格を新規発注に使う最大経過分数 +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260524_000548 b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260524_000548 new file mode 100644 index 0000000..724829b --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260524_000548 @@ -0,0 +1,441 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input int input_entry_max_candidate_age_minutes = 45; // H1候補価格を新規発注に使う最大経過分数 +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260527_224059 b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260527_224059 new file mode 100644 index 0000000..3c2d013 --- /dev/null +++ b/Experts/MyProject/HIT-EA_refactor_ver6.mq5.bak_codex_20260527_224059 @@ -0,0 +1,462 @@ +//+------------------------------------------------------------------+ +//| HIT_EA.mq5 | +//| Copyright 2026, nanpin-martin.com | +//| https://www.nanpin-martin.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2026, nanpin-martin.com" +#property link "https://nanpin-martin.com/" +#property version "1.01" + +#include +#include + +//+------------------------------------------------------------------+ +//| バッチファイル実行用の設定 +//+------------------------------------------------------------------+ + +// Windowsプロセス起動・終了確認用 +#define CREATE_NO_WINDOW 0x08000000 +#define PROCESS_QUERY_LIMITED_INFORMATION 0x00001000 +#define SYNCHRONIZE 0x00100000 +#define WAIT_OBJECT_0 0 +#define WAIT_TIMEOUT 258 +#define STILL_ACTIVE 259 + +struct STARTUPINFO_W + { + uint cb; + string lpReserved; + string lpDesktop; + string lpTitle; + uint dwX; + uint dwY; + uint dwXSize; + uint dwYSize; + uint dwXCountChars; + uint dwYCountChars; + uint dwFillAttribute; + uint dwFlags; + ushort wShowWindow; + ushort cbReserved2; + long lpReserved2; + long hStdInput; + long hStdOutput; + long hStdError; + }; + +struct PROCESS_INFORMATION + { + long hProcess; + long hThread; + uint dwProcessId; + uint dwThreadId; + }; + +#import "kernel32.dll" +int CreateProcessW(string lpApplicationName, string lpCommandLine, long lpProcessAttributes, long lpThreadAttributes, int bInheritHandles, uint dwCreationFlags, long lpEnvironment, string lpCurrentDirectory, STARTUPINFO_W &lpStartupInfo, PROCESS_INFORMATION &lpProcessInformation); +int WaitForSingleObject(long hHandle, uint dwMilliseconds); +int GetExitCodeProcess(long hProcess, uint &lpExitCode); +int CloseHandle(long hObject); +long OpenProcess(uint dwDesiredAccess, int bInheritHandle, uint dwProcessId); +#import + +// バッチファイルのパス +string get_trend_reply_bat = "C:\\ea_py\\bat\\get_trend_reply.bat"; // H4: trend +string get_entry_reply_bat = "C:\\ea_py\\bat\\get_entry_reply.bat"; // H1: entry + +// プロセス完了ファイルの設定 +string done_trend_file = "process_done_trend.txt"; +string done_entry_file = "process_done_entry.txt"; + +// Python出力ファイル +string trend_state_file = "trend_state.txt"; +string target_prices_file = "target_prices.txt"; +string target_zones_file = "target_zones.txt"; + +// Python実行中を判定するための管理ファイル +string running_trend_file = "process_running_trend.txt"; +string running_entry_file = "process_running_entry.txt"; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| インプットの設定 +//+------------------------------------------------------------------+ + +enum SplitLotMode + { + SPLIT_LOT_TOTAL = 0, // 総ロットをN分割 + SPLIT_LOT_FIXED = 1 // 1注文あたり固定ロット + }; + +//--- 入力パラメータ +input double lot_size = 0.01; // ロット数 +input double spread_limit = 60; // 許容スプレッド(point) +input int magic_number = 10001; // マジックナンバー +input int initial_order = 0; // 起動時の注文(0:なし, 1:あり) +input int input_position_limit = 10; // 自EAの未約定注文+ポジション上限 +input bool use_split_entry_zone = false; // H1予測ゾーンで分割エントリーする +input int split_entry_count = 3; // 分割エントリー本数(1..10) +input SplitLotMode split_lot_mode = SPLIT_LOT_TOTAL; // 分割ロット配分モード +input double split_total_lot_size = 0.09; // 総量分割モードの合計ロット +input double split_fixed_lot_size = 0.01; // 固定ロットモードの1注文ロット +input bool cancel_old_split_pending_on_new_zone = true; // 新ゾーン読込時に旧分割pendingを取消 +input bool use_m15_entry_filter = true; // M15確定足で発注タイミングを確認 +input double m15_entry_zone_atr_multiplier = 1.50; // M15平均レンジ何本分まで候補価格への接近を許可 +input bool use_m15_imbalance_confirmation = true; // T1/T3でM15初動確認を追加 +input int m15_imbalance_avg_body_period = 20; // M15平均実体計算期間 +input double m15_imbalance_sensitivity = 2.0; // M15初動検知感度 +input double m15_imbalance_min_avg_body_points = 1.0; // 平均実体の最小値(point) +input bool use_m15_imbalance_debug_log = false; // M15初動確認ログ +input int input_entry_max_candidate_age_minutes = 45; // H1候補価格を新規発注に使う最大経過分数 +input bool input_sltp_manager_enabled = false; // UI連動SLTP管理を有効化 +input bool input_sltp_show_panel = true; // SLTP操作パネルを表示 +input bool input_sltp_use_breakeven = false; // 通常ブレークイーブンを有効化 +input double input_sltp_breakeven_trigger_pips = 30.0; // 通常BE開始pips +input double input_sltp_breakeven_buffer_pips = 3.0; // 通常BE固定バッファpips +input bool input_sltp_use_elapsed_breakeven = false; // 保有時間BEを有効化 +input double input_sltp_elapsed_breakeven_hours = 4.0; // 保有時間BE開始時間 +input double input_sltp_elapsed_breakeven_buffer_pips = 3.0; // 保有時間BE固定バッファpips +input bool input_sltp_use_active_trailing = false; // アクティブトレーリングを有効化 +input double input_sltp_active_breakeven_pips = 30.0; // アクティブ開始pips +input double input_sltp_active_stop_loss_offset_pips = 5.0; // 初期固定pips +input double input_sltp_active_step_trigger_pips = 10.0; // ステップ更新間隔pips +input double input_sltp_active_step_move_pips = 5.0; // 1ステップ固定追加pips +input bool input_sltp_use_tp_progress_stop = false; // TP進捗SLを有効化 +input double input_sltp_tp_progress_trigger_percent = 70.0; // TP進捗SL開始率 +input double input_sltp_tp_progress_sl_lock_percent = 30.0; // TP距離固定率 +input bool input_sltp_use_high_volatility_limit = false; // 急変時SL引き締めを有効化 +ulong slippage = 10; // スリッページ + +//--- 主要な定数・設定 +#define POSITION_LIMIT 48 +#define ENTRY_H1_LIMIT 1 // H1本数(=1時間) +#define CLOSE_H1_LIMIT 12 // H1本数(=12時間) +#define HISTORY_BARS 72 +#define OHLC_START_SHIFT 1 // 1: 確定足のみをPythonへ渡す +#define M15_CONFIRM_BARS 30 +#define M15_MIN_ENTRY_ZONE_POINTS 10 +#define M15_MIN_BODY_RATIO 0.25 +#define M15_REJECTION_WICK_RATIO 0.35 +#define ENTRY_RETRY_SECONDS 60 +#define ENTRY_RETRY_LIMIT 10 +// ANALYZE_TIMEFRAME は削除(H4/H1 両方使うため定数ではなく直接指定) +#define TARGET_SIZE 13 +#define TARGET_ZONE_SCHEMA_VERSION 2 +#define DEFAULT_TARGET_PRICE 0.0 +#define PYTHON_TIMEOUT_SECONDS 600 // Python完了待ちの上限(秒) + +#define MARKET_LOW_VOL_RANGE 0 +#define MARKET_HIGH_VOL_RANGE 1 +#define MARKET_LOW_VOL_UP 2 +#define MARKET_HIGH_VOL_UP 3 +#define MARKET_LOW_VOL_DOWN 4 +#define MARKET_HIGH_VOL_DOWN 5 +#define MARKET_TECHNICAL_ERROR_STOP 6 + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| EAの状態をまとめる構造体 +//+------------------------------------------------------------------+ + +struct EAState + { + int trend_state; // H4 market_state(0..5、6は技術エラー停止)を格納する変数 + int res_chk; // GPTの回答が正しく取得できたか + double en_price[5]; // エントリー基準価格 + double tp_price[5]; // 利益確定価格 + double sl_price[5]; // ロスカット基準価格 + int zone_res_chk; // 予測ゾーンが正しく取得できたか + string zone_candidate_id; // H1確定足由来の候補ID + double zone_low[5]; // 分割エントリー用ゾーン下限 + double zone_high[5]; // 分割エントリー用ゾーン上限 + double zone_tp[5]; // 分割エントリー用TP + double zone_sl[5]; // 分割エントリー用SL + bool load_trend_flg; // ★変更:トレンドを更新するタイミングか + bool load_target_flg; // ★変更:ターゲット価格を更新するタイミングか + int chk_cnt; // エントリー判定の試行回数 + datetime last_trend_update; // ★追加:前回トレンドを更新した時刻 + datetime last_target_update; // ★変更:前回ターゲット価格を更新した時刻 + datetime target_loaded_at; // H1候補価格をEAへ読み込んだサーバー時刻 + datetime last_chk; + }; + +EAState g_ea; // EA全体の状態をここで管理 + +// initial_order 用の初回処理フラグ +// H4トレンド処理とH1エントリー処理で別々に管理し、初回起動時の再実行ループを防ぐ。 +bool g_init_trend_pending = false; +bool g_init_entry_pending = false; + +// H4/H1のバー更新状態 +int g_pre_bars_H4 = 0; +int g_pre_bars_H1 = 0; +int g_pre_bars_M15 = 0; +bool g_bars_H1_check = false; +bool g_bars_M15_check = false; + +// ティックごとの価格情報 +struct TickContext + { + double ask; + double bid; + double spread; + double spread_points; + int digits; + }; + +// 外部Pythonプロセスの状態 +struct ExternalProcessState + { + long handle; + uint process_id; + bool active; + bool exit_code_ready; + uint exit_code; + datetime started_at; + }; + +ExternalProcessState g_trend_process; +ExternalProcessState g_entry_process; + +CSLTPManager g_sltp_manager; +CSLTPManagerPanel g_sltp_panel; +SLTPManagerPanelSettings g_sltp_settings; +bool g_sltp_settings_valid = false; +bool g_sltp_panel_created = false; + +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +/** + * @brief EA起動時の初期化処理を行います。 + * + * Python連携用のdone/runningファイル状態を確認し、`initial_order` が有効な場合は + * H4トレンド判定とH1エントリー価格生成をそれぞれ初回実行対象にします。 + * H4/H1の初回フラグを分けることで、起動直後にH4処理だけが繰り返される状態を防ぎます。 + */ +int OnInit() +// プロセス完了ファイルと実行中ファイルの状態を初期化 + { + PrepareDoneFileOnInit(done_trend_file, running_trend_file, "trend"); + PrepareDoneFileOnInit(done_entry_file, running_entry_file, "entry"); + + // initial_order=1 の場合でも、H4/H1を別々に1回だけ初回実行する。 + g_init_trend_pending = (initial_order == 1); + g_init_entry_pending = (initial_order == 1); + + if(!InitializeSLTPManager()) + return INIT_FAILED; + + return INIT_SUCCEEDED; + } + +//+------------------------------------------------------------------+ +//| 終了時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) + { + ResetExternalProcessState(g_trend_process); + ResetExternalProcessState(g_entry_process); + + if(g_sltp_panel_created) + g_sltp_panel.Destroy(reason); + + // Clear chart status text left by Comment() when the EA is removed. + Comment(""); + ChartRedraw(0); + } + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +/** + * @brief ティック受信ごとにEA全体の処理を制御します。 + * + * 処理順序は、ティック情報取得、期限切れ注文/ポジション管理、H4トレンド更新、 + * H1エントリー価格更新、M15エントリータイミング更新、ステータス表示、新規注文判定の順です。 + * 時間制限処理はPython完了待ちやスプレッド判定より前に実行します。 + */ +void OnTick() + { + TickContext ctx; + if(!GetTickContext(ctx)) + return; + + // 期限切れ注文・ポジションは、Python待ちやスプレッド制限に関係なく先に処理する。 + ManageExpiredTrades(); + + // Existing-position protection stays active even while Python results are pending. + ManageSLTPPositions(); + + // 外部Pythonがdoneを返さない場合に、一定時間で待機状態を解除して再実行対象にする。 + RecoverTimedOutPythonProcesses(); + + // H4新バーまたは初回起動時に、トレンド判定用Pythonを起動する。 + ProcessTrendUpdate(g_ea); + + // トレンド判定が完了していない場合、新規注文側の処理だけをスキップする。 + if(!IsTrendResultReady()) + return; + + RefreshTrendState(g_ea); + + // H1新バーまたは初回起動時に、エントリー価格生成用Pythonを起動する。 + ProcessEntryUpdate(g_ea); + + // M15確定足ごとに、H1候補価格を発注してよいタイミングか再判定する。 + ProcessM15EntryTimingUpdate(); + + UpdateStatusComment(ctx); + + // ここから下は新規注文に関する処理。 + if(!IsSpreadAllowed(ctx)) + return; + + if(!IsEntryResultReady()) + return; + + RefreshTargetPrices(g_ea); + ProcessEntryDecisionIfNeeded(g_ea, ctx); + } + +//+------------------------------------------------------------------+ +//| SLTP manager panel and settings bridge +//+------------------------------------------------------------------+ +string SLTPBoolText(const bool value) + { + return value ? "ON" : "OFF"; + } + +void LoadSLTPInputSettings(SLTPManagerPanelSettings &settings) + { + settings.manager_enabled = input_sltp_manager_enabled; + + settings.use_breakeven = input_sltp_use_breakeven; + settings.breakeven_trigger_pips = input_sltp_breakeven_trigger_pips; + settings.breakeven_buffer_pips = input_sltp_breakeven_buffer_pips; + + settings.use_elapsed_breakeven = input_sltp_use_elapsed_breakeven; + settings.elapsed_breakeven_hours = input_sltp_elapsed_breakeven_hours; + settings.elapsed_breakeven_buffer_pips = input_sltp_elapsed_breakeven_buffer_pips; + + settings.use_active_trailing = input_sltp_use_active_trailing; + settings.active_breakeven_pips = input_sltp_active_breakeven_pips; + settings.active_stop_loss_offset_pips = input_sltp_active_stop_loss_offset_pips; + settings.active_step_trigger_pips = input_sltp_active_step_trigger_pips; + settings.active_step_move_pips = input_sltp_active_step_move_pips; + + settings.use_tp_progress_stop = input_sltp_use_tp_progress_stop; + settings.tp_progress_trigger_percent = input_sltp_tp_progress_trigger_percent; + settings.tp_progress_sl_lock_percent = input_sltp_tp_progress_sl_lock_percent; + + settings.use_high_volatility_limit = input_sltp_use_high_volatility_limit; + } + +bool ApplySLTPSettings(const SLTPManagerPanelSettings &settings, + const bool print_summary) + { + g_sltp_settings_valid = false; + + g_sltp_manager.SetMagicNumber((ulong)magic_number); + g_sltp_manager.SetSymbol(_Symbol); + g_sltp_manager.SetDeviationInPoints(slippage); + g_sltp_manager.SetBreakevenSettings(settings.use_breakeven, + settings.breakeven_trigger_pips, + settings.breakeven_buffer_pips); + g_sltp_manager.SetElapsedBreakevenSettings(settings.use_elapsed_breakeven, + settings.elapsed_breakeven_hours, + settings.elapsed_breakeven_buffer_pips); + g_sltp_manager.SetActiveTrailingSettings(settings.use_active_trailing, + settings.active_breakeven_pips, + settings.active_stop_loss_offset_pips, + settings.active_step_trigger_pips, + settings.active_step_move_pips); + g_sltp_manager.SetTpProgressStopSettings(settings.use_tp_progress_stop, + settings.tp_progress_trigger_percent, + settings.tp_progress_sl_lock_percent); + g_sltp_manager.SetHighVolatilityLimitSettings(settings.use_high_volatility_limit); + + if(!g_sltp_manager.ValidateSettings()) + { + Print("HIT SLTP settings rejected. Manager is disabled until valid settings are applied."); + return false; + } + + g_sltp_settings = settings; + g_sltp_settings_valid = true; + + if(print_summary) + { + Print("HIT SLTP settings: Manager=", SLTPBoolText(settings.manager_enabled), + " BE=", SLTPBoolText(settings.use_breakeven), + " ElapsedBE=", SLTPBoolText(settings.use_elapsed_breakeven), + " ActiveTrail=", SLTPBoolText(settings.use_active_trailing), + " TPProgress=", SLTPBoolText(settings.use_tp_progress_stop), + " HighVol=", SLTPBoolText(settings.use_high_volatility_limit)); + } + + return true; + } + +bool InitializeSLTPManager() + { + SLTPManagerPanelSettings initial_settings; + LoadSLTPInputSettings(initial_settings); + + if(!ApplySLTPSettings(initial_settings, true)) + return false; + + g_sltp_panel.Init(g_sltp_settings); + if(input_sltp_show_panel) + { + if(!g_sltp_panel.CreatePanel()) + { + Print("HIT SLTP panel is unavailable. EA continues with input-based SLTP settings."); + return true; + } + + g_sltp_panel.SetInitialValues(); + g_sltp_panel_created = true; + } + + return true; + } + +void ManageSLTPPositions() + { + if(!g_sltp_settings_valid) + return; + if(!g_sltp_settings.manager_enabled) + return; + + g_sltp_manager.ManagePositions(); + g_sltp_manager.HighVolatilityLimit(); + } + +void OnChartEvent(const int id, + const long &lparam, + const double &dparam, + const string &sparam) + { + if(!g_sltp_panel_created) + return; + + SLTPManagerPanelSettings candidate_settings = g_sltp_settings; + if(g_sltp_panel.HandleChartEvent(id, lparam, dparam, sparam, candidate_settings)) + { + if(!ApplySLTPSettings(candidate_settings, true)) + Print("HIT SLTP panel apply failed. Please review panel values."); + } + } + + +// Function implementations are kept in project headers so the EA entry points stay compact. +#include +#include +#include +#include +#include diff --git a/Experts/MyProject/TradingPanel.mq5 b/Experts/MyProject/TradingPanel.mq5 new file mode 100644 index 0000000..7a40d4d --- /dev/null +++ b/Experts/MyProject/TradingPanel.mq5 @@ -0,0 +1,395 @@ +//+------------------------------------------------------------------+ +//| TradingPanel EA entry point +//+------------------------------------------------------------------+ +#include +#include + +#define POSITION_TYPE_ALL ((ENUM_POSITION_TYPE) - 1) // フィルタ無し + +// トータルブレークイーブン +#define BE_BUY "BreakEvenBuy" +#define BE_SELL "BreakEvenSell" + +//--- カテゴリ1:取引設定 +input group "■ 取引設定"; +input int exit_time_interval = 4; // 時間制限クローズまでの保有時間(時間) + +//--- カテゴリ2:基本設定 +input group "■ 基本設定"; +input ulong slippage = 10; // スリッページ +input int magic_number = 10001; // マジックナンバー + +input group "■ SLTP設定: Common"; +input double take_profit_pips = 150.0; // TP設定値 +input double default_sl = 100.0; // SL初期値 +input double stop_offset_pips = 10.0; // ストップロスオフセット(pips) + +input group "■ SLTP設定: TRAIL"; +input bool enable_breakeven = true; // ブレークイーブンを有効化 +input double breakeven_pips = 30.0; // ブレークイーブン判定利幅 +input double stop_loss_offset_pips = 5.0; // 建値からのマージン +input double step_trigger_pips = 10.0; // トレール更新トリガーpips +input double step_move_pips = 8.0; // トレール更新pips +input double tp_edit_pips = 10.0; // TP更新初期pips + +//--- カテゴリ4:フィルター設定 +input group "■ レンジフィルター設定"; +input double range_pips = 80.0; // レンジ幅pips1 + +//--- カテゴリ5:時間設定 +input group "■ 取引時間設定"; +input bool isTradingTimeEnabled = true; // 取引時間を制限 +input int TradeStartHour = 4; // 開始時間GMT(時間) +input int TradeStartMin = 0; // 開始時間GMT(分) +input int TradeEndHour = 23; // 終了時間GMT(時間) +input int TradeEndMin = 0; // 終了時間GMT(分) + +int TimerPeriod_sec = 1; // n 秒ごとに OnTimer + +bool wasInTradingTime = false; +bool beforeTradingInitDone = false; +bool afterTradingInitDone = false; + +bool timelimit_exit = true; // 時間制限クローズ 初期状態 + +bool input_lock = true; // 時間制限クローズ 初期状態 + +// -------------------------------------------------------------- +// EAとの共有用グローバル変数 +// -------------------------------------------------------------- +int gl_exitTimeIntervalInSeconds = exit_time_interval * 3600; +double gl_breakeven_pips = breakeven_pips; +double gl_stop_loss_offset_pips = stop_loss_offset_pips; +double gl_step_trigger_pips = step_trigger_pips; +double gl_step_move_pips = step_move_pips; +double gl_default_sl_pips = default_sl; +double gl_take_profit_pips = take_profit_pips; +double gl_stop_offset_pips = stop_offset_pips; + +int buy_position = 0; // CountPositions() で随時更新 +int sell_position = 0; +datetime last_buy_time = 0; +datetime last_sell_time = 0; +bool buy_total_be_flg = false; +bool sell_total_be_flg = false; + +//+------------------------------------------------------------------+ +//| トータルのブレークイーブン用変数 +//+------------------------------------------------------------------+ +bool InpShowBuyLine = true; // 買いのブレークイーブンライン +bool InpShowSellLine = true; // 売りのブレークイーブンライン +double BufferPips = 2.0; // BE バッファ +int LabelShiftBars = 60; // 何本前のバーにラベルを置くか +double LabelOffsetPips = 4.0; // ライン ⇔ ラベルの距離 + +CTradingPanelStopManager g_stop_manager; +CTradingPanelPositionManager g_position_manager; +CTradingPanelBreakEvenLineManager g_break_even_manager; +CTradingPanelPanel g_panel; + +//+------------------------------------------------------------------+ +//| 起動時の処理 +//+------------------------------------------------------------------+ +int OnInit() +{ + g_stop_manager.Init(_Symbol, (ulong)magic_number, slippage); + g_position_manager.Init(_Symbol, (ulong)magic_number, slippage); + g_break_even_manager.Init(_Symbol, (ulong)magic_number); + g_panel.Init(exit_time_interval, + range_pips, + breakeven_pips, + stop_loss_offset_pips, + step_trigger_pips, + step_move_pips, + default_sl, + take_profit_pips, + tp_edit_pips, + stop_offset_pips); + + // EA設定変更用のパネルを表示 + if (!g_panel.CreatePanel(input_lock)) + return INIT_FAILED; + g_panel.SetInitialValues(); + + // Backfill missing initial stops for existing matching positions. + g_position_manager.ApplyInitialStops(gl_default_sl_pips, gl_take_profit_pips); + + // タイマーをセット + EventSetTimer(TimerPeriod_sec); + + return (INIT_SUCCEEDED); // 成功コードを明示しておく +} + +//+------------------------------------------------------------------+ +//| 削除時の処理 +//+------------------------------------------------------------------+ +void OnDeinit(const int reason) +{ + EventKillTimer(); + + // EA適用後のプロパティで変更すると、EAを再読み込みする。 + g_panel.Destroy(reason); + + // トータルブレークイーブンラインを削除 + if (InpShowBuyLine) + g_break_even_manager.DeleteLine(BE_BUY); + + if (InpShowSellLine) + g_break_even_manager.DeleteLine(BE_SELL); + + // EAが削除された際にコメントを消す + Comment(""); +} + +//+------------------------------------------------------------------+ +//| タイマー処理 +//+------------------------------------------------------------------+ +void OnTimer() +{ + // 時間状態の変化を検知 + HandleTradingTimeTransition(); + + // ティックが少ない時間帯でも時間制限クローズを監視する。 + g_position_manager.CloseTimedPositions(POSITION_TYPE_BUY, ORDER_TYPE_SELL, + timelimit_exit, gl_exitTimeIntervalInSeconds, slippage); + g_position_manager.CloseTimedPositions(POSITION_TYPE_SELL, ORDER_TYPE_BUY, + timelimit_exit, gl_exitTimeIntervalInSeconds, slippage); + + // 取引時間が指定時間内か確認 + if (!IsTradingTime()) + return; +} + +//+------------------------------------------------------------------+ +//| ティック毎の処理 +//+------------------------------------------------------------------+ +void OnTick() +{ + // 時間状態の変化を検知 + HandleTradingTimeTransition(); + + // EAの設定を画面上に表示 + DisplayEAValues(); + + // 時間制限クローズは取引時間フィルターとは独立して実行する。 + g_position_manager.CloseTimedPositions(POSITION_TYPE_BUY, ORDER_TYPE_SELL, + timelimit_exit, gl_exitTimeIntervalInSeconds, slippage); + g_position_manager.CloseTimedPositions(POSITION_TYPE_SELL, ORDER_TYPE_BUY, + timelimit_exit, gl_exitTimeIntervalInSeconds, slippage); + + // 取引時間が指定時間内か確認 + if (!IsTradingTime()) + return; + + // レート急伸SL + g_stop_manager.HighVolatilityLimit(); + + //------------------------------------ + // ブレークイーブン / トレールストップ監視 + //------------------------------------ + g_stop_manager.ManageStops(enable_breakeven, + gl_breakeven_pips, + gl_stop_loss_offset_pips, + gl_step_trigger_pips, + gl_step_move_pips); + + g_break_even_manager.ManageBuy(buy_total_be_flg && InpShowBuyLine, + buy_total_be_flg, + BufferPips, + LabelShiftBars, + LabelOffsetPips, + gl_default_sl_pips, + g_stop_manager); + g_break_even_manager.ManageSell(sell_total_be_flg && InpShowSellLine, + sell_total_be_flg, + BufferPips, + LabelShiftBars, + LabelOffsetPips, + gl_default_sl_pips, + g_stop_manager); + + // 保有ポジションの確認 + buy_position = g_position_manager.CountPositions(POSITION_TYPE_BUY, magic_number); + sell_position = g_position_manager.CountPositions(POSITION_TYPE_SELL, magic_number); +} + +//+------------------------------------------------------------------+ +//| 取引時間を指定する関数 +//+------------------------------------------------------------------+ +bool IsTradingTime() +{ + // 取引時間が有効化されていない場合、常にtrueを返す + if (!isTradingTimeEnabled) + { + return true; + } + + MqlDateTime structTime; + TimeCurrent(structTime); + structTime.sec = 0; + + structTime.hour = TradeStartHour; + structTime.min = TradeStartMin; + datetime timeStart = StructToTime(structTime); + + structTime.hour = TradeEndHour; + structTime.min = TradeEndMin; + datetime timeEnd = StructToTime(structTime); + + // エラーチェック + if (TradeStartHour >= TradeEndHour && TradeStartMin >= TradeEndMin) + { + Print("Error: Invalid Time input"); + return false; + } + + // 現在の時間を取得 + datetime now = TimeCurrent(); + + // 時間内かどうかをチェック + return (now >= timeStart && now < timeEnd); +} + +//+------------------------------------------------------------------+ +//| 取引時間内外に状態が変化した時の初期化処理 +//+------------------------------------------------------------------+ +void HandleTradingTimeTransition() +{ + bool nowInTradingTime = IsTradingTime(); + + // --- 時間内に入った瞬間 --- + if (nowInTradingTime && !wasInTradingTime) + { + if (!beforeTradingInitDone) + { + Print("取引時間内 初期化処理"); + beforeTradingInitDone = true; + } + afterTradingInitDone = false; + } + + // --- 時間外に出た瞬間 --- + if (!nowInTradingTime && wasInTradingTime) + { + if (!afterTradingInitDone) + { + Print("取引時間外 初期化処理"); + afterTradingInitDone = true; + } + beforeTradingInitDone = false; + } + + wasInTradingTime = nowInTradingTime; +} + +//+------------------------------------------------------------------+ +//| pipsを価格に換算する関数 +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +// 出力処理を関数にまとめる +//+------------------------------------------------------------------+ +void DisplayEAValues() +{ + Comment("\n", + "ブレークイーブン: ", (int)gl_breakeven_pips, "pips\n", + "時間制限クローズ: ", timelimit_exit ? "ON" : "OFF", + " | 保有時間: ", gl_exitTimeIntervalInSeconds / 3600, "時間\n", + "------------------------------------------------------------------------------------\n"); +} + +// +------------------------------------------------------------------+ +// | チャートイベントのハンドラ +// +------------------------------------------------------------------+ +void OnChartEvent(const int id, const long &lparam, const double &dparam, const string &sparam) +{ + ENUM_POSITION_TYPE tp_side = POSITION_TYPE_ALL; + double tp_adjust_pips = 0.0; + + if (g_panel.HandleChartEvent(id, + lparam, + dparam, + sparam, + exit_time_interval, + range_pips, + breakeven_pips, + stop_loss_offset_pips, + step_trigger_pips, + step_move_pips, + default_sl, + take_profit_pips, + tp_edit_pips, + stop_offset_pips, + timelimit_exit, + input_lock, + buy_total_be_flg, + sell_total_be_flg, + gl_exitTimeIntervalInSeconds, + gl_breakeven_pips, + gl_stop_loss_offset_pips, + gl_step_trigger_pips, + gl_step_move_pips, + gl_default_sl_pips, + gl_take_profit_pips, + gl_stop_offset_pips, + tp_side, + tp_adjust_pips)) + { + g_position_manager.AdjustTakeProfit(tp_adjust_pips, tp_side); + } +} + +//------------------------------------------------------------------ +// 取引イベントコールバック(再エントリーを防止) +//------------------------------------------------------------------ +void OnTradeTransaction(const MqlTradeTransaction &trans, + const MqlTradeRequest &request, + const MqlTradeResult &result) +{ + bool recalc_needed = false; // ポジション再集計フラグ + + // 1) Deal が追加された(約定) + if (trans.type == TRADE_TRANSACTION_DEAL_ADD) + { + recalc_needed = true; + + //---- Deal 詳細を取得 -------------------------------------- + if (HistoryDealSelect(trans.deal)) // Deal を選択 + { + ENUM_DEAL_REASON deal_reason = (ENUM_DEAL_REASON) + HistoryDealGetInteger(trans.deal, DEAL_REASON); + + ENUM_DEAL_ENTRY deal_entry = (ENUM_DEAL_ENTRY) + HistoryDealGetInteger(trans.deal, DEAL_ENTRY); + + //==== ★ クローズ条件(手動 or SL/TP) =================== + bool is_close = + (deal_entry == DEAL_ENTRY_OUT) && + (deal_reason == DEAL_REASON_CLIENT || // 手動決済 + deal_reason == DEAL_REASON_MOBILE || // スマホアプリ決済 + deal_reason == DEAL_REASON_WEB || // WEB版決済 + deal_reason == DEAL_REASON_EXPERT || // EAのタイマー決済 + deal_reason == DEAL_REASON_SL || // SL ヒット(部分/全量とも) + deal_reason == DEAL_REASON_TP); // TP ヒット(部分/全量とも) + + if (is_close) + { + Print("Position closed (", EnumToString(deal_reason), ") — EA waits for next prediction."); + } + } + } + + // 2) サーバ側でポジション内容が変更 (数量減少・SL変更等) + else if (trans.type == TRADE_TRANSACTION_POSITION) + { + recalc_needed = true; + } + + // 3) 必要に応じてポジション再集計 + if (recalc_needed) + { + g_position_manager.CheckPositions(buy_position, sell_position, last_buy_time, last_sell_time); + g_position_manager.ApplyInitialStops(gl_default_sl_pips, gl_take_profit_pips); + } +} + diff --git a/Include/Arrays/Array.mqh b/Include/Arrays/Array.mqh new file mode 100644 index 0000000..861d118 --- /dev/null +++ b/Include/Arrays/Array.mqh @@ -0,0 +1,182 @@ +//+------------------------------------------------------------------+ +//| Array.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CArray | +//| Purpose: Base class of dynamic arrays. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CArray : public CObject + { +protected: + int m_step_resize; // increment size of the array + int m_data_total; // number of elements + int m_data_max; // maximmum size of the array without memory reallocation + int m_sort_mode; // mode of array sorting + +public: + CArray(void); + ~CArray(void); + //--- methods of access to protected data + int Step(void) const { return(m_step_resize); } + bool Step(const int step); + int Total(void) const { return(m_data_total); } + int Available(void) const { return(m_data_max-m_data_total); } + int Max(void) const { return(m_data_max); } + bool IsSorted(const int mode=0) const { return(m_sort_mode==mode); } + int SortMode(void) const { return(m_sort_mode); } + //--- cleaning method + void Clear(void) { m_data_total=0; } + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + //--- sorting method + void Sort(const int mode=0); + +protected: + virtual void QuickSort(int beg,int end,const int mode=0) { m_sort_mode=-1; } + //--- templates for methods of searching for minimum and maximum + template + int Minimum(const T &data[],const int start,const int count) const; + template + int Maximum(const T &data[],const int start,const int count) const; + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CArray::CArray(void) : m_step_resize(16), + m_data_total(0), + m_data_max(0), + m_sort_mode(-1) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CArray::~CArray(void) + { + } +//+------------------------------------------------------------------+ +//| Method Set for variable m_step_resize | +//+------------------------------------------------------------------+ +bool CArray::Step(const int step) + { +//--- check + if(step>0) + { + m_step_resize=step; + return(true); + } +//--- failure + return(false); + } +//+------------------------------------------------------------------+ +//| Sorting an array in ascending order | +//+------------------------------------------------------------------+ +void CArray::Sort(const int mode) + { +//--- check + if(IsSorted(mode)) + return; + m_sort_mode=mode; + if(m_data_total<=1) + return; +//--- sort + QuickSort(0,m_data_total-1,mode); + } +//+------------------------------------------------------------------+ +//| Writing header of array to file | +//+------------------------------------------------------------------+ +bool CArray::Save(const int file_handle) + { +//--- check handle + if(file_handle!=INVALID_HANDLE) + { + //--- write start marker - 0xFFFFFFFFFFFFFFFF + if(FileWriteLong(file_handle,-1)==sizeof(long)) + { + //--- write array type + if(FileWriteInteger(file_handle,Type(),INT_VALUE)==INT_VALUE) + return(true); + } + } +//--- failure + return(false); + } +//+------------------------------------------------------------------+ +//| Reading header of array from file | +//+------------------------------------------------------------------+ +bool CArray::Load(const int file_handle) + { +//--- check handle + if(file_handle!=INVALID_HANDLE) + { + //--- read and check start marker - 0xFFFFFFFFFFFFFFFF + if(FileReadLong(file_handle)==-1) + { + //--- read and check array type + if(FileReadInteger(file_handle,INT_VALUE)==Type()) + return(true); + } + } +//--- failure + return(false); + } +//+------------------------------------------------------------------+ +//| Find minimum of array | +//+------------------------------------------------------------------+ +template +int CArray::Minimum(const T &data[],const int start,const int count) const + { + int real_count; +//--- check for empty array + if(m_data_total<1) + { + SetUserError(ERR_USER_ARRAY_IS_EMPTY); + return(-1); + } + //--- check for start is out of range + if(start<0 || start>=m_data_total) + { + SetUserError(ERR_USER_ITEM_NOT_FOUND); + return(-1); + } +//--- compute count of elements + real_count=(count==WHOLE_ARRAY || start+count>m_data_total) ? m_data_total-start : count; +#ifdef __MQL5__ + return(ArrayMinimum(data,start,real_count)); +#else + return(ArrayMinimum(data,real_count,start)); +#endif + } +//+------------------------------------------------------------------+ +//| Find maximum of array | +//+------------------------------------------------------------------+ +template +int CArray::Maximum(const T &data[],const int start,const int count) const + { + int real_count; +//--- check for empty array + if(m_data_total<1) + { + SetUserError(ERR_USER_ARRAY_IS_EMPTY); + return(-1); + } + //--- check for start is out of range + if(start<0 || start>=m_data_total) + { + SetUserError(ERR_USER_ITEM_NOT_FOUND); + return(-1); + } +//--- compute count of elements + real_count=(count==WHOLE_ARRAY || start+count>m_data_total) ? m_data_total-start : count; +#ifdef __MQL5__ + return(ArrayMaximum(data,start,real_count)); +#else + return(ArrayMaximum(data,real_count,start)); +#endif + } +//+------------------------------------------------------------------+ diff --git a/Include/Arrays/ArrayChar.mqh b/Include/Arrays/ArrayChar.mqh new file mode 100644 index 0000000..41ae17d --- /dev/null +++ b/Include/Arrays/ArrayChar.mqh @@ -0,0 +1,770 @@ +//+------------------------------------------------------------------+ +//| ArrayChar.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "Array.mqh" +//+------------------------------------------------------------------+ +//| Class CArrayChar. | +//| Purpose: Class of dynamic array of char type values. | +//| Derives from class CArray. | +//+------------------------------------------------------------------+ +class CArrayChar : public CArray + { +protected: + char m_data[]; // data array + +public: + CArrayChar(void); + ~CArrayChar(void); + //--- method of identifying the object + virtual int Type(void) const { return(TYPE_CHAR); } + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + //--- methods of managing dynamic memory + bool Reserve(const int size); + bool Resize(const int size); + bool Shutdown(void); + //--- methods of filling the array + bool Add(const char element); + bool AddArray(const char &src[]); + bool AddArray(const CArrayChar *src); + bool Insert(const char element,const int pos); + bool InsertArray(const char &src[],const int pos); + bool InsertArray(const CArrayChar *src,const int pos); + bool AssignArray(const char &src[]); + bool AssignArray(const CArrayChar *src); + //--- method of access to the array + char At(const int index) const; + char operator[](const int index) const { return(At(index)); } + //--- methods of searching for minimum and maximum + int Minimum(const int start,const int count) const { return(CArray::Minimum(m_data,start,count)); } + int Maximum(const int start,const int count) const { return(CArray::Maximum(m_data,start,count)); } + //--- methods of changing + bool Update(const int index,const char element); + bool Shift(const int index,const int shift); + //--- methods of deleting + bool Delete(const int index); + bool DeleteRange(int from,int to); + //--- methods for comparing arrays + bool CompareArray(const char &array[]) const; + bool CompareArray(const CArrayChar *array) const; + //--- methods for working with the sorted array + bool InsertSort(const char element); + int Search(const char element) const; + int SearchGreat(const char element) const; + int SearchLess(const char element) const; + int SearchGreatOrEqual(const char element) const; + int SearchLessOrEqual(const char element) const; + int SearchFirst(const char element) const; + int SearchLast(const char element) const; + int SearchLinear(const char element) const; + +protected: + virtual void QuickSort(int beg,int end,const int mode=0); + int QuickSearch(const char element) const; + int MemMove(const int dest,const int src,int count); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CArrayChar::CArrayChar(void) + { +//--- initialize protected data + m_data_max=ArraySize(m_data); + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CArrayChar::~CArrayChar(void) + { + if(m_data_max!=0) + Shutdown(); + } +//+------------------------------------------------------------------+ +//| Moving the memory within a single array | +//+------------------------------------------------------------------+ +int CArrayChar::MemMove(const int dest,const int src,int count) + { + int i; +//--- check parameters + if(dest<0 || src<0 || count<0) + return(-1); +//--- check count + if(src+count>m_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayChar::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayChar::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayChar::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayChar::Add(const char element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayChar::AddArray(const char &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(CHAR_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayChar::Update(const int index,const char element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayChar::Shift(const int index,const int shift) + { + char tmp_char; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_char=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_char; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayChar::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayChar::CompareArray(const char &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_char=m_data[(beg+end)>>1]; + while(ip_char) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_char=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_char; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchLinear(const char element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_char=m_data[m]; + if(t_char==element) + break; + if(t_char>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::Search(const char element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchGreat(const char element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchLess(const char element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchGreatOrEqual(const char element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchLessOrEqual(const char element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchFirst(const char element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayChar::SearchLast(const char element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayChar::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayColor::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayColor::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayColor::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayColor::Add(const color element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayColor::AddArray(const color &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(clrNONE); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayColor::Update(const int index,const color element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayColor::Shift(const int index,const int shift) + { +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + color tmp_long=m_data[index]; + + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + + m_data[index+shift]=tmp_long; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayColor::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayColor::CompareArray(const color &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + color p_long=m_data[(beg+end)>>1]; + + while(ip_long) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + color tmp=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=tmp; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchLinear(const color element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_long=m_data[m]; + if(t_long==element) + break; + if(t_long>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::Search(const color element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchGreat(const color element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchLess(const color element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchGreatOrEqual(const color element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchLessOrEqual(const color element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchFirst(const color element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayColor::SearchLast(const color element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayColor::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Add(const datetime element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayDatetime::AddArray(const datetime &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(LONG_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Update(const int index,const datetime element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Shift(const int index,const int shift) + { +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + datetime tmp_long=m_data[index]; + + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + + m_data[index+shift]=tmp_long; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayDatetime::CompareArray(const datetime &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + datetime p_long=m_data[(beg+end)>>1]; + + while(ip_long) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + datetime tmp=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=tmp; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchLinear(const datetime element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_long=m_data[m]; + if(t_long==element) + break; + if(t_long>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::Search(const datetime element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchGreat(const datetime element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchLess(const datetime element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchGreatOrEqual(const datetime element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchLessOrEqual(const datetime element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchFirst(const datetime element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDatetime::SearchLast(const datetime element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayDatetime::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayDouble::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayDouble::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayDouble::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayDouble::Add(const double element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayDouble::AddArray(const double &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(DBL_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayDouble::Update(const int index,const double element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayDouble::Shift(const int index,const int shift) + { + double tmp_double; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_double=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_double; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayDouble::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayDouble::CompareArray(const double &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_double=m_data[(beg+end)>>1]; + while(ip_double) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_double=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_double; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchLinear(const double element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_double=m_data[m]; + //--- compare with delta + if(MathAbs(t_double-element)<=m_delta) + break; + if(t_double>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::Search(const double element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + if(MathAbs(m_data[pos]-element)<=m_delta) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchGreat(const double element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + while(m_data[pos]<=element+m_delta) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchLess(const double element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + while(m_data[pos]>=element-m_delta) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchGreatOrEqual(const double element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchLessOrEqual(const double element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchFirst(const double element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + //--- compare with delta + while(MathAbs(m_data[pos]-element)<=m_delta) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayDouble::SearchLast(const double element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + //--- compare with delta + while(MathAbs(m_data[pos]-element)<=m_delta) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayDouble::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayFloat::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayFloat::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayFloat::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayFloat::Add(const float element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayFloat::AddArray(const float &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(FLT_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayFloat::Update(const int index,const float element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayFloat::Shift(const int index,const int shift) + { + float tmp_float; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_float=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_float; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayFloat::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayFloat::CompareArray(const float &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_float=m_data[(beg+end)>>1]; + while(ip_float) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_float=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_float; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchLinear(const float element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_float=m_data[m]; + //--- compare with delta + if(MathAbs(t_float-element)<=m_delta) + break; + if(t_float>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::Search(const float element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + if(MathAbs(m_data[pos]-element)<=m_delta) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchGreat(const float element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + while(m_data[pos]<=element+m_delta) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchLess(const float element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); +//--- compare with delta + while(m_data[pos]>=element-m_delta) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchGreatOrEqual(const float element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchLessOrEqual(const float element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchFirst(const float element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + //--- compare with delta + while(MathAbs(m_data[pos]-element)<=m_delta) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayFloat::SearchLast(const float element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + //--- compare with delta + while(MathAbs(m_data[pos]-element)<=m_delta) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayFloat::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayInt::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayInt::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayInt::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayInt::Add(const int element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayInt::AddArray(const int &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(INT_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayInt::Update(const int index,const int element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayInt::Shift(const int index,const int shift) + { + int tmp_int; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_int=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_int; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayInt::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayInt::CompareArray(const int &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_int=m_data[(beg+end)>>1]; + while(ip_int) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_int=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_int; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchLinear(const int element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_int=m_data[m]; + if(t_int==element) + break; + if(t_int>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::Search(const int element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchGreat(const int element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchLess(const int element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchGreatOrEqual(const int element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchLessOrEqual(const int element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchFirst(const int element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayInt::SearchLast(const int element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayInt::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayLong::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayLong::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayLong::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayLong::Add(const long element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayLong::AddArray(const long &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(LONG_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayLong::Update(const int index,const long element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayLong::Shift(const int index,const int shift) + { + long tmp_long; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_long=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_long; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayLong::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayLong::CompareArray(const long &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_long=m_data[(beg+end)>>1]; + while(ip_long) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_long=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_long; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchLinear(const long element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_long=m_data[m]; + if(t_long==element) + break; + if(t_long>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::Search(const long element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchGreat(const long element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchLess(const long element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchGreatOrEqual(const long element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchLessOrEqual(const long element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchFirst(const long element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayLong::SearchLast(const long element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayLong::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + { + //--- "physical" removal of the object (if necessary and possible) + if(m_free_mode && CheckPointer(m_data[dest+i])==POINTER_DYNAMIC) + delete m_data[dest+i]; + //--- + m_data[dest+i]=m_data[src+i]; + m_data[src+i]=NULL; + } + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayObj::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayObj::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_total>size) + { + //--- "physical" removal of the object (if necessary and possible) + if(m_free_mode) + for(int i=size;i=m_data_total) + return(NULL); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayObj::Update(const int index,CObject *element) + { +//--- check + if(index<0 || !CheckPointer(element) || index>=m_data_total) + return(false); +//--- "physical" removal of the object (if necessary and possible) + if(m_free_mode && CheckPointer(m_data[index])==POINTER_DYNAMIC) + delete m_data[index]; +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayObj::Shift(const int index,const int shift) + { + CObject *tmp_node; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_node=m_data[index]; + m_data[index]=NULL; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_node; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayObj::Delete(const int index) + { +//--- check + if(index>=m_data_total) + return(false); +//--- delete + if(index=0 && MemMove(index,index+1,m_data_total-index-1)<0) + return(false); + } + else + if(m_free_mode && CheckPointer(m_data[index])==POINTER_DYNAMIC) + delete m_data[index]; + m_data_total--; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Detach element from the specified position | +//+------------------------------------------------------------------+ +CObject *CArrayObj::Detach(const int index) + { + CObject *result; +//--- check + if(index>=m_data_total) + return(NULL); +//--- detach + result=m_data[index]; +//--- reset the array element, so as not remove the method MemMove + m_data[index]=NULL; + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + for(int i=to-from+1;i>0;i--,m_data_total--) + if(m_free_mode && CheckPointer(m_data[m_data_total-1])==POINTER_DYNAMIC) + delete m_data[m_data_total-1]; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Clearing of array without the release of memory | +//+------------------------------------------------------------------+ +void CArrayObj::Clear(void) + { +//--- "physical" removal of the object (if necessary and possible) + if(m_free_mode) + { + for(int i=0;i>1" is quick division by 2 + p_node=m_data[(beg+end)>>1]; + while(i0) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_node=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_node; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(beg0) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=mode; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Quick search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::QuickSearch(const CObject *element) const + { + int i,j,m=-1; + CObject *t_node; +//--- search + i=0; + j=m_data_total-1; + while(j>=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m==m_data_total-1) + break; + t_node=m_data[m]; + if(t_node.Compare(element,m_sort_mode)==0) + break; + if(t_node.Compare(element,m_sort_mode)>0) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::Search(const CObject *element) const + { + int pos; +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos].Compare(element,m_sort_mode)==0) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchGreat(const CObject *element) const + { + int pos; +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos].Compare(element,m_sort_mode)<=0) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchLess(const CObject *element) const + { + int pos; +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos].Compare(element,m_sort_mode)>=0) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchGreatOrEqual(const CObject *element) const + { +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=0) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchLessOrEqual(const CObject *element) const + { +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos].Compare(element,m_sort_mode)<=0) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchFirst(const CObject *element) const + { + int pos; +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos].Compare(element,m_sort_mode)==0) + { + while(m_data[pos].Compare(element,m_sort_mode)==0) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayObj::SearchLast(const CObject *element) const + { + int pos; +//--- check + if(m_data_total==0 || !CheckPointer(element) || m_sort_mode==-1) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos].Compare(element,m_sort_mode)==0) + { + while(m_data[pos].Compare(element,m_sort_mode)==0) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayObj::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayShort::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayShort::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayShort::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayShort::Add(const short element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayShort::AddArray(const short &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(SHORT_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayShort::Update(const int index,const short element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayShort::Shift(const int index,const int shift) + { + short tmp_short; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_short=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_short; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayShort::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayShort::CompareArray(const short &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_short=m_data[(beg+end)>>1]; + while(ip_short) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_short=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_short; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchLinear(const short element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_short=m_data[m]; + if(t_short==element) + break; + if(t_short>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::Search(const short element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchGreat(const short element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchLess(const short element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchGreatOrEqual(const short element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchLessOrEqual(const short element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchFirst(const short element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayShort::SearchLast(const short element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayShort::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayString::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayString::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayString::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayString::Add(const string element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayString::AddArray(const string &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(""); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayString::Update(const int index,const string element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayString::Shift(const int index,const int shift) + { + string tmp_string; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_string=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_string; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayString::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayString::CompareArray(const string &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_string=m_data[(beg+end)>>1]; + while(ip_string) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_string=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_string; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayString::SearchLinear(const string element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_string=m_data[m]; + if(t_string==element) + break; + if(t_string>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::Search(const string element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchGreat(const string element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchLess(const string element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchGreatOrEqual(const string element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchLessOrEqual(const string element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchFirst(const string element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayString::SearchLast(const string element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayString::Save(const int file_handle) + { + int i=0,len; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayUChar::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayUChar::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayUChar::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayUChar::Add(const uchar element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayUChar::AddArray(const uchar &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(CHAR_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayUChar::Update(const int index,const uchar element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayUChar::Shift(const int index,const int shift) + { + uchar tmp_char; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_char=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_char; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayUChar::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayUChar::CompareArray(const uchar &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_char=m_data[(beg+end)>>1]; + while(ip_char) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_char=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_char; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchLinear(const uchar element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_char=m_data[m]; + if(t_char==element) + break; + if(t_char>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::Search(const uchar element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchGreat(const uchar element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchLess(const uchar element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchGreatOrEqual(const uchar element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchLessOrEqual(const uchar element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchFirst(const uchar element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUChar::SearchLast(const uchar element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayUChar::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayUInt::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayUInt::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayUInt::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayUInt::Add(const uint element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayUInt::AddArray(const uint &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(INT_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayUInt::Update(const int index,const uint element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayUInt::Shift(const int index,const int shift) + { +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + uint tmp_int=m_data[index]; + + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + + m_data[index+shift]=tmp_int; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayUInt::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayUInt::CompareArray(const uint &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + uint p_int=m_data[(beg+end)>>1]; + + while(ip_int) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + uint t_uint=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_uint; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchLinear(const uint element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + + uint t_int=m_data[m]; + + if(t_int==element) + break; + if(t_int>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::Search(const uint element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchGreat(const uint element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchLess(const uint element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchGreatOrEqual(const uint element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchLessOrEqual(const uint element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchFirst(const uint element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUInt::SearchLast(const uint element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayUInt::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayULong::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayULong::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayULong::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayULong::Add(const ulong element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayULong::AddArray(const ulong &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(LONG_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayULong::Update(const int index,const ulong element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayULong::Shift(const int index,const int shift) + { + ulong tmp_long; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_long=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_long; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayULong::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayULong::CompareArray(const ulong &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_long=m_data[(beg+end)>>1]; + while(ip_long) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_long=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_long; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchLinear(const ulong element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_long=m_data[m]; + if(t_long==element) + break; + if(t_long>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::Search(const ulong element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchGreat(const ulong element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchLess(const ulong element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchGreatOrEqual(const ulong element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchLessOrEqual(const ulong element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchFirst(const ulong element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayULong::SearchLast(const ulong element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayULong::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;im_data_total) + count=m_data_total-src; + if(count<0) + return(-1); +//--- no need to copy + if(dest==src || count==0) + return(dest); +//--- check data total + if(dest+count>m_data_total) + { + if(m_data_max=0;i--) + m_data[dest+i]=m_data[src+i]; + } +//--- successful + return(dest); + } +//+------------------------------------------------------------------+ +//| Request for more memory in an array. Checks if the requested | +//| number of free elements already exists; allocates additional | +//| memory with a given step | +//+------------------------------------------------------------------+ +bool CArrayUShort::Reserve(const int size) + { + int new_size; +//--- check + if(size<=0) + return(false); +//--- resize array + if(Available()=size); + } +//+------------------------------------------------------------------+ +//| Resizing (with removal of elements on the right) | +//+------------------------------------------------------------------+ +bool CArrayUShort::Resize(const int size) + { + int new_size; +//--- check + if(size<0) + return(false); +//--- resize array + new_size=m_step_resize*(1+size/m_step_resize); + if(m_data_max!=new_size) + { + if((m_data_max=ArrayResize(m_data,new_size))==-1) + { + m_data_max=ArraySize(m_data); + return(false); + } + } + if(m_data_total>size) + m_data_total=size; +//--- result + return(m_data_max==new_size); + } +//+------------------------------------------------------------------+ +//| Complete cleaning of the array with the release of memory | +//+------------------------------------------------------------------+ +bool CArrayUShort::Shutdown(void) + { +//--- check + if(m_data_max==0) + return(true); +//--- clean + if(ArrayResize(m_data,0)==-1) + return(false); + m_data_total=0; + m_data_max=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array | +//+------------------------------------------------------------------+ +bool CArrayUShort::Add(const ushort element) + { +//--- check/reserve elements of array + if(!Reserve(1)) + return(false); +//--- add + m_data[m_data_total++]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Adding an element to the end of the array from another array | +//+------------------------------------------------------------------+ +bool CArrayUShort::AddArray(const ushort &src[]) + { + int num=ArraySize(src); +//--- check/reserve elements of array + if(!Reserve(num)) + return(false); +//--- add + for(int i=0;i=m_data_total) + return(SHORT_MAX); +//--- result + return(m_data[index]); + } +//+------------------------------------------------------------------+ +//| Updating element in the specified position | +//+------------------------------------------------------------------+ +bool CArrayUShort::Update(const int index,const ushort element) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- update + m_data[index]=element; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Moving element from the specified position | +//| on the specified shift | +//+------------------------------------------------------------------+ +bool CArrayUShort::Shift(const int index,const int shift) + { + ushort tmp_short; +//--- check + if(index<0 || index+shift<0 || index+shift>=m_data_total) + return(false); + if(shift==0) + return(true); +//--- move + tmp_short=m_data[index]; + if(shift>0) + { + if(MemMove(index,index+1,shift)<0) + return(false); + } + else + { + if(MemMove(index+shift+1,index+shift,-shift)<0) + return(false); + } + m_data[index+shift]=tmp_short; + m_sort_mode=-1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Deleting element from the specified position | +//+------------------------------------------------------------------+ +bool CArrayUShort::Delete(const int index) + { +//--- check + if(index<0 || index>=m_data_total) + return(false); +//--- delete + if(indexto || from>=m_data_total) + return(false); +//--- delete + if(to>=m_data_total-1) + to=m_data_total-1; + if(MemMove(from,to+1,m_data_total-to-1)<0) + return(false); + m_data_total-=to-from+1; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Equality comparison of two arrays | +//+------------------------------------------------------------------+ +bool CArrayUShort::CompareArray(const ushort &array[]) const + { +//--- compare + if(m_data_total!=ArraySize(array)) + return(false); + for(int i=0;i>1" is quick division by 2 + p_short=m_data[(beg+end)>>1]; + while(ip_short) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + if(i<=j) + { + t_short=m_data[i]; + m_data[i++]=m_data[j]; + m_data[j]=t_short; + //--- control the output of the array bounds + if(j==0) + break; + j--; + } + } + if(begelement) + Insert(element,pos); + else + Insert(element,pos+1); +//--- restore the sorting flag after Insert(...) + m_sort_mode=0; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchLinear(const ushort element) const + { +//--- check + if(m_data_total==0) + return(-1); +//--- + for(int i=0;i=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_short=m_data[m]; + if(t_short==element) + break; + if(t_short>element) + j=m-1; + else + i=m+1; + } +//--- position + return(m); + } +//+------------------------------------------------------------------+ +//| Search of position of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::Search(const ushort element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than | +//| specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchGreat(const ushort element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]<=element) + if(++pos==m_data_total) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than | +//| specified in the sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchLess(const ushort element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + while(m_data[pos]>=element) + if(pos--==0) + return(-1); +//--- position + return(pos); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is greater than or | +//| equal to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchGreatOrEqual(const ushort element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Search position of the first element which is less than or equal | +//| to the specified in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchLessOrEqual(const ushort element) const + { +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + for(int pos=QuickSearch(element);pos>=0;pos--) + if(m_data[pos]<=element) + return(pos); +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of first appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchFirst(const ushort element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(pos--==0) + break; + return(pos+1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Find position of last appearance of element in a sorted array | +//+------------------------------------------------------------------+ +int CArrayUShort::SearchLast(const ushort element) const + { + int pos; +//--- check + if(m_data_total==0 || !IsSorted()) + return(-1); +//--- search + pos=QuickSearch(element); + if(m_data[pos]==element) + { + while(m_data[pos]==element) + if(++pos==m_data_total) + break; + return(pos-1); + } +//--- not found + return(-1); + } +//+------------------------------------------------------------------+ +//| Writing array to file | +//+------------------------------------------------------------------+ +bool CArrayUShort::Save(const int file_handle) + { + int i=0; +//--- check + if(!CArray::Save(file_handle)) + return(false); +//--- write array length + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- write array + for(i=0;i +//+------------------------------------------------------------------+ +//| Class CList. | +//| Purpose: Provides the possibility of working with the list of | +//| CObject instances and its dervivatives | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CList : public CObject + { +protected: + CObject *m_first_node; // pointer to the first element of the list + CObject *m_last_node; // pointer to the last element of the list + CObject *m_curr_node; // pointer to the current element of the list + int m_curr_idx; // index of the current list item + int m_data_total; // number of elements + bool m_free_mode; // flag of the necessity of "physical" deletion of object + bool m_data_sort; // flag if the list is sorted or not + int m_sort_mode; // mode of sorting of array + +public: + CList(void); + ~CList(void); + //--- methods of access to protected data + bool FreeMode(void) const { return(m_free_mode); } + void FreeMode(bool mode) { m_free_mode=mode; } + int Total(void) const { return(m_data_total); } + bool IsSorted(void) const { return(m_data_sort); } + int SortMode(void) const { return(m_sort_mode); } + //--- method of identifying the object + virtual int Type(void) const { return(0x7779); } + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + //--- method of creating an element of the list + virtual CObject *CreateElement(void) { return(NULL); } + //--- methods of filling the list + int Add(CObject *new_node); + int Insert(CObject *new_node,int index); + //--- methods for navigating + int IndexOf(CObject *node); + CObject *GetNodeAtIndex(int index); + CObject *GetFirstNode(void); + CObject *GetPrevNode(void); + CObject *GetCurrentNode(void); + CObject *GetNextNode(void); + CObject *GetLastNode(void); + //--- methods for deleting + CObject *DetachCurrent(void); + bool DeleteCurrent(void); + bool Delete(int index); + void Clear(void); + //--- method for comparing lists + bool CompareList(CList *List); + //--- methods for changing + void Sort(int mode); + bool MoveToIndex(int index); + bool Exchange(CObject *node1,CObject *node2); + //--- method for searching + CObject *Search(CObject *element); +protected: + void QuickSort(int beg,int end,int mode); + CObject *QuickSearch(CObject *element); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CList::CList(void) : m_first_node(NULL), + m_last_node(NULL), + m_curr_node(NULL), + m_curr_idx(-1), + m_data_total(0), + m_free_mode(true), + m_data_sort(false), + m_sort_mode(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CList::~CList(void) + { + Clear(); + } +//+------------------------------------------------------------------+ +//| Method QuickSort | +//+------------------------------------------------------------------+ +void CList::QuickSort(int beg,int end,int mode) + { + int i,j,k; + CObject *i_ptr,*j_ptr,*k_ptr; +//--- + i_ptr=GetNodeAtIndex(i=beg); + j_ptr=GetNodeAtIndex(j=end); + while(i>1" is quick division by 2 + k_ptr=GetNodeAtIndex(k=(beg+end)>>1); + while(i0) + { + //--- control the output of the array bounds + if(j==0) + break; + j--; + j_ptr=j_ptr.Prev(); + } + if(i<=j) + { + Exchange(i_ptr,j_ptr); + i++; + i_ptr=GetNodeAtIndex(i); + //--- control the output of the array bounds + if(j==0) + break; + else + { + j--; + j_ptr=GetNodeAtIndex(j); + } + } + } + if(begm_data_total || index<0) + return(-1); +//--- adjust + if(index==-1) + { + if(m_curr_node==NULL) + return(Add(new_node)); + } + else + { + if(GetNodeAtIndex(index)==NULL) + return(Add(new_node)); + } +//--- no need to check m_curr_node + tmp_node=m_curr_node.Prev(); + new_node.Prev(tmp_node); + if(tmp_node!=NULL) + tmp_node.Next(new_node); + else + m_first_node=new_node; + new_node.Next(m_curr_node); + m_curr_node.Prev(new_node); + m_data_total++; + m_data_sort=false; + m_curr_node=new_node; +//--- result + return(index); + } +//+------------------------------------------------------------------+ +//| Get a pointer to the position of element in the list | +//+------------------------------------------------------------------+ +CObject *CList::GetNodeAtIndex(int index) + { + int i; + bool revers; + CObject *result; +//--- check + if(index>=m_data_total) + return(NULL); + if(index==m_curr_idx) + return(m_curr_node); +//--- optimize bust list + if(indexindex;i--) + { + result=result.Prev(); + if(result==NULL) + return(NULL); + } + } + else + { + //--- search from left to right + for(;i=m_data_total || !CheckPointer(m_curr_node)) + return(false); +//--- tune + if(m_curr_idx==index) + return(true); + if(m_curr_idx=i) + { + //--- ">>1" is quick division by 2 + m=(j+i)>>1; + if(m<0 || m>=m_data_total) + break; + t_node=GetNodeAtIndex(m); + if(t_node.Compare(element,m_sort_mode)==0) + break; + if(t_node.Compare(element,m_sort_mode)>0) + j=m-1; + else + i=m+1; + t_node=NULL; + } +//--- result + return(t_node); + } +//+------------------------------------------------------------------+ +//| Search position of an element in a sorted list | +//+------------------------------------------------------------------+ +CObject *CList::Search(CObject *element) + { + CObject *result; +//--- check + if(!CheckPointer(element) || !m_data_sort) + return(NULL); +//--- search + result=QuickSearch(element); +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Writing list to file | +//+------------------------------------------------------------------+ +bool CList::Save(const int file_handle) + { + CObject *node; + bool result=true; +//--- check + if(!CheckPointer(m_curr_node) || file_handle==INVALID_HANDLE) + return(false); +//--- write start marker - 0xFFFFFFFFFFFFFFFF + if(FileWriteLong(file_handle,-1)!=sizeof(long)) + return(false); +//--- write type + if(FileWriteInteger(file_handle,Type(),INT_VALUE)!=INT_VALUE) + return(false); +//--- write list size + if(FileWriteInteger(file_handle,m_data_total,INT_VALUE)!=INT_VALUE) + return(false); +//--- sequential scannning of elements in the list using the call of method Save() + node=m_first_node; + while(node!=NULL) + { + result&=node.Save(file_handle); + node=node.Next(); + } +//--- successful + return(result); + } +//+------------------------------------------------------------------+ +//| Reading list from file | +//+------------------------------------------------------------------+ +bool CList::Load(const int file_handle) + { + uint i,num; + CObject *node; + bool result=true; +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- read and checking begin marker - 0xFFFFFFFFFFFFFFFF + if(FileReadLong(file_handle)!=-1) + return(false); +//--- read and checking type + if(FileReadInteger(file_handle,INT_VALUE)!=Type()) + return(false); +//--- read list size + num=FileReadInteger(file_handle,INT_VALUE); +//--- sequential creation of list items using the call of method Load() + Clear(); + for(i=0;i0) + p_node.Left(new_node); + else + p_node.Right(new_node); + new_node.Parent(p_node); + Balance(p_node); + } + else + m_root_node=new_node; +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Method of removing a node from the tree | +//+------------------------------------------------------------------+ +bool CTree::Delete(CTreeNode *node) + { +//--- check + if(!CheckPointer(node)) + return(false); +//--- delete + if(Detach(node) && CheckPointer(node)==POINTER_DYNAMIC) + delete node; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Method of detaching node from the tree | +//+------------------------------------------------------------------+ +bool CTree::Detach(CTreeNode *node) + { + CTreeNode *curr_node,*tmp_node; + CTreeNode *nodeA,*nodeB; +//--- check + curr_node=node; + if(!CheckPointer(curr_node)) + return(false); +//--- detach + if(curr_node.BalanceL()>curr_node.BalanceR()) + { + nodeA=curr_node.Left(); + while(nodeA.Right()!=NULL) + nodeA=nodeA.Right(); + nodeB=nodeA.Parent(); + if(nodeB!=curr_node) + { + nodeB.Right(nodeA.Left()); + tmp_node=nodeB.Right(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeB); + tmp_node=curr_node.Left(); + nodeA.Left(tmp_node); + tmp_node.Parent(nodeA); + } + //--- left link of curr_node is already installed as it should be + curr_node.Left(NULL); + //--- transferring the right link of curr_node to nodeA + nodeA.Right(curr_node.Right()); + tmp_node=curr_node.Right(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + curr_node.Right(NULL); + //--- transferring the root link of curr_node to nodeA + tmp_node=curr_node.Parent(); + nodeA.Parent(tmp_node); + if(tmp_node!=NULL) + { + if(tmp_node.Left()==curr_node) + tmp_node.Left(nodeA); + else + tmp_node.Right(nodeA); + } + else + { + curr_node.Parent(NULL); + m_root_node=nodeA; + tmp_node=nodeA; + } + Balance(tmp_node); + } + else + { + if(curr_node.BalanceR()>0) + { + nodeA=curr_node.Right(); + while(nodeA.Left()!=NULL) + nodeA=nodeA.Left(); + nodeB=nodeA.Parent(); + if(nodeB!=curr_node) + { + nodeB.Left(nodeA.Right()); + tmp_node=nodeB.Left(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeB); + tmp_node=curr_node.Right(); + nodeA.Right(tmp_node); + tmp_node.Parent(nodeA); + } + //--- right link of curr_node is already installed as it should be + curr_node.Right(NULL); + //--- transferring the left link of curr_node to nodeA + nodeA.Left(curr_node.Left()); + tmp_node=curr_node.Left(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + curr_node.Left(NULL); + //--- transferring the root link of curr_node to nodeA + tmp_node=curr_node.Parent(); + nodeA.Parent(tmp_node); + if(tmp_node!=NULL) + { + if(tmp_node.Left()==curr_node) + tmp_node.Left(nodeA); + else + tmp_node.Right(nodeA); + } + else + { + curr_node.Parent(NULL); + m_root_node=nodeA; + tmp_node=nodeA; + } + Balance(tmp_node); + } + else + { + //--- node list + if(curr_node.Parent()==NULL) + m_root_node=NULL; + else + { + tmp_node=curr_node.Parent(); + if(tmp_node.Left()==curr_node) + tmp_node.Left(NULL); + else + tmp_node.Right(NULL); + curr_node.Parent(NULL); + } + Balance(curr_node.Parent()); + } + } +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Method of cleaning the tree | +//+------------------------------------------------------------------+ +void CTree::Clear(void) + { + if(CheckPointer(m_root_node)==POINTER_DYNAMIC) + delete m_root_node; + m_root_node=NULL; + } +//+------------------------------------------------------------------+ +//| Method of searching for a node in the tree | +//+------------------------------------------------------------------+ +CTreeNode *CTree::Find(const CTreeNode *node) + { + CTreeNode *result=m_root_node; +//--- find + while(result!=NULL && result.Compare(node)!=0) + result=result.GetNext(node); +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Method of balancing the tree | +//+------------------------------------------------------------------+ +void CTree::Balance(CTreeNode *node) + { + CTreeNode *nodeA,*nodeB,*nodeC,*curr_node,*tmp_node; +//--- + curr_node=node; + while(curr_node!=NULL) + { + curr_node.RefreshBalance(); + if(MathAbs(curr_node.BalanceL()-curr_node.BalanceR())<=1) + curr_node=curr_node.Parent(); + else + { + if(curr_node.BalanceR()>curr_node.BalanceL()) + { + //--- rotation to the right + tmp_node=curr_node.Right(); + if(tmp_node.BalanceL()>tmp_node.BalanceR()) + { + //--- great rotation to the right + nodeA=curr_node; + nodeB=nodeA.Right(); + nodeC=nodeB.Left(); + nodeC.Parent(nodeA.Parent()); + tmp_node=nodeC.Parent(); + if(tmp_node!=NULL) + { + if(tmp_node.Right()==nodeA) + tmp_node.Right(nodeC); + else + tmp_node.Left(nodeC); + } + else + m_root_node=nodeC; + nodeA.Parent(nodeC); + nodeB.Parent(nodeC); + nodeA.Right(nodeC.Left()); + tmp_node=nodeA.Right(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + nodeC.Left(nodeA); + nodeB.Left(nodeC.Right()); + tmp_node=nodeB.Left(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeB); + nodeC.Right(nodeB); + if(m_root_node==nodeA) + m_root_node=nodeC; + curr_node=nodeC.Parent(); + } + else + { + //--- slight rotation to the right + nodeA=curr_node; + nodeB=nodeA.Right(); + nodeB.Parent(nodeA.Parent()); + tmp_node=nodeB.Parent(); + if(tmp_node!=NULL) + { + if(tmp_node.Right()==nodeA) + tmp_node.Right(nodeB); + else + tmp_node.Left(nodeB); + } + else + m_root_node=nodeB; + nodeA.Parent(nodeB); + nodeA.Right(nodeB.Left()); + tmp_node=nodeA.Right(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + nodeB.Left(nodeA); + if(m_root_node==nodeA) + m_root_node=nodeB; + curr_node=nodeB.Parent(); + } + } + else + { + //--- rotation to the left + tmp_node=curr_node.Left(); + if(tmp_node.BalanceR()>tmp_node.BalanceL()) + { + //--- great rotation to the left + nodeA=curr_node; + nodeB=nodeA.Left(); + nodeC=nodeB.Right(); + nodeC.Parent(nodeA.Parent()); + tmp_node=nodeC.Parent(); + if(tmp_node!=NULL) + { + if(tmp_node.Right()==nodeA) + tmp_node.Right(nodeC); + else + tmp_node.Left(nodeC); + } + else + m_root_node=nodeC; + nodeA.Parent(nodeC); + nodeB.Parent(nodeC); + nodeA.Left(nodeC.Right()); + tmp_node=nodeA.Left(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + nodeC.Right(nodeA); + nodeB.Right(nodeC.Left()); + tmp_node=nodeB.Right(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeB); + nodeC.Left(nodeB); + if(m_root_node==nodeA) + m_root_node=nodeC; + curr_node=nodeC.Parent(); + } + else + { + //--- small rotation to the left + nodeA=curr_node; + nodeB=nodeA.Left(); + nodeB.Parent(nodeA.Parent()); + tmp_node=nodeB.Parent(); + if(tmp_node!=NULL) + { + if(tmp_node.Right()==nodeA) + tmp_node.Right(nodeB); + else + tmp_node.Left(nodeB); + } + else + m_root_node=nodeB; + nodeA.Parent(nodeB); + nodeA.Left(nodeB.Right()); + tmp_node=nodeA.Left(); + if(tmp_node!=NULL) + tmp_node.Parent(nodeA); + nodeB.Right(nodeA); + if(m_root_node==nodeA) + m_root_node=nodeB; + curr_node=nodeB.Parent(); + } + } + } + } + } +//+------------------------------------------------------------------+ +//| Writing tree to file | +//+------------------------------------------------------------------+ +bool CTree::Save(const int file_handle) + { +//--- check + if(file_handle==INVALID_HANDLE) + return(false); + if(m_root_node==NULL) + return(true); +//--- result + return(m_root_node.SaveNode(file_handle)); + } +//+------------------------------------------------------------------+ +//| Reading tree from file | +//+------------------------------------------------------------------+ +bool CTree::Load(const int file_handle) + { +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- create root node only + Clear(); + Insert(CreateElement()); +//--- result + return(m_root_node.LoadNode(file_handle,m_root_node)); + } +//+------------------------------------------------------------------+ diff --git a/Include/Arrays/TreeNode.mqh b/Include/Arrays/TreeNode.mqh new file mode 100644 index 0000000..494b313 --- /dev/null +++ b/Include/Arrays/TreeNode.mqh @@ -0,0 +1,174 @@ +//+------------------------------------------------------------------+ +//| TreeNode.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CTreeNode. | +//| Purpose: Base class of node of binary tree CTree. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CTreeNode : public CObject + { +private: + CTreeNode *m_p_node; // link to node up + CTreeNode *m_l_node; // link to node left + CTreeNode *m_r_node; // link to node right + //--- variables + int m_balance; // balance of node + int m_l_balance; // balance of the left branch + int m_r_balance; // balance of the right branch + +public: + CTreeNode(void); + ~CTreeNode(void); + //--- methods of access to protected data + CTreeNode* Parent(void) const { return(m_p_node); } + void Parent(CTreeNode *node) { m_p_node=node; } + CTreeNode* Left(void) const { return(m_l_node); } + void Left(CTreeNode *node) { m_l_node=node; } + CTreeNode* Right(void) const { return(m_r_node); } + void Right(CTreeNode *node) { m_r_node=node; } + int Balance(void) const { return(m_balance); } + int BalanceL(void) const { return(m_l_balance); } + int BalanceR(void) const { return(m_r_balance); } + //--- method of identifying the object + virtual int Type(void) const { return(0x8888); } + //--- methods for controlling + int RefreshBalance(void); + CTreeNode *GetNext(const CTreeNode *node); + //--- methods for working with files + bool SaveNode(const int file_handle); + bool LoadNode(const int file_handle,CTreeNode *main); + +protected: + //--- method for creating an instance of class + virtual CTreeNode *CreateSample(void) { return(NULL); } + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CTreeNode::CTreeNode(void) : m_p_node(NULL), + m_l_node(NULL), + m_r_node(NULL), + m_balance(0), + m_l_balance(0), + m_r_balance(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CTreeNode::~CTreeNode(void) + { +//--- delete nodes of the next level + if(m_l_node!=NULL) + delete m_l_node; + if(m_r_node!=NULL) + delete m_r_node; + } +//+------------------------------------------------------------------+ +//| Calculating the balance of the node | +//+------------------------------------------------------------------+ +int CTreeNode::RefreshBalance(void) + { +//--- calculate the balance of the left branch + if(m_l_node==NULL) + m_l_balance=0; + else + m_l_balance=m_l_node.RefreshBalance(); +//--- calculate the balance of the right branch + if(m_r_node==NULL) + m_r_balance=0; + else + m_r_balance=m_r_node.RefreshBalance(); +//--- calculate the balance of the node + if(m_r_balance>m_l_balance) + m_balance=m_r_balance+1; + else + m_balance=m_l_balance+1; +//--- result + return(m_balance); + } +//+------------------------------------------------------------------+ +//| Selecting next node | +//+------------------------------------------------------------------+ +CTreeNode *CTreeNode::GetNext(const CTreeNode *node) + { + if(Compare(node)>0) + return(m_l_node); +//--- result + return(m_r_node); + } +//+------------------------------------------------------------------+ +//| Writing node data to file | +//+------------------------------------------------------------------+ +bool CTreeNode::SaveNode(const int file_handle) + { + bool result=true; +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- write left node (if it is available) + if(m_l_node!=NULL) + { + FileWriteInteger(file_handle,'L',SHORT_VALUE); + result&=m_l_node.SaveNode(file_handle); + } + else + FileWriteInteger(file_handle,'X',SHORT_VALUE); +//--- write data of current node + result&=Save(file_handle); +//--- write right node (if it is available) + if(m_r_node!=NULL) + { + FileWriteInteger(file_handle,'R',SHORT_VALUE); + result&=m_r_node.SaveNode(file_handle); + } + else + FileWriteInteger(file_handle,'X',SHORT_VALUE); +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Reading node data from file | +//+------------------------------------------------------------------+ +bool CTreeNode::LoadNode(const int file_handle,CTreeNode *main) + { + bool result=true; + short s_val; + CTreeNode *node; +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- read directions + s_val=(short)FileReadInteger(file_handle,SHORT_VALUE); + if(s_val=='L') + { + //--- read left node (if there is data) + node=CreateSample(); + if(node==NULL) + return(false); + m_l_node=node; + node.Parent(main); + result&=node.LoadNode(file_handle,node); + } +//--- read data of current node + result&=Load(file_handle); +//--- read directions + s_val=(short)FileReadInteger(file_handle,SHORT_VALUE); + if(s_val=='R') + { + //--- read right node (if there is data) + node=CreateSample(); + if(node==NULL) + return(false); + m_r_node=node; + node.Parent(main); + result&=node.LoadNode(file_handle,node); + } +//--- result + return(result); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/Canvas.mqh b/Include/Canvas/Canvas.mqh new file mode 100644 index 0000000..3e07876 --- /dev/null +++ b/Include/Canvas/Canvas.mqh @@ -0,0 +1,4861 @@ +//+------------------------------------------------------------------+ +//| Canvas.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include +#include + +#define SIGN(i) ((i<0) ? -1 : 1) + +//+------------------------------------------------------------------+ +//| Macro to generate color | +//+------------------------------------------------------------------+ +#define XRGB(r,g,b) (0xFF000000|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b)) +#define ARGB(a,r,g,b) ((uchar(a)<<24)|(uchar(r)<<16)|(uchar(g)<<8)|uchar(b)) +#define TRGB(a,rgb) ((uchar(a)<<24)|(rgb)) +#define GETRGB(clr) ((clr)&0xFFFFFF) +#define GETRGBA(clr) uchar((clr)>>24) +#define GETRGBR(clr) uchar((clr)>>16) +#define GETRGBG(clr) uchar((clr)>>8) +#define GETRGBB(clr) uchar(clr) +#define COLOR2RGB(clr) (0xFF000000|(uchar(clr)<<16)|(uchar((clr)>>8)<<8)|uchar((clr)>>16)) +#define RGB2COLOR(rgb) ((uchar(rgb)<<16)|(uchar((rgb)>>8)<<8)|uchar((rgb)>>16)) + +//+------------------------------------------------------------------+ +//| Line end style (round, butt, square) | +//+------------------------------------------------------------------+ +enum ENUM_LINE_END + { + LINE_END_ROUND, + LINE_END_BUTT, + LINE_END_SQUARE, + }; + +//+------------------------------------------------------------------+ +//| Class CCanvas | +//| Usage: class for working with a dynamic resource | +//+------------------------------------------------------------------+ +class CCanvas + { +private: + uint m_style; // line style template + uint m_style_idx; // variable - current index of bit in line style template + static uint m_default_colors[9]; // default colors + +protected: + long m_chart_id; // chart ID + string m_objname; // object name + ENUM_OBJECT m_objtype; // object type + string m_rcname; // resource name + int m_width; // canvas width + int m_height; // canvas height + ENUM_COLOR_FORMAT m_format; // method of color processing + //--- for text + string m_fontname; // font name + int m_fontsize; // font size + uint m_fontflags; // font flags + uint m_fontangle; // angle of text tilt to the X axis in 0.1 degrees + //--- data + uint m_pixels[]; // array of pixels + +public: + CCanvas(void); + ~CCanvas(void); + //--- create/attach/destroy + virtual bool Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + bool CreateBitmap(const string name,const datetime time,const double price, + const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + bool CreateBitmap(const long chart_id,const int subwin,const string name, + const datetime time,const double price,const int width,const int height, + ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + bool CreateBitmapLabel(const string name,const int x,const int y, + const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + bool CreateBitmapLabel(const long chart_id,const int subwin,const string name, + const int x,const int y,const int width,const int height, + ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + virtual bool Attach(const long chart_id,const string objname,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + virtual bool Attach(const long chart_id,const string objname,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + virtual void Destroy(void); + //--- properties + string ChartObjectName(void) const { return(m_objname); } + string ResourceName(void) const { return(m_rcname); } + int Width(void) const { return(m_width); } + int Height(void) const { return(m_height); } + //--- update object on screen + void Update(const bool redraw=true); + bool Resize(const int width,const int height); + //--- clear/fill color + void Erase(const uint clr=0); + //--- data access + uint PixelGet(const int x,const int y) const; + void PixelSet(const int x,const int y,const uint clr); + //--- draw primitives + void LineVertical(int x,int y1,int y2,const uint clr); + void LineHorizontal(int x1,int x2,int y,const uint clr); + void Line(int x1,int y1,int x2,int y2,const uint clr); + void Polyline(int &x[],int &y[],const uint clr); + void Polygon(int &x[],int &y[],const uint clr); + void Rectangle(int x1,int y1,int x2,int y2,const uint clr); + void Triangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr); + void Circle(int x,int y,int r,const uint clr); + void Ellipse(int x1,int y1,int x2,int y2,const uint clr); + void Arc(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,const uint clr); + void Arc(int x,int y,int rx,int ry,double fi3,double fi4,const uint clr); + void Arc(int x,int y,int rx,int ry,double fi3,double fi4,int &x3,int &y3,int &x4,int &y4,const uint clr); + void Pie(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,const uint clr,const uint fill_clr); + void Pie(int x,int y,int rx,int ry,double fi3,double fi4,const uint clr,const uint fill_clr); + //--- draw filled primitives + void FillRectangle(int x1,int y1,int x2,int y2,const uint clr); + void FillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr); + void FillPolygon(int &x[],int &y[],const uint clr); + void FillCircle(int x,int y,int r,const uint clr); + void FillEllipse(int x1,int y1,int x2,int y2,const uint clr); + void Fill(int x,int y,const uint clr); + void Fill(int x,int y,const uint clr,const uint threshould); + //--- draw primitives with antialiasing + void PixelSetAA(const double x,const double y,const uint clr); + void LineAA(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX); + void PolylineAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX); + void PolygonAA(int &x[],int &y[],const uint clr,const uint style=UINT_MAX); + void TriangleAA(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3, + const uint clr,const uint style=UINT_MAX); + void CircleAA(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX); + void EllipseAA(const double x1,const double y1,const double x2,const double y2,const uint clr,const uint style=UINT_MAX); + //--- draw primitives with antialiasing by Wu's algorithm + void LineWu(int x1,int y1,int x2,int y2,const uint clr,const uint style=UINT_MAX); + void PolylineWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX); + void PolygonWu(const int &x[],const int &y[],const uint clr,const uint style=UINT_MAX); + void TriangleWu(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style=UINT_MAX); + void CircleWu(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX); + void EllipseWu(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style=UINT_MAX); + //--- draw primitives with prefiltered antialiasing + void LineThickVertical(const int x,const int y1,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style); + void LineThickHorizontal(const int x1,const int x2,const int y,const uint clr,const int size,const uint style,ENUM_LINE_END end_style); + void LineThick(const int x1,const int y1,const int x2,const int y2,const uint clr,const int size,const uint style,ENUM_LINE_END end_style); + void PolylineThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style); + void PolygonThick(const int &x[],const int &y[],const uint clr,const int size,const uint style,ENUM_LINE_END end_style); + //--- draw primitives smoothing polyline and polygon + void PolylineSmooth(const int &x[],const int &y[],const uint clr,const int size, + ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND, + double tension=0.5,double step=10); + void PolygonSmooth(int &x[],int &y[],const uint clr,const int size, + ENUM_LINE_STYLE style=STYLE_SOLID,ENUM_LINE_END end_style=LINE_END_ROUND, + double tension=0.5,double step=10); + //--- BitBlt + void BitBlt(int dst_x,int dst_y,const uint &src[],int src_width,int src_height,int src_x,int src_y,int src_dx,int src_dy,uint mode=0); + //--- for text + bool FontSet(const string name,const int size,const uint flags=0,const uint angle=0); + bool FontNameSet(string name); + bool FontSizeSet(int size); + bool FontFlagsSet(uint flags); + bool FontAngleSet(uint angle); + void FontGet(string &name,int &size,uint &flags,uint &angle); + string FontNameGet(void) const { return(m_fontname); } + int FontSizeGet(void) const { return(m_fontsize); } + uint FontFlagsGet(void) const { return(m_fontflags); } + uint FontAngleGet(void) const { return(m_fontangle); } + void TextOut(int x,int y,string text,const uint clr,uint alignment=0); + int TextWidth(const string text); + int TextHeight(const string text); + void TextSize(const string text,int &width,int &height); + //--- services + static uint GetDefaultColor(const int i); + void TransparentLevelSet(const uchar value); + //--- load bitmap from file + bool LoadFromFile(const string filename); + //--- line style property + uint LineStyleGet(void) const; + void LineStyleSet(const uint style); + //--- load bitmap from file to buffer + static bool LoadBitmap(const string filename,uint &data[],int &width,int &height); + +private: + bool FontSet(void); + void TextOutFast(int x,int y,string text,const uint clr,uint alignment=0); + bool PixelsSimilar(const uint clr0,const uint clr1,const uint threshould); + //--- for Wu's algorithm + void PixelTransform(const int x,const int y,const uint clr,const double alpha); + //--- for circle and ellipse + void PixelTransform4(const int x,const int y,const int dx,const int dy,const uint clr,const double alpha); + void PixelSet4AA(const double x,const double y,const double dx,const double dy,const uint clr); + //--- for thick line + void SegmentVertical(const int x,const int y1,const int y2,const int ysign,const double r,const uint clr,ENUM_LINE_END end_style); + void SegmentHorizontal(const int x1,const int x2,const int y,const int xsign,const double r,const uint clr,ENUM_LINE_END end_style); + void Segment(const int x1,const int y1,const int x2,const int y2,const double kp0,const double kp1,const int xsign,const int ysign, + const double rcos_k,const double rsin_k,const double r,const uint clr,ENUM_LINE_END end_style); + double DistancePointSegment(const double px,const double py,const double x1,const double y1,const double x2,const double y2); + //--- for pie + double AngleCalc(int x1,int y1,int x2,int y2); + //--- for polygon + int PointClassify(const CPoint &p0,const CPoint &p1,const CPoint &p2); + int PolygonClassify(const CPoint &p[]); + bool IsPolygonConvex(CPoint &p[]); + void PolygonNormalize(CPoint &p[]); + void PolygonIntersect(CPoint &p[],CPoint &add[]); + void PolygonFill(CPoint &p[],const uint clr); + //--- for smoothing polyline and polygon + void CalcCurveBezierEndp(const double xend,const double yend,const double xadj,const double yadj,const double tension,double &x,double &y); + void CalcCurveBezier(const int &x[],const int &y[],const int i,const double tension,double &x1,double &y1,double &x2,double &y2); + double CalcBezierX(const double t,const double x0,const double x1,const double x2,const double x3); + double CalcBezierY(const double t,const double y0,const double y1,const double y2,const double y3); + +protected: + //--- method for prefiltered antialiasing + virtual double FilterFunction(const double x); + }; +//+------------------------------------------------------------------+ +//| Initialize static array | +//+------------------------------------------------------------------+ +uint CCanvas::m_default_colors[9]= + { + XRGB(0,0,255), // blue + XRGB(255,0,0), // red + XRGB(0,128,0), // green + XRGB(255,242,0), // yellow + XRGB(255,0,128), // pink + XRGB(0,255,0), // lime + XRGB(185,0,61), // crimson + XRGB(0,183,239), // sky blue + XRGB(255,128,0) // orange + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CCanvas::CCanvas(void) : m_chart_id(0), + m_objname(NULL), + m_objtype(WRONG_VALUE), + m_rcname(NULL), + m_width(0), + m_height(0), + m_format(COLOR_FORMAT_XRGB_NOALPHA), + m_fontname("arial"), + m_fontsize(-120), + m_fontflags(0), + m_fontangle(0), + m_style(UINT_MAX), + m_style_idx(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CCanvas::~CCanvas(void) + { + } +//+------------------------------------------------------------------+ +//| Create dynamic resource | +//+------------------------------------------------------------------+ +bool CCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { + Destroy(); +//--- prepare data array + if(width>0 && height>0 && ArrayResize(m_pixels,width*height)>0) + { + //--- generate uniq resource name + string uniq=(string)ChartID()+(string)GetTickCount()+"."+(string)(GetMicrosecondCount()&0x3FF); + + m_rcname="::" + StringSubstr(name,0,63-StringLen(uniq))+uniq; + //--- initialize data with zeros + ArrayInitialize(m_pixels,0); + //--- create dynamic resource + if(ResourceCreate(m_rcname,m_pixels,width,height,0,0,0,clrfmt)) + { + //--- successfully created + //--- complete initialization + m_width =width; + m_height=height; + m_format=clrfmt; + //--- succeed + return(true); + } + } +//--- error - destroy object + Destroy(); + return(false); + } +//+------------------------------------------------------------------+ +//| Create object on chart with attached dynamic resource | +//+------------------------------------------------------------------+ +bool CCanvas::CreateBitmap(const string name,const datetime time,const double price, + const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { + return(CreateBitmap(0,0,name,time,price,width,height,clrfmt)); + } +//+------------------------------------------------------------------+ +//| Create object on chart with attached dynamic resource | +//+------------------------------------------------------------------+ +bool CCanvas::CreateBitmap(const long chart_id,const int subwin,const string name, + const datetime time,const double price,const int width,const int height, + ENUM_COLOR_FORMAT clrfmt) + { +//--- create canvas + if(Create(name,width,height,clrfmt)) + { + //--- create attached object + if(ObjectCreate(chart_id,name,OBJ_BITMAP,subwin,time,price)) + { + //--- bind object with resource + if(ObjectSetString(chart_id,name,OBJPROP_BMPFILE,m_rcname)) + { + //--- successfully created + //--- complete initialization + m_chart_id=chart_id; + m_objname =name; + m_objtype =OBJ_BITMAP; + //--- succeed + return(true); + } + } + } +//--- error + return(false); + } +//+------------------------------------------------------------------+ +//| Create object on chart with attached dynamic resource | +//+------------------------------------------------------------------+ +bool CCanvas::CreateBitmapLabel(const string name,const int x,const int y, + const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { + return(CreateBitmapLabel(0,0,name,x,y,width,height,clrfmt)); + } +//+------------------------------------------------------------------+ +//| Create object on chart with attached dynamic resource | +//+------------------------------------------------------------------+ +bool CCanvas::CreateBitmapLabel(const long chart_id,const int subwin,const string name, + const int x,const int y,const int width,const int height, + ENUM_COLOR_FORMAT clrfmt) + { +//--- create canvas + if(Create(name,width,height,clrfmt)) + { + //--- create attached object + if(ObjectCreate(chart_id,name,OBJ_BITMAP_LABEL,subwin,0,0)) + { + //--- set x,y and bind object with resource + if(ObjectSetInteger(chart_id,name,OBJPROP_XDISTANCE,x) && + ObjectSetInteger(chart_id,name,OBJPROP_YDISTANCE,y) && + ObjectSetString(chart_id,name,OBJPROP_BMPFILE,m_rcname)) + { + //--- successfully created + //--- complete initialization + m_chart_id=chart_id; + m_objname =name; + m_objtype =OBJ_BITMAP_LABEL; + //--- succeed + return(true); + } + } + } +//--- error + return(false); + } +//+------------------------------------------------------------------+ +//| Attach new object with bitmap resource | +//+------------------------------------------------------------------+ +bool CCanvas::Attach(const long chart_id,const string objname,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA) + { + if(OBJ_BITMAP_LABEL==ObjectGetInteger(chart_id,objname,OBJPROP_TYPE)) + { + string rcname=ObjectGetString(chart_id,objname,OBJPROP_BMPFILE); + rcname=StringSubstr(rcname,StringFind(rcname,"::")); + if(ResourceReadImage(rcname,m_pixels,(uint&)m_width,(uint&)m_height)) + { + m_chart_id=chart_id; + m_objname=objname; + m_rcname=rcname; + m_format=clrfmt; + m_objtype=OBJ_BITMAP_LABEL; + //--- success + return(true); + } + } +//--- failed + return(false); + } +//+------------------------------------------------------------------+ +//| Attach new object without bitmap resource | +//+------------------------------------------------------------------+ +bool CCanvas::Attach(const long chart_id,const string objname,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA) + { + if(OBJ_BITMAP_LABEL==ObjectGetInteger(chart_id,objname,OBJPROP_TYPE)) + { + string rcname=ObjectGetString(chart_id,objname,OBJPROP_BMPFILE); + if(StringLen(rcname)==0 && width>0 && height>0 && ArrayResize(m_pixels,width*height)>0) + { + ZeroMemory(m_pixels); + if(ResourceCreate("::"+objname,m_pixels,width,height,0,0,0,clrfmt) && + ObjectSetString(chart_id,objname,OBJPROP_BMPFILE,"::"+objname)) + { + m_chart_id=chart_id; + m_width=width; + m_height=height; + m_objname=objname; + m_rcname="::"+objname; + m_format=clrfmt; + m_objtype=OBJ_BITMAP_LABEL; + //--- success + return(true); + } + } + } +//--- failed + return(false); + } +//+------------------------------------------------------------------+ +//| Remove object from chart and deallocate data array | +//+------------------------------------------------------------------+ +void CCanvas::Destroy(void) + { +//--- delete object + if(m_objname!=NULL) + { + ObjectDelete(m_chart_id,m_objname); + m_chart_id=0; + m_objname =NULL; + m_objtype =WRONG_VALUE; + } +//--- deallocate array + ArrayFree(m_pixels); +//--- free resource + if(m_rcname!=NULL) + { + ResourceFree(m_rcname); + m_rcname=NULL; + } +//--- zeroize data + m_width =0; + m_height=0; + } +//+------------------------------------------------------------------+ +//| Update object on screen (redraw) | +//+------------------------------------------------------------------+ +void CCanvas::Update(const bool redraw) + { +//--- check + if(m_rcname==NULL) + return; +//--- update resource and redraw + if(ResourceCreate(m_rcname,m_pixels,m_width,m_height,0,0,0,m_format) && redraw) + ChartRedraw(this.m_chart_id); + } +//+------------------------------------------------------------------+ +//| Resize | +//+------------------------------------------------------------------+ +bool CCanvas::Resize(const int width,const int height) + { +//--- check + if(m_rcname!=NULL && width>0 && height>0) + if(ArrayResize(m_pixels,width*height)>0) + { + m_width =width; + m_height=height; + //--- initialize data with zeros + ArrayInitialize(m_pixels,0); + //--- create dynamic resource + if(ResourceCreate(m_rcname,m_pixels,m_width,m_height,0,0,0,m_format)) + { + //--- bind object with resource + if(m_objname!=NULL && ObjectSetString(m_chart_id,m_objname,OBJPROP_BMPFILE,m_rcname)) + return(true); + } + } +//--- error + return(false); + } +//+------------------------------------------------------------------+ +//| Clear/Fill color | +//+------------------------------------------------------------------+ +void CCanvas::Erase(const uint clr) + { + ArrayInitialize(m_pixels,clr); + } +//+------------------------------------------------------------------+ +//| Get pixel color | +//+------------------------------------------------------------------+ +uint CCanvas::PixelGet(const int x,const int y) const + { +//--- check coordinates + if(x>=0 && x=0 && y=0 && x=0 && y=m_width || y<0 || y>=m_height) + return; +//--- + int index=y*m_width+x; + uint old_clr=m_pixels[index]; +//--- check if replacement is necessary + if(old_clr==clr) + return; +//--- use pseudo stack to emulate deeply-nested recursive calls + int stack[]; + uint count=1; + int idx; + int total=ArraySize(m_pixels); +//--- allocate memory for stack + if(ArrayResize(stack,total)==-1) + return; + stack[0]=index; + m_pixels[index]=clr; + for(uint i=0; i0 && m_pixels[idx]==old_clr) + { + m_pixels[idx]=clr; + stack[count++]=idx; + } + //--- top adjacent point + idx=index-m_width; + if(idx>=0 && m_pixels[idx]==old_clr) + { + m_pixels[idx]=clr; + stack[count++]=idx; + } + //--- right adjacent point + idx=index+1; + if(x=m_width || y<0 || y>=m_height || threshould>255) + return; +//--- + int index=y*m_width+x; + uint old_clr=m_pixels[index]; +//--- check if replacement is necessary + if(old_clr==clr) + return; +//--- use pseudo stack to emulate deeply-nested recursive calls + int stack[]; + uint count=1; + int idx; + int total=ArraySize(m_pixels); +//--- allocate memory for stack + if(ArrayResize(stack,total)==-1) + return; + stack[0]=index; + m_pixels[index]=clr; + for(uint i=0; i0 && PixelsSimilar(m_pixels[idx],old_clr,threshould) && m_pixels[idx]!=clr) + { + m_pixels[idx]=clr; + stack[count++]=idx; + } + //--- top adjacent point + idx=index-m_width; + if(idx>=0 && PixelsSimilar(m_pixels[idx],old_clr,threshould) && m_pixels[idx]!=clr) + { + m_pixels[idx]=clr; + stack[count++]=idx; + } + //--- right adjacent point + idx=index+1; + if(xy2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } +//--- line is out of image boundaries + if(y2<0 || y1>=m_height || x<0 || x>=m_width) + return; +//--- stay withing image boundaries + if(y1<0) + y1=0; + if(y2>=m_height) + y2=m_height-1; +//--- draw line + int index=y1*m_width+x; + for(int i=y1; i<=y2; i++,index+=m_width) + m_pixels[index]=clr; + } +//+------------------------------------------------------------------+ +//| Draw horizontal line | +//+------------------------------------------------------------------+ +void CCanvas::LineHorizontal(int x1,int x2,int y,const uint clr) + { + int tmp; +//--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } +//--- line is out of image boundaries + if(x2<0 || x1>=m_width || y<0 || y>=m_height) + return; +//--- stay withing image boundaries + if(x1<0) + x1=0; + if(x2>=m_width) + x2=m_width-1; +//--- draw line + ArrayFill(m_pixels,y*m_width+x1,(x2-x1)+1,clr); + } +//+------------------------------------------------------------------+ +//| Draw line according to Bresenham's algorithm | +//+------------------------------------------------------------------+ +void CCanvas::Line(int x1,int y1,int x2,int y2,const uint clr) + { +//--- line is out of image boundaries + if((x1<0 && x2<0) || (y1<0 && y2<0)) + return; + if(x1>=m_width && x2>=m_width) + return; + if(y1>=m_height && y2>=m_height) + return; +//--- get length by X and Y + int dx=(x2>x1)? x2-x1 : x1-x2; + int dy=(y2>y1)? y2-y1 : y1-y2; + if(dx==0) + { + //--- vertical line + LineVertical(x1,y1,y2,clr); + return; + } + if(dy==0) + { + //--- horizontal line + LineHorizontal(x1,x2,y1,clr); + return; + } +//--- get direction by X and Y + int sx=(x1=m_width || + y1<0 || y1>=m_height) + { + if(draw) + return; + } + else + { + //--- draw pixel + m_pixels[y1*m_width+x1]=clr; + draw=true; + } + //--- get coordinates of next pixel + int er2=er<<1; + if(er2>-dy) + { + er-=dy; + x1+=sx; + } + if(er2ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; + total--; +//--- draw + for(int i=0; iArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; + total--; +//--- draw + for(int i=0; i=dx) + { + xx=x+dx; + if(xx>=0 && xx=0 && yy=0 && yy=0 && xx=0 && yy=0 && yy=0 && xx=0 && yy=0 && yy=0 && xx=0 && yy=0 && yy=0) + { + dy--; + dd_y+=2; + f+=dd_y; + } + dx++; + dd_x+=2; + f+=dd_x; + } + } +//+------------------------------------------------------------------+ +//| Draw ellipse according to Bresenham's algorithm | +//+------------------------------------------------------------------+ +void CCanvas::Ellipse(int x1,int y1,int x2,int y2,const uint clr) + { + int x,y; + int rx,ry; + int dx,dy; + int xx,yy; + int rx_sq,ry_sq; + int f; + int tmp; +//--- handle extreme conditions + if(x1==x2) + { + if(y1==y2) + PixelSet(x1,y1,clr); + else + LineVertical(x1,y1,y2,clr); + return; + } + if(y1==y2) + { + LineHorizontal(x1,x2,y1,clr); + return; + } +//--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } +//--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + x =(x2+x1)>>1; + y =(y2+y1)>>1; + rx=(x2-x1)>>1; + ry=(y2-y1)>>1; + dx=0; + dy=ry; + rx_sq=rx*rx; + ry_sq=ry*ry; + f=(rx_sq<<1)*((dy-1)*dy)+rx_sq+(ry_sq<<1)*(1-rx_sq); + while(rx_sq*dy>ry_sq*dx) + { + yy=y+dy; + if(yy>=0 && yy=0 && xx=0 && xx=0 && yy=0 && xx=0 && xx=0) + { + dy--; + f-=(rx_sq<<2)*dy; + } + f+=(ry_sq<<1)*(3+(dx<<1)); + dx++; + } + f=(ry_sq<<1)*(dx+1)*dx+(rx_sq<<1)*(dy*(dy-2)+1)+(1-(rx_sq<<1))*ry_sq; + while(dy>=0) + { + yy=y+dy; + if(yy>=0 && yy=0 && xx=0 && xx=0 && yy=0 && xx=0 && xxx2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } +//--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + x =(x2+x1)>>1; + y =(y2+y1)>>1; +//--- check rays + if(x3==x && y3==y) + return; + if(x4==x && y4==y) + return; +//--- calculate parameters of ray x3,y3 + fi3=AngleCalc(x,y,x3,y3); +//--- calculate parameters of ray x4,y4 + fi4=AngleCalc(x,y,x4,y4); +//--- draw arc + Arc(x,y,x2-x,y2-y,fi3,fi4,clr); + } +//+------------------------------------------------------------------+ +//| Draws ellipse arc | +//+------------------------------------------------------------------+ +void CCanvas::Arc(int x,int y,int rx,int ry,double fi3,double fi4,const uint clr) + { + int x3,y3,x4,y4; +//--- check + if(rx<10 || ry<10) + return; + if(rx<0) + rx=-rx; + if(ry<0) + ry=-ry; +//--- check rays + if(fi3==fi4) + return; +//--- adjustment for passing through 0 + if(fi40) || // ray 3 is in the 1st or 2nd quadrant + (fi0) || // ray 4 is in the 1st or 2nd quadrant + (fi4-fi3>=M_PI)) // arc will pass through the top of the ellipse + { + dx=0; + dy=ry; + f=(rx_sq<<1)*((dy-1)*dy)+rx_sq+(ry_sq<<1)*(1-rx_sq); + while(rx_sq*dy>=ry_sq*dx) + { + yy=y-dy; + if(dx==0) + { + //--- central point + fi=AngleCalc(0,0,0,-dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(x,yy,clr); + ckw=ackw=true; + } + else + ckw=ackw=false; + xx_c=x; + yy_c=yy; + fi_c=fi; + xx_a=x; + yy_a=yy; + fi_a=fi; + } + else + { + //--- iterate clockwise + xx=x+dx; + fi=AngleCalc(0,0,dx,-dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 4 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 4 + if(!ckw) + { + ckw=true; + if(!ray4) + { + if(MathAbs(fi_c-MathMod(fi4,2*M_PI))MathAbs(fi-fi3)) + PixelSet(x3=xx,y3=yy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + ray3=true; + } + ckw=false; + } + //--- save parameters of the last iteration + xx_c=xx; + yy_c=yy; + fi_c=fi; + //--- iterate counterclockwise + xx=x-dx; + fi=AngleCalc(0,0,-dx,-dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 3 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 3 + if(!ackw) + { + ackw=true; + if(!ray3) + { + if(MathAbs(fi_a-fi3)MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=xx,y4=yy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + ray4=true; + } + ackw=false; + } + //--- save parameters of the last iteration + xx_a=xx; + yy_a=yy; + fi_a=fi; + } + //--- calculate coordinates of the next point + if(f>=0) + { + dy--; + f-=(rx_sq<<2)*dy; + } + f+=(ry_sq<<1)*(3+(dx<<1)); + dx++; + } + //--- if arc has been drawn clockwise "to the end" and ray 3 had not been found + if(ckw && !ray3) + { + fi=AngleCalc(0,0,dx,-dy); + if(MathAbs(fi_c-fi3)>MathAbs(fi-fi3)) + PixelSet(x3=x+dx,y3=y-dy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + } + //--- if arc has been drawn counterclockwise "to the end" and ray 4 had not been found + if(ackw && !ray4) + { + fi=AngleCalc(0,0,-dx,-dy); + if(MathAbs(fi_a-MathMod(fi4,2*M_PI))>MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=x-dx,y4=y-dy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + } + } +//--- 2 left +//--- +//--- if arc is obviously not within the rays range, don't draw + fi=MathMod(fi4,2*M_PI); + if((fi3>M_PI_2 && fi3<3*M_PI_2) || // ray 3 is in the 2nd or 3rd quadrant + (fi>M_PI_2 && fi<3*M_PI_2) || // ray 4 is in the 2nd or 3rd quadrant + (fi4-fi3>=M_PI)) // arc will pass through the left part of the ellipse + { + dx=rx; + dy=0; + f=(ry_sq<<1)*((dx-1)*dx)+ry_sq+(rx_sq<<1)*(1-ry_sq); + while(ry_sq*dx>=rx_sq*dy) + { + xx=x-dx; + if(dy==0) + { + //--- central point + fi=AngleCalc(0,0,-dx,0); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,y,clr); + ckw=ackw=true; + } + else + ckw=ackw=false; + xx_c=xx; + yy_c=y; + fi_c=fi; + xx_a=xx; + yy_a=y; + fi_a=fi; + } + else + { + //--- iterate clockwise + yy=y-dy; + fi=AngleCalc(0,0,-dx,-dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 4 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 4 + if(!ckw) + { + ckw=true; + if(!ray4) + { + if(MathAbs(fi_c-MathMod(fi4,2*M_PI))MathAbs(fi-fi3)) + PixelSet(x3=xx,y3=yy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + ray3=true; + } + ckw=false; + } + //--- save parameters of the last iteration + xx_c=xx; + yy_c=yy; + fi_c=fi; + //--- iterate counterclockwise + yy=y+dy; + fi=AngleCalc(0,0,-dx,dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 3 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 3 + if(!ackw) + { + ackw=true; + if(!ray3) + { + if(MathAbs(fi_a-fi3)MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=xx,y4=yy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + ray4=true; + } + ackw=false; + } + //--- save parameters of the last iteration + xx_a=xx; + yy_a=yy; + fi_a=fi; + } + //--- calculate coordinates of the next point + if(f>=0) + { + dx--; + f-=(ry_sq<<2)*dx; + } + f+=(rx_sq<<1)*(3+(dy<<1)); + dy++; + } + //--- if arc has been drawn clockwise "to the end" and ray 3 had not been found + if(ckw && !ray3) + { + fi=AngleCalc(0,0,-dx,-dy); + if(MathAbs(fi_c-fi3)>MathAbs(fi-fi3)) + PixelSet(x3=x-dx,y3=y-dy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + } + //--- if arc has been drawn counterclockwise "to the end" and ray 4 had not been found + if(ackw && !ray4) + { + fi=AngleCalc(0,0,-dx,dy); + if(MathAbs(fi_a-MathMod(fi4,2*M_PI))>MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=x-dx,y4=y+dy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + } + } +//--- 3 bottom +//--- +//--- if arc is obviously not within the rays range, don't draw + fi=MathMod(fi4,2*M_PI); + if((fi3>M_PI && fi3<2*M_PI) || // ray 3 is in the 3rd or 4th quadrant + (fi>M_PI && fi<2*M_PI) || // ray 4 is in the 3rd or 4th quadrant + (fi4-fi3>=M_PI)) // arc will pass through the bottom of the ellipse + { + dx=0; + dy=ry; + f=(rx_sq<<1)*((dy-1)*dy)+rx_sq+(ry_sq<<1)*(1-rx_sq); + while(rx_sq*dy>=ry_sq*dx) + { + yy=y+dy; + if(dx==0) + { + //--- central point + fi=AngleCalc(0,0,0,dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(x,yy,clr); + ckw=ackw=true; + } + else + ckw=ackw=false; + xx_c=x; + yy_c=yy; + fi_c=fi; + xx_a=x; + yy_a=yy; + fi_a=fi; + } + else + { + //--- iterate clockwise + xx=x-dx; + fi=AngleCalc(0,0,-dx,dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 4 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 4 + if(!ckw) + { + ckw=true; + if(!ray4) + { + if(MathAbs(fi_c-MathMod(fi4,2*M_PI))MathAbs(fi-fi3)) + PixelSet(x3=xx,y3=yy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + ray3=true; + } + ckw=false; + } + //--- save parameters of the last iteration + xx_c=xx; + yy_c=yy; + fi_c=fi; + //--- iterate counterclockwise + xx=x+dx; + fi=AngleCalc(0,0,dx,dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 3 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 3 + if(!ackw) + { + ackw=true; + if(!ray3) + { + if(MathAbs(fi_a-fi3)MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=xx,y4=yy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + ray4=true; + } + ackw=false; + } + //--- save parameters of the last iteration + xx_a=xx; + yy_a=yy; + fi_a=fi; + } + //--- calculate coordinates of the next point + if(f>=0) + { + dy--; + f-=(rx_sq<<2)*dy; + } + f+=(ry_sq<<1)*(3+(dx<<1)); + dx++; + } + //--- if arc has been drawn clockwise "to the end" and ray 3 had not been found + if(ckw && !ray3) + { + fi=AngleCalc(0,0,-dx,dy); + if(MathAbs(fi_c-fi3)>MathAbs(fi-fi3)) + PixelSet(x3=x-dx,y3=y+dy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + } + //--- if arc has been drawn counterclockwise "to the end" and ray 4 had not been found + if(ackw && !ray4) + { + fi=AngleCalc(0,0,dx,dy); + if(MathAbs(fi_a-MathMod(fi4,2*M_PI))>MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=x+dx,y4=y+dy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + } + } +//--- 4 right +//--- +//--- if arc is obviously not within the rays range, don't draw + fi=MathMod(fi4,2*M_PI); + if((fi33*M_PI_2) || // ray 3 is 1 or 4 quadrant + (fi3*M_PI_2) || // ray 4 is 1 or 4 quadrant + (fi4-fi3>=M_PI)) // arc will pass through the right side of the ellipse + { + dx=rx; + dy=0; + f=(ry_sq<<1)*((dx-1)*dx)+ry_sq+(rx_sq<<1)*(1-ry_sq); + while(ry_sq*dx>=rx_sq*dy) + { + xx=x+dx; + if(dy==0) + { + //--- central point + fi=AngleCalc(0,0,dx,0); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,y,clr); + ckw=ackw=true; + } + else + ckw=ackw=false; + xx_c=xx; + yy_c=y; + fi_c=fi; + xx_a=xx; + yy_a=y; + fi_a=fi; + } + else + { + //--- iterate clockwise + yy=y+dy; + fi=AngleCalc(0,0,dx,dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 4 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 4 + if(!ckw) + { + ckw=true; + if(!ray4) + { + if(MathAbs(fi_c-MathMod(fi4,2*M_PI))MathAbs(fi-fi3)) + PixelSet(x3=xx,y3=yy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + ray3=true; + } + ckw=false; + } + //--- save parameters of the last iteration + xx_c=xx; + yy_c=yy; + fi_c=fi; + //--- iterate counterclockwise + yy=y-dy; + fi=AngleCalc(0,0,dx,-dy); + if((fi<=fi4 && fi3<=fi) || (fi4>=2*M_PI && fi<=fi4-2*M_PI)) + { + PixelSet(xx,yy,clr); + //--- if arc haven't been drawn before and intersection point of ray 3 and arc is not defined + //--- this means that we (while iterating over points of the ellipse) had just crossed ray 3 + if(!ackw) + { + ackw=true; + if(!ray3) + { + if(MathAbs(MathMod(fi_a,2*M_PI)-fi3)MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=xx,y4=yy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + ray4=true; + } + ackw=false; + } + //--- save parameters of the last iteration + xx_a=xx; + yy_a=yy; + fi_a=fi; + } + //--- calculate coordinates of the next point + if(f>=0) + { + dx--; + f-=(ry_sq<<2)*dx; + } + f+=(rx_sq<<1)*(3+(dy<<1)); + dy++; + } + //--- if arc has been drawn clockwise "to the end" and ray 3 had not been found + if(ckw && !ray3) + { + fi=AngleCalc(0,0,dx,dy); + if(MathAbs(fi_c-fi3)>MathAbs(fi-fi3)) + PixelSet(x3=x+dx,y3=y+dy,clr); + else + { + x3=xx_c; + y3=yy_c; + } + } + //--- if arc has been drawn counterclockwise "to the end" and ray 4 had not been found + if(ackw && !ray4) + { + fi=AngleCalc(0,0,dx,-dy); + if(MathAbs(MathMod(fi_a,2*M_PI)-MathMod(fi4,2*M_PI))>MathAbs(fi-MathMod(fi4,2*M_PI))) + PixelSet(x4=x+dx,y4=y-dy,clr); + else + { + x4=xx_a; + y4=yy_a; + } + } + } + } +//+------------------------------------------------------------------+ +//| Draws ellipse pie | +//+------------------------------------------------------------------+ +void CCanvas::Pie(int x1,int y1,int x2,int y2,int x3,int y3,int x4,int y4,const uint clr,const uint fill_clr) + { + int tmp; + int x,y; +//--- + double fi3; + double fi4; +//--- check + if(x1==x2 || y1==y2) + return; +//--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } +//--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + x =(x2+x1)>>1; + y =(y2+y1)>>1; +//--- check rays + if(x3==x && y3==y) + return; + if(x4==x && y4==y) + return; +//--- calculate parameters of ray x3,y3 + fi3=AngleCalc(x,y,x3,y3); +//--- calculate parameters of ray x4,y4 + fi4=AngleCalc(x,y,x4,y4); +//--- draw pie + Pie(x,y,x2-x,y2-y,fi3,fi4,clr,fill_clr); + } +//+------------------------------------------------------------------+ +//| Draws ellipse pie | +//+------------------------------------------------------------------+ +void CCanvas::Pie(int x,int y,int rx,int ry,double fi3,double fi4,const uint clr,const uint fill_clr) + { + int x3=x; + int y3=y; + int x4=x; + int y4=y; +//--- check + if(rx==0 || ry==0) + return; + if(rx<0) + rx=-rx; + if(ry<0) + ry=-ry; +//--- check rays + if(fi3==fi4) + return; +//--- adjustment for passing through 0 + if(fi4ry) + rx=ry; + double fi=(fi3+fi4)/2; + int xf=x+(int)(0.9*rx*cos(fi)); + int yf=y-(int)(0.9*rx*sin(fi)); + Fill(xf,yf,fill_clr); + } +//+------------------------------------------------------------------+ +//| Draw filled circle | +//+------------------------------------------------------------------+ +void CCanvas::FillCircle(int x,int y,int r,const uint clr) + { + int f =1-r; + int dd_x=1; + int dd_y=-2*r; + int dx =0; + int dy =r; +//--- draw + while(dy>=dx) + { + LineHorizontal(x-dy,x+dy,y-dx,clr); + LineHorizontal(x-dy,x+dy,y+dx,clr); + //--- + if(f>=0) + { + LineHorizontal(x-dx,x+dx,y-dy,clr); + LineHorizontal(x-dx,x+dx,y+dy,clr); + dy--; + dd_y+=2; + f+=dd_y; + } + dx++; + dd_x+=2; + f+=dd_x; + } + } +//+------------------------------------------------------------------+ +//| Draw filled ellipse | +//+------------------------------------------------------------------+ +void CCanvas::FillEllipse(int x1,int y1,int x2,int y2,const uint clr) + { + int x,y; + int rx,ry; + int dx,dy; + int rx_sq,ry_sq; + int f; + int tmp; +//--- handle extreme conditions + if(x1==x2) + { + if(y1==y2) + PixelSet(x1,y1,clr); + else + LineVertical(x1,y1,y2,clr); + return; + } + if(y1==y2) + { + LineHorizontal(x1,x2,y1,clr); + return; + } +//--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } +//--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + x =(x2+x1)>>1; + y =(y2+y1)>>1; + rx=(x2-x1)>>1; + ry=(y2-y1)>>1; + dx=0; + dy=ry; + rx_sq=rx*rx; + ry_sq=ry*ry; + f=(rx_sq<<1)*((dy-1)*dy)+rx_sq+(ry_sq<<1)*(1-rx_sq); + while(rx_sq*dy>ry_sq*(dx)) + { + LineHorizontal(x-dx,x+dx,y+dy,clr); + LineHorizontal(x-dx,x+dx,y-dy,clr); + if(f>=0) + { + dy--; + f-=(rx_sq<<2)*dy; + } + f+=(ry_sq<<1)*(3+(dx<<1)); + dx++; + } + f=(ry_sq<<1)*(dx+1)*dx+(rx_sq<<1)*(dy*(dy-2)+1)+(1-(rx_sq<<1))*ry_sq; + while(dy>=0) + { + LineHorizontal(x-dx,x+dx,y+dy,clr); + LineHorizontal(x-dx,x+dx,y-dy,clr); + if(f<=0) + { + dx++; + f+=(ry_sq<<2)*dx; + } + dy--; + f+=(rx_sq<<1)*(3-(dy<<1)); + } + } +//+------------------------------------------------------------------+ +//| Draw filled rectangle | +//+------------------------------------------------------------------+ +void CCanvas::FillRectangle(int x1,int y1,int x2,int y2,const uint clr) + { + int tmp; +//--- sort vertexes + if(x2=m_width || y1>=m_height) + return; +//--- stay withing screen boundaries + if(x1<0) + x1=0; + if(y1<0) + y1=0; + if(x2>=m_width) + x2=m_width -1; + if(y2>=m_height) + y2=m_height-1; + int len=(x2-x1)+1; +//--- set pixels + for(; y1<=y2; y1++) + ArrayFill(m_pixels,y1*m_width+x1,len,clr); + } +//+------------------------------------------------------------------+ +//| Draw filled triangle | +//+------------------------------------------------------------------+ +void CCanvas::FillTriangle(int x1,int y1,int x2,int y2,int x3,int y3,const uint clr) + { + int xx1,xx2,tmp; + double k1=0,k2=0,xd1,xd2; +//--- sort vertexes from lesser to greater + if(y1>y2) + { + tmp=y2; + y2 =y1; + y1 =tmp; + tmp=x2; + x2 =x1; + x1=tmp; + } + if(y1>y3) + { + tmp=y1; + y1 =y3; + y3 =tmp; + tmp=x1; + x1 =x3; + x3 =tmp; + } + if(y2>y3) + { + tmp=y2; + y2 =y3; + y3 =tmp; + tmp=x2; + x2 =x3; + x3 =tmp; + } +//--- all vertexes are out of image boundaries + if(y3<0 || y1>m_height) + return; + if(x1<0 && x2<0 && x3<0) + return; + if(x1>m_width && x2>m_width && x3>m_width) + return; +//--- find coefficients of lines + if((tmp=y1-y2)!=0) + k1=(x1-x2)/(double)tmp; + if((tmp=y1-y3)!=0) + k2=(x1-x3)/(double)tmp; +//--- + xd1=x1; + xd2=x1; +//--- + for(int i=y1; i<=y3; i++) + { + if(i==y2) + { + if((tmp=y2-y3)!=0) + k1=(x2-x3)/(double)tmp; + xd1=x2; + } + //--- calculate new boundaries of triangle line + xx1 =(int)xd1; + xd1+=k1; + xx2 =(int)xd2; + xd2+=k2; + //--- triangle line is out of screen boundaries + if(i<0 || i>=m_height) + continue; + //--- sort + if(xx1>xx2) + { + tmp=xx1; + xx1=xx2; + xx2=tmp; + } + //--- line is out of screen boundaries + if(xx2<0 || xx1>=m_width) + continue; + //--- draw only what is within screen boundaries + if(xx1<0) + xx1=0; + if(xx2>=m_width) + xx2=m_width-1; + //--- draw horizontal line of triangle + ArrayFill(m_pixels,i*m_width+xx1,xx2-xx1,clr); + } + } +//+------------------------------------------------------------------+ +//| Draw filled poligon | +//+------------------------------------------------------------------+ +void CCanvas::FillPolygon(int &x[],int &y[],const uint clr) + { + static CPoint p[]; + int total=ArraySize(x); + if(total>ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<3) + return; +//--- resize array of points + ArrayResize(p,total); +//--- find top-left point + int imin=0; + int xmin=x[0]; + int ymin=y[0]; + for(int i=1; iymin) + continue; + if(y[i]==ymin) + { + if(x[i]0.0) + xx[1]=xx[3]=ix+1; + if(dy<0.0) + yy[2]=yy[3]=iy-1; + if(dy==0.0) + yy[2]=yy[3]=iy; + if(dy>0.0) + yy[2]=yy[3]=iy+1; +//--- calculate radii and sum of their squares + for(int i=0; i<4; i++) + { + dx=xx[i]-x; + dy=yy[i]-y; + rr[i]=1/(dx*dx+dy*dy); + rrr+=rr[i]; + } +//--- draw pixels + for(int i=0; i<4; i++) + { + k=rr[i]/rrr; + c=PixelGet(xx[i],yy[i]); + a=(uchar)(k*GETRGBA(clr)+(1-k)*GETRGBA(c)); + r=(uchar)(k*GETRGBR(clr)+(1-k)*GETRGBR(c)); + g=(uchar)(k*GETRGBG(clr)+(1-k)*GETRGBG(c)); + b=(uchar)(k*GETRGBB(clr)+(1-k)*GETRGBB(c)); + PixelSet(xx[i],yy[i],ARGB(a,r,g,b)); + } + } +//+------------------------------------------------------------------+ +//| Get line style | +//+------------------------------------------------------------------+ +uint CCanvas::LineStyleGet(void) const + { + switch(m_style) + { + case 0xFFFFFF: + return(STYLE_SOLID); + break; + case 0x3FFFF: + return(STYLE_DASH); + break; + case 0x1C71C7: + return(STYLE_DOT); + break; + case 0x381FF: + return(STYLE_DASHDOT); + break; + case 0x1C71FF: + return(STYLE_DASHDOTDOT); + break; + default: + return (m_style); + break; + } + } +//+------------------------------------------------------------------+ +//| Set line style | +//+------------------------------------------------------------------+ +void CCanvas::LineStyleSet(const uint style) + { + switch(style) + { + case STYLE_SOLID: + m_style=0xFFFFFF; + break; + case STYLE_DASH: + m_style=0x3FFFF; + break; + case STYLE_DOT: + m_style=0x1C71C7; + break; + case STYLE_DASHDOT: + m_style=0x381FF; + break; + case STYLE_DASHDOTDOT: + m_style=0x1C71FF; + break; + default: + //--- high-order bit must be set then custom style + if((style&0x80000000)!=0) + { + m_style=style; + } + break; + } + m_style_idx=0; + } +//+------------------------------------------------------------------+ +//| Draw line with antialiasing (with style) | +//+------------------------------------------------------------------+ +void CCanvas::LineAA(const int x1,const int y1,const int x2,const int y2,const uint clr,const uint style) + { +//--- line is out of image boundaries + if((x1<0 && x2<0) || (y1<0 && y2<0)) + return; + if(x1>=m_width && x2>=m_width) + return; + if(y1>=m_height && y2>=m_height) + return; +//--- check + if(x1==x2 && y1==y2) + { + PixelSet(x1,y1,clr); + return; + } +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); +//--- preliminary calculations + double dx=x2-x1; + double dy=y2-y1; + double xy=sqrt(dx*dx+dy*dy); + double xx=x1; + double yy=y1; + uint mask=1<=fabs(dx) && fabs(y2-yy)>=fabs(dy)); +//--- set last pixel + if((m_style&mask)==mask) + { + PixelSetAA(x2,y2,clr); + } +//--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + } +//+------------------------------------------------------------------+ +//| Draw polyline with antialiasing (with style) | +//+------------------------------------------------------------------+ +void CCanvas::PolylineAA(int &x[],int &y[],const uint clr,const uint style) + { +//--- check arrays + int total=ArraySize(x); + if(total>ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; + total--; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<=m_width && x2>=m_width) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + if(y1>=m_height && y2>=m_height) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- check + if(x1==x2 && y1==y2) + { + PixelSet(x1,y1,clr); + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- preliminary calculations + double dx=x2-x1; + double dy=y2-y1; + double xy=sqrt(dx*dx+dy*dy); + double xx=x1; + double yy=y1; + //--- set pixels + dx/=xy; + dy/=xy; + do + { + if((m_style&mask)==mask) + { + PixelSetAA(xx,yy,clr); + } + xx+=dx; + yy+=dy; + mask<<=1; + if(mask==0x1000000) + mask=1; + } + while(fabs(x2-xx)>=fabs(dx) && fabs(y2-yy)>=fabs(dy)); + //--- set last pixel + if((m_style&mask)==mask) + { + PixelSetAA(x2,y2,clr); + } + mask<<=1; + if(mask==0x1000000) + mask=1; + } +//--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + } +//+------------------------------------------------------------------+ +//| Draw polygon with antialiasing (with style) | +//+------------------------------------------------------------------+ +void CCanvas::PolygonAA(int &x[],int &y[],const uint clr,const uint style) + { +//--- check arrays + int total=ArraySize(x); + if(total>ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<=m_width && x2>=m_width) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + if(y1>=m_height && y2>=m_height) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- check + if(x1==x2 && y1==y2) + { + PixelSet(x1,y1,clr); + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- preliminary calculations + double dx=x2-x1; + double dy=y2-y1; + double xy=sqrt(dx*dx+dy*dy); + double xx=x1; + double yy=y1; + //--- set pixels + dx/=xy; + dy/=xy; + do + { + if((m_style&mask)==mask) + { + PixelSetAA(xx,yy,clr); + } + xx+=dx; + yy+=dy; + mask<<=1; + if(mask==0x1000000) + mask=1; + } + while(fabs(x2-xx)>=fabs(dx) && fabs(y2-yy)>=fabs(dy)); + //--- set last pixel + if((m_style&mask)==mask) + { + PixelSetAA(x2,y2,clr); + } + } +//--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + } +//+------------------------------------------------------------------+ +//| Draw triangle with antialiasing | +//+------------------------------------------------------------------+ +void CCanvas::TriangleAA(const int x1,const int y1,const int x2,const int y2,const int x3,const int y3,const uint clr,const uint style) + { +//--- draw + int x[3]; + int y[3]; + x[0] = x1; + x[1] = x2; + x[2] = x3; + y[0] = y1; + y[1] = y2; + y[2] = y3; + PolygonAA(x,y,clr,style); + } +//+------------------------------------------------------------------+ +//| Draw circle with antialiasing | +//+------------------------------------------------------------------+ +void CCanvas::CircleAA(const int x,const int y,const double r,const uint clr,const uint style=UINT_MAX) + { + if(r<=0) + return; +//--- preliminary calculations + double xx=x+r; + double yy=y; + double fi=0; + double df=M_PI_2/MathCeil(r); +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<M_PI) + df/=2; + do + { + xx=x+r*cos(fi); + yy=y-r*sin(fi); + if((m_style&mask)==mask) + PixelSetAA(xx,yy,clr); + mask<<=1; + if(mask==0x1000000) + mask=1; + fi+=df; + } + while(fabs(2*M_PI-fi)>=df/2); +//--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + } +//+------------------------------------------------------------------+ +//| Draw ellipse with antialiasing | +//+------------------------------------------------------------------+ +void CCanvas::EllipseAA(const double x1,const double y1,const double x2,const double y2,const uint clr,const uint style=UINT_MAX) + { + double rx = (x2-x1)/2; + double ry = (y2-y1)/2; +//--- preliminary calculations + double x=(x2>x1) ? x1+rx : x2+rx; + double y=(y2>y1) ? y1+ry : y2+ry; + double rx2=rx*rx; + double ry2=ry*ry; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<ym) + continue; + if((p[i].y==ym) && (p[i].x>xm)) + continue; + im=i; + xm=p[i].x; + ym=p[i].y; + } +//--- check the orientation of triangle + return PointClassify(p[(im-1+total)%total],p[im],p[(im+1)%total]); + } +//+------------------------------------------------------------------+ +//| Checks convexity of polygon | +//+------------------------------------------------------------------+ +bool CCanvas::IsPolygonConvex(CPoint &p[]) + { + int total=ArraySize(p); +//--- triangle - always convex + if(total==3) + return(true); + int res=SIGN(PointClassify(p[0],p[1],p[2])); + for(int i=1; iymin) + continue; + if(p[i].y==ymin) + { + if(p[i].xp[il].y) + return; + if(yy!=p[il].y) + { + dl=(p[il].x-xl)/(p[il].y-yy); + //--- make adjustment for half of left increment dl/2 + LineHorizontal((int)MathCeil(xl+dl/2),(int)MathFloor(xl),yy,clr); + xl+=dl/2; + } + else + LineHorizontal((int)MathCeil(xl),(int)MathFloor(p[il].x),yy,clr); + } + while(yy==p[ir].y) + { + ir=(ir+1)%total; + if(yy>p[ir].y) + return; + if(yy!=p[ir].y) + { + dr=(p[ir].x-xr)/(p[ir].y-yy); + //--- make adjustment for half of right increment dr/2 + LineHorizontal((int)MathCeil(xr),(int)MathFloor(xr+dr/2),yy,clr); + xr+=dr/2; + } + else + LineHorizontal((int)MathCeil(p[ir].x),(int)MathFloor(xr),yy,clr); + } + yy++; + if(yy==p[il].y) + xl=p[il].x; + else + xl+=dl; + if(yy==p[ir].y) + xr=p[ir].x; + else + xr+=dr; + LineHorizontal((int)MathCeil(xl),(int)MathFloor(xr),yy,clr); + } + while(il>=ir && ir!=0); + } +//+------------------------------------------------------------------+ +//| Draw line according to Wu's algorithm | +//+------------------------------------------------------------------+ +void CCanvas::LineWu(int x1,int y1,int x2,int y2,const uint clr,const uint style=UINT_MAX) + { +//--- calculating the variation of the coordinates + int dx = (x2 > x1) ? (x2 - x1) : (x1 - x2); + int dy = (y2 > y1) ? (y2 - y1) : (y1 - y2); +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + //--- line is out of image boundaries + if(y2<0 || y1>=m_height || x1<0 || x1>=m_width) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- stay withing image boundaries + if(y1<0) + y1=0; + if(y2>=m_height-1) + y2=m_height-1; + //--- draw line + int index=y1*m_width+x1; + for(int i=y1; i<=y2; i++,index+=m_width) + { + if((m_style&mask)==mask) + m_pixels[index]=clr; + + mask<<=1; + if(mask==0x1000000) + mask=1; + } + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + //--- success + return; + } +//--- check if dy==0 then draw horizontal line + if(dy==0) + { + //--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } + //--- line is out of image boundaries + if(x2<0 || x1>=m_width || y1<0 || y1>=m_height) + { + //--- set the previous line style + if(style!=UINT_MAX) + m_style=prev_style; + return; + } + //--- stay withing image boundaries + if(x1<0) + x1=0; + if(x2>=m_width) + x2=m_width-1; + //--- draw line + for(int i=0,index=y1*m_width+x1; i 1) + else + { + //--- first point has to have a smaller Y coordinate + if(y2ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; + total--; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1< x1) ? (x2 - x1) : (x1 - x2); + int dy = (y2 > y1) ? (y2 - y1) : (y1 - y2); + int tmp; + //--- check if dx==0 then draw vertical line + if(dx==0) + { + //--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + //--- line is out of image boundaries + if(y2<0 || y1>=m_height || x1<0 || x1>=m_width) + continue; + //--- stay withing image boundaries + if(y1<0) + y1=0; + if(y2>=m_height-1) + y2=m_height-1; + //--- draw line + int index=y1*m_width+x1; + for(int j=y1; j<=y2; j++,index+=m_width) + { + if((m_style&mask)==mask) + m_pixels[index]=clr; + + mask<<=1; + if(mask==0x1000000) + mask=1; + } + continue; + } + //--- check if dy==0 then draw horizontal line + if(dy==0) + { + //--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } + //--- line is out of image boundaries + if(x2<0 || x1>=m_width || y1<0 || y1>=m_height) + continue; + //--- stay withing image boundaries + if(x1<0) + x1=0; + if(x2>=m_width) + x2=m_width-1; + //--- draw line + for(int j=0,index=y1*m_width+x1; j 1) + else + { + //--- first point has to have a smaller Y coordinate + if(y2ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1< x1) ? (x2 - x1) : (x1 - x2); + int dy = (y2 > y1) ? (y2 - y1) : (y1 - y2); + int tmp; + //--- check if dx==0 then draw vertical line + if(dx==0) + { + //--- sort by Y + if(y1>y2) + { + tmp=y1; + y1 =y2; + y2 =tmp; + } + //--- line is out of image boundaries + if(y2<0 || y1>=m_height || x1<0 || x1>=m_width) + continue; + //--- stay withing image boundaries + if(y1<0) + y1=0; + if(y2>=m_height-1) + y2=m_height-1; + //--- draw line + int index=y1*m_width+x1; + for(int j=y1; j<=y2; j++,index+=m_width) + { + if((m_style&mask)==mask) + m_pixels[index]=clr; + + mask<<=1; + if(mask==0x1000000) + mask=1; + } + continue; + } + //--- check if dy==0 then draw horizontal line + if(dy==0) + { + //--- sort by X + if(x1>x2) + { + tmp=x1; + x1 =x2; + x2 =tmp; + } + //--- line is out of image boundaries + if(x2<0 || x1>=m_width || y1<0 || y1>=m_height) + continue; + //--- stay withing image boundaries + if(x1<0) + x1=0; + if(x2>=m_width) + x2=m_width-1; + //--- draw line + for(int j=0,index=y1*m_width+x1; j 1) + else + { + //--- first point has to have a smaller Y coordinate + if(y2x1) ? x1+rx : x2+rx; + int y=(y2>y1) ? y1+ry : y2+ry; + if(rx<=0 || ry<=0) + return; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<0) + LineWu(x,y1,x,y2,clr,style); + return; + } +//--- r be the filter radius (and also the half-width of the wide line) + double r=(size/2.0); +//--- primary calculate + int dy=MathAbs(y2-y1); + int sign=(y10) + LineWu(x1,y,x2,y,clr,style); + return; + } +//--- r be the filter radius (and also the half-width of the wide line) + double r=(size/2.0); +//--- primary calculate + int dx=MathAbs(x2-x1); + int sign=(x10) + LineWu(x1,y1,x2,y2,clr,style); + return; + } +//--- r be the filter radius (and also the half-width of the wide line) + double r=(size/2.0); +//--- compute x and y deltas + double dx=MathAbs(x2-x1); + double dy=MathAbs(y2-y1); + if(dx==0) + { + LineThickVertical(x1,y1,y2,clr,size,style,end_style); + return; + } + if(dy==0) + { + LineThickHorizontal(x1,x2,y1,clr,size,style,end_style); + return; + } +//--- compute the linear coefficients of the two (scaled) edge functions + double k=MathArctan(dx/dy); + double rcos_k=r*cos(k); + double rsin_k=r*sin(k); +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<0) + PolylineWu(x,y,clr,style); + return; + } +//--- check arrays + int total=ArraySize(x); + if(total>ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; + total--; +//--- r be the filter radius (and also the half-width of the wide line) + double r=(size/2.0); +//--- + double gap=1.0; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<0) + PolylineWu(x,y,clr,style); + return; + } +//--- check arrays + int total=ArraySize(x); + if(total>ArraySize(y)) + total=ArraySize(y); +//--- check + if(total<2) + return; +//--- r be the filter radius (and also the half-width of the wide line) + double r=(size/2.0); +//--- + double gap=1.0; +//--- set the line style + uint prev_style=m_style; + if(style!=UINT_MAX) + LineStyleSet(style); + uint mask=1<>16) &0xff) - + uint((clr1>>16) &0xff)); + uint dg=MathAbs(uint((clr0>>8) &0xff) - + uint((clr1>>8) &0xff)); + uint db=MathAbs(uint((clr0>>0) &0xff) - + uint((clr1>>0) &0xff)); +//--- return + return (dr<=threshould || dg<=threshould || db<=threshould); + } +//+------------------------------------------------------------------+ +//| Calculate and set new color | +//+------------------------------------------------------------------+ +void CCanvas::PixelTransform(const int x,const int y,const uint clr,const double alpha) + { + int index=y*m_width+x; +//--- check + if(x<0 || y<0 || x>m_width || y>m_height || index>=ArraySize(m_pixels)) + return; +//--- check alpha + if(alpha==1) + { + m_pixels[index]=clr; + return; + } +//--- get pixel color + uint clr0=m_pixels[index]; +//--- transform of color component for the background + double r0 = ((clr0>>16) & 0xFF) * (1.0-alpha); + double g0 = ((clr0>>8) & 0xFF) * (1.0-alpha); + double b0 = ((clr0>>0) & 0xFF) * (1.0-alpha); +//--- transform of color component + double r1 = ((clr>>16) & 0xFF) * (alpha); + double g1 = ((clr>>8) & 0xFF) * (alpha); + double b1 = ((clr>>0) & 0xFF) * (alpha); +//--- components of the new color + int r = (int)(r0+r1); + int g = (int)(g0+g1); + int b = (int)(b0+b1); +//--- set new color + m_pixels[y*m_width+x]=((r<<16)|(g<<8)|(b<<0)|(255<<24)); + } +//+------------------------------------------------------------------+ +//| Draw 4 pixel with PixelTransform method | +//+------------------------------------------------------------------+ +void CCanvas::PixelTransform4(const int x,const int y,const int dx,const int dy,const uint clr,const double alpha) + { + PixelTransform(x+dx,y+dy,clr,alpha); + PixelTransform(x-dx,y+dy,clr,alpha); + PixelTransform(x+dx,y-dy,clr,alpha); + PixelTransform(x-dx,y-dy,clr,alpha); + } +//+------------------------------------------------------------------+ +//| Draw 4 pixel with antialiasing | +//+------------------------------------------------------------------+ +void CCanvas::PixelSet4AA(const double x,const double y,const double dx,const double dy,const uint clr) + { + PixelSetAA(x+dx,y+dy,clr); + PixelSetAA(x-dx,y+dy,clr); + PixelSetAA(x+dx,y-dy,clr); + PixelSetAA(x-dx,y-dy,clr); + } +//+------------------------------------------------------------------+ +//| Draw solid segment for vertical thick line | +//+------------------------------------------------------------------+ +void CCanvas::SegmentVertical(const int x,const int y1,const int y2,const int ysign,const double r,const uint clr,ENUM_LINE_END end_style) + { +//--- compute the constol points of the solid segment + int ye1,ye2; + int ys1,ys2; + switch(end_style) + { + case LINE_END_ROUND: + { + ye1=y1; + ye2=y2; + ys1=y1-(int)(ysign*r); + ys2=y2+(int)(ysign*r); + break; + } + case LINE_END_BUTT: + { + ye1=y1; + ye2=y2; + ys1=y1; + ys2=y2; + break; + } + case LINE_END_SQUARE: + { + ye1=y1-(int)(ysign*r); + ye2=y2+(int)(ysign*r); + ys1=ye1; + ys2=ye2; + break; + } + default: + return; + }; +//--- darw solid segment + for(int i=0; i<=MathAbs(ys2-ys1); i++) + { + double yi=ys1+(ysign*i); + for(int j=0; j<2*r; j++) + { + double xi=x-r+j; + double dist=DistancePointSegment(xi,yi,x,ye1,x,ye2); + double val=MathAbs(dist/r); + PixelTransform((int)xi,(int)yi,clr,FilterFunction(val)); + } + } + } +//+------------------------------------------------------------------+ +//| Draw solid segment for horizontal thick line | +//+------------------------------------------------------------------+ +void CCanvas::SegmentHorizontal(const int x1,const int x2,const int y,const int xsign,const double r,const uint clr,ENUM_LINE_END end_style) + { +//--- compute the constol points of the solid segment + int xe1,xe2; + int xs1,xs2; + switch(end_style) + { + case LINE_END_ROUND: + { + xe1=x1; + xe2=x2; + xs1=x1-(int)(xsign*r); + xs2=x2+(int)(xsign*r); + break; + } + case LINE_END_BUTT: + { + xe1=x1; + xe2=x2; + xs1=x1; + xs2=x2; + break; + } + case LINE_END_SQUARE: + { + xe1=x1-(int)(xsign*r); + xe2=x2+(int)(xsign*r); + xs1=xe1; + xs2=xe2; + break; + } + default: + return; + }; +//--- draw solid segment + for(int i=0; i<=MathAbs(xs2-xs1); i++) + { + double xi=xs1+(xsign*i); + for(int j=0; j<2*r; j++) + { + double yi=y-r+j; + double dist=DistancePointSegment(xi,yi,xe1,y,xe2,y); + double val=MathAbs(dist/r); + PixelTransform((int)xi,(int)yi,clr,FilterFunction(val)); + } + } + } +//+------------------------------------------------------------------+ +//| Draw solid segment for thick line | +//+------------------------------------------------------------------+ +void CCanvas::Segment(const int x1,const int y1,const int x2,const int y2,const double kp0,const double kp1,const int xsign,const int ysign, + const double rcos_k,const double rsin_k,const double r,const uint clr,ENUM_LINE_END end_style) + { + if(x1==x2 && y1==y2) + return; + if(x1==x2) + { + SegmentVertical(x1,y1,y2,ysign,r,clr,end_style); + return; + } + if(y1==y2) + { + SegmentHorizontal(x1,x2,y1,xsign,r,clr,end_style); + return; + } +//--- compute the constol points of the solid segment + int xe1,ye1,xe2,ye2; + int xs1,ys1,xs2,ys2; + switch(end_style) + { + case LINE_END_ROUND: + { + xe1=x1; + ye1=y1; + xe2=x2; + ye2=y2; + xs1=x1-(xsign)*(int)(rsin_k); + ys1=y1-(ysign)*(int)(rcos_k); + xs2=x2+(xsign)*(int)(rsin_k); + ys2=y2+(ysign)*(int)(rcos_k); + break; + } + case LINE_END_BUTT: + { + xe1=x1; + ye1=y1; + xe2=x2; + ye2=y2; + xs1=x1; + ys1=y1; + xs2=x2; + ys2=y2; + break; + } + case LINE_END_SQUARE: + { + xe1=x1-(xsign)*(int)(rsin_k); + ye1=y1-(ysign)*(int)(rcos_k); + xe2=x2+(xsign)*(int)(rsin_k); + ye2=y2+(ysign)*(int)(rcos_k); + xs1=xe1; + ys1=ye1; + xs2=xe2; + ys2=ye2; + break; + } + default: + return; + }; +//--- compute the four corners of the wide line + double p0x=xs1+(xsign)*(rcos_k); + double p0y=ys1-(ysign)*(rsin_k); + double p1x=xs1-(xsign)*(rcos_k); + double p1y=ys1+(ysign)*(rsin_k); + double p2x=xs2+(xsign)*(rcos_k); + double p2y=ys2-(ysign)*(rsin_k); + double p3x=xs2-(xsign)*(rcos_k); + double p3y=ys2+(ysign)*(rsin_k); +//--- draw solid segment + if(MathAbs(kp0)>=1) + { + double xi0,xi1; + double height=MathAbs(p3y-p0y); + for(int i=0; i<=height; i++) + { + double y=p0y+(ysign*i); + double xi00 = MathRound(p0x + (y-p0y)/kp0); + double xi01 = MathRound(p1x + (y-p1y)/kp1); + double xi02 = MathRound(p2x + (y-p2y)/kp1); + double xi03 = MathRound(p3x + (y-p3y)/kp0); + if(xsign==1) + { + xi0 = MathMax(xi00,xi01); + xi1 = MathMin(xi02,xi03); + } + else + { + xi0 = MathMin(xi00,xi01); + xi1 = MathMax(xi02,xi03); + } + double width=MathAbs(MathRound(xi1-xi0)); + for(int j=0; j<=width; j++) + { + double xi=xi0+(xsign*j); + double dist=DistancePointSegment(xi,y,xe1,ye1,xe2,ye2); + double val = MathAbs(dist/r); + PixelTransform((int)xi,(int)y,clr,FilterFunction(val)); + } + } + } + else + { + double yi0,yi1; + double width=MathAbs(p2x-p1x); + for(int i=0; i<=width; i++) + { + double x=p1x+(xsign*i); + double yi00 = MathRound(p0y + (x-p0x)*kp0); + double yi01 = MathRound(p1y + (x-p1x)*kp1); + double yi02 = MathRound(p2y + (x-p2x)*kp1); + double yi03 = MathRound(p3y + (x-p3x)*kp0); + if(ysign==1) + { + yi0 = MathMax(yi00,yi02); + yi1 = MathMin(yi01,yi03); + } + else + { + yi0 = MathMin(yi00,yi02); + yi1 = MathMax(yi01,yi03); + } + double height=MathAbs(yi1-yi0); + for(int j=0; j<=height; j++) + { + double yi=yi0+(ysign*j); + double dist=DistancePointSegment(x,yi,xe1,ye1,xe2,ye2); + double val=MathAbs(dist/r); + PixelTransform((int)x,(int)yi,clr,FilterFunction(val)); + } + } + } + } +//+------------------------------------------------------------------+ +//| Filter function for calculating alpha channel | +//+------------------------------------------------------------------+ +double CCanvas::FilterFunction(const double x) + { + if(x<=0.8) + return(1.0); + else + return MathExp(-(x-0.8)*(x-0.8)*50); + } +//+------------------------------------------------------------------+ +//| Calculate distance between point and segment | +//+------------------------------------------------------------------+ +double CCanvas::DistancePointSegment(const double px,const double py,const double x1,const double y1,const double x2,const double y2) + { +//--- primary calculate + double a=(px-x1)*(px-x1)+(py-y1)*(py-y1); + double b=(px-x2)*(px-x2)+(py-y2)*(py-y2); + double c=(x2-x1)*(x2-x1)+(y2-y1)*(y2-y1); +//--- check + if(a>=b+c) + return (MathSqrt(b)); + if(b>=a+c) + return (MathSqrt(a)); +//--- calculate distance + a=MathSqrt(a); + b=MathSqrt(b); + c=MathSqrt(c); + double p=(a+b+c)/2; + double s=MathSqrt((p-a)*(p-b)*(p-c)*p); +//--- check distance + if(MathIsValidNumber(s)) + return(s*2.0/c); + else + return(0); + } +//+------------------------------------------------------------------+ +//| Draw smothing polyline | +//+------------------------------------------------------------------+ +void CCanvas::PolylineSmooth(const int &x[],const int &y[],const uint clr,const int size,ENUM_LINE_STYLE style=STYLE_SOLID, + ENUM_LINE_END end_style=LINE_END_ROUND,double tension=0.5,double step=10) + { +//--- + int arr_size= ArraySize(x); + if(arr_size!=ArraySize(y)) + return; +//--- + double x1,x2,y1,y2; + tension*=0.3; +//--- coordinates of Bezier curve + int xc[]; + int yc[]; +//--- initialize control points + double ptX[]; + double ptY[]; + int size_pt=arr_size*3-2; + + ArrayResize(ptX,size_pt); + ArrayResize(ptY,size_pt); +//--- calculation of control points + CalcCurveBezierEndp(x[0],y[0],x[1],y[1],tension,x1,y1); + + ptX[0] = x[0]; + ptY[0] = y[0]; + ptX[1] = x1; + ptY[1] = y1; + + for(int i=0; i0.0) ?(int)(distance/step) : 1; + if(size_i<1) + size_i=2; + ArrayResize(xc,ArraySize(xc)+size_i,1024); + ArrayResize(yc,ArraySize(yc)+size_i,1024); + for(int t=0; t0.0) ?(int)(distance/step) : 1; + if(size_i<1) + size_i=2; + ArrayResize(xc,ArraySize(xc)+size_i,1024); + ArrayResize(yc,ArraySize(yc)+size_i,1024); + for(int t=0; t0.0) ?(int)(distance/step) : 1; + if(size_i<1) + size_i=2; + ArrayResize(xc,ArraySize(xc)+size_i,1024); + ArrayResize(yc,ArraySize(yc)+size_i,1024); + for(int t=0; tZ?ch^!)F?*PH9jN6p7Q_8 zZ_mM#;Y77=qs|bTNX~4b=N-@+$JtjTTm!B9r+gYTyBPT1| zo#y-IG4OLEY28M%*6^3x_^>D1)?l(l&8}y&EMQ!NX>~`4%Ne z$)~3!yVH_W@)nl9)cj2}jh%joHoR(ej9 z=84=o#Au&_W8}dAbLnlf>VxR$Vd4#aA@XAfDENib7+QJyQ=>k6(5Y=$s13Amkjjg_ z`m$0>G={oJ%6yFh^pL)y*kh%WrC7RTUyLrJV!L36a#}nUl%rvikRyzN-b|^DHa*^+ ztM5^w=fL?WLtucF@mHRnVWMI`Q4(tpz@H<0HsSBK>A45aao&%c4-0*eKb%b|v4Qb& zr0ksJMStV>};-eB!To6tHVbq^E$`D&n ze-pI^t|eMhZ7I%77m7BaV^7fsk>eN?T3b0)!jqLyV-zK%#@j7#hm0@vt7zru4J-gn zs0-W8HOR1yw%&%``A^Gcj%tnLlv-g`{ITx6&eMeTEwQD6@x>i=hnfQVw1v$*1>QzRw6<5pFTXa}wcMbjHYz zQQCKi+t$${Ji{&;Tt80h`Z-J}dqs$88t%vaJR-lFS9A2Vd`Y_Z> zdLO2jU3jyCzLm?7H$+F_3uk~flnBLM;`k#E#lJF6TBKVW-=BjIM0Sl-Wi{;T4-LFrfnOn`zz%Pl(Nr(3%mXWuQly^Y| zPs1(H^DI%(2WsvlPA@o|C-=88`b{A8GSHNEV2I!1sQrAA1B{8(&Wlx{&DaHJ{oHvh z70;(FOaA+l({{a2eiuC*L%#BO?P?`9i*%PX%=gLbQooN*`}MfZ`N~hc2bf_NU1%vGYh<4JW6Oytp#6m%I{L6I!lF@VrQ# z;+(D5=(?I}l&t0W=w!BNH{UonedMO`*0r;Ij9{MkLx(9Y&1XVUvb}1lQK7cId=@9_ zFCw4555_OsNwS$Qvf202ZvC~R;`{K?4vxtW)<=>?J@le zy7&rf7R>B$ErIu6;InW_#bQRy64rQ1zrHU1`t~gD^(dfY=BgNTgqD%zt`Fu(2gvqu zjh!nfysNw2bUzf=dbkI(#+ztMy>2gIj|DTYOZewGHTxPjvOb7s64TzqDiiafTz8N$ z?WI*uj*~g>IyG%<@?+C^QH(0_Jm;(JGMkL69u>QBHP(28dB}MbN1oTnXYRaQ^=-Gg z8t;i-mAJZvIeY;LJ;DF7ib-xV-!p*JTE=jcRrU09O_we1Od$g|((?eqC#6}uDc_T> zu0(!&n%g{?#DZ$SUINL}OWWVJNwfMMpLkK*Z&t<6tlv2`TR+=cn{SVM-RZ9IGOoaz zCfQEAt3l(tc-nqW%&ylpayj&H>0QrCce=GRPQ!?ArQL9*6SKXVZw-44ZcVc$;VrcH zAiL;YZKi5E==)kuo@r?vCvo8MSaxlW@$AsMhUakLThb4_3@%tZdIq=on@Zr zebA$htHm*UBl-66011J6??(cP`(sKvNj@@m@tl-N73Z_HNgBbQ^X-$Ztvzdb{|@)I%{<9aX@ClLy+ZPFcTUT%z|Wn0#^I95K|0{h); zO26f95^S{5O)qWM!XHQOJSMI>9j)&K9fyO)afx=zGCMD_rWhA@XV#vAH3^yBRsUp- zW1|#|+hJDQ16%Xqqx?mV$KA*!86@7->nxABH!rfNxEDwH=a$83`-oeOZ~$^%3e9Ym;V}{O63VZ#H-RX{R=pY;0E>=MU3;Y z@(__Q<79dMn$L=JpVOPvCUCvHS{k1N z)K*I)dkfl_Q7+}h+VQXQX?z|ekHE|;xqg4_@({wFWcey7Wqj*;;D;AR;K9QSv^nNSIM&4Jv+bX>lRjeIy_g) z^pLMSv-LhcIS;L^+DN5DeY{JUFP=oQ2C1YD#5uS2^(sm)z04ET99>I12W>(Bnm-|X zW=xVtS(^5lSh@bNeQUc*cIVZ!SNdOgUWt+fREmE|b)j3oUPYAzI)ObQ^rKQ-MZXfoKJZ{_P%B&f7h3PSjTy z|9zNZuhQ9{CgtzO^oR`{fx_NXF=MPD*Xm*`{3ZrH5b9F@u~b&|REd_`+$eYI_DEXR3>Sq2$v z&TWpX%~cymY*lr{_*TzyNdIL%vwpvEoKI?UmphS~r})X)PG0lG`;}ON=WQMX*?FpU zu3Mg4-LLzJUp*Z9T#+@lN2jfOFC${MJPkLRW1eS!99-u61SrX~MdwRg=DD^xhUw}N<($6aMQ4Z3 zce<_WSAVU6*Hn*dJR#T4_ASK=5i#=U+2hw%!X{ckB9o#}8PzWBL`2j}JJEmteGPK0 z=jRfSh*^?j_0fBGZ+!BS^JynM-=;sCca--{&ywd1&{7b&;%q5SYZmoNicz(UKR=i| z`sH0J~;%}LRnBu@*7a5z)UsB-SjukW{)`v{{_-4N9B<5$jJL;MS+%lhn@-5$;S<8F|%C~%LW-ar{CExPOOj?$CzLMs)olQ}B;qUUq#t?m) z97b<<5%XBGn=+#jfj!!`XUGs&Gwfb+nQcEzXg{6g+S?*)l4~ACo>N&p()vl^hoNM%N_WQ5AlUR-;rz1Nxczet#AeF6 zo%kk{)XLB+Y@b6_-*H*yyKkF1X-`*md+wqv6HA!W+8e^U?Pd1TYdcj{CPhL_Vik!f zFMgaR=}SSP>Cu&Eq@-`-@F&vB{e$g&v$2KJt_MS|KJGa9Ciu(0hqpNYzM@78wz@^B z&^N%C?;~E1m#G-7u$IQCxd!C+MxIaTYKN&ohnVfx=!G%OwE7S&kKa9~dnqLI$Lt`v z!GG;hmHDJrBHwlK*jYQC)#gWTve@f4N?DAGEm7H8(HzC8!&)7`(Ina9MBU<#;-UZ|9O{a<0Yd$u6Yw?HBd1jyZ zUO)eaMo$OdUCh?3MZXWJJ{E1coy(cUZXU7!+5=aJJ;jEtQSPJ0kBTegUAyglhSHk# xs#kG@!Y%7rEWL5hQ|fElu1()UEd4N{+e`Q|+IRGe^;h4nZy}bB{FomE{6BI@H7fuB literal 0 HcmV?d00001 diff --git a/Include/Canvas/Charts/ChartCanvas.mqh b/Include/Canvas/Charts/ChartCanvas.mqh new file mode 100644 index 0000000..f033ea6 --- /dev/null +++ b/Include/Canvas/Charts/ChartCanvas.mqh @@ -0,0 +1,1046 @@ +//+------------------------------------------------------------------+ +//| ChartCanvas.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "..\Canvas.mqh" +#include +#include +#include +#include +//--- enumerations +enum ENUM_SHOW_FLAGS + { + FLAG_SHOW_NONE =0, + FLAG_SHOW_LEGEND =1, + FLAG_SHOW_SCALE_LEFT =2, + FLAG_SHOW_SCALE_RIGHT =4, + FLAG_SHOW_SCALE_TOP =8, + FLAG_SHOW_SCALE_BOTTOM=16, + FLAG_SHOW_GRID =32, + FLAG_SHOW_DESCRIPTORS =64, + FLAG_SHOW_VALUE =128, + FLAG_SHOW_PERCENT =256, + FLAGS_SHOW_SCALES =(FLAG_SHOW_SCALE_LEFT+FLAG_SHOW_SCALE_RIGHT+ + FLAG_SHOW_SCALE_TOP+FLAG_SHOW_SCALE_BOTTOM), + FLAGS_SHOW_ALL =(FLAG_SHOW_LEGEND+FLAGS_SHOW_SCALES+FLAG_SHOW_GRID+ + FLAG_SHOW_DESCRIPTORS+FLAG_SHOW_VALUE+FLAG_SHOW_PERCENT) + }; +enum ENUM_ALIGNMENT + { + ALIGNMENT_LEFT = 1, // align by left border + ALIGNMENT_TOP = 2, // align by top border + ALIGNMENT_RIGHT = 4, // align by right border + ALIGNMENT_BOTTOM = 8 // align by bottom border + }; +//--- macro +#define IS_SHOW_LEGEND ((m_show_flags&FLAG_SHOW_LEGEND) !=0) +#define IS_SHOW_SCALES ((m_show_flags&FLAGS_SHOW_SCALES) !=0) +#define IS_SHOW_SCALE_LEFT ((m_show_flags&FLAG_SHOW_SCALE_LEFT) !=0) +#define IS_SHOW_SCALE_RIGHT ((m_show_flags&FLAG_SHOW_SCALE_RIGHT) !=0) +#define IS_SHOW_SCALE_TOP ((m_show_flags&FLAG_SHOW_SCALE_TOP) !=0) +#define IS_SHOW_SCALE_BOTTOM ((m_show_flags&FLAG_SHOW_SCALE_BOTTOM)!=0) +#define IS_SHOW_GRID ((m_show_flags&FLAG_SHOW_GRID) !=0) +#define IS_SHOW_DESCRIPTORS ((m_show_flags&FLAG_SHOW_DESCRIPTORS) !=0) +#define IS_SHOW_VALUE ((m_show_flags&FLAG_SHOW_VALUE) !=0) +#define IS_SHOW_PERCENT ((m_show_flags&FLAG_SHOW_PERCENT) !=0) +//+------------------------------------------------------------------+ +//| Class CChartCanvas | +//| Usage: base class for graphical charts | +//+------------------------------------------------------------------+ +class CChartCanvas : public CCanvas + { +protected: + //--- colors + uint m_color_background; + uint m_color_border; + uint m_color_text; + uint m_color_grid; + //--- adjusted parameters + uint m_max_data; + uint m_max_descr_len; + uint m_allowed_show_flags; + uint m_show_flags; + ENUM_ALIGNMENT m_legend_alignment; + uint m_threshold_drawing; + bool m_accumulative; + //--- parameters for scales and grid + double m_v_scale_min; + double m_v_scale_max; + uint m_num_grid; + int m_scale_digits; + //--- data + int m_data_offset; + uint m_data_total; + CArray* m_data; + CArrayUInt m_colors; + CArrayString m_descriptors; + //--- + CArrayInt m_index; + uint m_index_size; + double m_sum; + double m_others; + uint m_max_descr_width; + uint m_max_value_width; + //--- variables + CRect m_data_area; + //--- variables for scaling and scales + double m_scale_x; + int m_x_min; + int m_x_0; + int m_x_max; + int m_dx_grid; + double m_scale_y; + int m_y_min; + int m_y_0; + int m_y_max; + int m_dy_grid; + string m_scale_text[]; + +public: + CChartCanvas(void); + ~CChartCanvas(void); + //--- create + virtual bool Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + //--- colors + uint ColorBackground(void) const { return(m_color_background); } + void ColorBackground(const uint value); + uint ColorBorder(void) const { return(m_color_border); } + void ColorBorder(const uint value); + uint ColorText(void) const { return(m_color_text); } + void ColorText(const uint value); + uint ColorGrid(void) const { return(m_color_grid); } + void ColorGrid(const uint value) { m_color_grid=value; } + //--- adjusted parameters + uint MaxData(void) const { return(m_max_data); } + void MaxData(const uint value); + uint MaxDescrLen(void) const { return(m_max_descr_len); } + void MaxDescrLen(const uint value); + //--- show flags + void AllowedShowFlags(const uint flags); + uint ShowFlags(void) const { return(m_show_flags); } + void ShowFlags(const uint flags); + bool IsShowLegend(void) const { return(IS_SHOW_LEGEND); } + bool IsShowScaleLeft(void) const { return(IS_SHOW_SCALE_LEFT); } + bool IsShowScaleRight(void) const { return(IS_SHOW_SCALE_RIGHT); } + bool IsShowScaleTop(void) const { return(IS_SHOW_SCALE_TOP); } + bool IsShowScaleBottom(void) const { return(IS_SHOW_SCALE_BOTTOM); } + bool IsShowGrid(void) const { return(IS_SHOW_GRID); } + bool IsShowDescriptors(void) const { return(IS_SHOW_DESCRIPTORS); } + bool IsShowPercent(void) const { return(IS_SHOW_PERCENT); } + void ShowLegend(const bool flag=true); + void ShowScaleLeft(const bool flag=true); + void ShowScaleRight(const bool flag=true); + void ShowScaleTop(const bool flag=true); + void ShowScaleBottom(const bool flag=true); + void ShowGrid(const bool flag=true); + void ShowDescriptors(const bool flag=true); + void ShowValue(const bool flag=true); + void ShowPercent(const bool flag=true); + void LegendAlignment(const ENUM_ALIGNMENT value); + void Accumulative(const bool flag=true); + //--- for scales and grid + double VScaleMin(void) const { return(m_v_scale_min); } + void VScaleMin(const double value); + double VScaleMax(void) const { return(m_v_scale_max); } + void VScaleMax(const double value); + uint NumGrid(void) const { return(m_num_grid); } + void NumGrid(const uint value); + void VScaleParams(const double max,const double min,const uint grid); + //--- state + int DataOffset(void) const { return(m_data_offset); } + void DataOffset(const int value); + //--- data + uint DataTotal(void) const { return(m_data_total); } + bool DescriptorUpdate(const uint pos,const string descr); + bool ColorUpdate(const uint pos,const uint clr); + +protected: + virtual void ValuesCheck(void); + virtual void Redraw(void); + virtual void DrawBackground(void); + virtual void DrawLegend(void); + int DrawLegendVertical(const int w,const int h); + int DrawLegendHorizontal(const int w,const int h); + virtual void CalcScales(void); + virtual void DrawScales(void); + virtual int DrawScaleLeft(const bool draw=true); + virtual int DrawScaleRight(const bool draw=true); + virtual int DrawScaleTop(const bool draw=true); + virtual int DrawScaleBottom(const bool draw=true); + virtual void DrawGrid(void); + virtual void DrawDescriptors(void) {} + virtual void DrawChart(void); + virtual void DrawData(const uint idx=0) {} + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CChartCanvas::CChartCanvas(void) : m_color_background(XRGB(0xFF,0xFF,0xFF)), + m_color_border(XRGB(0x9F,0x9F,0x9F)), + m_color_text(XRGB(0x3F,0x3F,0x3F)), + m_color_grid(XRGB(0xCF,0xCF,0xCF)), + m_max_data(10), + m_max_descr_len(10), + m_allowed_show_flags(FLAGS_SHOW_ALL), + m_show_flags(FLAG_SHOW_NONE), + m_legend_alignment(ALIGNMENT_BOTTOM), + m_threshold_drawing(2), + m_accumulative(false), + m_data_offset(0), + m_data_total(0), + m_data(NULL), + m_v_scale_min(0.0), + m_v_scale_max(10.0), + m_num_grid(5), + m_scale_digits(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CChartCanvas::~CChartCanvas(void) + { + if(m_data!=NULL) + delete m_data; + } +//+------------------------------------------------------------------+ +//| Create dynamic resource | +//+------------------------------------------------------------------+ +bool CChartCanvas::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { +//--- call method of parent class + if(!CCanvas::Create(name,width,height,clrfmt)) + return(false); +//--- set font + FontSet("Tahoma",-100); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Sets background color | +//+------------------------------------------------------------------+ +void CChartCanvas::ColorBackground(const uint value) + { + m_color_background=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets border color | +//+------------------------------------------------------------------+ +void CChartCanvas::ColorBorder(const uint value) + { + m_color_border=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets text color | +//+------------------------------------------------------------------+ +void CChartCanvas::ColorText(const uint value) + { + m_color_text=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets maximum amount of data | +//+------------------------------------------------------------------+ +void CChartCanvas::MaxData(const uint value) + { +//--- check + if((value==0) || (m_data_total==value)) + return; +//--- save + m_max_data=value; + if(m_data_total>m_max_data) + { + m_data_total=value; + m_colors.Resize(value); + m_descriptors.Resize(value); + } + } +//+------------------------------------------------------------------+ +//| Sets maximum length of descriptor | +//+------------------------------------------------------------------+ +void CChartCanvas::MaxDescrLen(const uint value) + { + m_max_descr_len=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets allowed visibility flags | +//+------------------------------------------------------------------+ +void CChartCanvas::AllowedShowFlags(const uint flags) + { + m_allowed_show_flags=flags; + m_show_flags&=m_allowed_show_flags; + } +//+------------------------------------------------------------------+ +//| Sets visibility flags | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowFlags(const uint flags) + { + m_show_flags=flags&m_allowed_show_flags; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for "legend" | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowLegend(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_LEGEND)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_LEGEND; + else + m_show_flags&=~FLAG_SHOW_LEGEND; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for left scale | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowScaleLeft(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_SCALE_LEFT)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_SCALE_LEFT; + else + m_show_flags&=~FLAG_SHOW_SCALE_LEFT; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for right scale | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowScaleRight(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_SCALE_RIGHT)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_SCALE_RIGHT; + else + m_show_flags&=~FLAG_SHOW_SCALE_RIGHT; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for top scale | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowScaleTop(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_SCALE_TOP)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_SCALE_TOP; + else + m_show_flags&=~FLAG_SHOW_SCALE_TOP; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for bottom scale | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowScaleBottom(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_SCALE_BOTTOM)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_SCALE_BOTTOM; + else + m_show_flags&=~FLAG_SHOW_SCALE_BOTTOM; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for grid | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowGrid(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_GRID)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_GRID; + else + m_show_flags&=~FLAG_SHOW_GRID; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for descriptors | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowDescriptors(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_DESCRIPTORS)!=0) + { + if(flag) + m_show_flags|=FLAG_SHOW_DESCRIPTORS; + else + m_show_flags&=~FLAG_SHOW_DESCRIPTORS; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for value | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowValue(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_VALUE)!=0) + { + if(flag) + { + m_show_flags|=FLAG_SHOW_VALUE; + m_show_flags&=~FLAG_SHOW_PERCENT; + } + else + m_show_flags&=~FLAG_SHOW_VALUE; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets visibility flag for percentage | +//+------------------------------------------------------------------+ +void CChartCanvas::ShowPercent(const bool flag) + { + if((m_allowed_show_flags&FLAG_SHOW_PERCENT)!=0) + { + if(flag) + { + m_show_flags|=FLAG_SHOW_PERCENT; + m_show_flags&=~FLAG_SHOW_VALUE; + } + else + m_show_flags&=~FLAG_SHOW_PERCENT; + //--- redraw + if(m_data_total>0) + Redraw(); + } + } +//+------------------------------------------------------------------+ +//| Sets legend alignment | +//+------------------------------------------------------------------+ +void CChartCanvas::LegendAlignment(const ENUM_ALIGNMENT value) + { + m_legend_alignment=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets accumulative flag | +//+------------------------------------------------------------------+ +void CChartCanvas::Accumulative(const bool flag=true) + { + m_accumulative=flag; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets lower limit for vertical scale | +//+------------------------------------------------------------------+ +void CChartCanvas::VScaleMin(const double value) + { +//--- check + if(value==m_v_scale_max) + return; +//--- save + m_v_scale_min=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets upper limit for vertical scale | +//+------------------------------------------------------------------+ +void CChartCanvas::VScaleMax(const double value) + { + if(value==m_v_scale_min) + return; +//--- save + m_v_scale_max=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets number of vertical scale divisions | +//+------------------------------------------------------------------+ +void CChartCanvas::NumGrid(const uint value) + { +//--- save + m_num_grid=value; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets parameters for vertical scale | +//+------------------------------------------------------------------+ +void CChartCanvas::VScaleParams(const double max,const double min,const uint grid) + { +//--- check + if(max<=min) + return; +//--- save + m_v_scale_max=max; + m_v_scale_min=min; + m_num_grid =grid; +//--- redraw + if(m_data_total>0) + Redraw(); + } +//+------------------------------------------------------------------+ +//| Sets data offset | +//+------------------------------------------------------------------+ +void CChartCanvas::DataOffset(const int value) + { + m_data_offset=value; +//--- redraw + Redraw(); + } +//+------------------------------------------------------------------+ +//| Updates parameter descriptor only (in specified position) | +//+------------------------------------------------------------------+ +bool CChartCanvas::DescriptorUpdate(const uint pos,const string descr) + { +//--- update + if(descr!=NULL && !m_descriptors.Update(pos,descr)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates parameter color only (in specified position) | +//+------------------------------------------------------------------+ +bool CChartCanvas::ColorUpdate(const uint pos,const uint clr) + { +//--- update + if(clr!=0 && !m_colors.Update(pos,clr)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Checks values for insignificance | +//+------------------------------------------------------------------+ +void CChartCanvas::ValuesCheck(void) + { + string text; + int w,h; +//--- clear + m_max_value_width=0; + m_sum =0; + m_others =0; + m_index_size =0; + m_index.Clear(); +//--- check + if(m_data==NULL) + return; + if(m_data.Type()==TYPE_DOUBLE) + { + //--- single-series chart + //--- calculate sum of all values + for(uint i=0;im_height) + { + cols++; + rows=(int)m_index_size/cols; + if((int)m_index_size%cols!=0) + rows++; + } +//--- draw + int x0=(m_legend_alignment==ALIGNMENT_RIGHT) ? width-w*cols+h : h; + int x=0; + int y =-h/2; + int i; + if(m_data_total==m_index_size) + { + for(i=0;i<(int)m_data_total;i++,x+=w) + { + if(i%cols==0) + { + x=x0; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,(uint)m_colors[i]); + TextOut(x+h,y," - "+m_descriptors[i],m_color_text); + } + } + else + { + for(i=0;i<(int)m_index_size;i++,x+=w) + { + int index=m_index[i]; + if(i%cols==0) + { + x=x0; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,(uint)m_colors[index]); + TextOut(x+h,y," - "+m_descriptors[index],m_color_text); + } + if(i%cols==0) + { + x=x0; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,COLOR2RGB(clrBlack)); + TextOut(x+h,y," - Others",m_color_text); + } +//--- width + return(w*cols); + } +//+------------------------------------------------------------------+ +//| Draw horizontal "legend" | +//+------------------------------------------------------------------+ +int CChartCanvas::DrawLegendHorizontal(const int w,const int h) + { + int width =m_data_area.Width(); + int height=m_data_area.Height(); + int rows =1; + int cols =(int)m_index_size; +//--- calculate + while(w*cols>m_width) + { + rows++; + cols=(int)m_index_size/rows; + if((int)m_index_size%rows!=0) + cols++; + } +//--- draw + int dx=width/(cols+1); + int x =dx-w/2+h; + int dy=(int)(1.5*h); + int y =(m_legend_alignment==ALIGNMENT_BOTTOM) ? height-dy*(rows+1) : -h/2; + int i; + if(m_data_total==m_index_size) + { + for(i=0;i<(int)m_data_total;i++,x+=dx) + { + if(i%cols==0) + { + x=dx-w/2+h; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,(uint)m_colors[i]); + TextOut(x+h,y," - "+m_descriptors[i],m_color_text); + } + } + else + { + for(i=0;i<(int)m_index_size;i++,x+=dx) + { + int index=m_index[i]; + if(i%cols==0) + { + x=dx-w/2+h; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,(uint)m_colors[index]); + TextOut(x+h,y," - "+m_descriptors[index],m_color_text); + } + if(i%cols==0) + { + x=dx-w/2+h; + y+=dy; + } + FillRectangle(x,y,x+h,y+h,COLOR2RGB(clrBlack)); + TextOut(x+h,y," - Others",m_color_text); + } +//--- height + return(dy*(rows+1)); + } +//+------------------------------------------------------------------+ +//| Calculates coordinates of scales | +//+------------------------------------------------------------------+ +void CChartCanvas::CalcScales(void) + { + int width =m_data_area.Width(); + int height=m_data_area.Height(); +//--- limits + m_y_max=m_data_area.top+DrawScaleTop(false); + m_y_min=m_data_area.bottom-DrawScaleBottom(false); +//--- additional + if(m_num_grid > 0) + { + m_dy_grid=(int)((m_y_min-m_y_max)/m_num_grid); + m_y_max+=(int)(((m_y_min-m_y_max)-m_dy_grid*m_num_grid)/2); + m_y_min=(int)(m_y_max+m_dy_grid*m_num_grid); + } +//--- normalize + if(m_v_scale_min>=0.0) + m_y_0=m_y_min; + else + { + if(m_v_scale_max<=0.0) + m_y_0=m_y_max; + else + m_y_0=(int)(m_y_max+(m_y_min-m_y_max)*m_v_scale_max/(m_v_scale_max-m_v_scale_min)); + } +//--- scale + m_scale_y=(m_v_scale_max!=m_v_scale_min) ? (m_y_max-m_y_min)/(m_v_scale_max-m_v_scale_min) : 1; +//--- labels on scale + if(ArraySize(m_scale_text)!=m_num_grid+1 && ArrayResize(m_scale_text,m_num_grid+1)==-1) + return; + double val=m_v_scale_min; + double dval=(m_num_grid==0) ? 0 : (m_v_scale_max-m_v_scale_min)/m_num_grid; + for(uint i=0;i<=m_num_grid;i++,val+=dval) + m_scale_text[i]=DoubleToString(val,m_scale_digits); + } +//+------------------------------------------------------------------+ +//| Redraws scales | +//+------------------------------------------------------------------+ +void CChartCanvas::DrawScales(void) + { +//--- recalculate + CalcScales(); +//--- redraw scales + if(IS_SHOW_SCALE_LEFT) + DrawScaleLeft(); + if(IS_SHOW_SCALE_RIGHT) + DrawScaleRight(); + if(IS_SHOW_SCALE_TOP) + DrawScaleTop(); + if(IS_SHOW_SCALE_BOTTOM) + DrawScaleBottom(); + } +//+------------------------------------------------------------------+ +//| Redraws left scale | +//+------------------------------------------------------------------+ +int CChartCanvas::DrawScaleLeft(const bool draw) + { +//--- check flag + if(!IS_SHOW_SCALE_LEFT) + return(0); +//--- variables + int x1=m_data_area.left; + int x2; + int y=m_y_min; +//--- calculate scale width + int size=0; + for(uint i=0;i<=m_num_grid;i++) + { + if(size +//+------------------------------------------------------------------+ +//| Class CHistogramChart | +//| Usage: generates histogram chart | +//+------------------------------------------------------------------+ +class CHistogramChart : public CChartCanvas + { +private: + //--- colors + uint m_fill_brush[]; + //--- adjusted parameters + bool m_gradient; + uint m_bar_gap; + uint m_bar_min_size; + uint m_bar_border; + //--- data + CArrayObj *m_values; + +public: + CHistogramChart(void); + ~CHistogramChart(void); + //--- create + virtual bool Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_ARGB_NORMALIZE); + //--- adjusted parameters + void Gradient(const bool flag=true) { m_gradient=flag; } + void BarGap(const uint value) { m_bar_gap=value; } + void BarMinSize(const uint value) { m_bar_min_size=value; } + void BarBorder(const uint value) { m_bar_border=value; } + //--- data + bool SeriesAdd(const double &value[],const string descr="",const uint clr=0); + bool SeriesInsert(const uint pos,const double &value[],const string descr="",const uint clr=0); + bool SeriesUpdate(const uint pos,const double &value[],const string descr=NULL,const uint clr=0); + bool SeriesDelete(const uint pos); + bool ValueUpdate(const uint series,const uint pos,double value); + +protected: + virtual void DrawData(const uint idx); + void DrawBar(const int x,const int y,const int w,const int h,const uint clr); + void GradientBrush(const int size,const uint fill_clr); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CHistogramChart::CHistogramChart(void) : m_gradient(true), + m_bar_gap(3), + m_bar_min_size(5), + m_bar_border(0) + { + ShowFlags(FLAG_SHOW_LEGEND|FLAGS_SHOW_SCALES|FLAG_SHOW_GRID); + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CHistogramChart::~CHistogramChart(void) + { + if(ArraySize(m_fill_brush)!=0) + ArrayFree(m_fill_brush); + } +//+------------------------------------------------------------------+ +//| Create dynamic resource | +//+------------------------------------------------------------------+ +bool CHistogramChart::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { +//--- create object to store data + if((m_values=new CArrayObj)==NULL) + return(false); +//--- pass responsibility for its destruction to the parent class + m_data=m_values; +//--- call method of parent class + if(!CChartCanvas::Create(name,width,height,clrfmt)) + return(false); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Adds data series | +//+------------------------------------------------------------------+ +bool CHistogramChart::SeriesAdd(const double &value[],const string descr,const uint clr) + { +//--- check + if(m_data_total==m_max_data) + return(false); +//--- add + CArrayDouble *arr=new CArrayDouble; + if(!m_values.Add(arr)) + return(false); + if(!arr.AssignArray(value)) + return(false); + if(!m_colors.Add((clr==0) ? GetDefaultColor(m_data_total) : clr)) + return(false); + if(!m_descriptors.Add(descr)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Inserts data series | +//+------------------------------------------------------------------+ +bool CHistogramChart::SeriesInsert(const uint pos,const double &value[],const string descr,const uint clr) + { +//--- check + if(m_data_total==m_max_data) + return(false); + if(pos>=m_data_total) + return(false); +//--- insert + CArrayDouble *arr=new CArrayDouble; + if(!m_values.Insert(arr,pos)) + return(false); + if(!arr.AssignArray(value)) + return(false); + if(!m_colors.Insert((clr==0) ? GetDefaultColor(m_data_total) : clr,pos)) + return(false); + if(!m_descriptors.Insert(descr,pos)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates data series | +//+------------------------------------------------------------------+ +bool CHistogramChart::SeriesUpdate(const uint pos,const double &value[],const string descr,const uint clr) + { +//--- check + if(pos>=m_data_total) + return(false); + CArrayDouble *data=m_values.At(pos); + if(data==NULL) + return(false); +//--- update + if(!data.AssignArray(value)) + return(false); + if(clr!=0 && !m_colors.Update(pos,clr)) + return(false); + if(descr!=NULL && !m_descriptors.Update(pos,descr)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Deletes data series | +//+------------------------------------------------------------------+ +bool CHistogramChart::SeriesDelete(const uint pos) + { +//--- check + if(pos>=m_data_total && m_data_total!=0) + return(false); +//--- delete + if(!m_values.Delete(pos)) + return(false); + m_data_total--; + if(!m_colors.Delete(pos)) + return(false); + if(!m_descriptors.Delete(pos)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates element in data series | +//+------------------------------------------------------------------+ +bool CHistogramChart::ValueUpdate(const uint series,const uint pos,double value) + { + CArrayDouble *data=m_values.At(series); +//--- check + if(data==NULL) + return(false); +//--- update + if(!data.Update(pos,value)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Draws histogram | +//+------------------------------------------------------------------+ +void CHistogramChart::DrawData(const uint idx) + { + double value=0.0; +//--- check + CArrayDouble *data=m_values.At(idx); + if(data==NULL) + return; + int total=data.Total(); + if(total==0 || (int)idx>=total) + return; +//--- calculate + int x1=m_data_area.left; + int x2=m_data_area.right; + int dx=(x2-x1)/total; + uint clr=m_colors[idx]; + uint w=dx/m_data_total; + if(w0) + { + y=(m_y_0+(int)(value*m_scale_y)); + h=m_y_0-y; + } + else + { + y=m_y_0; + h=(int)(value*m_scale_y); + } + DrawBar(x,y,w,h,clr); + //--- draw text of value + if(IS_SHOW_VALUE) + { + string text =DoubleToString(value,2); + int width=(int)(TextWidth(text)+w); + if(value>0) + { + if(width>y-m_y_max) + TextOut(x+w/2,y+w,text,m_color_text,TA_RIGHT|TA_VCENTER); + else + TextOut(x+w/2,y-w,text,m_color_text,TA_LEFT|TA_VCENTER); + } + else + { + if(width>m_y_min-y-h) + TextOut(x+w/2,y+h-w,text,m_color_text,TA_LEFT|TA_VCENTER); + else + TextOut(x+w/2,y+h+w,text,m_color_text,TA_RIGHT|TA_VCENTER); + } + } + } + if(IS_SHOW_VALUE) + FontSet(fontname,fontsize,fontflags,fontangle); + } +//+------------------------------------------------------------------+ +//| Draws bar | +//+------------------------------------------------------------------+ +void CHistogramChart::DrawBar(const int x,const int y,const int w,const int h,const uint clr) + { +//--- draw bar + if(!m_gradient || ArraySize(m_fill_brush)>1; + if((r&1)==0) + i1--; + //--- calculate + while(dy>=dx) + { + clr=fill_clr; + dclr=GETRGB(XRGB((r-dy)*GETRGBR(clr)/r,(r-dy)*GETRGBG(clr)/r,(r-dy)*GETRGBB(clr)/r)); + clr-=dclr; + m_fill_brush[i1]=clr; + m_fill_brush[i2]=clr; + //--- + if(f>=0) + { + dy--; + dd_y+=2; + f+=dd_y; + } + dx++; + if(--i1<0) + break; + i2++; + dd_x+=2; + f+=dd_x; + } + } + else + ArrayFree(m_fill_brush); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/Charts/LineChart.mqh b/Include/Canvas/Charts/LineChart.mqh new file mode 100644 index 0000000..289108b --- /dev/null +++ b/Include/Canvas/Charts/LineChart.mqh @@ -0,0 +1,383 @@ +//+------------------------------------------------------------------+ +//| LineChart.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "ChartCanvas.mqh" +#include +//+------------------------------------------------------------------+ +//| Class CLineChart | +//| Usage: generates line chart | +//+------------------------------------------------------------------+ +class CLineChart : public CChartCanvas + { +private: + //--- data + CArrayObj* m_values; + //--- adjusted parameters + bool m_filled; + +public: + CLineChart(void); + ~CLineChart(void); + //--- create + virtual bool Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_ARGB_NORMALIZE); + //--- adjusted parameters + void Filled(const bool flag=true) { m_filled=flag; } + //--- set up + bool SeriesAdd(const double &value[],const string descr="",const uint clr=0); + bool SeriesInsert(const uint pos,const double &value[],const string descr="",const uint clr=0); + bool SeriesUpdate(const uint pos,const double &value[],const string descr=NULL,const uint clr=0); + bool SeriesDelete(const uint pos); + bool ValueUpdate(const uint series,const uint pos,double value); + +protected: + virtual void DrawChart(void); + virtual void DrawData(const uint index=0); + +private: + double CalcArea(const uint index); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CLineChart::CLineChart(void) : m_filled(false) + { + ShowFlags(FLAG_SHOW_LEGEND|FLAGS_SHOW_SCALES|FLAG_SHOW_GRID); + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CLineChart::~CLineChart(void) + { + } +//+------------------------------------------------------------------+ +//| Create dynamic resource | +//+------------------------------------------------------------------+ +bool CLineChart::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { +//--- create object to store data + if((m_values=new CArrayObj)==NULL) + return(false); +//--- pass responsibility for its destruction to the parent class + m_data=m_values; +//--- call method of parent class + if(!CChartCanvas::Create(name,width,height,clrfmt)) + return(false); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Adds data series | +//+------------------------------------------------------------------+ +bool CLineChart::SeriesAdd(const double &value[],const string descr,const uint clr) + { +//--- check + if(m_data_total==m_max_data) + return(false); +//--- add + CArrayDouble *arr=new CArrayDouble; + if(!m_values.Add(arr)) + return(false); + if(!arr.AssignArray(value)) + return(false); + if(!m_colors.Add((clr==0) ? GetDefaultColor(m_data_total) : clr)) + return(false); + if(!m_descriptors.Add(descr)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Inserts data series | +//+------------------------------------------------------------------+ +bool CLineChart::SeriesInsert(const uint pos,const double &value[],const string descr,const uint clr) + { +//--- check + if(m_data_total==m_max_data) + return(false); + if(pos>=m_data_total) + return(false); +//--- insert + CArrayDouble *arr=new CArrayDouble; + if(!m_values.Insert(arr,pos)) + return(false); + if(!arr.AssignArray(value)) + return(false); + if(!m_colors.Insert((clr==0) ? GetDefaultColor(m_data_total) : clr,pos)) + return(false); + if(!m_descriptors.Insert(descr,pos)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates data series | +//+------------------------------------------------------------------+ +bool CLineChart::SeriesUpdate(const uint pos,const double &value[],const string descr,const uint clr) + { +//--- check + if(pos>=m_data_total) + return(false); + CArrayDouble *data=m_values.At(pos); + if(data==NULL) + return(false); +//--- update + if(!data.AssignArray(value)) + return(false); + if(clr!=0 && !m_colors.Update(pos,clr)) + return(false); + if(descr!=NULL && !m_descriptors.Update(pos,descr)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Deletes data series | +//+------------------------------------------------------------------+ +bool CLineChart::SeriesDelete(const uint pos) + { +//--- check + if(pos>=m_data_total && m_data_total!=0) + return(false); +//--- delete + if(!m_values.Delete(pos)) + return(false); + m_data_total--; + if(!m_colors.Delete(pos)) + return(false); + if(!m_descriptors.Delete(pos)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates element in data series | +//+------------------------------------------------------------------+ +bool CLineChart::ValueUpdate(const uint series,const uint pos,double value) + { + CArrayDouble *data=m_values.At(series); +//--- check + if(data==NULL) + return(false); +//--- update + if(!data.Update(pos,value)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Redraws data | +//+------------------------------------------------------------------+ +void CLineChart::DrawChart(void) + { + if(m_filled) + { + //--- calculate areas of filling + double s[]; + ArrayResize(s,m_data_total); + ArrayInitialize(s,0); + for(uint i=0;im_y_0 && y2m_y_0)) + { + //--- draw two triangles + int x3; + if(y1>y2) + { + x3=x+dx*(y1-m_y_0)/(y1-y2); + FillTriangle(x,y1,x3,m_y_0,x,m_y_0,(uint)m_colors[index]); + FillTriangle(x+dx,y2,x3,m_y_0,x+dx,m_y_0,(uint)m_colors[index]); + } + else + { + x3=x+dx*(m_y_0-y1)/(y2-y1); + FillTriangle(x,y1,x3,m_y_0,x,m_y_0,(uint)m_colors[index]); + FillTriangle(x+dx,y2,x3,m_y_0,x+dx,m_y_0,(uint)m_colors[index]); + } + continue; + } + if(y1y2) + FillTriangle(x,y1,x+dx,y2,x+dx,y1,(uint)m_colors[index]); + if(y1m_y_0 || y2>m_y_0) + { + if(y1y2) + { + FillTriangle(x,y1,x+dx,y2,x,y2,(uint)m_colors[index]); + y1=y2; + } + } + FillRectangle(x,m_y_0,x+dx,y1,(uint)m_colors[index]); + } + else + LineAA(x,y1,x+dx,y2,(uint)m_colors[index],STYLE_SOLID); + } + } +//+------------------------------------------------------------------+ +//| Area of filling | +//+------------------------------------------------------------------+ +double CLineChart::CalcArea(const uint index) + { + double area =0; + double value=0; + int dx =100; +//--- + CArrayDouble *data=m_values.At(index); + if(data==NULL) + return(0); + int total=data.Total(); + if(total<=1) + return(0); + int y1=0; + int y2=(int)(m_y_0-data[0]*m_scale_y); + for(int i=0;im_y_0 && y2m_y_0)) + { + //--- line of values crosses the Y axis + int x; + if(y1>y2) + { + //--- from the bottom up + x=dx*(y1-m_y_0)/(y1-y2); + //--- add area of lower triangle + area+=x*(y1-m_y_0)/2; + //--- add area of upper triangle + area+=(dx-x)*(m_y_0-y2)/2; + } + else + { + //--- from top down + x=dx*(m_y_0-y1)/(y2-y1); + //--- add area of upper triangle + area+=x*(m_y_0-y1)/2; + //--- add area of lower triangle + area+=(dx-x)*(y2-m_y_0)/2; + } + continue; + } + if(y1y2) + { + //--- add area of triangle + area+=dx*(y1-y2)/2; + //--- add area of rectangle + area+=dx*(m_y_0-y2); + } + if(y1m_y_0 || y2>m_y_0) + { + //--- both values are less than zero + if(y1y2) + { + //--- add area of triangle + area+=dx*(y1-y2)/2; + //--- add area of rectangle + area+=dx*(y2-m_y_0); + } + } + } +//--- + return(area); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/Charts/PieChart.mqh b/Include/Canvas/Charts/PieChart.mqh new file mode 100644 index 0000000..7a9eac2 --- /dev/null +++ b/Include/Canvas/Charts/PieChart.mqh @@ -0,0 +1,414 @@ +//+------------------------------------------------------------------+ +//| PieChart.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "ChartCanvas.mqh" +//+------------------------------------------------------------------+ +//| Class CPieChart | +//| Usage: generates pie chart | +//+------------------------------------------------------------------+ +class CPieChart : public CChartCanvas + { +private: + //--- data + CArrayDouble *m_values; + //--- for draw + int m_x0; + int m_y0; + int m_r; + +public: + CPieChart(void); + ~CPieChart(void); + //--- create + virtual bool Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt=COLOR_FORMAT_XRGB_NOALPHA); + //--- data + bool SeriesSet(const double &value[],const string &text[],const uint &clr[]); + bool ValueAdd(const double value,const string descr="",const uint clr=0); + bool ValueInsert(const uint pos,const double value,const string descr="",const uint clr=0); + bool ValueUpdate(const uint pos,const double value,const string descr=NULL,const uint clr=0); + bool ValueDelete(const uint pos); + +protected: + virtual void DrawChart(void); + void DrawPie(double fi3,double fi4,int idx,CPoint &p[],const uint clr); + string LabelMake(const string text,const double value,const bool to_left); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CPieChart::CPieChart(void) + { + uint flags=FLAG_SHOW_LEGEND|FLAG_SHOW_DESCRIPTORS|FLAG_SHOW_VALUE|FLAG_SHOW_PERCENT; + AllowedShowFlags(flags); + ShowFlags(flags); + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CPieChart::~CPieChart(void) + { + } +//+------------------------------------------------------------------+ +//| Create dynamic resource | +//+------------------------------------------------------------------+ +bool CPieChart::Create(const string name,const int width,const int height,ENUM_COLOR_FORMAT clrfmt) + { +//--- create object to store data + if((m_values=new CArrayDouble)==NULL) + return(false); +//--- pass responsibility for its destruction to the parent class + m_data=m_values; +//--- call method of parent class + if(!CChartCanvas::Create(name,width,height,clrfmt)) + return(false); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Sets displayed parameters | +//+------------------------------------------------------------------+ +bool CPieChart::SeriesSet(const double &value[],const string &text[],const uint &clr[]) + { +//--- !!! user is responsible for correct filling of arrays !!! +//--- check + if(m_values==NULL) + return(false); +//--- set + if(!m_values.AssignArray(value)) + return(false); + if(!m_descriptors.AssignArray(text)) + return(false); + if(!m_colors.AssignArray(clr)) + return(false); + m_data_total=m_values.Total(); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Adds displayed parameter (to the end) | +//+------------------------------------------------------------------+ +bool CPieChart::ValueAdd(const double value,const string descr,const uint clr) + { +//--- check + if((value<=0)) + return(false); +//--- add + if(!m_values.Add(value)) + return(false); + if(!m_descriptors.Add(descr)) + return(false); + if(!m_colors.Add((clr==0) ? GetDefaultColor(m_data_total) : clr)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Inserts displayed parameter (to specified position) | +//+------------------------------------------------------------------+ +bool CPieChart::ValueInsert(const uint pos,const double value,const string descr,const uint clr) + { +//--- check + if((value<=0)) + return(false); +//--- insert + if(!m_values.Insert(value,pos)) + return(false); + if(!m_descriptors.Insert(descr,pos)) + return(false); + if(!m_colors.Insert((clr==0) ? GetDefaultColor(m_data_total) : clr,pos)) + return(false); + m_data_total++; +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Updates displayed parameter (in specified position) | +//+------------------------------------------------------------------+ +bool CPieChart::ValueUpdate(const uint pos,const double value,const string descr,const uint clr) + { +//--- check + if((value<=0)) + return(false); +//--- update + if(!m_values.Update(pos,value)) + return(false); + if(descr!=NULL && !m_descriptors.Update(pos,descr)) + return(false); + if(clr!=0 && !m_colors.Update(pos,clr)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Deletes displayed parameter (from specified position) | +//+------------------------------------------------------------------+ +bool CPieChart::ValueDelete(const uint pos) + { +//--- delete + if(!m_values.Delete(pos)) + return(false); + m_data_total--; + if(!m_descriptors.Delete(pos)) + return(false); + if(!m_colors.Delete(pos)) + return(false); +//--- redraw + Redraw(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Draws | +//+------------------------------------------------------------------+ +void CPieChart::DrawChart(void) + { +//--- check + if(m_data_total==0) + return; +//--- variables + string text=""; + double angle=M_PI*(m_data_offset%360)/180; + int width,height; + int dw=0; + int dh=0; + int index; + CPoint p0[]; + CPoint p1[]; +//--- calculate geometry + width =(m_data_area.Width()<<3)/10; + height=(m_data_area.Height()<<3)/10; + if(IS_SHOW_LEGEND || !IS_SHOW_DESCRIPTORS) + { + if(IS_SHOW_VALUE) + dw=(int)m_max_value_width; + else + { + if(IS_SHOW_PERCENT) + dw=TextWidth("100.00%"); + } + } + else + { + if(IS_SHOW_DESCRIPTORS) + { + if(IS_SHOW_VALUE) + dw=(int)m_max_value_width+TextWidth(" ()"); + else + { + if(IS_SHOW_PERCENT) + dw=TextWidth(" (100.00%)"); + } + dw+=(int)m_max_descr_width; + } + } +//--- pie chart will always be round + width -=2*dw+10; + height-=20; + m_x0=m_data_area.left+(m_data_area.Width()>>1); + m_y0=m_data_area.top+(m_data_area.Height()>>1); + m_r =(((width>height) ? height : width)>>1); +//--- draw pie chart + if(ArrayResize(p0,m_data_total+1)==-1) + return; + if(m_data_total==1) + { + FillCircle(m_x0,m_y0,m_r,m_colors[0]); + Circle(m_x0,m_y0,m_r,m_color_border); + } + else + { + Circle(m_x0,m_y0,m_r,m_color_border); + for(uint i=0;i=m_x0) + { + x=p1[i].x+10; + y=p1[i].y; + index=m_index[i]; + text=LabelMake(m_descriptors[index],m_values[index],false); + if(text!="") + { + Line(p0[i].x,p0[i].y,p1[i].x,p1[i].y,m_color_border); + Line(p1[i].x,p1[i].y,x,y,m_color_border); + TextOut(x+5,y,text,m_color_text,TA_LEFT|TA_VCENTER); + } + } + if(m_data_total!=m_index_size) + { + index=(int)m_data_total-1; + if(p0[index].x>=m_x0) + { + x=p1[index].x+10; + y=p1[index].y; + text=LabelMake("Others",m_others,true); + TextOut(x+5,y,text,m_color_text,TA_LEFT|TA_VCENTER); + } + else + { + x=p1[index].x-10; + y=p1[index].y; + text=LabelMake("Others",m_others,false); + TextOut(x-5,y,text,m_color_text,TA_RIGHT|TA_VCENTER); + } + if(text!="") + { + Line(p0[index].x,p0[index].y,p1[index].x,p1[index].y,m_color_border); + Line(p1[index].x,p1[index].y,x,y,m_color_border); + } + } + ArrayFree(p1); + ArrayFree(p0); + } +//+------------------------------------------------------------------+ +//| Draw pie | +//+------------------------------------------------------------------+ +void CPieChart::DrawPie(double fi3,double fi4,int idx,CPoint &p[],const uint clr) + { +//--- draw arc + Arc(m_x0,m_y0,m_r,m_r,fi3,fi4,p[idx].x,p[idx].y,p[idx+1].x,p[idx+1].y,clr); +//--- variables + int x3=p[idx].x; + int y3=p[idx].y; + int x4=p[idx+1].x; + int y4=p[idx+1].y; +//--- draw radii + if(idx==0) + Line(m_x0,m_y0,x3,y3,clr); + if(idx!=m_data_total-1) + Line(m_x0,m_y0,x4,y4,clr); +//--- fill + double fi=(fi3+fi4)/2; + int xf=m_x0+(int)(0.99*m_r*cos(fi)); + int yf=m_y0-(int)(0.99*m_r*sin(fi)); + Fill(xf,yf,clr); +//--- for small pie + if(fi4-fi3<=M_PI_4) + Line(m_x0,m_y0,xf,yf,clr); + } +//+------------------------------------------------------------------+ +//| Make label for pie | +//+------------------------------------------------------------------+ +string CPieChart::LabelMake(const string text,const double value,const bool to_left) + { + string label=""; +//--- + if(to_left) + { + if(IS_SHOW_LEGEND || !IS_SHOW_DESCRIPTORS) + { + if(IS_SHOW_VALUE) + label=DoubleToString(value,2); + else + { + if(IS_SHOW_PERCENT) + label=DoubleToString(100*value/m_sum,2)+"%"; + } + } + else + { + label=text; + if(IS_SHOW_VALUE) + label+=" ("+DoubleToString(value,2)+")"; + else + { + if(IS_SHOW_PERCENT) + label+=" ("+DoubleToString(100*value/m_sum,2)+"%)"; + } + } + } + else + { + if(IS_SHOW_LEGEND || !IS_SHOW_DESCRIPTORS) + { + if(IS_SHOW_VALUE) + label=DoubleToString(value,2); + else + { + if(IS_SHOW_PERCENT) + label=DoubleToString(100*value/m_sum,2)+"%"; + } + } + else + { + if(IS_SHOW_VALUE) + label="("+DoubleToString(value,2)+") "; + else + { + if(IS_SHOW_PERCENT) + label="("+DoubleToString(100*value/m_sum,2)+"%) "; + } + label+=text; + } + } +//--- + return(label); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/DX/DXBox.mqh b/Include/Canvas/DX/DXBox.mqh new file mode 100644 index 0000000..fc0caa0 --- /dev/null +++ b/Include/Canvas/DX/DXBox.mqh @@ -0,0 +1,75 @@ +//+------------------------------------------------------------------+ +//| DXBox.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2000-2026, MetaQuotes Ltd." +#property link "https://www.mql5.com" +//--- +#include "DXMesh.mqh" +#include "DXMath.mqh" +#include "DXUtils.mqh" +//+------------------------------------------------------------------+ +//| 3D Box object | +//+------------------------------------------------------------------+ +class CDXBox : public CDXMesh + { +public: + CDXBox(); + ~CDXBox(); + //--- create bon in specified context + bool Create(CDXDispatcher &dispatcher,CDXInput* buffer_scene,const DXVector3 &from,const DXVector3 &to); + //--- update box + bool Update(const DXVector3 &from,const DXVector3 &to); + +private: + //--- + void PrepareVertices(const DXVector3 &from,const DXVector3 &to); + }; +//+------------------------------------------------------------------+ +//| Class constructor | +//+------------------------------------------------------------------+ +void CDXBox::CDXBox() : CDXMesh() + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +void CDXBox::~CDXBox(void) + { + } +//+------------------------------------------------------------------+ +//| Create box in specified context | +//+------------------------------------------------------------------+ +bool CDXBox::Create(CDXDispatcher &dispatcher,CDXInput* buffer_scene,const DXVector3 &from,const DXVector3 &to) + { +//--- release previous buffers + Shutdown(); +//--- + DXVertex vertices[]; + uint indices[]; +//--- prepare box vertices and indices + if(!DXComputeBox(from,to,vertices,indices)) + return(false); + for(int i=0; i + bool Create(int context_handle,const TVertex &vertices[],uint start=0,uint count=WHOLE_ARRAY) + { + Shutdown(); + m_context=context_handle; + m_handle=DXBufferCreate(m_context,DX_BUFFER_VERTEX,vertices,start,count); + return(m_handle!=INVALID_HANDLE); + } + //+------------------------------------------------------------------+ + //| Render | + //+------------------------------------------------------------------+ + bool Render(uint start=0,uint count=WHOLE_ARRAY) + { + return(DXBufferSet(m_context,m_handle,start,count)); + } + //+------------------------------------------------------------------+ + //| Shutdown | + //+------------------------------------------------------------------+ + void Shutdown(void) + { + //--- relase handle + if(m_handle!=INVALID_HANDLE) + DXRelease(m_handle); + m_handle=INVALID_HANDLE; + } + }; +//+------------------------------------------------------------------+ +//| DX index buffer | +//+------------------------------------------------------------------+ +class CDXIndexBuffer : public CDXHandleShared + { +public: + //+------------------------------------------------------------------+ + //| Destructor | + //+------------------------------------------------------------------+ + virtual ~CDXIndexBuffer(void) + { + Shutdown(); + } + //+------------------------------------------------------------------+ + //| Create index buffer in specified context | + //+------------------------------------------------------------------+ + bool Create(int context_handle,const uint &indices[],uint start=0,uint count=WHOLE_ARRAY) + { + Shutdown(); + m_context=context_handle; + m_handle=DXBufferCreate(m_context,DX_BUFFER_INDEX,indices,start,count); + return(m_handle!=INVALID_HANDLE); + } + //+------------------------------------------------------------------+ + //| Render | + //+------------------------------------------------------------------+ + bool Render(uint start=0,uint count=WHOLE_ARRAY) + { + return(DXBufferSet(m_context,m_handle,start,count)); + } + //+------------------------------------------------------------------+ + //| Shutdown | + //+------------------------------------------------------------------+ + void Shutdown(void) + { + //--- relase handle + if(m_handle!=INVALID_HANDLE) + DXRelease(m_handle); + m_handle=INVALID_HANDLE; + } + }; +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/DX/DXData.mqh b/Include/Canvas/DX/DXData.mqh new file mode 100644 index 0000000000000000000000000000000000000000..ef25f2566190b636b30c4137ba9692c8b3e704e6 GIT binary patch literal 4048 zcmeHJT~8WO5IxUL{0|#^REwe32TdQWRAWM=Rbxzy*-%-OX8CY;(GvBqt7m4taFL6E zp=3?W%`VICov$-z=Fa0o9(gQbB?D^-3;0BD^0RHaw5@@HCPZy+m0CB#=g zozuIbBt@b{*)<1}ZGMT#n}k=3?Ae@BzNwlUWLWsB zY{6kW(Rvckszb8UJ3`g%&i=K^raNq(1tnp7o!>p((`0-u#7f-PrG<`ujp5T?|II1U zITO-sjI)w|g6vBUQV ztnw}H@%i+Hy>(ab-}SzwciC6sPTA$*p7Mq>DZ;nZyx-GQdbT6q{*SFA{Gj*Sv?$&e zr;2MZ7MmgNxWtOYeqcp4i?|Geh8Y85yP7ro~nnbS6j@ZIq%1Y;cTVD<-m(E;1yK9{qU(+_O+uGWs0}l6H ziEEse3TLN3ib<1k`P!XdYI$wuj|wIs7oMNi%u`4^E%FRN-C z`=knLTpgam!3g%HbJaLa0j<}mMdl`%2HZ@ZOjpdFES1^O=_4ActEM$4lg((J4eOHW b?uPoK0kkI3Ri#G_t}aeFJ-8;JtetgFjrhKV literal 0 HcmV?d00001 diff --git a/Include/Canvas/DX/DXDispatcher.mqh b/Include/Canvas/DX/DXDispatcher.mqh new file mode 100644 index 0000000..c597a7a --- /dev/null +++ b/Include/Canvas/DX/DXDispatcher.mqh @@ -0,0 +1,373 @@ +//+------------------------------------------------------------------+ +//| DXDispatcher.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2000-2026, MetaQuotes Ltd." +#property link "https://www.mql5.com" +//--- +#include "DXObjectBase.mqh" +#include "DXData.mqh" +#include "DXBuffers.mqh" +#include "DXInput.mqh" +#include "DXTexture.mqh" +#include "DXShader.mqh" +//--- default shaders +#resource "Shaders/DefaultShaderVertex.hlsl" as string ExtDefaultShaderVertex; +#resource "Shaders/DefaultShaderPixel.hlsl" as string ExtDefaultShaderPixel; +//+------------------------------------------------------------------+ +//| DX dispatcher which holds all resources | +//+------------------------------------------------------------------+ +class CDXDispatcher : public CDXObjectBase + { +protected: + CDXObjectBase m_dx_resources; // DX resources list (Textures, Shaders, etc.) + //--- default shaders + CDXShader* m_default_vs; + CDXShader* m_default_ps; + +public: + CDXDispatcher(void); + ~CDXDispatcher(void); + //--- create/destroy + bool Create(int context); + void Destroy(void); + //--- check resources + void Check(void); + //--- get DX Context Handle + int DXContext(void) const { return(m_context); } + //--- create shaders + CDXShader* ShaderCreateDefault(ENUM_DX_SHADER_TYPE shader_type); + CDXShader* ShaderCreateFromFile(ENUM_DX_SHADER_TYPE shader_type,string path,string entry_point); + CDXShader* ShaderCreateFromSource(ENUM_DX_SHADER_TYPE shader_type,string source,string entry_point); + //--- create buffers + template + CDXVertexBuffer* VertexBufferCreate(const TVertex &vertices[],uint start=0,uint count=WHOLE_ARRAY); + CDXIndexBuffer* IndexBufferCreate(const uint &indicies[],uint start=0,uint count=WHOLE_ARRAY); + //--- create shader inputs + template + CDXInput* InputCreate(void); + CDXTexture* TextureCreateFromFile(string path,uint data_x=0,uint data_y=0,uint data_width=0,uint data_height=0); + CDXTexture* TextureCreateFromData(ENUM_DX_FORMAT format,uint width,uint height,const uint &data[],uint data_x=0,uint data_y=0,uint data_width=0,uint data_height=0); + +private: + //--- add resource to list + bool ResourceAdd(CDXObjectBase *resource); + //--- check resources + void ResourcesCheck(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CDXDispatcher::CDXDispatcher(void) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CDXDispatcher::~CDXDispatcher(void) + { + Destroy(); + } +//+------------------------------------------------------------------+ +//| Create | +//+------------------------------------------------------------------+ +bool CDXDispatcher::Create(int context) + { +//--- check if context already exist + if(m_context!=INVALID_HANDLE) + return(false); +//--- save context + m_context=context; +//--- + return(true); + } +//+------------------------------------------------------------------+ +//| Destroy | +//+------------------------------------------------------------------+ +void CDXDispatcher::Destroy(void) + { +//--- release default shaders + if(m_default_ps) + { + m_default_ps.Release(); + m_default_ps=NULL; + } + if(m_default_vs) + { + m_default_vs.Release(); + m_default_vs=NULL; + } +//--- release and delete all DX resources + while(m_dx_resources.Next()) + { + CDXHandleShared* resource=(CDXHandleShared*)m_dx_resources.Next(); + resource.Release(); + } +//--- forget context + m_context=INVALID_HANDLE; + } +//+------------------------------------------------------------------+ +//| Check resources | +//+------------------------------------------------------------------+ +void CDXDispatcher::Check(void) + { +//--- + CDXHandleShared* resource=(CDXHandleShared*)m_dx_resources.Next(); +//--- release and delete all DX resources + while(CheckPointer(resource)!=POINTER_INVALID) + { + CDXHandleShared* next=(CDXHandleShared*)resource.Next(); + //--- if references count 1 or less, then only we hold the resource + if(resource.References()<=1) + resource.Release(); + //--- + resource=next; + } + } +//+------------------------------------------------------------------+ +//| Create default shader of specified type | +//+------------------------------------------------------------------+ +CDXShader* CDXDispatcher::ShaderCreateDefault(ENUM_DX_SHADER_TYPE shader_type) + { + switch(shader_type) + { + //--- default pixel shader + case DX_SHADER_PIXEL: + { + if(m_default_ps==NULL) + m_default_ps=ShaderCreateFromSource(DX_SHADER_PIXEL,ExtDefaultShaderPixel,"PSMain"); + return(m_default_ps); + } + //--- default vertex shader + case DX_SHADER_VERTEX: + { + if(m_default_vs==NULL) + { + m_default_vs=ShaderCreateFromSource(DX_SHADER_VERTEX,ExtDefaultShaderVertex,"VSMain"); + if(m_default_vs && !m_default_vs.LayoutSet()) + { + m_default_vs.Release(); + m_default_vs=NULL; + } + } + return(m_default_vs); + } + } +//--- return result + return(NULL); + } +//+------------------------------------------------------------------+ +//| Create new shader of specified type from file | +//+------------------------------------------------------------------+ +CDXShader* CDXDispatcher::ShaderCreateFromFile(ENUM_DX_SHADER_TYPE shader_type,string path,string entry_point) + { +//--- open source file + int file=FileOpen(path,FILE_READ); + if(file==INVALID_HANDLE) + return(NULL); +//--- check file size + uint size=(uint)FileSize(file); + FileClose(file); + if(size>16*1024*1024) + return(NULL); +//--- prepare buffer + char buffer[]; + ArrayResize(buffer,size); +//--- read file into buffer + int read=(int)FileLoad(path,buffer); + if(read<=0) + return(NULL); +//--- convert vuffer to string + string source=CharArrayToString(buffer,0,WHOLE_ARRAY,CP_UTF8); +//--- add shader by source + return(ShaderCreateFromSource(shader_type,source,entry_point)); + } +//+------------------------------------------------------------------+ +//| Create new shader of specified type from source code | +//+------------------------------------------------------------------+ +CDXShader* CDXDispatcher::ShaderCreateFromSource(ENUM_DX_SHADER_TYPE shader_type,string source,string entry_point) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new shader + CDXShader* shader=new CDXShader(); + if(shader==NULL) + return(NULL); +//--- create shader + if(!shader.Create(m_context,shader_type,source,entry_point)) + { + shader.Release(); + return(NULL); + } +//--- add shader to resources list + if(!ResourceAdd(shader)) + { + shader.Release(); + return(NULL); + } +//--- return shader + return(shader); + } +//+------------------------------------------------------------------+ +//| Create vertex buffer | +//+------------------------------------------------------------------+ +template +CDXVertexBuffer* CDXDispatcher::VertexBufferCreate(const TVertex &vertices[],uint start=0,uint count=WHOLE_ARRAY) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new buffer + CDXVertexBuffer* buffer=new CDXVertexBuffer(); + if(buffer==NULL) + return(NULL); +//--- create buffer + if(!buffer.Create(m_context,vertices,start,count)) + { + buffer.Release(); + return(NULL); + } +//--- add buffer to resources list + if(!ResourceAdd(buffer)) + { + buffer.Release(); + return(NULL); + } +//--- return buffer + return(buffer); + } +//+------------------------------------------------------------------+ +//| Create vertex buffer | +//+------------------------------------------------------------------+ +CDXIndexBuffer* CDXDispatcher::IndexBufferCreate(const uint &indicies[],uint start=0,uint count=WHOLE_ARRAY) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new buffer + CDXIndexBuffer* buffer=new CDXIndexBuffer(); + if(buffer==NULL) + return(NULL); +//--- create buffer + if(!buffer.Create(m_context,indicies,start,count)) + { + buffer.Release(); + return(NULL); + } +//--- add buffer to resources list + if(!ResourceAdd(buffer)) + { + buffer.Release(); + return(NULL); + } +//--- return input buffer + return(buffer); + } +//+------------------------------------------------------------------+ +//| Create shader input buffer | +//+------------------------------------------------------------------+ +template +CDXInput* CDXDispatcher::InputCreate(void) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new input buffer + CDXInput* input_buffer=new CDXInput(); + if(input_buffer==NULL) + return(NULL); +//--- create buffer + if(!input_buffer.Create(m_context)) + { + input_buffer.Release(); + return(NULL); + } +//--- add buffer to resources list + if(!ResourceAdd(input_buffer)) + { + input_buffer.Release(); + return(NULL); + } +//--- return shader + return(input_buffer); + } +//+------------------------------------------------------------------+ +//| Create texture from bitmap file | +//+------------------------------------------------------------------+ +CDXTexture* CDXDispatcher::TextureCreateFromFile(string path,uint data_x=0,uint data_y=0,uint data_width=0,uint data_height=0) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new texture + CDXTexture* texture=new CDXTexture(); + if(texture==NULL) + return(NULL); +//--- create texture + if(!texture.Create(m_context,path,data_x,data_y,data_width,data_height)) + { + texture.Release(); + return(NULL); + } +//--- add texture to resources list + if(!ResourceAdd(texture)) + { + texture.Release(); + return(NULL); + } +//--- return shader + return(texture); + } +//+------------------------------------------------------------------+ +//| Create texture from raw data, only 32-bit pixel formats supported| +//+------------------------------------------------------------------+ +CDXTexture* CDXDispatcher::TextureCreateFromData(ENUM_DX_FORMAT format,uint width,uint height,const uint &data[],uint data_x=0,uint data_y=0,uint data_width=0,uint data_height=0) + { +//--- check context + if(m_context==INVALID_HANDLE) + return(NULL); +//--- allocate new texture + CDXTexture* texture=new CDXTexture(); + if(texture==NULL) + return(NULL); +//--- create texture + if(!texture.Create(m_context,format,width,height,data,data_x,data_y,data_width,data_height)) + { + texture.Release(); + return(NULL); + } +//--- add texture to resources list + if(!ResourceAdd(texture)) + { + texture.Release(); + return(NULL); + } +//--- return shader + return(texture); + } +//+------------------------------------------------------------------+ +//| Add DX resource | +//+------------------------------------------------------------------+ +bool CDXDispatcher::ResourceAdd(CDXObjectBase *resource) + { +//--- add resource + if(!CheckPointer(resource)) + return(false); +//--- + CDXObjectBase *last=&m_dx_resources; + while(CheckPointer(last.Next())!=POINTER_INVALID) + { + if(last==resource) + return(false); + //--- + last=last.Next(); + } +//--- + resource.Next(NULL); + resource.Prev(last); + last.Next(resource); + return(true); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/DX/DXHandle.mqh b/Include/Canvas/DX/DXHandle.mqh new file mode 100644 index 0000000000000000000000000000000000000000..e5207ef70646180567915ac829d07d17d72fc07d GIT binary patch literal 8568 zcmd^ET~8B16ur+T{)Z_hrUnrt(FX_*3=iYl}`seq&%*(9ImEf3_hw=c{%*(l$J3H3pv%HdB=}28#av;y7 zE#Kq_^Pk^{J!{gHWBDOHY04qSJ;BI}vS5!nd$lMpagmj?71$o--U;w(vlar+J|-RoIPdY&m-X6fsHMoSO z&ps-QZA63$Qnf|A9=W;QyQ2)%`TVM!u`|0WFHck5_=D|mtl9Pdtb(OgN*#u;yGnpN==`O)N4s^VL^9*|>Z+th?k^u>xE{{oazd@=0n~ zM{hxA?_jN8!<}_}-x$%qoMD>PqR&Gg!`o5_)1nrAT;j^nf5Oia12v&4GOT-jaQ1#^ zN#A>e>T}z=a$0#4)0IAUK66~%(u&32sXMI9OZLWn`3h!*tjc%O1}Z66d#;}{HhWkH z9zw5bN9PFCo9ytMle=FH8^Z?P+d-Y%z;4$^*&MEUe61VK-}hEWTCKyHTeB*cI|Wtj zt)=^+fo0jgo54;ZtyPw>pXkmC?>DWN27b_&Ir)kIat9`c5=Y=Jq^gbztjObC9nGU~fDM8AlCRRg}xc*{pH#=d0qknpwNA5zpP_DdFy>#W03iKDTd=$@hV~ z+dwg;^2^HMqV=X<#milLiF_LweTs98#mskhFaA#Ex_7^DH9SfEy)XQYuPXD*ReW@h zu9B#J?k$IBzH%-Mdw-_KvNwu2N!_MZHG3lXpbr!10B@Cufj3z^oKRv vX|?fv4%Hepqf=T&9$Pjx?cPr2d#ul7XgM_9>e$D@NBHi0VNuM=Uq}7|{Lt}L literal 0 HcmV?d00001 diff --git a/Include/Canvas/DX/DXInput.mqh b/Include/Canvas/DX/DXInput.mqh new file mode 100644 index 0000000000000000000000000000000000000000..6b9acb80a295bf71993a2fb235b06f517cf7884c GIT binary patch literal 4968 zcmeHKVQUjX5PhEo|HD~9t~51L&=1uJB@ryBP%GlMa80gycu6!(8mp~8z4~Ui+uPl{ zGhV1+3$irbyUXp&ym@bC^6RpVHny-Gz}muH+@UvZoFiJ}>fr0-i@&oD)Xk34tAlXvh)t!=g2!4tH2_cc;JO>jUzCfubs;oVo<4e8ZQ`O_5H z1!QSday;g4K<`G5KOynwK5-t?$DAk*iD85dNQPe$*)i|ryk~N}ah>6iA}|W*($|C> z5EgV*3Qb)pgq=F{=8*{hd zf2ppzQ%2O-9LHSgKj2&Vj1p`{#6qf(?4R-TjMpgg^__>^y!ZSVHMeHOlN9}ByI%ur_4WD&gEKX0T7m3<%#r4&TJYUT02phvFlWX2Ue!~>}XQol7_WrKz66J@!89}tvO$lnNpIkjLuSJ_~&Rw$$VGn z?U)hd)NERzB)6`}ck0;cTXJo= 1.0f) || (q.w == -1.0f)) + t = 1.0f; + else + t = (float)(acos(q.w)/sqrt(1.0f - q.w*q.w)); + out.x = t*q.x; + out.y = t*q.y; + out.z = t*q.z; + out.w = 0.0f; + } +//+------------------------------------------------------------------+ +//| Multiplies two quaternions. | +//+------------------------------------------------------------------+ +void DXQuaternionMultiply(DXQuaternion &pout,const DXQuaternion &pq1,const DXQuaternion &pq2) + { + DXQuaternion out; + out.x = pq2.w*pq1.x + pq2.x*pq1.w + pq2.y*pq1.z - pq2.z*pq1.y; + out.y = pq2.w*pq1.y - pq2.x*pq1.z + pq2.y*pq1.w + pq2.z*pq1.x; + out.z = pq2.w*pq1.z + pq2.x*pq1.y - pq2.y*pq1.x + pq2.z*pq1.w; + out.w = pq2.w*pq1.w - pq2.x*pq1.x - pq2.y*pq1.y - pq2.z*pq1.z; + pout = out; + } +//+------------------------------------------------------------------+ +//| Computes a unit length quaternion. | +//+------------------------------------------------------------------+ +void DXQuaternionNormalize(DXQuaternion &out,const DXQuaternion &q) + { + float norm = DXQuaternionLength(q); + if(!norm) + { + out.x = 0.0f; + out.y = 0.0f; + out.z = 0.0f; + out.w = 0.0f; + } + else + { + out.x = q.x/norm; + out.y = q.y/norm; + out.z = q.z/norm; + out.w = q.w/norm; + } + } +//+------------------------------------------------------------------+ +//| Rotates a quaternion about an arbitrary axis. | +//+------------------------------------------------------------------+ +void DXQuaternionRotationAxis(DXQuaternion &out,const DXVector3 &v,float angle) + { + DXVector3 temp; + DXVec3Normalize(temp,v); + out.x = (float)sin(angle/2.0f)*temp.x; + out.y = (float)sin(angle/2.0f)*temp.y; + out.z = (float)sin(angle/2.0f)*temp.z; + out.w = (float)cos(angle/2.0f); + } +//+------------------------------------------------------------------+ +//| Builds a quaternion from a rotation matrix. | +//+------------------------------------------------------------------+ +void DXQuaternionRotationMatrix(DXQuaternion &out,const DXMatrix &m) + { + float s; + float trace = m.m[0][0] + m.m[1][1] + m.m[2][2] + 1.0f; + if(trace>1.0f) + { + s = 2.0f*(float)sqrt(trace); + out.x = (m.m[1][2] - m.m[2][1])/s; + out.y = (m.m[2][0] - m.m[0][2])/s; + out.z = (m.m[0][1] - m.m[1][0])/s; + out.w = 0.25f*s; + } + else + { + int maxi = 0; + for(int i=1; i<3; i++) + { + if(m.m[i][i] > m.m[maxi][maxi]) + maxi = i; + } + switch(maxi) + { + case 0: + s = 2.0f*(float)sqrt(1.0f + m.m[0][0] - m.m[1][1] - m.m[2][2]); + out.x = 0.25f*s; + out.y = (m.m[0][1] + m.m[1][0])/s; + out.z = (m.m[0][2] + m.m[2][0])/s; + out.w = (m.m[1][2] - m.m[2][1])/s; + break; + + case 1: + s = 2.0f*(float)sqrt(1.0f + m.m[1][1] - m.m[0][0] - m.m[2][2]); + out.x = (m.m[0][1] + m.m[1][0])/s; + out.y = 0.25f*s; + out.z = (m.m[1][2] + m.m[2][1])/s; + out.w = (m.m[2][0] - m.m[0][2])/s; + break; + + case 2: + s = 2.0f*(float)sqrt(1.0f + m.m[2][2] - m.m[0][0] - m.m[1][1]); + out.x = (m.m[0][2] + m.m[2][0])/s; + out.y = (m.m[1][2] + m.m[2][1])/s; + out.z = 0.25f*s; + out.w = (m.m[0][1] - m.m[1][0])/s; + break; + } + } + } +//+------------------------------------------------------------------+ +//| Builds a quaternion with the given yaw, pitch, and roll. | +//+------------------------------------------------------------------+ +void DXQuaternionRotationYawPitchRoll(DXQuaternion &out,float yaw,float pitch,float roll) + { + float syaw = (float)sin(yaw/2.0f); + float cyaw = (float)cos(yaw/2.0f); + float spitch = (float)sin(pitch/2.0f); + float cpitch = (float)cos(pitch/2.0f); + float sroll = (float)sin(roll/2.0f); + float croll = (float)cos(roll/2.0f); +//--- + out.x = syaw*cpitch*sroll + cyaw*spitch*croll; + out.y = syaw*cpitch*croll - cyaw*spitch*sroll; + out.z = cyaw*cpitch*sroll - syaw*spitch*croll; + out.w = cyaw*cpitch*croll + syaw*spitch*sroll; + } +//+------------------------------------------------------------------+ +//| Interpolates between two quaternions, using spherical linear | +//| interpolation. | +//+------------------------------------------------------------------+ +void DXQuaternionSlerp(DXQuaternion &out,DXQuaternion &q1,DXQuaternion &q2,float t) + { + float temp = 1.0f - t; + float dot = DXQuaternionDot(q1,q2); + if(dot<0.0f) + { + t = -t; + dot = -dot; + } + if(1.0f-dot>0.001f) + { + float theta = (float)acos(dot); + temp = (float)sin(theta*temp)/(float)sin(theta); + t = (float)sin(theta*t)/(float)sin(theta); + } + out.x = temp*q1.x + t*q2.x; + out.y = temp*q1.y + t*q2.y; + out.z = temp*q1.z + t*q2.z; + out.w = temp*q1.w + t*q2.w; + } +//+------------------------------------------------------------------+ +//| Interpolates between quaternions, using spherical quadrangle | +//| interpolation. | +//+------------------------------------------------------------------+ +void DXQuaternionSquad(DXQuaternion &pout,DXQuaternion &pq1,DXQuaternion &pq2,DXQuaternion &pq3,DXQuaternion &pq4,float t) + { + DXQuaternion temp1,temp2; + DXQuaternionSlerp(temp1,pq1,pq4,t); + DXQuaternionSlerp(temp2,pq2,pq3,t); + DXQuaternionSlerp(pout,temp1,temp2,2.0f*t*(1.0f - t)); + } +//+------------------------------------------------------------------+ +//| add_diff | +//+------------------------------------------------------------------+ +DXQuaternion add_diff(const DXQuaternion &q1,const DXQuaternion &q2,const float add) + { + DXQuaternion temp; + temp.x = q1.x + add * q2.x; + temp.y = q1.y + add * q2.y; + temp.z = q1.z + add * q2.z; + temp.w = q1.w + add * q2.w; +//--- + return(temp); + } +//+------------------------------------------------------------------+ +//| Sets up control points for spherical quadrangle interpolation. | +//+------------------------------------------------------------------+ +void DXQuaternionSquadSetup(DXQuaternion &paout,DXQuaternion &pbout,DXQuaternion &pcout,DXQuaternion &pq0,DXQuaternion &pq1,DXQuaternion &pq2,DXQuaternion &pq3) + { + DXQuaternion q,temp1,temp2,temp3,zero; + DXQuaternion aout,cout; + zero.x = 0.0f; + zero.y = 0.0f; + zero.z = 0.0f; + zero.w = 0.0f; +//--- + if(DXQuaternionDot(pq0,pq1) < 0.0f) + temp2 = add_diff(zero,pq0,-1.0f); + else + temp2 = pq0; +//--- + if(DXQuaternionDot(pq1,pq2) < 0.0f) + cout = add_diff(zero,pq2,-1.0f); + else + cout = pq2; +//--- + if(DXQuaternionDot(cout,pq3) < 0.0f) + temp3 = add_diff(zero,pq3,-1.0f); + else + temp3 = pq3; +//--- + DXQuaternionInverse(temp1,pq1); + DXQuaternionMultiply(temp2,temp1,temp2); + DXQuaternionLn(temp2,temp2); + DXQuaternionMultiply(q,temp1,cout); + DXQuaternionLn(q,q); + temp1 = add_diff(temp2,q,1.0f); + temp1.x *= -0.25f; + temp1.y *= -0.25f; + temp1.z *= -0.25f; + temp1.w *= -0.25f; + DXQuaternionExp(temp1,temp1); + DXQuaternionMultiply(aout,pq1,temp1); +//--- + DXQuaternionInverse(temp1,cout); + DXQuaternionMultiply(temp2,temp1,pq1); + DXQuaternionLn(temp2,temp2); + DXQuaternionMultiply(q,temp1,temp3); + DXQuaternionLn(q,q); + temp1 = add_diff(temp2,q,1.0f); + temp1.x *= -0.25f; + temp1.y *= -0.25f; + temp1.z *= -0.25f; + temp1.w *= -0.25f; + DXQuaternionExp(temp1,temp1); + DXQuaternionMultiply(pbout,cout,temp1); + paout = aout; + pcout = cout; + } +//+------------------------------------------------------------------+ +//| Computes a quaternion's axis and angle of rotation. | +//+------------------------------------------------------------------+ +void DXQuaternionToAxisAngle(const DXQuaternion &pq,DXVector3 &paxis,float &pangle) + { +//--- paxis + paxis.x = pq.x; + paxis.y = pq.y; + paxis.z = pq.z; +//--- pangle + pangle = 2.0f*(float)acos(pq.w); + } +//+------------------------------------------------------------------+ +//| DXMatrixIdentity creates an identity matrix | +//+------------------------------------------------------------------+ +void DXMatrixIdentity(DXMatrix &out) + { + for(int j=0; j<4; j++) + for(int i=0; i<4; i++) + { + if(i==j) + out.m[j,i]=1.0f; + else + out.m[j,i]=0.0f; + } + } +//+------------------------------------------------------------------+ +//| Determines if a matrix is an identity matrix. | +//+------------------------------------------------------------------+ +bool DXMatrixIsIdentity(DXMatrix &pm) + { + for(int j=0; j<4; j++) + for(int i=0; i<4; i++) + { + if(i==j) + { + if(fabs(pm.m[j,i]-1.0f)>1e-6f) + return(false); + } + else + if(fabs(pm.m[j,i])>1e-6f) + return(false); + } +//--- + return(true); + } +//+------------------------------------------------------------------+ +//| Builds a 3D affine transformation matrix. | +//+------------------------------------------------------------------+ +//| This function calculates the affine transformation matrix | +//| with the following formula, with matrix concatenation | +//| evaluated in left-to-right order: | +//| Mout = Ms * (Mrc)-1 * Mr * Mrc * Mt | +//| where: | +//| Mout = output matrix (pOut) | +//| Ms = scaling matrix (Scaling) | +//| Mrc = center of rotation matrix (pRotationCenter) | +//| Mr = rotation matrix (pRotation) | +//| Mt = translation matrix (pTranslation) | +//+------------------------------------------------------------------+ +void DXMatrixAffineTransformation(DXMatrix &out,float scaling,const DXVector3 &rotationcenter,const DXQuaternion &rotation,const DXVector3 &translation) + { + DXMatrixIdentity(out); +//--- rotation + float temp00 = 1.0f - 2.0f*(rotation.y*rotation.y + rotation.z*rotation.z); + float temp01 = 2.0f * (rotation.x*rotation.y + rotation.z*rotation.w); + float temp02 = 2.0f * (rotation.x*rotation.z - rotation.y*rotation.w); + float temp10 = 2.0f * (rotation.x*rotation.y - rotation.z*rotation.w); + float temp11 = 1.0f - 2.0f*(rotation.x*rotation.x + rotation.z*rotation.z); + float temp12 = 2.0f * (rotation.y*rotation.z + rotation.x*rotation.w); + float temp20 = 2.0f * (rotation.x*rotation.z + rotation.y*rotation.w); + float temp21 = 2.0f * (rotation.y*rotation.z - rotation.x*rotation.w); + float temp22 = 1.0f - 2.0f*(rotation.x*rotation.x + rotation.y*rotation.y); +//--- scaling + out.m[0][0] = scaling*temp00; + out.m[0][1] = scaling*temp01; + out.m[0][2] = scaling*temp02; + out.m[1][0] = scaling*temp10; + out.m[1][1] = scaling*temp11; + out.m[1][2] = scaling*temp12; + out.m[2][0] = scaling*temp20; + out.m[2][1] = scaling*temp21; + out.m[2][2] = scaling*temp22; +//--- rotationcenter + out.m[3][0] = rotationcenter.x*(1.0f - temp00) - rotationcenter.y*temp10 - rotationcenter.z*temp20; + out.m[3][1] = rotationcenter.y*(1.0f - temp11) - rotationcenter.x*temp01 - rotationcenter.z*temp21; + out.m[3][2] = rotationcenter.z*(1.0f - temp22) - rotationcenter.x*temp02 - rotationcenter.y*temp12; +//--- translation + out.m[3][0] += translation.x; + out.m[3][1] += translation.y; + out.m[3][2] += translation.z; + } +//+------------------------------------------------------------------+ +//| Builds a 2D affine transformation matrix in the xy plane. | +//+------------------------------------------------------------------+ +//| This function calculates the affine transformation matrix | +//| with the following formula, with matrix concatenation evaluated | +//| in left-to-right order: | +//| Mout = Ms * (Mrc)^(-1) * Mr * Mrc * Mt | +//| where: | +//| Mout = output matrix (pOut) | +//| Ms = scaling matrix (Scaling) | +//| Mrc = center of rotation matrix (pRotationCenter) | +//| Mr = rotation matrix (Rotation) | +//| Mt = translation matrix (pTranslation) | +//+------------------------------------------------------------------+ +void DXMatrixAffineTransformation2D(DXMatrix &out,float scaling,const DXVector2 &rotationcenter,float rotation,const DXVector2 &translation) + { + float s = (float)sin(rotation/2.0f); + float tmp1 = 1.0f - 2.0f*s*s; + float tmp2 = 2.0f*s*(float)cos(rotation/2.0f); +//--- + DXMatrixIdentity(out); + out.m[0][0] = scaling*tmp1; + out.m[0][1] = scaling*tmp2; + out.m[1][0] = -scaling*tmp2; + out.m[1][1] = scaling*tmp1; +//--- rotationcenter + float x = rotationcenter.x; + float y = rotationcenter.y; + out.m[3][0] = y*tmp2 - x*tmp1 + x; + out.m[3][1] = -x*tmp2 - y*tmp1 + y; +//--- translation + out.m[3][0] += translation.x; + out.m[3][1] += translation.y; + } +#define D3DERR_INVALIDCALL -2005530516 +//#define S_OK 0; +//+------------------------------------------------------------------+ +//| Breaks down a general 3D transformation matrix into its scalar, | +//| rotational, and translational components. | +//+------------------------------------------------------------------+ +int DXMatrixDecompose(DXVector3 &poutscale,DXQuaternion &poutrotation,DXVector3 &pouttranslation,const DXMatrix &pm) + { + DXMatrix normalized; + DXVector3 vec; +//--- Compute the scaling part + vec.x=pm.m[0][0]; + vec.y=pm.m[0][1]; + vec.z=pm.m[0][2]; + poutscale.x=DXVec3Length(vec); + vec.x=pm.m[1][0]; + vec.y=pm.m[1][1]; + vec.z=pm.m[1][2]; + poutscale.y=DXVec3Length(vec); + vec.x=pm.m[2][0]; + vec.y=pm.m[2][1]; + vec.z=pm.m[2][2]; + poutscale.z=DXVec3Length(vec); +//--- compute the translation part + pouttranslation.x=pm.m[3][0]; + pouttranslation.y=pm.m[3][1]; + pouttranslation.z=pm.m[3][2]; +//--- let's calculate the rotation now + if((poutscale.x == 0.0f) || (poutscale.y == 0.0f) || (poutscale.z == 0.0f)) + return(D3DERR_INVALIDCALL); +//--- + normalized.m[0][0]=pm.m[0][0]/poutscale.x; + normalized.m[0][1]=pm.m[0][1]/poutscale.x; + normalized.m[0][2]=pm.m[0][2]/poutscale.x; + normalized.m[1][0]=pm.m[1][0]/poutscale.y; + normalized.m[1][1]=pm.m[1][1]/poutscale.y; + normalized.m[1][2]=pm.m[1][2]/poutscale.y; + normalized.m[2][0]=pm.m[2][0]/poutscale.z; + normalized.m[2][1]=pm.m[2][1]/poutscale.z; + normalized.m[2][2]=pm.m[2][2]/poutscale.z; + DXQuaternionRotationMatrix(poutrotation,normalized); +//--- + return(0); + } +//+------------------------------------------------------------------+ +//| Returns the determinant of a matrix. | +//+------------------------------------------------------------------+ +float DXMatrixDeterminant(const DXMatrix &pm) + { + float t[3],v[4]; + t[0] = pm.m[2][2]*pm.m[3][3] - pm.m[2][3]*pm.m[3][2]; + t[1] = pm.m[1][2]*pm.m[3][3] - pm.m[1][3]*pm.m[3][2]; + t[2] = pm.m[1][2]*pm.m[2][3] - pm.m[1][3]*pm.m[2][2]; + v[0] = pm.m[1][1]*t[0] - pm.m[2][1] * t[1] + pm.m[3][1]*t[2]; + v[1] = -pm.m[1][0]*t[0] + pm.m[2][0] * t[1] - pm.m[3][0]*t[2]; +//--- + t[0] = pm.m[1][0]*pm.m[2][1] - pm.m[2][0]*pm.m[1][1]; + t[1] = pm.m[1][0]*pm.m[3][1] - pm.m[3][0]*pm.m[1][1]; + t[2] = pm.m[2][0]*pm.m[3][1] - pm.m[3][0]*pm.m[2][1]; + v[2] = pm.m[3][3]*t[0] - pm.m[2][3]*t[1] + pm.m[1][3]*t[2]; + v[3] = -pm.m[3][2]*t[0] + pm.m[2][2]*t[1] - pm.m[1][2]*t[2]; +//--- + return(pm.m[0][0]*v[0] + pm.m[0][1]*v[1] + pm.m[0][2]*v[2] + pm.m[0][3]*v[3]); + } +//+------------------------------------------------------------------+ +//| Calculates the inverse of a matrix. | +//+------------------------------------------------------------------+ +void DXMatrixInverse(DXMatrix &pout,float &pdeterminant,const DXMatrix &pm) + { + float t[3],v[16]; + t[0] = pm.m[2][2]*pm.m[3][3] - pm.m[2][3]*pm.m[3][2]; + t[1] = pm.m[1][2]*pm.m[3][3] - pm.m[1][3]*pm.m[3][2]; + t[2] = pm.m[1][2]*pm.m[2][3] - pm.m[1][3]*pm.m[2][2]; + v[0] = pm.m[1][1]*t[0] - pm.m[2][1]*t[1] + pm.m[3][1]*t[2]; + v[4] = -pm.m[1][0]*t[0] + pm.m[2][0]*t[1] - pm.m[3][0]*t[2]; +//--- + t[0] = pm.m[1][0]*pm.m[2][1] - pm.m[2][0]*pm.m[1][1]; + t[1] = pm.m[1][0]*pm.m[3][1] - pm.m[3][0]*pm.m[1][1]; + t[2] = pm.m[2][0]*pm.m[3][1] - pm.m[3][0]*pm.m[2][1]; + v[8] = pm.m[3][3]*t[0] - pm.m[2][3]*t[1] + pm.m[1][3]*t[2]; + v[12] = -pm.m[3][2]*t[0] + pm.m[2][2]*t[1] - pm.m[1][2]*t[2]; +//--- + float det = pm.m[0][0]*v[0] + pm.m[0][1]*v[4] + pm.m[0][2]*v[8] + pm.m[0][3]*v[12]; + if(det == 0.0f) + { + for(int j=0; j<4; j++) + for(int i=0; i<4; i++) + { + pout.m[j,i]=0.0; + } + //--- + return; + } + if(pdeterminant) + pdeterminant = det; +//--- + t[0] = pm.m[2][2]*pm.m[3][3] - pm.m[2][3]*pm.m[3][2]; + t[1] = pm.m[0][2]*pm.m[3][3] - pm.m[0][3]*pm.m[3][2]; + t[2] = pm.m[0][2]*pm.m[2][3] - pm.m[0][3]*pm.m[2][2]; + v[1] = -pm.m[0][1]*t[0] + pm.m[2][1]*t[1] - pm.m[3][1]*t[2]; + v[5] = pm.m[0][0]*t[0] - pm.m[2][0]*t[1] + pm.m[3][0]*t[2]; +//--- + t[0] = pm.m[0][0]*pm.m[2][1] - pm.m[2][0] * pm.m[0][1]; + t[1] = pm.m[3][0]*pm.m[0][1] - pm.m[0][0] * pm.m[3][1]; + t[2] = pm.m[2][0]*pm.m[3][1] - pm.m[3][0] * pm.m[2][1]; + v[9] = -pm.m[3][3]*t[0] - pm.m[2][3]*t[1]- pm.m[0][3]*t[2]; + v[13] = pm.m[3][2]*t[0] + pm.m[2][2]*t[1] + pm.m[0][2]*t[2]; +//--- + t[0] = pm.m[1][2]*pm.m[3][3] - pm.m[1][3] * pm.m[3][2]; + t[1] = pm.m[0][2]*pm.m[3][3] - pm.m[0][3] * pm.m[3][2]; + t[2] = pm.m[0][2]*pm.m[1][3] - pm.m[0][3] * pm.m[1][2]; + v[2] = pm.m[0][1]*t[0] - pm.m[1][1]*t[1] + pm.m[3][1]*t[2]; + v[6] = -pm.m[0][0]*t[0] + pm.m[1][0]*t[1] - pm.m[3][0]*t[2]; +//--- + t[0] = pm.m[0][0]*pm.m[1][1] - pm.m[1][0] * pm.m[0][1]; + t[1] = pm.m[3][0]*pm.m[0][1] - pm.m[0][0] * pm.m[3][1]; + t[2] = pm.m[1][0]*pm.m[3][1] - pm.m[3][0] * pm.m[1][1]; + v[10] = pm.m[3][3]*t[0] + pm.m[1][3]*t[1] + pm.m[0][3]*t[2]; + v[14] = -pm.m[3][2]*t[0] - pm.m[1][2]*t[1] - pm.m[0][2]*t[2]; +//--- + t[0] = pm.m[1][2]*pm.m[2][3] - pm.m[1][3] * pm.m[2][2]; + t[1] = pm.m[0][2]*pm.m[2][3] - pm.m[0][3] * pm.m[2][2]; + t[2] = pm.m[0][2]*pm.m[1][3] - pm.m[0][3] * pm.m[1][2]; + v[3] = -pm.m[0][1]*t[0] + pm.m[1][1]*t[1] - pm.m[2][1]*t[2]; + v[7] = pm.m[0][0]*t[0] - pm.m[1][0]*t[1] + pm.m[2][0]*t[2]; +//--- + v[11] = -pm.m[0][0]*(pm.m[1][1]*pm.m[2][3] - pm.m[1][3]*pm.m[2][1]) + + pm.m[1][0]*(pm.m[0][1]*pm.m[2][3] - pm.m[0][3]*pm.m[2][1]) - + pm.m[2][0]*(pm.m[0][1]*pm.m[1][3] - pm.m[0][3]*pm.m[1][1]); +//--- + v[15] = pm.m[0][0]*(pm.m[1][1]*pm.m[2][2] - pm.m[1][2]*pm.m[2][1]) - + pm.m[1][0]*(pm.m[0][1]*pm.m[2][2] - pm.m[0][2]*pm.m[2][1]) + + pm.m[2][0]*(pm.m[0][1]*pm.m[1][2] - pm.m[0][2]*pm.m[1][1]); +//--- + det = 1.0f/det; + for(int i=0; i<4; i++) + for(int j=0; j<4; j++) + pout.m[i][j] = v[4*i + j]*det; + } +//+------------------------------------------------------------------+ +//| Builds a left-handed,look-at matrix. | +//| This function uses the following formula to compute | +//| the returned matrix. | +//| | +//| zaxis = normal(At - Eye) | +//| xaxis = normal(cross(Up,zaxis)) | +//| yaxis = cross(zaxis,xaxis) | +//| | +//| xaxis.x yaxis.x zaxis.x 0 | +//| xaxis.y yaxis.y zaxis.y 0 | +//| xaxis.z yaxis.z zaxis.z 0 | +//| -dot(xaxis,eye) -dot(yaxis,eye) -dot(zaxis,eye) 1 | +//+------------------------------------------------------------------+ +void DXMatrixLookAtLH(DXMatrix &out,const DXVector3 &eye,const DXVector3 &at,const DXVector3 &up) + { + DXVector3 right,upn,vec; + DXVec3Subtract(vec,at,eye); + DXVec3Normalize(vec,vec); + DXVec3Cross(right,up,vec); + DXVec3Cross(upn,vec,right); + DXVec3Normalize(right,right); + DXVec3Normalize(upn,upn); +//--- + out.m[0][0] = right.x; + out.m[1][0] = right.y; + out.m[2][0] = right.z; + out.m[3][0] = -DXVec3Dot(right,eye); + out.m[0][1] = upn.x; + out.m[1][1] = upn.y; + out.m[2][1] = upn.z; + out.m[3][1] = -DXVec3Dot(upn,eye); + out.m[0][2] = vec.x; + out.m[1][2] = vec.y; + out.m[2][2] = vec.z; + out.m[3][2] = -DXVec3Dot(vec,eye); + out.m[0][3] = 0.0f; + out.m[1][3] = 0.0f; + out.m[2][3] = 0.0f; + out.m[3][3] = 1.0f; + } +//+------------------------------------------------------------------+ +//| Builds a right-handed, look-at matrix. | +//+------------------------------------------------------------------+ +//| This function uses the following formula to compute | +//| the returned matrix. | +//| | +//| zaxis = normal(Eye - At) | +//| xaxis = normal(cross(Up,zaxis)) | +//| yaxis = cross(zaxis,xaxis) | +//| | +//| xaxis.x yaxis.x zaxis.x 0 | +//| xaxis.y yaxis.y zaxis.y 0 | +//| xaxis.z yaxis.z zaxis.z 0 | +//| dot(xaxis,eye) dot(yaxis,eye) dot(zaxis,eye) 1 | +//+------------------------------------------------------------------+ +void DXMatrixLookAtRH(DXMatrix &out,const DXVector3 &eye,const DXVector3 &at,const DXVector3 &up) + { + DXVector3 right,upn,vec; + DXVec3Subtract(vec,at,eye); + DXVec3Normalize(vec,vec); + DXVec3Cross(right,up,vec); + DXVec3Cross(upn,vec,right); + DXVec3Normalize(right,right); + DXVec3Normalize(upn,upn); +//--- + out.m[0][0] = -right.x; + out.m[1][0] = -right.y; + out.m[2][0] = -right.z; + out.m[3][0] = DXVec3Dot(right,eye); + out.m[0][1] = upn.x; + out.m[1][1] = upn.y; + out.m[2][1] = upn.z; + out.m[3][1] = -DXVec3Dot(upn,eye); + out.m[0][2] = -vec.x; + out.m[1][2] = -vec.y; + out.m[2][2] = -vec.z; + out.m[3][2] = DXVec3Dot(vec,eye); + out.m[0][3] = 0.0f; + out.m[1][3] = 0.0f; + out.m[2][3] = 0.0f; + out.m[3][3] = 1.0f; + } +//+------------------------------------------------------------------+ +//| Determines the product of two matrices. | +//+------------------------------------------------------------------+ +void DXMatrixMultiply(DXMatrix &pout,const DXMatrix &pm1,const DXMatrix &pm2) + { + DXMatrix out= {}; + for(int i=0; i<4; i++) + { + for(int j=0; j<4; j++) + { + out.m[i][j] = pm1.m[i][0]*pm2.m[0][j] + pm1.m[i][1]*pm2.m[1][j] + pm1.m[i][2]*pm2.m[2][j] + pm1.m[i][3]*pm2.m[3][j]; + } + } + pout = out; + } +//+------------------------------------------------------------------+ +//| Calculates the transposed product of two matrices. | +//+------------------------------------------------------------------+ +void DXMatrixMultiplyTranspose(DXMatrix &pout,const DXMatrix &pm1,const DXMatrix &pm2) + { + DXMatrix temp= {}; + for(int i = 0; i < 4; i++) + for(int j = 0; j < 4; j++) + temp.m[j][i] = pm1.m[i][0]*pm2.m[0][j] + pm1.m[i][1]*pm2.m[1][j] + pm1.m[i][2]*pm2.m[2][j] + pm1.m[i][3]*pm2.m[3][j]; + pout = temp; + } +//+------------------------------------------------------------------+ +//| Builds a left-handed orthographic projection matrix. | +//+------------------------------------------------------------------+ +void DXMatrixOrthoLH(DXMatrix &pout,float w,float h,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f/w; + pout.m[1][1] = 2.0f/h; + pout.m[2][2] = 1.0f/(zf - zn); + pout.m[3][2] = zn/(zn - zf); + } +//+------------------------------------------------------------------+ +//| Builds a customized,left-handed orthographic projection matrix. | +//+------------------------------------------------------------------+ +void DXMatrixOrthoOffCenterLH(DXMatrix &pout,float l,float r,float b,float t,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f/(r - l); + pout.m[1][1] = 2.0f/(t - b); + pout.m[2][2] = 1.0f/(zf -zn); + pout.m[3][0] = -1.0f -2.0f*l/(r - l); + pout.m[3][1] = 1.0f + 2.0f*t/(b - t); + pout.m[3][2] = zn/(zn -zf); + } +//+------------------------------------------------------------------+ +//| Builds a customized,right-handed orthographic projection matrix. | +//+------------------------------------------------------------------+ +void DXMatrixOrthoOffCenterRH(DXMatrix &pout,float l,float r,float b,float t,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f/(r - l); + pout.m[1][1] = 2.0f/(t - b); + pout.m[2][2] = 1.0f/(zn -zf); + pout.m[3][0] = -1.0f -2.0f*l/(r - l); + pout.m[3][1] = 1.0f + 2.0f*t/(b - t); + pout.m[3][2] = zn/(zn -zf); + } +//+------------------------------------------------------------------+ +//| Builds a right-handed orthographic projection matrix. | +//+------------------------------------------------------------------+ +//| All the parameters of the DXMatrixOrthoRH function | +//| are distances in camera space. The parameters describe | +//| the dimensions of the view volume. | +//| | +//| This function uses the following formula to compute | +//| the returned matrix: | +//| 2/w 0 0 0 | +//| 0 2/h 0 0 | +//| 0 0 1/(zn-zf) 0 | +//| 0 0 zn/(zn-zf) 1 | +//+------------------------------------------------------------------+ +void DXMatrixOrthoRH(DXMatrix &pout,float w,float h,float zn,float zf) + { + DXMatrixIdentity(pout); + pout.m[0][0] = 2.0f/w; + pout.m[1][1] = 2.0f/h; + pout.m[2][2] = 1.0f/(zn - zf); + pout.m[3][2] = zn/(zn - zf); + } +//+------------------------------------------------------------------+ +//| Builds a left-handed perspective projection matrix | +//| based on a field of view. | +//+------------------------------------------------------------------+ +//| This function computes the returned matrix as shown: | +//| xScale 0 0 0 | +//| 0 yScale 0 0 | +//| 0 0 zf/(zf-zn) 1 | +//| 0 0 -zn*zf/(zf-zn) 0 | +//| where: | +//| yScale = cot(fovY/2) | +//| xScale = yScale / aspect ratio | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveFovLH(DXMatrix &pout,float fovy,float aspect,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 1.0f/(aspect*(float)tan(fovy/2.0f)); + pout.m[1][1] = 1.0f/(float)tan(fovy/2.0f); + pout.m[2][2] = zf/(zf - zn); + pout.m[2][3] = 1.0f; + pout.m[3][2] = (zf*zn)/(zn - zf); + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a right-handed perspective projection matrix | +//| based on a field of view. | +//+------------------------------------------------------------------+ +//| This function computes the returned matrix as shown. | +//| xScale 0 0 0 | +//| 0 yScale 0 0 | +//| 0 0 zf/(zn-zf) -1 | +//| 0 0 zn*zf/(zn-zf) 0 | +//| where: | +//| yScale = cot(fovY/2) | +//| xScale = yScale / aspect ratio | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveFovRH(DXMatrix &pout,float fovy,float aspect,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 1.0f/(aspect*(float)tan(fovy/2.0f)); + pout.m[1][1] = 1.0f/(float)tan(fovy/2.0f); + pout.m[2][2] = zf/(zn - zf); + pout.m[2][3] = -1.0f; + pout.m[3][2] = (zf*zn)/(zn - zf); + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a left-handed perspective projection matrix | +//+------------------------------------------------------------------+ +//| This function uses the following formula to compute | +//| the returned matrix. | +//| 2*zn/w 0 0 0 | +//| 0 2*zn/h 0 0 | +//| 0 0 zf/(zf-zn) 1 | +//| 0 0 zn*zf/(zn-zf) 0 | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveLH(DXMatrix &pout,float w,float h,float zn,float zf) + { + DXMatrixIdentity(pout); + pout.m[0][0] = 2.0f*zn/w; + pout.m[1][1] = 2.0f*zn/h; + pout.m[2][2] = zf/(zf - zn); + pout.m[3][2] = (zn*zf)/(zn - zf); + pout.m[2][3] = 1.0f; + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a customized, left-handed perspective projection matrix. | +//+------------------------------------------------------------------+ +//| All the parameters of the DXMatrixPerspectiveOffCenterLH | +//| function are distances in camera space. The parameters describe | +//| the dimensions of the view volume. | +//| | +//| This function uses the following formula to compute | +//| the returned matrix. | +//| 2*zn/(r-l) 0 0 0 | +//| 0 2*zn/(t-b) 0 0 | +//| (l+r)/(l-r) (t+b)/(b-t) zf/(zf-zn) 1 | +//| 0 0 zn*zf/(zn-zf) 0 | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveOffCenterLH(DXMatrix &pout,float l,float r,float b,float t,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f*zn/(r - l); + pout.m[1][1] = -2.0f*zn/(b - t); + pout.m[2][0] = -1.0f - 2.0f*l/(r - l); + pout.m[2][1] = 1.0f + 2.0f*t/(b - t); + pout.m[2][2] = - zf/(zn - zf); + pout.m[3][2] = (zn*zf)/(zn -zf); + pout.m[2][3] = 1.0f; + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a customized, right-handed perspective projection matrix. | +//+------------------------------------------------------------------+ +//| All the parameters of the DXMatrixPerspectiveOffCenterRH | +//| function are distances in camera space. The parameters describe | +//| the dimensions of the view volume. | +//| | +//| This function uses the following formula to compute | +//| the returned matrix. | +//| 2*zn/(r-l) 0 0 0 | +//| 0 2*zn/(t-b) 0 0 | +//| (l+r)/(r-l) (t+b)/(t-b) zf/(zn-zf) -1 | +//| 0 0 zn*zf/(zn-zf) 0 | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveOffCenterRH(DXMatrix &pout,float l,float r,float b,float t,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f*zn/(r - l); + pout.m[1][1] = -2.0f*zn/(b - t); + pout.m[2][0] = 1.0f + 2.0f*l/(r - l); + pout.m[2][1] = -1.0f -2.0f*t/(b - t); + pout.m[2][2] = zf/(zn - zf); + pout.m[3][2] = (zn*zf)/(zn -zf); + pout.m[2][3] = -1.0f; + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a right-handed perspective projection matrix. | +//+------------------------------------------------------------------+ +//| All the parameters of the DXMatrixPerspectiveRH function | +//| are distances in camera space. The parameters describe | +//| the dimensions of the view volume. | +//| | +//| This function uses the following formula to compute | +//| the returned matrix. | +//| 2*zn/w 0 0 0 | +//| 0 2*zn/h 0 0 | +//| 0 0 zf/(zn-zf) -1 | +//| 0 0 zn*zf/(zn-zf) 0 | +//+------------------------------------------------------------------+ +void DXMatrixPerspectiveRH(DXMatrix &pout,float w,float h,float zn,float zf) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 2.0f*zn/w; + pout.m[1][1] = 2.0f*zn/h; + pout.m[2][2] = zf/(zn - zf); + pout.m[3][2] = (zn*zf)/(zn - zf); + pout.m[2][3] = -1.0f; + pout.m[3][3] = 0.0f; + } +//+------------------------------------------------------------------+ +//| Builds a matrix that reflects the coordinate system about a plane| +//| This function normalizes the plane equation before it creates | +//| the reflected matrix. | +//| | +//| This function uses the following formula to compute | +//| the returned matrix. | +//| P = normalize(Plane); | +//| -2 * P.a * P.a + 1 -2 * P.b * P.a -2 * P.c * P.a 0 | +//| -2 * P.a * P.b -2 * P.b * P.b + 1 -2 * P.c * P.b 0 | +//| -2 * P.a * P.c -2 * P.b * P.c -2 * P.c * P.c + 1 0 | +//| -2 * P.a * P.d -2 * P.b * P.d -2 * P.c * P.d 1 | +//+------------------------------------------------------------------+ +void DXMatrixReflect(DXMatrix &pout,const DXPlane &pplane) + { + DXPlane Nplane; + DXPlaneNormalize(Nplane,pplane); + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 1.0f - 2.0f*Nplane.a*Nplane.a; + pout.m[0][1] = -2.0f*Nplane.a*Nplane.b; + pout.m[0][2] = -2.0f*Nplane.a*Nplane.c; + pout.m[1][0] = -2.0f*Nplane.a*Nplane.b; + pout.m[1][1] = 1.0f - 2.0f*Nplane.b*Nplane.b; + pout.m[1][2] = -2.0f*Nplane.b*Nplane.c; + pout.m[2][0] = -2.0f*Nplane.c*Nplane.a; + pout.m[2][1] = -2.0f*Nplane.c*Nplane.b; + pout.m[2][2] = 1.0f - 2.0f*Nplane.c*Nplane.c; + pout.m[3][0] = -2.0f*Nplane.d*Nplane.a; + pout.m[3][1] = -2.0f*Nplane.d*Nplane.b; + pout.m[3][2] = -2.0f*Nplane.d*Nplane.c; + } +//+------------------------------------------------------------------+ +//| Builds a matrix that rotates around an arbitrary axis. | +//+------------------------------------------------------------------+ +void DXMatrixRotationAxis(DXMatrix &out,const DXVector3 &v,float angle) + { + DXVector3 nv; + DXVec3Normalize(nv,v); +//--- + float sangle = (float)sin(angle); + float cangle = (float)cos(angle); + float cdiff = 1.0f - cangle; +//--- + out.m[0][0] = cdiff*nv.x*nv.x + cangle; + out.m[1][0] = cdiff*nv.x*nv.y - sangle*nv.z; + out.m[2][0] = cdiff*nv.x*nv.z + sangle*nv.y; + out.m[3][0] = 0.0f; + out.m[0][1] = cdiff*nv.y*nv.x + sangle*nv.z; + out.m[1][1] = cdiff*nv.y*nv.y + cangle; + out.m[2][1] = cdiff*nv.y*nv.z - sangle*nv.x; + out.m[3][1] = 0.0f; + out.m[0][2] = cdiff*nv.z*nv.x - sangle*nv.y; + out.m[1][2] = cdiff*nv.z*nv.y + sangle*nv.x; + out.m[2][2] = cdiff*nv.z*nv.z + cangle; + out.m[3][2] = 0.0f; + out.m[0][3] = 0.0f; + out.m[1][3] = 0.0f; + out.m[2][3] = 0.0f; + out.m[3][3] = 1.0f; + } +//+------------------------------------------------------------------+ +//| Builds a rotation matrix from a quaternion. | +//+------------------------------------------------------------------+ +void DXMatrixRotationQuaternion(DXMatrix &pout,const DXQuaternion &pq) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = 1.0f - 2.0f*(pq.y*pq.y + pq.z*pq.z); + pout.m[0][1] = 2.0f*(pq.x*pq.y + pq.z*pq.w); + pout.m[0][2] = 2.0f*(pq.x*pq.z - pq.y*pq.w); + pout.m[1][0] = 2.0f*(pq.x*pq.y - pq.z*pq.w); + pout.m[1][1] = 1.0f - 2.0f * (pq.x*pq.x + pq.z*pq.z); + pout.m[1][2] = 2.0f*(pq.y*pq.z + pq.x*pq.w); + pout.m[2][0] = 2.0f*(pq.x*pq.z + pq.y*pq.w); + pout.m[2][1] = 2.0f*(pq.y*pq.z - pq.x*pq.w); + pout.m[2][2] = 1.0f - 2.0f*(pq.x*pq.x + pq.y*pq.y); + } +//+------------------------------------------------------------------+ +//| Builds a matrix that rotates around the x-axis. | +//+------------------------------------------------------------------+ +void DXMatrixRotationX(DXMatrix &pout,float angle) + { + DXMatrixIdentity(pout); +//--- + pout.m[1][1] = (float)cos(angle); + pout.m[2][2] = (float)cos(angle); + pout.m[1][2] = (float)sin(angle); + pout.m[2][1] = -(float)sin(angle); + } +//+------------------------------------------------------------------+ +//| Builds a matrix that rotates around the y-axis. | +//+------------------------------------------------------------------+ +void DXMatrixRotationY(DXMatrix &pout,float angle) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = (float)cos(angle); + pout.m[2][2] = (float)cos(angle); + pout.m[0][2] = -(float)sin(angle); + pout.m[2][0] = (float)sin(angle); + } +//+------------------------------------------------------------------+ +//| Builds a matrix with a specified yaw, pitch, and roll. | +//+------------------------------------------------------------------+ +//| The order of transformations is roll first, then pitch, then yaw.| +//| Relative to the object's local coordinate axis, this is | +//| equivalent to rotation around the z-axis, followed by rotation | +//| around the x-axis, followed by rotation around the y-axis. | +//+------------------------------------------------------------------+ +void DXMatrixRotationYawPitchRoll(DXMatrix &out,float yaw,float pitch,float roll) + { + float sroll = (float)sin(roll); + float croll = (float)cos(roll); + float spitch = (float)sin(pitch); + float cpitch = (float)cos(pitch); + float syaw = (float)sin(yaw); + float cyaw = (float)cos(yaw); +//--- + out.m[0][0] = sroll * spitch * syaw + croll * cyaw; + out.m[0][1] = sroll * cpitch; + out.m[0][2] = sroll * spitch * cyaw - croll * syaw; + out.m[0][3] = 0.0f; + out.m[1][0] = croll * spitch * syaw - sroll * cyaw; + out.m[1][1] = croll * cpitch; + out.m[1][2] = croll * spitch * cyaw + sroll * syaw; + out.m[1][3] = 0.0f; + out.m[2][0] = cpitch * syaw; + out.m[2][1] = -spitch; + out.m[2][2] = cpitch * cyaw; + out.m[2][3] = 0.0f; + out.m[3][0] = 0.0f; + out.m[3][1] = 0.0f; + out.m[3][2] = 0.0f; + out.m[3][3] = 1.0f; + } +//+------------------------------------------------------------------+ +//| Builds a matrix that rotates around the z-axis. | +//+------------------------------------------------------------------+ +void DXMatrixRotationZ(DXMatrix &pout,float angle) + { + DXMatrixIdentity(pout); +//--- + pout.m[0][0] = (float)cos(angle); + pout.m[1][1] = (float)cos(angle); + pout.m[0][1] = (float)sin(angle); + pout.m[1][0] = -(float)sin(angle); + } +//+------------------------------------------------------------------+ +//| Builds a matrix that scales along the x-axis, | +//| the y-axis,and the z-axis. | +//+------------------------------------------------------------------+ +void DXMatrixScaling(DXMatrix &pout,float sx,float sy,float sz) + { + DXMatrixIdentity(pout); + pout.m[0][0] = sx; + pout.m[1][1] = sy; + pout.m[2][2] = sz; + } +//+------------------------------------------------------------------+ +//| Builds a matrix that flattens geometry into a plane. | +//+------------------------------------------------------------------+ +//| The DXMatrixShadow function flattens geometry into a plane, as | +//| if casting a shadow from a light. | +//| This function uses the following formula to compute the returned | +//| matrix. | +//| | +//| P = normalize(Plane); | +//| L = Light; | +//| d = -dot(P,L) | +//| | +//| P.a * L.x + d P.a * L.y P.a * L.z P.a * L.w | +//| P.b * L.x P.b * L.y + d P.b * L.z P.b * L.w | +//| P.c * L.x P.c * L.y P.c * L.z + d P.c * L.w | +//| P.d * L.x P.d * L.y P.d * L.z P.d * L.w + d | +//| | +//| If the light's w-component is 0, the ray from the origin to the | +//| light represents a directional light. If it is 1,the light is | +//| a point light. | +//+------------------------------------------------------------------+ +void DXMatrixShadow(DXMatrix &pout,const DXVector4 &plight,const DXPlane &pplane) + { + DXPlane Nplane; + DXPlaneNormalize(Nplane,pplane); + float dot = DXPlaneDot(Nplane,plight); +//--- + pout.m[0][0] = dot - Nplane.a*plight.x; + pout.m[0][1] = -Nplane.a*plight.y; + pout.m[0][2] = -Nplane.a*plight.z; + pout.m[0][3] = -Nplane.a*plight.w; + pout.m[1][0] = -Nplane.b*plight.x; + pout.m[1][1] = dot - Nplane.b*plight.y; + pout.m[1][2] = -Nplane.b*plight.z; + pout.m[1][3] = -Nplane.b*plight.w; + pout.m[2][0] = -Nplane.c*plight.x; + pout.m[2][1] = -Nplane.c*plight.y; + pout.m[2][2] = dot - Nplane.c*plight.z; + pout.m[2][3] = -Nplane.c*plight.w; + pout.m[3][0] = -Nplane.d*plight.x; + pout.m[3][1] = -Nplane.d*plight.y; + pout.m[3][2] = -Nplane.d*plight.z; + pout.m[3][3] = dot - Nplane.d*plight.w; + } +//+------------------------------------------------------------------+ +//| Builds a transformation matrix. | +//+------------------------------------------------------------------+ +//| This function calculates the transformation matrix with the | +//| following formula, with matrix concatenation evaluated | +//| in left-to-right order: | +//| | +//| Mout = (Msc)^(-1)*(Msr)^(-1)*Ms*Msr*Msc*(Mrc)^(-1)*Mr*Mrc*Mt | +//| | +//| where: | +//| Mout = output matrix (pOut) | +//| Msc = scaling center matrix (pScalingCenter) | +//| Msr = scaling rotation matrix (pScalingRotation) | +//| Ms = scaling matrix (pScaling) | +//| Mrc = center of rotation matrix (pRotationCenter) | +//| Mr = rotation matrix (pRotation) | +//| Mt = translation matrix (pTranslation) | +//+------------------------------------------------------------------+ +void DXMatrixTransformation(DXMatrix &pout,const DXVector3 &pscalingcenter,const DXQuaternion &pscalingrotation,const DXVector3 &pscaling,const DXVector3 &protationcenter,const DXQuaternion &protation,const DXVector3 &ptranslation) + { + DXMatrix m1,m2,m3,m4,m5,m6,m7; + DXQuaternion prc; + DXVector3 psc,pt; +//--- pscalingcenter + psc.x = pscalingcenter.x; + psc.y = pscalingcenter.y; + psc.z = pscalingcenter.z; +//--- protationcenter + prc.x = protationcenter.x; + prc.y = protationcenter.y; + prc.z = protationcenter.z; +//--- ptranslation + pt.x = ptranslation.x; + pt.y = ptranslation.y; + pt.z = ptranslation.z; + DXMatrixTranslation(m1,-psc.x,-psc.y,-psc.z); +//--- + DXQuaternion temp; + DXMatrixRotationQuaternion(m4,pscalingrotation); + temp.w = pscalingrotation.w; + temp.x = -pscalingrotation.x; + temp.y = -pscalingrotation.y; + temp.z = -pscalingrotation.z; + DXMatrixRotationQuaternion(m2,temp); +//--- pscaling + DXMatrixScaling(m3,pscaling.x,pscaling.y,pscaling.z); +//--- protation + DXMatrixRotationQuaternion(m6,protation); +//--- + DXMatrixTranslation(m5,psc.x - prc.x,psc.y - prc.y,psc.z - prc.z); + DXMatrixTranslation(m7,prc.x + pt.x,prc.y + pt.y,prc.z + pt.z); + DXMatrixMultiply(m1,m1,m2); + DXMatrixMultiply(m1,m1,m3); + DXMatrixMultiply(m1,m1,m4); + DXMatrixMultiply(m1,m1,m5); + DXMatrixMultiply(m1,m1,m6); + DXMatrixMultiply(pout,m1,m7); + } +//+------------------------------------------------------------------+ +//| Builds a 2D transformation matrix that represents | +//| transformations in the xy plane. | +//+------------------------------------------------------------------+ +//| This function calculates the transformation matrix with the | +//| following formula, with matrix concatenation evaluated | +//| in left-to-right order: | +//| | +//| Mout = (Msc)^(-1)*(Msr)^(-1)*Ms*Msr*Msc*(Mrc)^(-1)*Mr*Mrc*Mt | +//| | +//| where: | +//| Mout = output matrix (pOut) | +//| Msc = scaling center matrix (pScalingCenter) | +//| Msr = scaling rotation matrix (pScalingRotation) | +//| Ms = scaling matrix (pScaling) | +//| Mrc = center of rotation matrix (pRotationCenter) | +//| Mr = rotation matrix (Rotation) | +//| Mt = translation matrix (pTranslation) | +//+------------------------------------------------------------------+ +void DXMatrixTransformation2D(DXMatrix &pout,const DXVector2 &pscalingcenter,float scalingrotation,const DXVector2 &pscaling,const DXVector2 &protationcenter,float rotation,const DXVector2 &ptranslation) + { + DXQuaternion rot,sca_rot; + DXVector3 rot_center,sca,sca_center,trans; +//--- pscalingcenter + sca_center.x=pscalingcenter.x; + sca_center.y=pscalingcenter.y; + sca_center.z=0.0f; +//--- pscaling + sca.x=pscaling.x; + sca.y=pscaling.y; + sca.z=1.0f; +//--- protationcenter + rot_center.x=protationcenter.x; + rot_center.y=protationcenter.y; + rot_center.z=0.0f; +//--- ptranslation + trans.x=ptranslation.x; + trans.y=ptranslation.y; + trans.z=0.0f; +//--- + rot.w=(float)cos(rotation/2.0f); + rot.x=0.0f; + rot.y=0.0f; + rot.z=(float)sin(rotation/2.0f); +//--- + sca_rot.w=(float)cos(scalingrotation/2.0f); + sca_rot.x=0.0f; + sca_rot.y=0.0f; + sca_rot.z=(float)sin(scalingrotation/2.0f); + DXMatrixTransformation(pout,sca_center,sca_rot,sca,rot_center,rot,trans); + } +//+------------------------------------------------------------------+ +//| Builds a matrix using the specified offsets. | +//+------------------------------------------------------------------+ +void DXMatrixTranslation(DXMatrix &pout,float x,float y,float z) + { + DXMatrixIdentity(pout); +//--- + pout.m[3][0] = x; + pout.m[3][1] = y; + pout.m[3][2] = z; + } +//+------------------------------------------------------------------+ +//| Returns the matrix transpose of a matrix. | +//+------------------------------------------------------------------+ +void DXMatrixTranspose(DXMatrix &pout,const DXMatrix &pm) + { + for(int i=0; i<4; i++) + for(int j=0; j<4; j++) + pout.m[i][j] = pm.m[j][i]; + } +//+------------------------------------------------------------------+ +//| Computes the dot product of a plane and a 4D vector. | +//+------------------------------------------------------------------+ +float DXPlaneDot(const DXPlane &p1,const DXVector4 &p2) + { + return(p1.a*p2.x + p1.b*p2.y + p1.c*p2.z + p1.d*p2.w); + } +//+------------------------------------------------------------------+ +//| Computes the dot product of a plane and a 3D vector. | +//| The w parameter of the vector is assumed to be 1. | +//+------------------------------------------------------------------+ +float DXPlaneDotCoord(const DXPlane &pp,const DXVector4 &pv) + { + return(pp.a*pv.x + pp.b*pv.y + pp.c*pv.z + pp.d); + } +//+------------------------------------------------------------------+ +//| Computes the dot product of a plane and a 3D vector. | +//| The w parameter of the vector is assumed to be 0. | +//+------------------------------------------------------------------+ +float DXPlaneDotNormal(const DXPlane &pp,const DXVector4 &pv) + { + return(pp.a*pv.x + pp.b*pv.y + pp.c*pv.z); + } +//+------------------------------------------------------------------+ +//| Constructs a plane from a point and a normal. | +//+------------------------------------------------------------------+ +void DXPlaneFromPointNormal(DXPlane &pout,const DXVector3 &pvpoint,const DXVector3 &pvnormal) + { + pout.a = pvnormal.x; + pout.b = pvnormal.y; + pout.c = pvnormal.z; + pout.d = -DXVec3Dot(pvpoint,pvnormal); + } +//+------------------------------------------------------------------+ +//| Constructs a plane from three points. | +//+------------------------------------------------------------------+ +void DXPlaneFromPoints(DXPlane &pout,const DXVector3 &pv1,const DXVector3 &pv2,const DXVector3 &pv3) + { + DXVector3 edge1,edge2,normal,Nnormal; +//--- + edge1.x = 0.0f; + edge1.y = 0.0f; + edge1.z = 0.0f; + edge2.x = 0.0f; + edge2.y = 0.0f; + edge2.z = 0.0f; +//--- + DXVec3Subtract(edge1,pv2,pv1); + DXVec3Subtract(edge2,pv3,pv1); + DXVec3Cross(normal,edge1,edge2); + DXVec3Normalize(Nnormal,normal); + DXPlaneFromPointNormal(pout,pv1,Nnormal); + } +//+------------------------------------------------------------------+ +//| Finds the intersection between a plane and a line. | +//| If the line is parallel to the plane, null vector is returned. | +//+------------------------------------------------------------------+ +void DXPlaneIntersectLine(DXVector3 &pout,const DXPlane &pp,const DXVector3 &pv1,const DXVector3 &pv2) + { + DXVector3 direction,normal; + normal.x = pp.a; + normal.y = pp.b; + normal.z = pp.c; + direction.x = pv2.x - pv1.x; + direction.y = pv2.y - pv1.y; + direction.z = pv2.z - pv1.z; +//--- + float dot = DXVec3Dot(normal,direction); + if(!dot) + { + pout.x=0.0f; + pout.y=0.0f; + pout.z=0.0f; + } + float temp = (pp.d + DXVec3Dot(normal,pv1))/dot; + pout.x = pv1.x - temp*direction.x; + pout.y = pv1.y - temp*direction.y; + pout.z = pv1.z - temp*direction.z; + } +//+------------------------------------------------------------------+ +//| Normalizes the plane coefficients so that the plane normal | +//| has unit length. | +//| This function normalizes a plane so that |a,b,c| == 1. | +//+------------------------------------------------------------------+ +void DXPlaneNormalize(DXPlane &out,const DXPlane &p) + { + float norm = (float)sqrt(p.a*p.a + p.b*p.b + p.c*p.c); + if(norm) + { + out.a = p.a/norm; + out.b = p.b/norm; + out.c = p.c/norm; + out.d = p.d/norm; + } + else + { + out.a = 0.0f; + out.b = 0.0f; + out.c = 0.0f; + out.d = 0.0f; + } + } +//+------------------------------------------------------------------+ +//| Scale the plane with the given scaling factor. | +//+------------------------------------------------------------------+ +void DXPlaneScale(DXPlane &pout,const DXPlane &p,float s) + { + pout.a = p.a*s; + pout.b = p.b*s; + pout.c = p.c*s; + pout.d = p.d*s; + }; +//+------------------------------------------------------------------+ +//| Transforms a plane by a matrix. | +//| The input matrix is the inverse transpose of the actual | +//| transformation. | +//+------------------------------------------------------------------+ +void DXPlaneTransform(DXPlane &pout,const DXPlane &plane,const DXMatrix &pm) + { + pout.a = pm.m[0][0]*plane.a + pm.m[1][0]*plane.b + pm.m[2][0]*plane.c + pm.m[3][0]*plane.d; + pout.b = pm.m[0][1]*plane.a + pm.m[1][1]*plane.b + pm.m[2][1]*plane.c + pm.m[3][1]*plane.d; + pout.c = pm.m[0][2]*plane.a + pm.m[1][2]*plane.b + pm.m[2][2]*plane.c + pm.m[3][2]*plane.d; + pout.d = pm.m[0][3]*plane.a + pm.m[1][3]*plane.b + pm.m[2][3]*plane.c + pm.m[3][3]*plane.d; + } +//+------------------------------------------------------------------+ +//| Adds two spherical harmonic (SH) vectors; in other words, | +//| out[i] = a[i] + b[i]. | +//+------------------------------------------------------------------+ +//| Each coefficient of the basis function Y(l,m) is stored | +//| at memory location l^2 + m + l,where: | +//| l is the degree of the basis function. | +//| m is the basis function index for the given l value | +//| and ranges from -l to l, inclusive. | +//+------------------------------------------------------------------+ +void DXSHAdd(float &out[],int order,const float &a[],const float &b[]) + { + for(int i = 0; i DX_PI/2.0f) ? (DX_PI/2.0f) : radius; + float norm = (float)sin(clamped_angle)*(float)sin(clamped_angle); + if(order > DXSH_MAXORDER) + { + //--- order clamped at DXSH_MAXORDER + order = DXSH_MAXORDER; + } +//--- + weightedcapintegrale(cap,order,radius); + DXSHEvalDirection(rout,order,dir); +//--- + for(int i = 0; i DXSH_MAXORDER)) + return; + + out[0] = 0.5f/(float)sqrt(DX_PI); + out[1] = -0.5f/(float)sqrt(DX_PI/3.0f)*dir.y; + out[2] = 0.5f/(float)sqrt(DX_PI/3.0f)*dir.z; + out[3] = -0.5f/(float)sqrt(DX_PI/3.0f)*dir.x; + if(order == 2) + return; + + out[4] = 0.5f/(float)sqrt(DX_PI/15.0f)*dirxy; + out[5] = -0.5f/(float)sqrt(DX_PI/15.0f)*diryz; + out[6] = 0.25f/(float)sqrt(DX_PI/5.0f)*(3.0f*dirzz - 1.0f); + out[7] = -0.5f/(float)sqrt(DX_PI/15.0f)*dirxz; + out[8] = 0.25f/(float)sqrt(DX_PI/15.0f)*(dirxx - diryy); + if(order == 3) + return; + + out[9] = -(float)sqrt(70.0f/DX_PI)/8.0f*dir.y*(3.0f*dirxx - diryy); + out[10] = (float)sqrt(105.0f/DX_PI)/2.0f*dirxy*dir.z; + out[11] = -(float)sqrt(42.0f/DX_PI)/8.0f*dir.y*(-1.0f + 5.0f*dirzz); + out[12] = (float)sqrt(7.0f/DX_PI)/4.0f*dir.z*(5.0f*dirzz - 3.0f); + out[13] = (float)sqrt(42.0f/DX_PI)/8.0f*dir.x*(1.0f - 5.0f*dirzz); + out[14] = (float)sqrt(105.0f/DX_PI)/4.0f*dir.z*(dirxx - diryy); + out[15] = -(float)sqrt(70.0f/DX_PI)/8.0f*dir.x*(dirxx - 3.0f*diryy); + if(order == 4) + return; + + out[16] = 0.75f*float(sqrt(35.0f/DX_PI))*dirxy*(dirxx - diryy); + out[17] = 3.0f*dir.z*out[9]; + out[18] = 0.75f*(float)sqrt(5.0f/DX_PI)*dirxy*(7.0f*dirzz - 1.0f); + out[19] = 0.375f*(float)sqrt(10.0f/DX_PI)*diryz*(3.0f - 7.0f*dirzz); + out[20] = 3.0f/(16.0f*(float)sqrt(DX_PI))*(35.0f*dirzzzz - 30.f*dirzz + 3.0f); + out[21] = 0.375f*(float)sqrt(10.0f/DX_PI)*dirxz*(3.0f - 7.0f*dirzz); + out[22] = 0.375f*(float)sqrt(5.0f/DX_PI)*(dirxx - diryy)*(7.0f*dirzz - 1.0f); + out[23] = 3.0f*dir.z*out[15]; + out[24] = 3.0f / 16.0f*float(sqrt(35.0f/DX_PI))*(dirxxxx - 6.0f*dirxyxy + diryyyy); + if(order == 5) + return; + + out[25] = -3.0f/32.0f*(float)sqrt(154.0f/DX_PI)*dir.y*(5.0f*dirxxxx - 10.0f*dirxyxy + diryyyy); + out[26] = 0.75f*(float)sqrt(385.0f/DX_PI)*dirxy*dir.z*(dirxx - diryy); + out[27] = (float)sqrt(770.0f/DX_PI)/32.0f*dir.y*(3.0f*dirxx - diryy)*(1.0f - 9.0f*dirzz); + out[28] = (float)sqrt(1155.0f/DX_PI)/4.0f*dirxy*dir.z*(3.0f*dirzz - 1.0f); + out[29] = (float)sqrt(165.0f/DX_PI)/16.0f*dir.y*(14.0f*dirzz - 21.0f*dirzzzz - 1.0f); + out[30] = (float)sqrt(11.0f/DX_PI)/16.0f*dir.z*(63.0f*dirzzzz - 70.0f*dirzz + 15.0f); + out[31] = (float)sqrt(165.0f/DX_PI)/16.0f*dir.x*(14.0f*dirzz - 21.0f*dirzzzz - 1.0f); + out[32] = (float)sqrt(1155.0f/DX_PI)/8.0f*dir.z*(dirxx - diryy)*(3.0f*dirzz - 1.0f); + out[33] = (float)sqrt(770.0f/DX_PI)/32.0f*dir.x*(dirxx - 3.0f*diryy)*(1.0f - 9.0f*dirzz); + out[34] = 3.0f/16.0f*(float)sqrt(385.0f/DX_PI)*dir.z*(dirxxxx - 6.0f*dirxyxy + diryyyy); + out[35] = -3.0f/32.0f*(float)sqrt(154.0f/DX_PI)*dir.x*(dirxxxx - 10.0f*dirxyxy + 5.0f*diryyyy); + } +//+------------------------------------------------------------------+ +//| Evaluates a directional light and | +//| returns spectral spherical harmonic (SH) data. | +//+------------------------------------------------------------------+ +//| The output vector is computed so that if the intensity ratio | +//| R/G/B is equal to 1 ,the resulting exit radiance of a point | +//| directly under the light on a diffuse object with an albedo | +//| of 1 would be 1.0. This will compute three spectral samples; | +//| rout[], gout[] and bout[] will be returned. | +//+------------------------------------------------------------------+ +int DXSHEvalDirectionalLight(int order,const DXVector3 &dir,float Rintensity,float Gintensity,float Bintensity,float &rout[],float &gout[],float &bout[]) + { + float s = 0.75f; + if(order > 2) + s += 5.0f/16.0f; + if(order > 4) + s -= 3.0f/32.0f; + s /= DX_PI; + + DXSHEvalDirection(rout,order,dir); + for(int j=0; j DXSH_MAXORDER) + order = DXSH_MAXORDER; +//--- check radius + if(radius < 0.0f) + radius = -radius; + + float dist = DXVec3Length(dir); + float clamped_angle = (dist <= radius) ? DX_PI/2.0f : (float)asin(radius / dist); + + weightedcapintegrale(cap,order,clamped_angle); + DXVec3Normalize(normal,dir); + DXSHEvalDirection(rout,order,normal); + + for(int i=0; iDXSH_MAXORDER) || (order0; j--) + { + out[sum - j] = 0.0f; + out[sum - j] = c[j - 1]*in[sum - j]; + out[sum - j] += s[j - 1]*in[sum + j]; + } + out[sum] = in[sum]; + //--- + for(int j=1; j(); + if(m_buffer_object!=NULL) + { + m_buffer_object.AddRef(); + //--- use default vertex shader + m_shader_vertex=dispatcher.ShaderCreateDefault(DX_SHADER_VERTEX); + if(m_shader_vertex!=NULL) + { + m_shader_vertex.AddRef(); + //--- use default pixel shader + m_shader_pixel=dispatcher.ShaderCreateDefault(DX_SHADER_PIXEL); + if(m_shader_pixel!=NULL) + { + m_shader_pixel.AddRef(); + //--- create vertex buffer + m_buffer_vertex=dispatcher.VertexBufferCreate(vertices); + if(m_buffer_vertex!=NULL) + { + m_buffer_vertex.AddRef(); + //--- create index buffer + m_buffer_index=dispatcher.IndexBufferCreate(indices); + if(m_buffer_index!=NULL) + m_buffer_index.AddRef(); + } + } + } + } + //--- set object buffer to shaders + if(m_buffer_object==NULL || m_shader_vertex==NULL || m_shader_pixel==NULL || m_buffer_vertex==NULL || m_buffer_index==NULL) + { + Shutdown(); + return(false); + } + //--- save scene buffer + m_buffer_scene=buffer_scene; + m_buffer_scene.AddRef(); + //--- save topology + m_topology=topology; + //--- + return(true); + } + //+------------------------------------------------------------------+ + //| Create mesh object from OBJ mesh file | + //+------------------------------------------------------------------+ + bool Create(CDXDispatcher &dispatcher,CDXInput* buffer_scene,string obj_path,float scale=1.0f,bool inverse_winding=false) + { + DXVertex vertices[]; + uint indices[]; + //--- load model + if(!DXLoadObjModel(obj_path,vertices,indices,scale)) + return(false); + //--- set white vertices color + int count=ArraySize(vertices); + for(int i=0; ia}2WwW}S}xLV5jw{fL=-N@Io9yC_2BTe<9C+aV^qQ_IBnckEt z)FbESoZQ#39X&hi>r98N4pES6q+89&$$86Iy=E4mYFm0Xb@o+e6s|-%Wj03Lw5F#` z{OsX#jK&BT13YxpQM1RFxSX+9_PS#`{afac=$y{gqf1tco8xGLD=AKmcm`B8wT%k% zZt_F+V_TpfBG_j zh#5rs_!q*3L*JWGC6$IO#`E=ki z@8jHa&rH9+Ja{Ogl!H~oHnxby!wAm5u2bCL1P$~Npo}hFaL@Xqjo|U|j2Q3eA;cZ? zCd^#HzF8$>Rd9$s?w+%HLKN2+Fec%@K6lS(wTWs|c-Ce16|z!RA{@|a5nXo%PoDI% zL!O_AF(Qj?a_Ha)muTXFc8grSxq5be*A@*fr=MJC=DLXHpMMRIH-EE(hA1rZ@+Ek4L9wgHdL5gv;XDy8Z7z*WGnD>%MQnHS4~1UqPDd?xX8m*tO)IxDEG* z8@O$^;||@N+jp<=y#p!ELY_tU(!Fx;(eu!~K)*-m`KG&3Uf-9WZn|Gw7kBSt{3FQm zn>$9EBYZ1xcNL$uA=O#QGp=UeI$olcJ@+#{Z9%&IK%O@F&o|KX0c6~R7HQFryXt7; zd(i9w?(E^7xAE1|!c{F+@cb+Ea@6qoK6-wKr;lOlc#O72*?nl`9W=HNscBQ{PUcMC zqot2=hd#TD&xd#~Osw#kX|FKKmfVl9)(^Ng-J*Mdn7EJEHALt=w}4-k-NTb#c(0Fp zYk1vszq&{6E-v|f!L7R$+}%L*JScx>UysUXWsaTFCpbzv2;c3PYWR;QO+ z&=({1Ii4FLQoZ&p&WR?8M%$$iP{mdyhwvU+UvNFA$@97=5vNkM zPgif?L5I-IF+6ZD>t%mu&}zoLfi{Qm-D}2gIZuf?t53!zxy3eGyu}(jgErPn`Rpgt zOWPQPT90gf5!%`*BbKYv0DlMg{ub|+FOWqLcgQpfypt(y;W=R%!q%*g#`)ZSpL&iN zzpU^kKU1s6nG(gwy~xAJ!UT)Dz|&@!Yjv;a+dYYXV&sd+67g?4<+z7H{Y{`9eVcNO z_HK*nl)`#?=o7?IKYPA6iHmrJ8X)fIMc(H^eZ?aX*TW}z(f2qG!|}E-pB=RL6C;NG z#3K#i?=$7f7e3c>J@C^t_ZS>)6`z(M5t-Z?KCQyKv3|XN7-N!O5HEI1Y$|XsMUGJ7 z0#AyDtVif-p3c{<*1aHx)sNCv*6N(`K73{fkEdvqIm=v70+AGy#Q{1F8QWv)hD zb2Zs-ARuK+1?6@kZPJ*1Du$5si^Ca$!KpIFb4wbG(z zH}cC{$WsfTza8~jX8pT-_@xo6wjpaK;!S?F zAHSL)&bRt#eH^}%4FXj$@Waevi zAIqJO#Z!K{4y2G-5SeE(l9EZTqqfQZ80V~xhRl-t2F%dnS7G#d3T4%(l-m9cm8$X17i%L6pP0zEah3D)pI~$s94+9Bqmwq{q zdn%*co5j9qm3%oHK0_bO+gEX=IgMgU2b$c0c3Dp;@X9(b@31CCe`H*!M#I%2uicz9 ze&wKV#;wFR>RZhLYpj&H`c>#NoD1*{i3@4U_e)0dZoSa0>m`;7m zi)B0Yvh<=AA>}(^`#EGpU)YYGaFla~$}5;1%eow|wO`nd)s^4d_Sl+x?+0wBK)x(_ z467G0M%OWyGzSWOI<1Q4vbY<_v$*QcSJnjLUYQB7MO|(u>#49P%k6kNg!mWFGt^Uc zyJi@pneW{)EA-Wk+)To}?(*aw>&UmH=E+(xYbZQ<$aRjVRm3+0)p`Flp57_{LQPNe zt^GC8_=HL>7h#6(^>h~dF}5o>HOM8~MQqR%iG8FqwG?W*D|DEH0pzT{3P$lkcM#AV-M ziO-)*gA9|jy?khrTkAIvo%S5hq=>@!URR?;WQ>lpz=M6Il*`;ECuV2Vq1 zqo-`#RuX0ITG>@x32UG!%1p*@w124$H0L(9Wm8_0wrR#`>NMn65vf0(m0sdrKY!lG z)q(p6!}2@==YM%lEXT+*2?kHsru_(>6)&o7t@&EgDZ=B~^Ld)fmz6$aBKsnpn^r$D2GiTE} jN_{Y??7 zTJ(*su>bA7$Wx>ab!m*X8+3_vL#(_(Pt}!ItqpoXIrJW3_mC>|4w7`xs#71mCA4ag z>b~Sz6xk8+rBP|pbF`|EuI0#+CjLAE&TYup1d26a7*K}T$_GHUjh?2kaYk7;R^0HS|j{(kIlFNU0|fnDd#{cM0Tmb2f4oitabam)gdHV_I_7Uq^lGKWad{>yJK@wN*DL$E0aKA9Oc)mNc1urHwgh<`Vp@ot2* zYHHo&`B#w0R60EB_pFaf_=&zmcpnH&tQSFu1u?F z`!ecPw4^I^;*I8K*7tC#t(4s8or6ajtGZ~eMr^0|GrugIu9`VgU0|;!q^-xqEImU; zF5Od~%!t$LMp+~7O_EhDZYFNT&o=%rf(Km1@J!2QP%S#uA6vUL;n5CoGqEZOS>EBk zadh?X1NR|NXa8sefA&3&v&{1vo?2+@{*L;O$Ha1=09zW5Tm?6xKBe)Ie&^0f!Zd1s z$^WX8Elga~XR_oBVOZ>I<#N-FpnQIjk&{mcW!k5=@Sr2w#(A(zJM@y?;6A~@hhY&T+Q_W|s4F^MGqpt`9yI`&M@oU>wl^N_Q26IaJZ~ad@iS5>}2kj95kLE zIC(WUsW!OAka_s=l;xbU<(iWH$+g1rYYjKzu%A`wD|DJCQtFIg(b}47&py5;p6nR@ zYQwd#^k_M6?97v+;wy<8RXf*ufqKZI?q`oDHEAG-;K2X4lVH19Z~R} z{kSLh^HvAnZ3m{pmv<#og^i#n(v+IcbJIueOZooH8)Hk@~7caD6LyN_dbmlk$2 zsIh^YNqyAV2K+6Jdmd@GurJ!M2*c4mz7!_E7?b}OjS4-q3jAg7nm+J*K9Xdk0h zqP@R5)B67EX2E4Vr9ad9-*C*A8HtXM<09X0q&G4X^|wZ@&83g|KByzt;#~FD(OPgd nd!qK{(;uJ5vZ$9_FOOD~L1fHqPw_p^R-|{6D8*Q$=_}qZK!HS{ literal 0 HcmV?d00001 diff --git a/Include/Canvas/DX/DXUtils.mqh b/Include/Canvas/DX/DXUtils.mqh new file mode 100644 index 0000000..a31522e --- /dev/null +++ b/Include/Canvas/DX/DXUtils.mqh @@ -0,0 +1,942 @@ +//+------------------------------------------------------------------+ +//| DXUtils.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#property copyright "Copyright 2000-2026, MetaQuotes Ltd." +#property link "https://www.mql5.com" +//--- +#include "DXMath.mqh" +#include "DXData.mqh" +//--- data types in Wavefront OBJ file format +#define OBJ_DATA_UNKNOWN 0 +#define OBJ_DATA_V 1 +#define OBJ_DATA_VT 2 +#define OBJ_DATA_VN 3 +#define OBJ_DATA_F 4 +//+------------------------------------------------------------------+ +//| OBJFaceType | +//+------------------------------------------------------------------+ +struct OBJFaceType + { + int total; + int v[4]; + int t[4]; + int n[4]; + }; +//+------------------------------------------------------------------+ +//| Transforms right to left hand system, or backward | +//| TVertex must have | +//| DXVector4 normal and DXVector2 tcoord members | +//+------------------------------------------------------------------+ +template +void DXInverseWinding(TVertex &vertices[],uint &indices[]) + { +//--- proccess vertices + uint count=ArraySize(vertices); + for(uint i=0; i0) + faces[idx].v[i]=(int)StringToInteger(str_parsed_faces[0]); + if(elements>1) + faces[idx].t[i]=(int)StringToInteger(str_parsed_faces[1]); + if(elements>2) + faces[idx].n[i]=(int)StringToInteger(str_parsed_faces[2]); + } + //--- debug message + if(show_debug) + printf("%face d: %d vertex(%d,%d,%d,%d) texture(%d,%d,%d,%d) normal(%d,%d,%d,%d)",idx+1, + faces[idx].v[0],faces[idx].v[1],faces[idx].v[2],faces[idx].v[3], + faces[idx].t[0],faces[idx].t[1],faces[idx].t[2],faces[idx].t[3], + faces[idx].n[0],faces[idx].n[1],faces[idx].n[2],faces[idx].n[3]); + //--- + break; + } + + default: + { + break; + } + } + } +//--- close the file + FileClose(file_handle); +//--- + if(show_debug) + { + printf("File %s loaded successfully. Total lines: %d",filename,total_lines); + printf("total v_positions=%d",total_positions); + printf("total v_normals=%d",total_normals); + printf("total v_tcoords=%d",total_tcoords); + printf("total faces=%d",total_faces); + } +//--- + return(true); + } +//+------------------------------------------------------------------+ +//| Load static 3D models from Maya 2011 | +//| https://en.wikibooks.org/wiki/DirectX/10.0/Direct3D/Loading_Maya | +//| TVertex must have | +//| DXVector4 position, normal and DXVector2 tcoord members | +//+------------------------------------------------------------------+ +template +bool DXLoadObjModel(const string filename,TVertex &vertices[],uint &indices[],float scale=1.0f) + { +//--- intermediate data arrays + DXVector4 v_positions[]; + DXVector2 v_tcoords[]; + DXVector4 v_normals[]; + OBJFaceType faces[]; +//--- load data + if(!DXLoadObjData(filename,v_positions,v_tcoords,v_normals,faces,false)) + { + printf("Error loading model data from %s",filename); + return(false); + } +//--- + int total_v_positions=ArraySize(v_positions); + int total_v_tcoords =ArraySize(v_tcoords); + int total_v_normals =ArraySize(v_normals); + int total_faces =ArraySize(faces); +//--- check faces + if(total_faces==0) + { + printf("No model data."); + return(false); + } +//--- check consistency of the indices + int vertices_count=0; + int indices_count =0; + bool split_vertices=false; + for(int i=0; i4) + { + printf("Error in %d face, face vertices count is %d",i,faces[i].total); + return(false); + } + //--- + for(int j=0; jtotal_v_positions) + { + printf("Error in %d face, %d vertext index is %d. Total posiitons=%d",i,j,faces[i].v[j],total_v_positions); + return(false); + } + if(total_v_tcoords>0) + { + if(faces[i].t[j]<=0 || faces[i].t[j]>total_v_tcoords) + { + printf("Error in %d face, %d tcoord index is %d. Total tcoords=%d",i,j,faces[i].v[j],total_v_positions); + return(false); + } + if(faces[i].t[j]!=faces[i].v[j]) + split_vertices=true; + } + if(total_v_normals>0) + { + if(faces[i].n[j]<=0 || faces[i].n[j]>total_v_normals) + { + printf("Error in %d face, %d normal index is %d. Total normals=%d",i,j,faces[i].v[j],total_v_positions); + return(false); + } + if(faces[i].n[j]!=faces[i].v[j]) + split_vertices=true; + } + } + //--- calc counts + vertices_count+=faces[i].total; + if(faces[i].total<4) + indices_count+=3; + else + indices_count+=6; + } + printf("Data consistency checked."); +//--- prepare arrays + if(!split_vertices) + vertices_count=total_v_positions; + ArrayResize(vertices,vertices_count); + ArrayResize(indices, indices_count); + int v_idx=0,i_idx=0; + for(int i=0; i +bool DXComputeBox(const DXVector3 &from,const DXVector3 &to,TVertex &vertices[],uint &indices[]) + { +//--- prepare arrays + const int faces=6; + if(ArrayResize(vertices,4*faces)!=4*faces) + return(false); +//--- set indices + uint ind[]= {0,1,2, 2,3,0, 4,5,6, 6,7,4, 8,9,10, 10,11,8, 12,13,14, 14,15,12, 16,17,18, 18,19,16, 20,21,22, 22,23,20}; + ArrayResize(indices,0); + if(ArrayCopy(indices,ind)!=ArraySize(ind)) + return(false); +//--- prepare boundaries + float left=from.x,right=to.x,bottom=from.y,top=to.y,near=from.z,far=to.z; + if(from.x>to.x) + { + right=from.x; + left= to.x; + } + if(from.y>to.y) + { + top= from.y; + bottom=to.y; + } + if(from.z>to.z) + { + far=from.z; + near=to.z; + } +//--- left face + vertices[0].position=DXVector4(left,top, far, 1.0); + vertices[1].position=DXVector4(left,top, near,1.0); + vertices[2].position=DXVector4(left,bottom,near,1.0); + vertices[3].position=DXVector4(left,bottom,far, 1.0); + for(int i=0; i<4; i++) + vertices[i].normal=DXVector4(-1.0,0.0,0.0,0.0); +//--- right face + vertices[4].position=DXVector4(right,top, near,1.0); + vertices[5].position=DXVector4(right,top, far, 1.0); + vertices[6].position=DXVector4(right,bottom,far, 1.0); + vertices[7].position=DXVector4(right,bottom,near,1.0); + for(int i=4; i<8; i++) + vertices[i].normal=DXVector4(1.0,0.0,0.0,0.0); +//--- front face + vertices[8].position =DXVector4(left, top, near,1.0); + vertices[9].position =DXVector4(right,top, near,1.0); + vertices[10].position=DXVector4(right,bottom,near,1.0); + vertices[11].position=DXVector4(left, bottom,near,1.0); + for(int i=8; i<12; i++) + vertices[i].normal=DXVector4(0.0,0.0,-1.0,0.0); +//--- back face + vertices[12].position=DXVector4(right,top, far,1.0); + vertices[13].position=DXVector4(left, top, far,1.0); + vertices[14].position=DXVector4(left, bottom,far,1.0); + vertices[15].position=DXVector4(right,bottom,far,1.0); + for(int i=12; i<16; i++) + vertices[i].normal=DXVector4(0.0,0.0,1.0,0.0); +//--- top face + vertices[16].position=DXVector4(left, top,far, 1.0); + vertices[17].position=DXVector4(right,top,far, 1.0); + vertices[18].position=DXVector4(right,top,near,1.0); + vertices[19].position=DXVector4(left, top,near,1.0); + for(int i=16; i<20; i++) + vertices[i].normal=DXVector4(0.0,1.0,0.0,0.0); +//--- bottom face + vertices[20].position=DXVector4(left, bottom,near,1.0); + vertices[21].position=DXVector4(right,bottom,near,1.0); + vertices[22].position=DXVector4(right,bottom,far, 1.0); + vertices[23].position=DXVector4(left, bottom,far, 1.0); + for(int i=20; i<24; i++) + vertices[i].normal=DXVector4(0.0,-1.0,0.0,0.0); +//--- texture coordinates + for(int i=0; i +bool DXComputeSphere(float radius,uint tessellation,TVertex &vertices[],uint &indices[]) + { + if(tessellation<3) + tessellation=3; + uint segments_y =tessellation; + uint segments_xz=tessellation*2; +//--- prepare arrays + uint count=(segments_y+1)*(segments_xz+1); + if(ArrayResize(vertices,count)!=count) + return(false); + count=6*segments_y*(segments_xz); + if(ArrayResize(indices,count)!=count) + return(false); +//--- create rings of vertices at progressively higher latitudes. + for(uint i=0,idx=0; i<=segments_y; i++) + { + DXVector2 tcoord=DXVector2(0.0f,1.0f-(float)i/segments_y); + float latitude=(i*DX_PI/segments_y)-DX_PI_DIV2; + float dy =(float)sin(latitude); + float dxz=(float)cos(latitude); + //--- create a single ring of vertices at this latitude. + for(uint j=0; j<=segments_xz; j++,idx++) + { + float longitude=(j%segments_xz)*DX_PI_MUL2/segments_xz; + //--- normal + DXVector3 normal=DXVector3((float)sin(longitude)*dxz,dy,(float)cos(longitude)*dxz); + vertices[idx].normal =DXVector4(normal.x,normal.y,normal.z,0.0); + //--- position + DXVec3Scale(normal,normal,radius); + vertices[idx].position=DXVector4(normal.x,normal.y,normal.z,1.0); + //--- texture coords + tcoord.x = float(j)/segments_xz; + vertices[idx].tcoord =tcoord; + } + } +//--- fill the index buffer with triangles joining each pair of latitude rings. + uint stride=segments_xz+1; + uint idx=0; + for(uint i=0; i +bool DXComputeTorus(float outer_radius,float inner_radius,uint tessellation,TVertex &vertices[],uint &indices[]) + { + if(tessellation<3) + tessellation=3; +//--- prepare arrays + uint count=(tessellation+1)*(tessellation+1); + if(ArrayResize(vertices,count)!=count) + return(false); + count=6*tessellation*tessellation; + if(ArrayResize(indices,count)!=count) + return(false); +//--- + uint v=0,idx=0; + uint stride=tessellation+1; +//--- first we loop around the main ring of the torus. + for(uint i=0; i<=tessellation; i++) + { + DXVector2 tcoord=DXVector2(float(i)/tessellation,0.0f); + //--- create a transform matrix that will align geometry to slice perpendicularly though the current ring position. + DXMatrix rotation,transform; + DXMatrixRotationY(rotation,-1.0f*(i%tessellation)*DX_PI_MUL2/tessellation-DX_PI_DIV2); + DXMatrixTranslation(transform,outer_radius,0.0f,0.0f); + DXMatrixMultiply(transform,transform,rotation); + //--- now we loop along the other axis, around the side of the tube. + for(uint j=0; j<=tessellation; j++) + { + //--- calc normal and position + float angle=(j%tessellation)*DX_PI_MUL2/tessellation+DX_PI; + vertices[v].normal=DXVector4((float)cos(angle),(float)sin(angle),0.0f,0.0f); + vertices[v].position=DXVector4(vertices[v].normal.x*inner_radius,vertices[v].normal.y*inner_radius,0.0f,1.0f); + DXVec4Transform(vertices[v].normal, vertices[v].normal, transform); + DXVec4Transform(vertices[v].position,vertices[v].position,transform); + //--- calc texture coord + tcoord.y=1-float(j)/tessellation; + vertices[v].tcoord=tcoord; + v++; + //--- create indices for two triangles. + if(i +bool DXComputeCylinder(float radius,float height,uint tessellation,TVertex &vertices[],uint &indices[]) + { + return(DXComputeTruncatedCone(radius,radius,height,tessellation,vertices,indices)); + } +//+------------------------------------------------------------------+ +//| Truncated Cone | +//| TVertex must have | +//| DXVector4 position, DXVector4 normal and DXVector2 tcoord members| +//+------------------------------------------------------------------+ +template +bool DXComputeTruncatedCone(float radius_top,float radius_bottom,float height,uint tessellation,TVertex &vertices[],uint &indices[]) + { + if(tessellation<3) + tessellation=3; +//--- prepare arrays + uint count=2*(tessellation+1)+2*tessellation; + if(ArrayResize(vertices,count)!=count) + return(false); + count=6*tessellation+6*(tessellation-1); + if(ArrayResize(indices,count)!=count) + return(false); +//--- prepare normal + DXVector2 normal=DXVector2(height,radius_bottom-radius_top); + DXVec2Normalize(normal,normal); + float dy=height/2.0f; + uint v=0,idx=0; + uint stride=2; +//--- create top and bottom rings of vertices + for(uint i=0; i<=tessellation; i++) + { + float u=1.0f-(float)i/tessellation; + float angle=(i*DX_PI_MUL2/tessellation); + float dx=(float)sin(angle); + float dz=(float)cos(angle); + //--- + vertices[v].normal =DXVector4(dx*normal.x,normal.y,dz*normal.x,0.0f); + vertices[v].position=DXVector4(dx*radius_bottom,-dy,dz*radius_bottom,1.0f); + vertices[v].tcoord =DXVector2(u,1.0f); + v++; + vertices[v].normal =vertices[v-1].normal; + vertices[v].position=DXVector4(dx*radius_top,dy,dz*radius_top,1.0f); + vertices[v].tcoord =DXVector2(u,0.0f); + v++; + //--- creater side surface + if(i0 && i +bool DXComputeCone(float radius,float height,uint tessellation,TVertex &vertices[],uint &indices[]) + { + if(tessellation<3) + tessellation=3; +//--- prepare arrays + uint count=2*(tessellation+1)+tessellation; + if(ArrayResize(vertices,count)!=count) + return(false); + count=3*tessellation+3*(tessellation-1); + if(ArrayResize(indices,count)!=count) + return(false); +//--- prepare normal + DXVector2 normal=DXVector2(height,radius); + DXVec2Normalize(normal,normal); + float dy=height/2.0f; + uint v=0,idx=0; + uint stride=2; +//--- create top and bottom rings of vertices + for(uint i=0; i<=tessellation; i++) + { + float u=1.0f-(float)i/tessellation; + float angle=(i*DX_PI_MUL2/tessellation); + float dx=(float)sin(angle); + float dz=(float)cos(angle); + //--- + vertices[v].normal =DXVector4(dx*normal.x,normal.y,dz*normal.x,0.0f); + vertices[v].position=DXVector4(dx*radius,-dy,dz*radius,1.0f); + vertices[v].tcoord =DXVector2(u,1.0f); + v++; + vertices[v].normal =vertices[v-1].normal; + vertices[v].position=DXVector4(0.0f,dy,0.0f,1.0f); + vertices[v].tcoord =DXVector2(u,0.0f); + v++; + //--- creater side surface + if(i0 && i +bool DXComputeSurface(double &data[],uint data_width,uint data_height,double data_range, + const DXVector3 &from,const DXVector3 &to,DXVector2 &texture_size, + bool two_sided,bool use_normals, + TVertex &vertices[],uint &indices[]) + { +//--- + if(data_width<2 || data_height<2) + return(false); +//--- prepare arrays for vertices and triangles + uint count=data_width*data_height*(two_sided?2:1); + if(!ArrayResize(vertices,count)) + return(false); + count=6*(data_width-1)*(data_height-1)*(two_sided?2:1); + if(!ArrayResize(indices,count)) + return(false); +//--- find min and max value + float min_value=+FLT_MAX; + float max_value=-FLT_MAX; + for(uint j=0; jvalue) + min_value=value; + if(max_value=data_width-1) + DXVec4Subtract(v1,vertices[j*data_width+i-1].position,vertices[j*data_width+i].position); + else + DXVec4Subtract(v1,vertices[j*data_width+i-1].position,vertices[j*data_width+i+1].position); + //--- v2 + if(j<=0) + DXVec4Subtract(v2,vertices[j*data_width+i].position,vertices[(j+1)*data_width+i].position); + else + if(j>=data_height-1) + DXVec4Subtract(v2,vertices[(j-1)*data_width+i].position,vertices[j*data_width+i].position); + else + DXVec4Subtract(v2,vertices[(j-1)*data_width+i].position,vertices[(j+1)*data_width+i].position); + //--- normal + DXVec4Cross(normal,v2,v1,DXVector4(0.0f,0.0f,0.0f,1.0f)); + float inv_len=(float)(1.0/sqrt(normal.x*normal.x+normal.y*normal.y+normal.z*normal.z)); + vertices[j*data_width+i].normal.x=normal.x*inv_len; + vertices[j*data_width+i].normal.y=normal.y*inv_len; + vertices[j*data_width+i].normal.z=normal.z*inv_len; + vertices[j*data_width+i].normal.w=0.0f; + } + } + } + else + { + DXVector4 n=DXVector4(0.0f,0.0f,0.0f,0.0f); + for(int i=0; i blue > light blue > green > yellow > red > dark red | +//+---------------------------------------------------------------------+ +void DXComputeColorJet(const float value,DXColor &cout) + { + float v=value*1.1f-0.05f; + cout.r = fmin(fmax(v<0.75f ? 4*v-1.5f : 4.5f-4*v,0.0f),1.0f); + cout.g = fmin(fmax(v<0.5f ? 4*v-0.5f : 3.5f-4*v,0.0f),1.0f); + cout.b = fmin(fmax(v<0.25f ? 4*v+0.5f : 2.5f-4*v,0.0f),1.0f); + cout.a = 1.0; + } +//+---------------------------------------------------------------------+ +//| Computes hot to cold color scheme colors on [0;1] range | +//| blue > light blue > green > yellow > red | +//+---------------------------------------------------------------------+ +void DXComputeColorColdToHot(const float value,DXColor &cout) + { + float v=2*value-1.0f; + cout.r = fmin(fmax(2*v,0.0f),1.0f); + cout.g = fmin(fmax(2.0f-2*fabs(v),0.0f),1.0f); + cout.b = fmin(fmax(-2*v,0.0f),1.0f); + cout.a = 1.0; + } +//+---------------------------------------------------------------------+ +//| Computes red to green color scheme colors on [0;1] range | +//| red > yellow > dark green | +//+---------------------------------------------------------------------+ +void DXComputeColorRedToGreen(const float value,DXColor &cout) + { + if(value<=0.5) + { + cout.r=1.0f; + cout.g=DXScalarLerp(0.01f,0.95f,fmin(fmax(2*value,0.0f),1.0f)); + } + else + { + cout.r=DXScalarLerp(0.1f,1.0f, fmin(fmax(2.0f-2*value,0.0f),1.0f)); + cout.g=DXScalarLerp(0.6f,0.95f,fmin(fmax(2.0f-2*value,0.0f),1.0f)); + } + cout.b=0.0f; + cout.a=1.0f; + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/DX/Shaders/DefaultShaderPixel.hlsl b/Include/Canvas/DX/Shaders/DefaultShaderPixel.hlsl new file mode 100644 index 0000000..e3c25dc --- /dev/null +++ b/Include/Canvas/DX/Shaders/DefaultShaderPixel.hlsl @@ -0,0 +1,71 @@ +//+------------------------------------------------------------------+ +//| Default Pixel Shader | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +//+------------------------------------------------------------------+ +//| Inputs for whole scene | +//+------------------------------------------------------------------+ +cbuffer InputScene : register(b0) + { + matrix view; + matrix projection; + float4 light_direction; + float4 light_color; + float4 ambient_color; + }; +//+------------------------------------------------------------------+ +//| Inputs for single object | +//+------------------------------------------------------------------+ +cbuffer InputObject : register(b1) + { + matrix transform; + float4 diffuse_color; + float4 emission_color; + float4 specular_color; + float specular_power; + float dummy[3]; + }; +//+------------------------------------------------------------------+ +//| Input texture | +//+------------------------------------------------------------------+ +Texture2D diffuse_tex : register(t0); +//+------------------------------------------------------------------+ +//| Texture sampler | +//+------------------------------------------------------------------+ +SamplerState diffuse_samp + { + Filter =MIN_MAG_MIP_LINEAR; + AddressU=Wrap; + AddressV=Wrap; + }; +//+------------------------------------------------------------------+ +//| Pixel shader input type | +//+------------------------------------------------------------------+ +struct PSInput + { + float4 position : SV_POSITION; + float4 camera : CAMERA; + float4 normal : NORMAL; + float2 tcoord : TEXCOORD; + float4 color : COLOR; + }; +//+------------------------------------------------------------------+ +//| Pixel shader entry point | +//+------------------------------------------------------------------+ +float4 PSMain(PSInput input) : SV_TARGET + { + float3 diffuse =saturate(-dot(light_direction.xyz,input.normal.xyz))*light_color.rgb*light_color.a; + float3 ambient =ambient_color.rgb *ambient_color.a; + float3 light =(diffuse+ambient)*diffuse_color.rgb*diffuse_color.a+emission_color.rgb*emission_color.a; + float4 specular=float4(light_color.rgb*specular_color.rgb,pow(saturate(dot(reflect(normalize(light_direction.xyz),input.normal.xyz),normalize(input.camera.xyz))),specular_power)*light_color.a*specular_color.a); + float4 clr=input.color; + //--- use texture if it exist + uint width,height; + diffuse_tex.GetDimensions(width,height); + if(width*height>0) + clr*=diffuse_tex.Sample(diffuse_samp,frac(input.tcoord)); + //--- combine light with colors + return(lerp(float4(light*clr.rgb,clr.a),float4(specular.rgb,1.0),specular.a)); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/DX/Shaders/DefaultShaderVertex.hlsl b/Include/Canvas/DX/Shaders/DefaultShaderVertex.hlsl new file mode 100644 index 0000000..82349b6 --- /dev/null +++ b/Include/Canvas/DX/Shaders/DefaultShaderVertex.hlsl @@ -0,0 +1,72 @@ +//+------------------------------------------------------------------+ +//| Default Vertex Shader | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ + +//+------------------------------------------------------------------+ +//| Inputs for whole scene | +//+------------------------------------------------------------------+ +cbuffer InputScene : register(b0) + { + matrix view; + matrix projection; + float4 light_direction; + float4 light_color; + float4 ambient_color; + }; +//+------------------------------------------------------------------+ +//| Inputs for single object | +//+------------------------------------------------------------------+ +cbuffer InputObject : register(b1) + { + matrix transform; + float4 diffuse_color; + float4 emission_color; + float4 specular_color; + float specular_power; + float dummy[3]; + }; +//+------------------------------------------------------------------+ +//| Vertex shader input type | +//+------------------------------------------------------------------+ +struct VSInput + { + float4 position : POSITION; + float4 normal : NORMAL; + float2 tcoord : TEXCOORD; + float4 color : COLOR; + }; +//+------------------------------------------------------------------+ +//| Pixel shader input type | +//+------------------------------------------------------------------+ +struct PSInput + { + float4 position : SV_POSITION; + float4 camera : CAMERA; + float4 normal : NORMAL; + float2 tcoord : TEXCOORD; + float4 color : COLOR; + }; +//+------------------------------------------------------------------+ +//| Vertex shader entry point | +//+------------------------------------------------------------------+ +PSInput VSMain(VSInput input) + { + PSInput output; + //--- posiiton and camera direction + output.position=mul(input .position,transform); + output.position=mul(output.position,view); + output.camera =-output.position; + output.position=mul(output.position,projection); + //--- transform normals + output.normal = mul(input.normal, transform); + output.normal = mul(output.normal, view); + output.normal = normalize(output.normal); + //--- color and texture coordinates + output.tcoord =input.tcoord; + output.color =input.color; +//--- + return(output); + } +//+------------------------------------------------------------------+ diff --git a/Include/Canvas/FlameCanvas.mqh b/Include/Canvas/FlameCanvas.mqh new file mode 100644 index 0000000..92ae0fe --- /dev/null +++ b/Include/Canvas/FlameCanvas.mqh @@ -0,0 +1,751 @@ +//+------------------------------------------------------------------+ +//| FlameCanvas.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "Canvas.mqh" +#include +//+------------------------------------------------------------------+ +//| Gradient descriptors | +//+------------------------------------------------------------------+ +struct GRADIENT_COLOR + { + uint clr; // color in ARGB format + uint pos; // position of color in percentage of gradient range + }; +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +struct GRADIENT_SIZE + { + uint size; // width of gradient fill in percentage of base fill + uint pos; // position of color in percentage of gradient length + }; +//+------------------------------------------------------------------+ +//| Class CFlameCanvas | +//| Usage: generates flame | +//+------------------------------------------------------------------+ +class CFlameCanvas : public CCanvas + { +private: + //--- parameters + uint m_bar_gap; + uint m_bar_width; + uint m_chart_scale; + double m_chart_price_min; + double m_chart_price_max; + ENUM_TIMEFRAMES m_timeframe; + string m_symbol; + int m_future_bars; + int m_back_bars; + int m_rates_total; + uint m_palette[256]; // flame palette + uchar m_flame[]; // buffer for calculation of flame + uint m_time_redraw; + uint m_delay; + // bool m_resize_flag; + // int m_tick_cnt; + //--- flame parameters + datetime m_tb1; + double m_pb1; + datetime m_te1; + double m_pe1; + datetime m_tb2; + double m_pb2; + datetime m_te2; + double m_pe2; + //--- equation parameters for flame + int m_cloud_axis[100]; + double m_a1; + double m_b1; + double m_a2; + double m_b2; + int m_xb1; + int m_yb1; + int m_xe1; + int m_ye1; + int m_xb2; + int m_yb2; + int m_xe2; + int m_ye2; + +public: + CFlameCanvas(void); + ~CFlameCanvas(void); + //--- create + bool FlameCreate(const string name,const datetime time,const int future_bars,const int back_bars=0); + void RatesTotal(const int value); + //--- setting + void PaletteSet(uint clr=0xFF0000); + //--- draw + void FlameDraw(const double &prices[],const int width,const int lenght); + void FlameSet(datetime xb1,double yb1,datetime xe1,double ye1,datetime xb2,double yb2,datetime xe2,double ye2); + //--- event handler + void ChartEventHandler(const int id,const long &lparam,const double &dparam,const string &sparam); + +protected: + bool Resize(void); + void ChartScale(void); + void FlameSet(void); + void CloudDraw(const double &prices[],const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[],const uchar t_level=255,const bool custom_gradient=true); + void FlameDraw(const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[]); + void GradientVertical(const int xb,const int xe,const int yb1,const int ye1,const int yb2,const int ye2,const GRADIENT_COLOR &gradient[]); + void GradientVerticalLine(int x,int y1,int y2,const GRADIENT_COLOR &gradient[]); + void GradientVerticalLineMonochrome(int x,int y1,int y2,uint clr1,uint clr2); + void FlameCreate(void); + void FlameCalculate(void); + void Delay(const uint value); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CFlameCanvas::CFlameCanvas(void) : m_bar_gap(16), + m_bar_width(8), + m_chart_scale(1), + m_chart_price_min(0.0), + m_chart_price_max(0.0), + m_timeframe(PERIOD_CURRENT), + m_symbol(NULL), + m_future_bars(0), + m_back_bars(0), + m_rates_total(0), + m_time_redraw(0), + m_delay(50), + m_tb1(0), + m_pb1(0), + m_te1(0), + m_pe1(0), + m_tb2(0), + m_pb2(0), + m_te2(0), + m_pe2(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CFlameCanvas::~CFlameCanvas(void) + { + Destroy(); + } +//+------------------------------------------------------------------+ +//| Creates dynamic resource with object | +//+------------------------------------------------------------------+ +bool CFlameCanvas::FlameCreate(const string name,const datetime time,const int future_bars,const int back_bars) + { +//--- get chart parameters + ChartScale(); +//--- create + int width =(int)m_bar_gap*(future_bars+back_bars); + int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS); + if(!CreateBitmap(0,0,name,time-back_bars*PeriodSeconds(),m_chart_price_max,width,height,COLOR_FORMAT_ARGB_NORMALIZE)) + return(false); + ArrayResize(m_flame,width*height); +//--- save parameters + m_future_bars=future_bars; + m_back_bars =back_bars; +//--- settings + PaletteSet(); + m_timeframe =Period(); + m_symbol =Symbol(); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Resize | +//+------------------------------------------------------------------+ +bool CFlameCanvas::Resize(void) + { + int x,y; +//--- get limits + double min=ChartGetDouble(0,CHART_PRICE_MIN); + double max=ChartGetDouble(0,CHART_PRICE_MAX); + if(m_chart_price_max!=max) + { + //--- move object + ObjectSetDouble(0,m_objname,OBJPROP_PRICE,0,max); + } +//--- check + if(m_chart_price_min==min && m_chart_price_max==max) + return(false); + m_chart_price_min=min; + m_chart_price_max=max; +//--- grt size + ChartTimePriceToXY(0,0,m_tb1,min,x,y); + int width =(int)ChartGetInteger(0,CHART_WIDTH_IN_PIXELS)-x; + int height=(int)ChartGetInteger(0,CHART_HEIGHT_IN_PIXELS); +//--- resize + if(width80) g+=dg; + if(i>160) b+=db; + } + } +//+------------------------------------------------------------------+ +//| Draws the flame | +//+------------------------------------------------------------------+ +void CFlameCanvas::FlameDraw(const double &prices[],const int width,const int lenght) + { + static GRADIENT_SIZE sword[]={{100,0},{150,70},{0,100}}; + static GRADIENT_COLOR flame[]={{0x00,0},{0x7F7F7F,12},{0xCCCCCC,30},{0xFFFFFF,45},{0xFFFFFF,55},{0xCCCCCC,70},{0x7F7F7F,88},{0x00,100}}; +//--- draw + CloudDraw(prices,width,lenght,sword,flame); +//--- copy flame buffer + FlameCalculate(); +//--- start timer + EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +void CFlameCanvas::FlameDraw(const int width,const int lenght,GRADIENT_SIZE &size[],GRADIENT_COLOR &gradient[]) + { +//--- check + int total=ArraySize(m_cloud_axis); + if(total<2) + return; + if(total>lenght) + total=lenght; +//--- draw + int xb,xe; // coordinates of the segment + int ybm,yem; // coordinates of the center line + int yb1,ye1; // coordinates of the first line + int yb2,ye2; // coordinates of the second line +//--- for implementation of variable width + int w_total=ArraySize(size); + if(w_total<2) + return; + int w_i =0; + int w_is=(int)size[w_i].pos*total/100; + int w_ie=(int)size[w_i+1].pos*total/100; + double w =size[w_i].size*width/100; + double dw =(size[w_i+1].size*width/100-w)/(w_ie-w_is); +//--- draw from left to right + xb=0; + ybm=m_cloud_axis[0]; + yb1=ybm-(int)(w/2); + yb2=ybm+(int)(w/2); +//--- draw + for(int i=1;i=m_width) + break; + yb1=ye1; + yb2=ye2; + while(i>=w_ie-1 && i!=total-1) + { + w_i++; + w_is=(int)size[w_i].pos*total/100; + w_ie=(int)size[w_i+1].pos*total/100; + w =size[w_i].size*width/100; + if(w_ie==w_is) + { + //--- for "instant" resize + dw=size[w_i+1].size*width/100-w; + w+=dw; + ye1=yem-(int)(w/2); + ye2=yem+(int)(w/2); + //--- draw the segment of 'cloud' + GradientVertical(xb,xe,yb1,ye1,yb2,ye2,gradient); + yb1=ye1; + yb2=ye2; + } + else + { + dw=(size[w_i+1].size*width/100-w)/(w_ie-w_is); + break; + } + } + } +//--- copy flame buffer + FlameCalculate(); + } +//+------------------------------------------------------------------+ +//| Sets parameters of the flame and starts to draw | +//+------------------------------------------------------------------+ +void CFlameCanvas::FlameSet(void) + { + m_a1=m_bar_gap*((m_ye1-m_yb1)/((double)m_xe1-m_xb1)); + m_a2=m_bar_gap*((m_ye2-m_yb2)/((double)m_xe2-m_xb2)); + } +//+------------------------------------------------------------------+ +//| Sets parameters of the flame and starts to draw | +//+------------------------------------------------------------------+ +void CFlameCanvas::FlameSet(datetime tb1,double pb1, + datetime te1,double pe1, + datetime tb2,double pb2, + datetime te2,double pe2) + { + datetime obj_time =(datetime)ObjectGetInteger(0,m_objname,OBJPROP_TIME); + double obj_price=ObjectGetDouble(0,m_objname,OBJPROP_PRICE); + int dx,dy; +//--- save parameters + m_tb1=tb1; + m_pb1=pb1; + m_te1=te1; + m_pe1=pe1; + m_tb2=tb2; + m_pb2=pb2; + m_te2=te2; + m_pe2=pe2; +//--- resize + Resize(); +//--- convert + if(ChartTimePriceToXY(0,0,obj_time,obj_price,dx,dy)) + { + dy=m_yb1; + if(ChartTimePriceToXY(0,0,tb1,pb1,m_xb1,m_yb1)) + if(ChartTimePriceToXY(0,0,te1,pe1,m_xe1,m_ye1)) + if(ChartTimePriceToXY(0,0,tb2,pb2,m_xb2,m_yb2)) + if(ChartTimePriceToXY(0,0,te2,pe2,m_xe2,m_ye2)) + { + //--- convert to canvas coordinates + m_xb1-=dx; + m_xe1-=dx; + m_xb2-=dx; + m_xe2-=dx; + //--- + FlameSet(); + } + } +//--- start timer + EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); + } +//+------------------------------------------------------------------+ +//| Generate array that describes the body of flame | +//+------------------------------------------------------------------+ +void CFlameCanvas::FlameCreate(void) + { + static GRADIENT_SIZE sword[]={{100,0},{150,70},{0,100}}; + static GRADIENT_COLOR flame[]={{0x00,0},{0x7F7F7F,12},{0xCCCCCC,30},{0xFFFFFF,45},{0xFFFFFF,55},{0xCCCCCC,70},{0x7F7F7F,88},{0x00,100}}; +//--- + double a=rand(); // parameter of line a*x+b + double b=rand(); // parameter of line a*x+b + double c=rand(); // parameter of sine c*Sin(d*x) + double d=rand(); // parameter of sine c*Sin(d*x) + int w=rand(); // width at the base + int l=rand(); // length +//--- normalize + a=fmod(a,(m_a2-m_a1))+m_a1; + b=(m_yb1+m_yb2)/2; + c=fmod(c,20); + d=fmod(d,3*M_PI)+M_PI; +//--- shape + w%=150; + if(w<10) + w=10; // but no less than 10 + sword[1].size=w; + w=rand(); + l%=50; + sword[1].pos=l+30; + l=rand(); +//--- sizes + w=(m_yb2-m_yb1!=0) ? w%(m_yb2-m_yb1) : 10; // proportional to the starting width + if(w<10) + w=10; // but no less than 10 + l=l%((m_xe1-m_xb1)/(int)m_bar_gap-20)+20; // proportional to length +//--- create + int total=ArraySize(m_cloud_axis); + for(int i=0;ilenght) + total=lenght; +//--- draw + int xb,xe; // coordinates of the segment + int ybm,yem; // coordinates of the center line + int yb1,ye1; // coordinates of the first line + int yb2,ye2; // coordinates of the second line + int xx; +//--- for implementation of variable width + int w_total=ArraySize(size); + if(w_total<2) + return; + int w_i =0; + int w_is=(int)size[w_i].pos*total/100; + int w_ie=(int)size[w_i+1].pos*total/100; + double w =size[w_i].size*width/100; + double dw =(size[w_i+1].size*width/100-w)/(w_ie-w_is); +//--- draw from left to right + xb=0; + ChartTimePriceToXY(0,0,0,prices[0],xx,ybm); + yb1=ybm-(int)(w/2); + yb2=ybm+(int)(w/2); +//--- draw + for(int i=1;i=m_width) + break; + yb1=ye1; + yb2=ye2; + while(i>=w_ie-1 && i!=total-1) + { + w_i++; + w_is=(int)size[w_i].pos*total/100; + w_ie=(int)size[w_i+1].pos*total/100; + w =size[w_i].size*width/100; + if(w_ie==w_is) + { + //--- for "instant" resize + dw=size[w_i+1].size*width/100-w; + w+=dw; + ye1=yem-(int)(w/2); + ye2=yem+(int)(w/2); + //--- draw the segment of 'cloud' + GradientVertical(xb,xe,yb1,ye1,yb2,ye2,gradient); + yb1=ye1; + yb2=ye2; + } + else + { + dw=(size[w_i+1].size*width/100-w)/(w_ie-w_is); + break; + } + } + } + } +//+------------------------------------------------------------------+ +//| Draws area with vertical fill using specified gradient | +//+------------------------------------------------------------------+ +void CFlameCanvas::GradientVertical(const int xb,const int xe,const int yb1,const int ye1,const int yb2,const int ye2,const GRADIENT_COLOR &gradient[]) + { +//--- it is assumed that the colors array has sufficient size and positions are already sorted in ascending order +//--- get length by X and Y + int x1 =xb; + int y1 =yb1; + int x2 =xb; + int y2 =yb2; + int dx =(xe>xb)? xe-xb : xb-xe; + int dy1=(ye1>yb1)? ye1-yb1 : yb1-ye1; + int dy2=(ye2>yb2)? ye2-yb2 : yb2-ye2; +//--- get direction by X and Y + int sx =(xb-dy1) + { + //--- try to change X coordinate of the first line + //--- draw the second line + while(x2!=xe || y2!=ye2) + { + //--- calculate coordinates of next pixel of the second line + if((er2<<1)>-dy2) + { + //--- try to change X coordinate of the second line + //--- gradient fill + GradientVerticalLine(x1,y1,y2,gradient); + er2-=dy2; + if(x2!=xe) + x2+=sx; + } + if((er2<<1)0) + { + dd=dy; + dy=1; + } + else + { + dd=-dy; + dy=-1; + } +//--- increments for the color components + dc=(double)((uchar)clr2-clr)/dd; +//--- draw + for(int i=0;y1!=y2;i++,y1+=dy) + { + int idx=y1*m_width+x; + //--- check range + if(idx<0 || idx>=ArraySize(m_flame)) + continue; + if(x>=0 && x=0 && y1m_time_redraw) + { + //--- add the body of flame + FlameCreate(); + //--- draw frame + FlameCalculate(); + Update(); + //--- calculate time for the next frame + m_time_redraw=GetTickCount()+m_delay; + } + //--- generate next event for custom timer + EventChartCustom(CONTROLS_SELF_MESSAGE,1302,0,0,NULL); + break; + } + } +//+------------------------------------------------------------------+ +//| Delay | +//+------------------------------------------------------------------+ +void CFlameCanvas::Delay(const uint value) + { +//--- too small + if(value<10) + return; +//--- start delay + uint cnt=GetTickCount()+value; +//--- delay + while(cnt>=GetTickCount()); + } +//+------------------------------------------------------------------+ diff --git a/Include/ChartObjects/ChartObject.mqh b/Include/ChartObjects/ChartObject.mqh new file mode 100644 index 0000000..f926bb3 --- /dev/null +++ b/Include/ChartObjects/ChartObject.mqh @@ -0,0 +1,1000 @@ +//+------------------------------------------------------------------+ +//| ChartObject.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include +//+------------------------------------------------------------------+ +//| Class CChartObject. | +//| Pupose: Base class of chart objects. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CChartObject : public CObject + { +protected: + long m_chart_id; // identifier of chart the object belongs to + int m_window; // number of subwindow (0 - main window) + string m_name; // unique name object name + int m_num_points; // number of anchor points of object + +public: + CChartObject(void); + ~CChartObject(void); + //--- method of identifying the object + virtual int Type(void) const { return(0x8888); } + //--- methods of access to protected data + long ChartId(void) const { return(m_chart_id); } + int Window(void) const { return(m_window); } + string Name(void) const { return(m_name); } + bool Name(const string name); + int NumPoints(void) const { return(m_num_points); } + //--- methods of filling the object + bool Attach(long chart_id,const string name,const int window,const int points); + bool SetPoint(const int point,const datetime time,const double price) const; + //--- methods of deleting + bool Delete(void); + void Detach(void); + //--- methods of access to properties of the object + datetime Time(const int point) const; + bool Time(const int point,const datetime time) const; + double Price(const int point) const; + bool Price(const int point,const double price) const; + color Color(void) const; + bool Color(const color new_color) const; + ENUM_LINE_STYLE Style(void) const; + bool Style(const ENUM_LINE_STYLE new_style) const; + int Width(void) const; + bool Width(const int new_width) const; + bool Background(void) const; + bool Background(const bool new_back) const; + bool Fill(void) const; + bool Fill(const bool new_fill) const; + long Z_Order(void) const; + bool Z_Order(const long value) const; + bool Selected(void) const; + bool Selected(const bool new_sel) const; + bool Selectable(void) const; + bool Selectable(const bool new_sel) const; + string Description(void) const; + bool Description(const string new_text) const; + string Tooltip(void) const; + bool Tooltip(const string new_text) const; + int Timeframes(void) const; + virtual bool Timeframes(const int timeframes) const; + datetime CreateTime(void) const; + int LevelsCount(void) const; + bool LevelsCount(const int new_count) const; + //--- methods to access the properties of levels of objects + color LevelColor(const int level) const; + bool LevelColor(const int level,const color new_color) const; + ENUM_LINE_STYLE LevelStyle(const int level) const; + bool LevelStyle(const int level,const ENUM_LINE_STYLE new_style) const; + int LevelWidth(const int level) const; + bool LevelWidth(const int level,const int new_width) const; + double LevelValue(const int level) const; + bool LevelValue(const int level,const double new_value) const; + string LevelDescription(const int level) const; + bool LevelDescription(const int level,const string new_text) const; + //--- access methods to the API functions of MQL5 + long GetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier=-1) const; + bool GetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier,long &value) const; + bool SetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier,const long value) const; + bool SetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const long value) const; + double GetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier=-1) const; + bool GetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier,double &value) const; + bool SetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier,const double value) const; + bool SetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const double value) const; + string GetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier=-1) const; + bool GetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier,string &value) const; + bool SetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier,const string value) const; + bool SetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const string value) const; + //--- methods of moving + bool ShiftObject(const datetime d_time,const double d_price) const; + bool ShiftPoint(const int point,const datetime d_time,const double d_price) const; + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CChartObject::CChartObject(void) + { +//--- initialize protected data + Detach(); + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CChartObject::~CChartObject(void) + { + if(m_chart_id!=-1) + ObjectDelete(m_chart_id,m_name); + } +//+------------------------------------------------------------------+ +//| Changing name of the object | +//+------------------------------------------------------------------+ +bool CChartObject::Name(const string name) + { +//--- check + if(m_chart_id==-1) + return(false); +//--- change + if(ObjectSetString(m_chart_id,m_name,OBJPROP_NAME,name)) + { + m_name=name; + return(true); + } +//--- failure + return(false); + }; +//+------------------------------------------------------------------+ +//| Attach object | +//+------------------------------------------------------------------+ +bool CChartObject::Attach(long chart_id,const string name,const int window,const int points) + { +//--- check + if(ObjectFind(chart_id,name)<0) + return(false); +//--- attach + if(chart_id==0) + chart_id=ChartID(); + m_chart_id =chart_id; + m_window =window; + m_name =name; + m_num_points=points; +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Setting new coordinates of anchor point of an object | +//+------------------------------------------------------------------+ +bool CChartObject::SetPoint(const int point,const datetime time,const double price) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(point>=m_num_points) + return(false); +//--- result + return(ObjectMove(m_chart_id,m_name,point,time,price)); + } +//+------------------------------------------------------------------+ +//| Delete an object | +//+------------------------------------------------------------------+ +bool CChartObject::Delete(void) + { +//--- checki + if(m_chart_id==-1) + return(false); +//--- actions + bool result=ObjectDelete(m_chart_id,m_name); + Detach(); +//--- result + return(result); + } +//+------------------------------------------------------------------+ +//| Detach object | +//+------------------------------------------------------------------+ +void CChartObject::Detach(void) + { + m_chart_id =-1; + m_window =-1; + m_name =NULL; + m_num_points=0; + } +//+------------------------------------------------------------------+ +//| Get the time coordinate of the specified anchor point of object | +//+------------------------------------------------------------------+ +datetime CChartObject::Time(const int point) const + { +//--- check + if(m_chart_id==-1) + return(0); + if(point>=m_num_points) + return(0); +//--- result + return((datetime)ObjectGetInteger(m_chart_id,m_name,OBJPROP_TIME,point)); + } +//+------------------------------------------------------------------+ +//| Set the time coordinate of the specified anchor point of object | +//+------------------------------------------------------------------+ +bool CChartObject::Time(const int point,const datetime time) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(point>=m_num_points) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_TIME,point,time)); + } +//+------------------------------------------------------------------+ +//| Get the price coordinate of the specified anchor point of object.| +//+------------------------------------------------------------------+ +double CChartObject::Price(const int point) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); + if(point>=m_num_points) + return(EMPTY_VALUE); +//--- result + return(ObjectGetDouble(m_chart_id,m_name,OBJPROP_PRICE,point)); + } +//+------------------------------------------------------------------+ +//| Set the price coordinate of the specified anchor point of object.| +//+------------------------------------------------------------------+ +bool CChartObject::Price(const int point,const double price) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(point>=m_num_points) + return(false); +//--- result + return(ObjectSetDouble(m_chart_id,m_name,OBJPROP_PRICE,point,price)); + } +//+------------------------------------------------------------------+ +//| Get object color | +//+------------------------------------------------------------------+ +color CChartObject::Color(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ObjectGetInteger(m_chart_id,m_name,OBJPROP_COLOR)); + } +//+------------------------------------------------------------------+ +//| Set object color | +//+------------------------------------------------------------------+ +bool CChartObject::Color(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_COLOR,new_color)); + } +//+------------------------------------------------------------------+ +//| Get style of line of object | +//+------------------------------------------------------------------+ +ENUM_LINE_STYLE CChartObject::Style(void) const + { +//--- check + if(m_chart_id==-1) + return(WRONG_VALUE); +//--- result + return((ENUM_LINE_STYLE)ObjectGetInteger(m_chart_id,m_name,OBJPROP_STYLE)); + } +//+------------------------------------------------------------------+ +//| Set style of line of object | +//+------------------------------------------------------------------+ +bool CChartObject::Style(const ENUM_LINE_STYLE new_style) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_STYLE,new_style)); + } +//+------------------------------------------------------------------+ +//| Get width of line of object | +//+------------------------------------------------------------------+ +int CChartObject::Width(void) const + { +//--- check + if(m_chart_id==-1) + return(-1); +//--- result + return((int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_WIDTH)); + } +//+------------------------------------------------------------------+ +//| Set width of line of object | +//+------------------------------------------------------------------+ +bool CChartObject::Width(const int new_width) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_WIDTH,new_width)); + } +//+------------------------------------------------------------------+ +//| Get the "Draw object as background" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Background(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ObjectGetInteger(m_chart_id,m_name,OBJPROP_BACK)); + } +//+------------------------------------------------------------------+ +//| Set the "Draw object as background" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Background(const bool new_back) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_BACK,new_back)); + } +//+------------------------------------------------------------------+ +//| Get the "Filling" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Fill(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ObjectGetInteger(m_chart_id,m_name,OBJPROP_FILL)); + } +//+------------------------------------------------------------------+ +//| Set the "Filling" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Fill(const bool new_fill) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_FILL,new_fill)); + } +//+------------------------------------------------------------------+ +//| Get the "Z-order" property | +//+------------------------------------------------------------------+ +long CChartObject::Z_Order(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ObjectGetInteger(m_chart_id,m_name,OBJPROP_ZORDER)); + } +//+------------------------------------------------------------------+ +//| Set the "Z-order" property | +//+------------------------------------------------------------------+ +bool CChartObject::Z_Order(const long value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_ZORDER,value)); + } +//+------------------------------------------------------------------+ +//| Get the "selected" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Selected(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ObjectGetInteger(m_chart_id,m_name,OBJPROP_SELECTED)); + } +//+------------------------------------------------------------------+ +//| Set the "selected" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Selected(const bool new_sel) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_SELECTED,new_sel)); + } +//+------------------------------------------------------------------+ +//| Get the "selectable" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Selectable(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ObjectGetInteger(m_chart_id,m_name,OBJPROP_SELECTABLE)); + } +//+------------------------------------------------------------------+ +//| Set flag the "selectable" flag | +//+------------------------------------------------------------------+ +bool CChartObject::Selectable(const bool new_sel) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_SELECTABLE,new_sel)); + } +//+------------------------------------------------------------------+ +//| Get comment of object | +//+------------------------------------------------------------------+ +string CChartObject::Description(void) const + { +//--- check + if(m_chart_id==-1) + return(""); +//--- result + return(ObjectGetString(m_chart_id,m_name,OBJPROP_TEXT)); + } +//+------------------------------------------------------------------+ +//| Set comment of object | +//+------------------------------------------------------------------+ +bool CChartObject::Description(const string new_text) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- tune + if(new_text=="") + return(ObjectSetString(m_chart_id,m_name,OBJPROP_TEXT," ")); +//--- result + return(ObjectSetString(m_chart_id,m_name,OBJPROP_TEXT,new_text)); + } +//+------------------------------------------------------------------+ +//| Get tooltip of object | +//+------------------------------------------------------------------+ +string CChartObject::Tooltip(void) const + { +//--- check + if(m_chart_id==-1) + return(""); +//--- result + return(ObjectGetString(m_chart_id,m_name,OBJPROP_TOOLTIP)); + } +//+------------------------------------------------------------------+ +//| Set tooltip of object | +//+------------------------------------------------------------------+ +bool CChartObject::Tooltip(const string new_text) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- tune + if(new_text=="") + return(ObjectSetString(m_chart_id,m_name,OBJPROP_TOOLTIP," ")); +//--- result + return(ObjectSetString(m_chart_id,m_name,OBJPROP_TOOLTIP,new_text)); + } +//+------------------------------------------------------------------+ +//| Get the "Timeframes" (visibility) flag | +//+------------------------------------------------------------------+ +int CChartObject::Timeframes(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_TIMEFRAMES)); + } +//+------------------------------------------------------------------+ +//| Set the "Timeframes" (visibility) flag | +//+------------------------------------------------------------------+ +bool CChartObject::Timeframes(const int timeframes) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_TIMEFRAMES,timeframes)); + } +//+------------------------------------------------------------------+ +//| Get time of object creation | +//+------------------------------------------------------------------+ +datetime CChartObject::CreateTime(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((datetime)ObjectGetInteger(m_chart_id,m_name,OBJPROP_CREATETIME)); + } +//+------------------------------------------------------------------+ +//| Get number of levels of object | +//+------------------------------------------------------------------+ +int CChartObject::LevelsCount(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_LEVELS)); + } +//+------------------------------------------------------------------+ +//| Set number of levels of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelsCount(const int new_count) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_LEVELS,new_count)); + } +//+------------------------------------------------------------------+ +//| Get color of the specified level of object | +//+------------------------------------------------------------------+ +color CChartObject::LevelColor(const int level) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); + if(level>=LevelsCount()) + return(clrNONE); +//--- result + return((color)ObjectGetInteger(m_chart_id,m_name,OBJPROP_LEVELCOLOR,level)); + } +//+------------------------------------------------------------------+ +//| Set color of the specified level of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelColor(const int level,const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(level>=LevelsCount()) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_LEVELCOLOR,level,new_color)); + } +//+------------------------------------------------------------------+ +//| Get line style of the specified level of object | +//+------------------------------------------------------------------+ +ENUM_LINE_STYLE CChartObject::LevelStyle(const int level) const + { +//--- check + if(m_chart_id==-1) + return(WRONG_VALUE); + if(level>=LevelsCount()) + return(WRONG_VALUE); +//--- result + return((ENUM_LINE_STYLE)ObjectGetInteger(m_chart_id,m_name,OBJPROP_LEVELSTYLE,level)); + } +//+------------------------------------------------------------------+ +//| Set line style of the specified level of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelStyle(const int level,const ENUM_LINE_STYLE new_style) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(level>=LevelsCount()) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_LEVELSTYLE,level,new_style)); + } +//+------------------------------------------------------------------+ +//| Get line width of the specified level of object | +//+------------------------------------------------------------------+ +int CChartObject::LevelWidth(const int level) const + { +//--- check + if(m_chart_id==-1) + return(-1); + if(level>=LevelsCount()) + return(-1); +//--- result + return((int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_LEVELWIDTH,level)); + } +//+------------------------------------------------------------------+ +//| Set line width of the specified level of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelWidth(const int level,const int new_width) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(level>=LevelsCount()) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,OBJPROP_LEVELWIDTH,level,new_width)); + } +//+------------------------------------------------------------------+ +//| Get value of the specified level of object | +//+------------------------------------------------------------------+ +double CChartObject::LevelValue(const int level) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); + if(level>=LevelsCount()) + return(EMPTY_VALUE); +//--- result + return(ObjectGetDouble(m_chart_id,m_name,OBJPROP_LEVELVALUE,level)); + } +//+------------------------------------------------------------------+ +//| Set value of the specified level of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelValue(const int level,const double new_value) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(level>=LevelsCount()) + return(false); +//--- result + return(ObjectSetDouble(m_chart_id,m_name,OBJPROP_LEVELVALUE,level,new_value)); + } +//+------------------------------------------------------------------+ +//| Get comment of of the specified level of object | +//+------------------------------------------------------------------+ +string CChartObject::LevelDescription(const int level) const + { +//--- check + if(m_chart_id==-1) + return(""); + if(level>=LevelsCount()) + return(""); +//--- result + return(ObjectGetString(m_chart_id,m_name,OBJPROP_LEVELTEXT,level)); + } +//+------------------------------------------------------------------+ +//| Set comment to the specified level of object | +//+------------------------------------------------------------------+ +bool CChartObject::LevelDescription(const int level,const string new_text) const + { +//--- checking + if(m_chart_id==-1) + return(false); + if(level>=LevelsCount()) + return(false); +//--- result + return(ObjectSetString(m_chart_id,m_name,OBJPROP_LEVELTEXT,level,new_text)); + } +//+------------------------------------------------------------------+ +//| Access function long ObjectGetInteger(...) | +//+------------------------------------------------------------------+ +long CChartObject::GetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- + if(modifier==-1) + return(ObjectGetInteger(m_chart_id,m_name,prop_id)); +//--- result + return(ObjectGetInteger(m_chart_id,m_name,prop_id,modifier)); + } +//+------------------------------------------------------------------+ +//| Access function bool ObjectGetInteger(...) | +//+------------------------------------------------------------------+ +bool CChartObject::GetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier,long &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectGetInteger(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetInteger(.,modifier,.) | +//+------------------------------------------------------------------+ +bool CChartObject::SetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const int modifier,const long value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetInteger(...) | +//+------------------------------------------------------------------+ +bool CChartObject::SetInteger(const ENUM_OBJECT_PROPERTY_INTEGER prop_id,const long value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetInteger(m_chart_id,m_name,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function double ObjectGetDouble(...) | +//+------------------------------------------------------------------+ +double CChartObject::GetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- + if(modifier==-1) + return(ObjectGetDouble(m_chart_id,m_name,prop_id)); +//--- result + return(ObjectGetDouble(m_chart_id,m_name,prop_id,modifier)); + } +//+------------------------------------------------------------------+ +//| Access function bool ObjectGetDouble(...) | +//+------------------------------------------------------------------+ +bool CChartObject::GetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier,double &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectGetDouble(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetDouble(.,modifier,.) | +//+------------------------------------------------------------------+ +bool CChartObject::SetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const int modifier,const double value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetDouble(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetDouble(...) | +//+------------------------------------------------------------------+ +bool CChartObject::SetDouble(const ENUM_OBJECT_PROPERTY_DOUBLE prop_id,const double value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetDouble(m_chart_id,m_name,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function string ObjectGetString (...) | +//+------------------------------------------------------------------+ +string CChartObject::GetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier) const + { +//--- check + if(m_chart_id==-1) + return(""); +//--- + if(modifier==-1) + return(ObjectGetString(m_chart_id,m_name,prop_id)); +//--- result + return(ObjectGetString(m_chart_id,m_name,prop_id,modifier)); + } +//+------------------------------------------------------------------+ +//| Access function bool ObjectGetString(...) | +//+------------------------------------------------------------------+ +bool CChartObject::GetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier,string &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectGetString(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetString(.,modifier,.) | +//+------------------------------------------------------------------+ +bool CChartObject::SetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const int modifier,const string value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetString(m_chart_id,m_name,prop_id,modifier,value)); + } +//+------------------------------------------------------------------+ +//| Access function ObjectSetString(...) | +//+------------------------------------------------------------------+ +bool CChartObject::SetString(const ENUM_OBJECT_PROPERTY_STRING prop_id,const string value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ObjectSetString(m_chart_id,m_name,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Relative movement of object | +//+------------------------------------------------------------------+ +bool CChartObject::ShiftObject(const datetime d_time,const double d_price) const + { + bool result=true; + int i; +//--- check + if(m_chart_id==-1) + return(false); +//--- move + for(i=0;i=m_num_points) + return(false); +//--- move + datetime time=(datetime)ObjectGetInteger(m_chart_id,m_name,OBJPROP_TIME,point); + double price=ObjectGetDouble(m_chart_id,m_name,OBJPROP_PRICE,point); +//--- result + return(ObjectMove(m_chart_id,m_name,point,time+d_time,price+d_price)); + } +//+------------------------------------------------------------------+ +//| Writing object parameters to file | +//+------------------------------------------------------------------+ +bool CChartObject::Save(const int file_handle) + { + int i,len; + int levels; + string str; +//--- check + if(file_handle==INVALID_HANDLE || m_chart_id==-1) + return(false); +//--- write start marker - 0xFFFFFFFFFFFFFFFF + if(FileWriteLong(file_handle,-1)!=sizeof(long)) + return(false); +//--- write object type + if(FileWriteInteger(file_handle,Type(),INT_VALUE)!=INT_VALUE) + return(false); +//--- write object name + str=ObjectGetString(m_chart_id,m_name,OBJPROP_NAME); + len=StringLen(str); + if(FileWriteInteger(file_handle,len,INT_VALUE)!=INT_VALUE) + return(false); + if(len!=0) if(FileWriteString(file_handle,str,len)!=len) + return(false); +//--- write object color + if(FileWriteLong(file_handle,ObjectGetInteger(m_chart_id,m_name,OBJPROP_COLOR))!=sizeof(long)) + return(false); +//--- write object line style + if(FileWriteInteger(file_handle,(int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_STYLE))!=sizeof(int)) + return(false); +//--- write object line width + if(FileWriteInteger(file_handle,(int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_WIDTH))!=sizeof(int)) + return(false); +//--- write the property value "Background" + if(FileWriteInteger(file_handle,(int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_BACK),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write the property value "Selectable" + if(FileWriteInteger(file_handle,(int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_SELECTABLE),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write the property value "Timeframes" + if(FileWriteInteger(file_handle,(int)ObjectGetInteger(m_chart_id,m_name,OBJPROP_TIMEFRAMES),INT_VALUE)!=sizeof(int)) + return(false); +//--- write comment + str=ObjectGetString(m_chart_id,m_name,OBJPROP_TEXT); + len=StringLen(str); + if(FileWriteInteger(file_handle,len,INT_VALUE)!=INT_VALUE) + return(false); + if(len!=0) if(FileWriteString(file_handle,str,len)!=len) + return(false); +//--- write number of points + if(FileWriteInteger(file_handle,m_num_points,INT_VALUE)!=INT_VALUE) + return(false); +//--- write points + for(i=0;i +#include +#include +//+------------------------------------------------------------------+ +//| Class CChartObjectPanel. | +//| Purpose: Class for grouping objects for managing a chart | +//+------------------------------------------------------------------+ +class CChartObjectPanel : public CChartObjectButton + { +protected: + CArrayObj m_attachment; // array of attached objects + CArrayInt m_dX; // array of dX attached objects + CArrayInt m_dY; // array of dY attached objects + bool m_expanded; // collapsed/expanded flag + +public: + CChartObjectPanel(); + ~CChartObjectPanel(); + //--- method for attaching objects + bool Attach(CChartObjectLabel *chart_object); + bool X_Distance(const int X); + int X_Distance(void) const { return(CChartObjectButton::X_Distance()); } + bool Y_Distance(const int Y); + int Y_Distance(void) const { return(CChartObjectButton::Y_Distance()); } + int X_Size() const; + int X_Size(const int Y) const { return(CChartObjectButton::X_Size()); } + int Y_Size() const; + int Y_Size(const int Y) const { return(CChartObjectButton::Y_Size()); } + + int Timeframes(void) const { return(CChartObjectButton::Timeframes()); } + virtual bool Timeframes(const int timeframes); + bool State(const bool state); + bool State(void) const { return(CChartObjectButton::State()); } + bool CheckState(); + +protected: + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +void CChartObjectPanel::CChartObjectPanel(void) : m_expanded(true) + { + } +//+------------------------------------------------------------------+ +//| Destructor. | +//+------------------------------------------------------------------+ +void CChartObjectPanel::~CChartObjectPanel(void) + { +//--- All objects added by the method Add(), deleted automatically + } +//+------------------------------------------------------------------+ +//| Method Attach | +//+------------------------------------------------------------------+ +bool CChartObjectPanel::Attach(CChartObjectLabel *chart_object) + { + if(m_attachment.Add(chart_object)) + { + int x,y; + x=chart_object.X_Distance(); + m_dX.Add(chart_object.X_Distance()); + x+=X_Distance(); + chart_object.X_Distance(X_Distance()+chart_object.X_Distance()); + y=CChartObjectButton::Y_Size(); + y+=chart_object.Y_Distance(); + m_dY.Add(chart_object.Y_Distance()+CChartObjectButton::Y_Size()+2); + chart_object.Y_Distance(Y_Distance()+chart_object.Y_Distance()+CChartObjectButton::Y_Size()+2); + return(true); + } +//--- + return(false); + } +//+------------------------------------------------------------------+ +//| Method X_Distance | +//+------------------------------------------------------------------+ +bool CChartObjectPanel::X_Distance(const int X) + { + CChartObjectLabel *chart_object; +//--- + for(int i=0;i +//+------------------------------------------------------------------+ +//| Class CChart. | +//| Purpose: Class of the "Chart" object. | +//| Derives from class CObject. | +//+------------------------------------------------------------------+ +class CChart : public CObject + { +protected: + long m_chart_id; // chart identifier +public: + CChart(void); + ~CChart(void); + //--- methods of access to protected data + long ChartId(void) const { return(m_chart_id); } + //--- method of identifying the object + virtual int Type(void) const { return(0x1111); } + //--- methods of access to properties of the chart + //--- common properties + ENUM_CHART_MODE Mode(void) const; + bool Mode(const ENUM_CHART_MODE mode) const; + bool Foreground(void) const; + bool Foreground(const bool foreground) const; + bool Shift(void) const; + bool Shift(const bool shift) const; + double ShiftSize(void) const; + bool ShiftSize(double shift) const; + bool AutoScroll(void) const; + bool AutoScroll(const bool auto_scroll) const; + int Scale(void) const; + bool Scale(int scale) const; + bool ScaleFix(void) const; + bool ScaleFix(const bool scale_fix) const; + bool ScaleFix_11(void) const; + bool ScaleFix_11(const bool scale_fix_11) const; + double FixedMax(void) const; + bool FixedMax(const double fixed_max) const; + double FixedMin(void) const; + bool FixedMin(const double fixed_min) const; + bool ScalePPB(void) const; + bool ScalePPB(const bool scale_ppb) const; + double PointsPerBar(void) const; + bool PointsPerBar(const double points_per_bar) const; + //--- show properties + bool ShowOHLC(void) const; + bool ShowOHLC(const bool show) const; + bool ShowLineBid(void) const; + bool ShowLineBid(const bool show) const; + bool ShowLineAsk(void) const; + bool ShowLineAsk(const bool show) const; + bool ShowLastLine(void) const; + bool ShowLastLine(const bool show) const; + bool ShowPeriodSep(void) const; + bool ShowPeriodSep(const bool show) const; + bool ShowGrid(void) const; + bool ShowGrid(const bool show) const; + ENUM_CHART_VOLUME_MODE ShowVolumes(void) const; + bool ShowVolumes(const ENUM_CHART_VOLUME_MODE show) const; + bool ShowObjectDescr(void) const; + bool ShowObjectDescr(const bool show) const; + bool ShowDateScale(const bool show) const; + bool ShowPriceScale(const bool show) const; + //--- color properties + color ColorBackground(void) const; + bool ColorBackground(const color new_color) const; + color ColorForeground(void) const; + bool ColorForeground(const color new_color) const; + color ColorGrid(void) const; + bool ColorGrid(const color new_color) const; + color ColorBarUp(void) const; + bool ColorBarUp(const color new_color) const; + color ColorBarDown(void) const; + bool ColorBarDown(const color new_color) const; + color ColorCandleBull(void) const; + bool ColorCandleBull(const color new_color) const; + color ColorCandleBear(void) const; + bool ColorCandleBear(const color new_color) const; + color ColorChartLine(void) const; + bool ColorChartLine(const color new_color) const; + color ColorVolumes(void) const; + bool ColorVolumes(const color new_color) const; + color ColorLineBid(void) const; + bool ColorLineBid(const color new_color) const; + color ColorLineAsk(void) const; + bool ColorLineAsk(const color new_color) const; + color ColorLineLast(void) const; + bool ColorLineLast(const color new_color) const; + color ColorStopLevels(void) const; + bool ColorStopLevels(const color new_color) const; + //--- other properties + bool BringToTop(void) const; + bool EventObjectCreate(const bool flag=true) const; + bool EventObjectDelete(const bool flag=true) const; + bool EventMouseMove(const bool flag=true) const; + bool MouseScroll(const bool flag=true) const; + //--- methods of access to READ ONLY properties of the chart + int VisibleBars(void) const; + int WindowsTotal(void) const; + bool WindowIsVisible(const int num) const; + int WindowHandle(void) const; + int FirstVisibleBar(void) const; + int WidthInBars(void) const; + int WidthInPixels(void) const; + int HeightInPixels(const int num) const; + int SubwindowY(const int num) const; + double PriceMin(const int num) const; + double PriceMax(const int num) const; + bool IsObject(void) const; + //--- methods of binding chart + void Attach(void) { m_chart_id=ChartID(); } + void Attach(const long chart) { m_chart_id=chart; } + void FirstChart(void) { m_chart_id=ChartFirst(); } + void NextChart(void) { m_chart_id=ChartNext(m_chart_id); } + long Open(const string symbol_name,const ENUM_TIMEFRAMES timeframe); + void Detach(void) { m_chart_id=-1; } + void Close(void); + //--- navigation method + bool Navigate(const ENUM_CHART_POSITION position,const int shift=0) const; + //--- methods of access to the API functions of MQL5 + string Symbol(void) const { return(ChartSymbol(m_chart_id)); } + ENUM_TIMEFRAMES Period(void) const { return(ChartPeriod(m_chart_id)); } + void Redraw(void) const { ChartRedraw(m_chart_id); } + long GetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const int sub_window=0) const; + bool GetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const int sub_window,long &value) const; + bool SetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const long value) const; + double GetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const int sub_window=0) const; + bool GetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const int sub_window,double &value) const; + bool SetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const double value) const; + string GetString(const ENUM_CHART_PROPERTY_STRING prop_id) const; + bool GetString(const ENUM_CHART_PROPERTY_STRING prop_id,string &value) const; + bool SetString(const ENUM_CHART_PROPERTY_STRING prop_id,const string value) const; + bool SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES period) const; + bool ApplyTemplate(const string filename) const; + bool ScreenShot(const string filename,const int width,const int height, + const ENUM_ALIGN_MODE align_mode=ALIGN_RIGHT) const; + int WindowOnDropped(void) const; + double PriceOnDropped(void) const; + datetime TimeOnDropped(void) const; + int XOnDropped(void) const; + int YOnDropped(void) const; + //--- methods for working with indicators + bool IndicatorAdd(const int subwin,const int handle) const; + bool IndicatorDelete(const int subwin,const string name) const; + int IndicatorsTotal(const int subwin) const; + string IndicatorName(const int subwin,const int index) const; + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CChart::CChart(void) : m_chart_id(-1) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CChart::~CChart(void) + { + if(m_chart_id!=-1) + Close(); + } +//+------------------------------------------------------------------+ +//| Opening chart | +//+------------------------------------------------------------------+ +long CChart::Open(const string symbol_name,const ENUM_TIMEFRAMES timeframe) + { + m_chart_id=ChartOpen(symbol_name,timeframe); + if(m_chart_id==0) + m_chart_id=-1; + return(m_chart_id); + } +//+------------------------------------------------------------------+ +//| Get the type of representation of chart | +//+------------------------------------------------------------------+ +ENUM_CHART_MODE CChart::Mode(void) const + { +//--- check + if(m_chart_id==-1) + return(WRONG_VALUE); +//--- result + return((ENUM_CHART_MODE)ChartGetInteger(m_chart_id,CHART_MODE)); + } +//+------------------------------------------------------------------+ +//| Set the type of representation chart | +//+------------------------------------------------------------------+ +bool CChart::Mode(const ENUM_CHART_MODE mode) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_MODE,mode)); + } +//+------------------------------------------------------------------+ +//| Get value of the "Foreground" property | +//+------------------------------------------------------------------+ +bool CChart::Foreground(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_FOREGROUND)); + } +//+------------------------------------------------------------------+ +//| Set value of the "Foreground" property | +//+------------------------------------------------------------------+ +bool CChart::Foreground(const bool foreground) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_FOREGROUND,foreground)); + } +//+------------------------------------------------------------------+ +//| Get value of the "Shift" property | +//+------------------------------------------------------------------+ +bool CChart::Shift(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHIFT)); + } +//+------------------------------------------------------------------+ +//| Set value of the "Shift"property | +//+------------------------------------------------------------------+ +bool CChart::Shift(const bool shift) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHIFT,shift)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShiftSize" property | +//+------------------------------------------------------------------+ +double CChart::ShiftSize(void) const + { +//--- check + if(m_chart_id==-1) + return(DBL_MAX); +//--- result + return(ChartGetDouble(m_chart_id,CHART_SHIFT_SIZE)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShiftSize" property | +//+------------------------------------------------------------------+ +bool CChart::ShiftSize(double shift) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(shift<10) + shift=10; + if(shift>50) + shift=50; +//--- result + return(ChartSetDouble(m_chart_id,CHART_SHIFT_SIZE,shift)); + } +//+------------------------------------------------------------------+ +//| Get value of the "AutoScroll" property | +//+------------------------------------------------------------------+ +bool CChart::AutoScroll(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_AUTOSCROLL)); + } +//+------------------------------------------------------------------+ +//| Set value of the "AutoScroll" property | +//+------------------------------------------------------------------+ +bool CChart::AutoScroll(const bool auto_scroll) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,auto_scroll)); + } +//+------------------------------------------------------------------+ +//| Get value of the "Scale" property | +//+------------------------------------------------------------------+ +int CChart::Scale(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_SCALE)); + } +//+------------------------------------------------------------------+ +//| Set value of the "Scale" property | +//+------------------------------------------------------------------+ +bool CChart::Scale(int shift) const + { +//--- check + if(m_chart_id==-1) + return(false); + if(shift<0) + shift=0; + if(shift>32) + shift=32; +//--- result + return(ChartSetInteger(m_chart_id,CHART_SCALE,shift)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ScaleFix" property | +//+------------------------------------------------------------------+ +bool CChart::ScaleFix(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SCALEFIX)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ScaleFix" property | +//+------------------------------------------------------------------+ +bool CChart::ScaleFix(const bool scale_fix) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SCALEFIX,scale_fix)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ScaleFix_11" property | +//+------------------------------------------------------------------+ +bool CChart::ScaleFix_11(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SCALEFIX_11)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ScaleFix_11" property | +//+------------------------------------------------------------------+ +bool CChart::ScaleFix_11(const bool scale_fix_11) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,scale_fix_11)); + } +//+------------------------------------------------------------------+ +//| Get value of the "FixedMax" property | +//+------------------------------------------------------------------+ +double CChart::FixedMax(void) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,CHART_FIXED_MAX)); + } +//+------------------------------------------------------------------+ +//| Set value of the "FixedMax" property | +//+------------------------------------------------------------------+ +bool CChart::FixedMax(const double fixed_max) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetDouble(m_chart_id,CHART_FIXED_MAX,fixed_max)); + } +//+------------------------------------------------------------------+ +//| Get value of the "FixedMin" property | +//+------------------------------------------------------------------+ +double CChart::FixedMin(void) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,CHART_FIXED_MIN)); + } +//+------------------------------------------------------------------+ +//| Set value of the "FixedMin" property | +//+------------------------------------------------------------------+ +bool CChart::FixedMin(const double fixed_min) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetDouble(m_chart_id,CHART_FIXED_MIN,fixed_min)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ScalePointsPerBar" property | +//+------------------------------------------------------------------+ +bool CChart::ScalePPB(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SCALE_PT_PER_BAR)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ScalePointsPerBar" property | +//+------------------------------------------------------------------+ +bool CChart::ScalePPB(const bool scale_ppb) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SCALE_PT_PER_BAR,scale_ppb)); + } +//+------------------------------------------------------------------+ +//| Get value of the "PointsPerBar" property | +//+------------------------------------------------------------------+ +double CChart::PointsPerBar(void) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,CHART_POINTS_PER_BAR)); + } +//+------------------------------------------------------------------+ +//| Set value of the "PointsPerBar" property | +//+------------------------------------------------------------------+ +bool CChart::PointsPerBar(const double points_per_bar) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetDouble(m_chart_id,CHART_POINTS_PER_BAR,points_per_bar)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowOHLC" property | +//+------------------------------------------------------------------+ +bool CChart::ShowOHLC(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_OHLC)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowOHLC" property | +//+------------------------------------------------------------------+ +bool CChart::ShowOHLC(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowLineBid" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLineBid(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_BID_LINE)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowLineBid" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLineBid(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowLineAsk" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLineAsk(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_ASK_LINE)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowLineAsk" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLineAsk(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowLastLine" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLastLine(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_LAST_LINE)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowLastLine" property | +//+------------------------------------------------------------------+ +bool CChart::ShowLastLine(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowPeriodSep" property | +//+------------------------------------------------------------------+ +bool CChart::ShowPeriodSep(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowPeriodSep" property | +//+------------------------------------------------------------------+ +bool CChart::ShowPeriodSep(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowGrid" property | +//+------------------------------------------------------------------+ +bool CChart::ShowGrid(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_GRID)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowGrid" property | +//+------------------------------------------------------------------+ +bool CChart::ShowGrid(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_GRID,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowVolumes" property | +//+------------------------------------------------------------------+ +ENUM_CHART_VOLUME_MODE CChart::ShowVolumes(void) const + { +//--- check + if(m_chart_id==-1) + return(WRONG_VALUE); +//--- result + return((ENUM_CHART_VOLUME_MODE)ChartGetInteger(m_chart_id,CHART_SHOW_VOLUMES)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowVolumes" property | +//+------------------------------------------------------------------+ +bool CChart::ShowVolumes(const ENUM_CHART_VOLUME_MODE show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,show)); + } +//+------------------------------------------------------------------+ +//| Get value of the "ShowObjectDescr" property | +//+------------------------------------------------------------------+ +bool CChart::ShowObjectDescr(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowObjectDescr" property | +//+------------------------------------------------------------------+ +bool CChart::ShowObjectDescr(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,show)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowDateScale" property | +//+------------------------------------------------------------------+ +bool CChart::ShowDateScale(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_DATE_SCALE,show)); + } +//+------------------------------------------------------------------+ +//| Set value of the "ShowPriceScale" property | +//+------------------------------------------------------------------+ +bool CChart::ShowPriceScale(const bool show) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_SHOW_PRICE_SCALE,show)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Background" property | +//+------------------------------------------------------------------+ +color CChart::ColorBackground(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_BACKGROUND)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Background" property | +//+------------------------------------------------------------------+ +bool CChart::ColorBackground(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_BACKGROUND,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Foreground" property | +//+------------------------------------------------------------------+ +color CChart::ColorForeground(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_FOREGROUND)); + } +//+------------------------------------------------------------------+ +//| Set color value for the "Foreground" property | +//+------------------------------------------------------------------+ +bool CChart::ColorForeground(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_FOREGROUND,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Grid" property | +//+------------------------------------------------------------------+ +color CChart::ColorGrid(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_GRID)); + } +//+------------------------------------------------------------------+ +//| Set color value for the "Grid" property | +//+------------------------------------------------------------------+ +bool CChart::ColorGrid(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_GRID,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Bar Up" property | +//+------------------------------------------------------------------+ +color CChart::ColorBarUp(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_CHART_UP)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Bar Up" property | +//+------------------------------------------------------------------+ +bool CChart::ColorBarUp(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_CHART_UP,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Bar Down" property | +//+------------------------------------------------------------------+ +color CChart::ColorBarDown(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_CHART_DOWN)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Bar Down" property | +//+------------------------------------------------------------------+ +bool CChart::ColorBarDown(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_CHART_DOWN,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Candle Bull" property | +//+------------------------------------------------------------------+ +color CChart::ColorCandleBull(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_CANDLE_BULL)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Candle Bull" property | +//+------------------------------------------------------------------+ +bool CChart::ColorCandleBull(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_CANDLE_BULL,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Candle Bear" property | +//+------------------------------------------------------------------+ +color CChart::ColorCandleBear(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_CANDLE_BEAR)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Candle Bear" property | +//+------------------------------------------------------------------+ +bool CChart::ColorCandleBear(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_CANDLE_BEAR,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Chart Line" property | +//+------------------------------------------------------------------+ +color CChart::ColorChartLine(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_CHART_LINE)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Chart Line" property | +//+------------------------------------------------------------------+ +bool CChart::ColorChartLine(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_CHART_LINE,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Volumes" property | +//+------------------------------------------------------------------+ +color CChart::ColorVolumes(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_VOLUME)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Volumes" property | +//+------------------------------------------------------------------+ +bool CChart::ColorVolumes(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_VOLUME,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Line Bid" property | +//+------------------------------------------------------------------+ +color CChart::ColorLineBid(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_BID)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Line Bid" property | +//+------------------------------------------------------------------+ +bool CChart::ColorLineBid(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_BID,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Line Ask" property | +//+------------------------------------------------------------------+ +color CChart::ColorLineAsk(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_ASK)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Line Ask" property | +//+------------------------------------------------------------------+ +bool CChart::ColorLineAsk(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_ASK,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Line Last" property | +//+------------------------------------------------------------------+ +color CChart::ColorLineLast(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_LAST)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Line Last" property | +//+------------------------------------------------------------------+ +bool CChart::ColorLineLast(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_LAST,new_color)); + } +//+------------------------------------------------------------------+ +//| Get color value of the "Stop Levels" property | +//+------------------------------------------------------------------+ +color CChart::ColorStopLevels(void) const + { +//--- check + if(m_chart_id==-1) + return(clrNONE); +//--- result + return((color)ChartGetInteger(m_chart_id,CHART_COLOR_STOP_LEVEL)); + } +//+------------------------------------------------------------------+ +//| Set color value of the "Stop Levels" property | +//+------------------------------------------------------------------+ +bool CChart::ColorStopLevels(const color new_color) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_COLOR_STOP_LEVEL,new_color)); + } +//+------------------------------------------------------------------+ +//| Shows chart always on top | +//+------------------------------------------------------------------+ +bool CChart::BringToTop(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_BRING_TO_TOP,true)); + } +//+------------------------------------------------------------------+ +//| Sets flag to generate event of creating objects | +//+------------------------------------------------------------------+ +bool CChart::EventObjectCreate(const bool flag) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_EVENT_OBJECT_CREATE,flag)); + } +//+------------------------------------------------------------------+ +//| Sets flag to generate event of deleting objects | +//+------------------------------------------------------------------+ +bool CChart::EventObjectDelete(const bool flag) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_EVENT_OBJECT_DELETE,flag)); + } +//+------------------------------------------------------------------+ +//| Sets flag to generate event of moving mouse cursor | +//+------------------------------------------------------------------+ +bool CChart::EventMouseMove(const bool flag) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_EVENT_MOUSE_MOVE,flag)); + } +//+------------------------------------------------------------------+ +//| Sets flag to mouse scrolling | +//+------------------------------------------------------------------+ +bool CChart::MouseScroll(const bool flag) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetInteger(m_chart_id,CHART_MOUSE_SCROLL,flag)); + } +//+------------------------------------------------------------------+ +//| Get value of the "VisibleBars" property | +//+------------------------------------------------------------------+ +int CChart::VisibleBars(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WIDTH_IN_BARS)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WindowsTotal" property | +//+------------------------------------------------------------------+ +int CChart::WindowsTotal(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WINDOWS_TOTAL)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WindowIsVisible" property | +//+------------------------------------------------------------------+ +bool CChart::WindowIsVisible(const int num) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_WINDOW_IS_VISIBLE,num)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WindowHandle" property | +//+------------------------------------------------------------------+ +int CChart::WindowHandle(void) const + { +//--- check + if(m_chart_id==-1) + return(INVALID_HANDLE); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WINDOW_HANDLE)); + } +//+------------------------------------------------------------------+ +//| Get value of the "FirstVisibleBar" property | +//+------------------------------------------------------------------+ +int CChart::FirstVisibleBar(void) const + { +//--- check + if(m_chart_id==-1) + return(-1); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_FIRST_VISIBLE_BAR)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WidthInBars" property | +//+------------------------------------------------------------------+ +int CChart::WidthInBars(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WIDTH_IN_BARS)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WidthInPixels" property | +//+------------------------------------------------------------------+ +int CChart::WidthInPixels(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WIDTH_IN_PIXELS)); + } +//+------------------------------------------------------------------+ +//| Get value of the "HeightInPixels" property | +//+------------------------------------------------------------------+ +int CChart::HeightInPixels(const int num) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_HEIGHT_IN_PIXELS,num)); + } +//+------------------------------------------------------------------+ +//| Get value of the "WindowYDistance" property | +//+------------------------------------------------------------------+ +int CChart::SubwindowY(const int num) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return((int)ChartGetInteger(m_chart_id,CHART_WINDOW_YDISTANCE,num)); + } +//+------------------------------------------------------------------+ +//| Get value of the "PriceMin" property | +//+------------------------------------------------------------------+ +double CChart::PriceMin(const int num) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,CHART_PRICE_MIN,num)); + } +//+------------------------------------------------------------------+ +//| Get value of the "PriceMax" property | +//+------------------------------------------------------------------+ +double CChart::PriceMax(const int num) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,CHART_PRICE_MAX,num)); + } +//+------------------------------------------------------------------+ +//| Get value of the "IsObject" property | +//+------------------------------------------------------------------+ +bool CChart::IsObject(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return((bool)ChartGetInteger(m_chart_id,CHART_IS_OBJECT)); + } +//+------------------------------------------------------------------+ +//| Chart close | +//+------------------------------------------------------------------+ +void CChart::Close(void) + { + if(m_chart_id!=-1) + { + ChartClose(m_chart_id); + m_chart_id=-1; + } + } +//+------------------------------------------------------------------+ +//| Chart navigation | +//+------------------------------------------------------------------+ +bool CChart::Navigate(const ENUM_CHART_POSITION position,const int shift) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartNavigate(m_chart_id,position,shift)); + } +//+------------------------------------------------------------------+ +//| Access functions long ChartGetInteger(...) | +//+------------------------------------------------------------------+ +long CChart::GetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const int subwindow) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ChartGetInteger(m_chart_id,prop_id,subwindow)); + } +//+------------------------------------------------------------------+ +//| Access function bool ChartGetInteger(...) | +//+------------------------------------------------------------------+ +bool CChart::GetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const int subwindow,long &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartGetInteger(m_chart_id,prop_id,subwindow,value)); + } +//+------------------------------------------------------------------+ +//| Access function ChartSetInteger(...) | +//+------------------------------------------------------------------+ +bool CChart::SetInteger(const ENUM_CHART_PROPERTY_INTEGER prop_id,const long value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- + return(ChartSetInteger(m_chart_id,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function double ChartGetDouble(...) | +//+------------------------------------------------------------------+ +double CChart::GetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const int subwindow) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartGetDouble(m_chart_id,prop_id,subwindow)); + } +//+------------------------------------------------------------------+ +//| Access function bool ChartGetDouble(...) | +//+------------------------------------------------------------------+ +bool CChart::GetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const int subwindow,double &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartGetDouble(m_chart_id,prop_id,subwindow,value)); + } +//+------------------------------------------------------------------+ +//| Access function ChartSetDouble(...) | +//+------------------------------------------------------------------+ +bool CChart::SetDouble(const ENUM_CHART_PROPERTY_DOUBLE prop_id,const double value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetDouble(m_chart_id,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function string ChartGetString(...) | +//+------------------------------------------------------------------+ +string CChart::GetString(const ENUM_CHART_PROPERTY_STRING prop_id) const + { +//--- check + if(m_chart_id==-1) + return(""); +//--- result + return(ChartGetString(m_chart_id,prop_id)); + } +//+------------------------------------------------------------------+ +//| Access functions bool ChartGetString(...) | +//+------------------------------------------------------------------+ +bool CChart::GetString(const ENUM_CHART_PROPERTY_STRING prop_id,string &value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartGetString(m_chart_id,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function ChartSetString(...) | +//+------------------------------------------------------------------+ +bool CChart::SetString(const ENUM_CHART_PROPERTY_STRING prop_id,const string value) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetString(m_chart_id,prop_id,value)); + } +//+------------------------------------------------------------------+ +//| Access function ChartSetSymbolPeriod(...) | +//+------------------------------------------------------------------+ +bool CChart::SetSymbolPeriod(const string symbol,const ENUM_TIMEFRAMES period) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartSetSymbolPeriod(m_chart_id,symbol,period)); + } +//+------------------------------------------------------------------+ +//| Access function ChartApplyTemplate(...) | +//+------------------------------------------------------------------+ +bool CChart::ApplyTemplate(const string filename) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartApplyTemplate(m_chart_id,filename)); + } +//+------------------------------------------------------------------+ +//| Access function ChartScreenShot(...) | +//+------------------------------------------------------------------+ +bool CChart::ScreenShot(const string filename,const int width,const int height,const ENUM_ALIGN_MODE align_mode) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartScreenShot(m_chart_id,filename,width,height,align_mode)); + } +//+------------------------------------------------------------------+ +//| Access function WindowOnDropped() | +//+------------------------------------------------------------------+ +int CChart::WindowOnDropped(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ChartWindowOnDropped()); + } +//+------------------------------------------------------------------+ +//| Access function PriceOnDropped() | +//+------------------------------------------------------------------+ +double CChart::PriceOnDropped(void) const + { +//--- check + if(m_chart_id==-1) + return(EMPTY_VALUE); +//--- result + return(ChartPriceOnDropped()); + } +//+------------------------------------------------------------------+ +//| Access function TimeOnDropped() | +//+------------------------------------------------------------------+ +datetime CChart::TimeOnDropped(void) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartTimeOnDropped()); + } +//+------------------------------------------------------------------+ +//| Access functions XOnDropped() | +//+------------------------------------------------------------------+ +int CChart::XOnDropped(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ChartXOnDropped()); + } +//+------------------------------------------------------------------+ +//| Access functions YOnDropped() | +//+------------------------------------------------------------------+ +int CChart::YOnDropped(void) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ChartYOnDropped()); + } +//+------------------------------------------------------------------+ +//| Adds indicator to chart | +//+------------------------------------------------------------------+ +bool CChart::IndicatorAdd(const int subwin,const int handle) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartIndicatorAdd(m_chart_id,subwin,handle)); + } +//+------------------------------------------------------------------+ +//| Deletes indicator from chart | +//+------------------------------------------------------------------+ +bool CChart::IndicatorDelete(const int subwin,const string name) const + { +//--- check + if(m_chart_id==-1) + return(false); +//--- result + return(ChartIndicatorDelete(m_chart_id,subwin,name)); + } +//+------------------------------------------------------------------+ +//| Gets number of indicators in chart subwindow | +//+------------------------------------------------------------------+ +int CChart::IndicatorsTotal(const int subwin) const + { +//--- check + if(m_chart_id==-1) + return(0); +//--- result + return(ChartIndicatorsTotal(m_chart_id,subwin)); + } +//+------------------------------------------------------------------+ +//| Gets short name of indicator | +//+------------------------------------------------------------------+ +string CChart::IndicatorName(const int subwin,const int index) const + { +//--- check + if(m_chart_id==-1) + return(""); +//--- result + return(ChartIndicatorName(m_chart_id,subwin,index)); + } +//+------------------------------------------------------------------+ +//| Writing parameters of chart to file | +//+------------------------------------------------------------------+ +bool CChart::Save(const int file_handle) + { + string work_str; + int work_int; +//--- check + if(file_handle==INVALID_HANDLE || m_chart_id==-1) + return(false); +//--- write start marker - 0xFFFFFFFFFFFFFFFF + if(FileWriteLong(file_handle,-1)!=sizeof(long)) + return(false); +//--- write chart type + if(FileWriteInteger(file_handle,Type(),INT_VALUE)!=INT_VALUE) + return(false); +//--- write chart symbol + work_str=Symbol(); + work_int=StringLen(work_str); + if(FileWriteInteger(file_handle,work_int,INT_VALUE)!=INT_VALUE) + return(false); + if(work_int!=0) if(FileWriteString(file_handle,work_str,work_int)!=work_int) + return(false); +//--- write period of chart + if(FileWriteInteger(file_handle,Period(),INT_VALUE)!=sizeof(int)) + return(false); +//--- write value of the "Mode" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_MODE),INT_VALUE)!=sizeof(int)) + return(false); +//--- write value of the "Foreground" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_FOREGROUND),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "Shift" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHIFT),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShiftSize" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHIFT),INT_VALUE)!=sizeof(int)) + return(false); +//--- write value of the "AutoScroll" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_AUTOSCROLL),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "Scale" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SCALE),INT_VALUE)!=sizeof(int)) + return(false); +//--- write value of the "ScaleFix" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SCALEFIX),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ScaleFix_11" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SCALEFIX_11),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "FixedMax" property + if(FileWriteDouble(file_handle,ChartGetDouble(m_chart_id,CHART_FIXED_MAX))!=sizeof(double)) + return(false); +//--- write value of the "FixedMin" property + if(FileWriteDouble(file_handle,ChartGetDouble(m_chart_id,CHART_FIXED_MIN))!=sizeof(double)) + return(false); +//--- write the "ScalePPB" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SCALE_PT_PER_BAR),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "PointsPerBar" property + if(FileWriteDouble(file_handle,ChartGetDouble(m_chart_id,CHART_POINTS_PER_BAR))!=sizeof(double)) + return(false); +//--- write value of the "ShowOHLC" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_OHLC),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowLineBid" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_BID_LINE),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowLineAsk" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_ASK_LINE),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowLastLine" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_LAST_LINE),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowPeriodSep" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowGrid" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_GRID),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- write value of the "ShowVolumes" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_VOLUMES),INT_VALUE)!=sizeof(int)) + return(false); +//--- write value of the "ShowObjectDescr" property + if(FileWriteInteger(file_handle,(int)ChartGetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR),CHAR_VALUE)!=sizeof(char)) + return(false); +//--- successful + return(true); + } +//+------------------------------------------------------------------+ +//| Reading parameters of chart from file | +//+------------------------------------------------------------------+ +bool CChart::Load(const int file_handle) + { + bool resutl=true; + string work_str; + int work_int; +//--- check + if(file_handle==INVALID_HANDLE || m_chart_id==-1) + return(false); +//--- read and checking start marker - 0xFFFFFFFFFFFFFFFF + if(FileReadLong(file_handle)!=-1) return(false); +//--- read and checking chart type + if(FileReadInteger(file_handle,INT_VALUE)!=Type()) return(false); +//--- read chart symbol + work_int=FileReadInteger(file_handle); + if(work_int!=0) work_str=FileReadString(file_handle,work_int); + else work_str=""; +//--- read chart period + work_int=FileReadInteger(file_handle); + SetSymbolPeriod(work_str,(ENUM_TIMEFRAMES)work_int); +//--- read value of the "Mode" property + if(!ChartSetInteger(m_chart_id,CHART_MODE,FileReadInteger(file_handle,INT_VALUE))) + return(false); +//--- read value of the "Foreground" property + if(!ChartSetInteger(m_chart_id,CHART_FOREGROUND,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "Shift" property + if(!ChartSetInteger(m_chart_id,CHART_SHIFT,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShiftSize" property + if(!ChartSetInteger(m_chart_id,CHART_SHIFT,FileReadInteger(file_handle,INT_VALUE))) + return(false); +//--- read value of the "AutoScroll" property + if(!ChartSetInteger(m_chart_id,CHART_AUTOSCROLL,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "Scale" property + if(!ChartSetInteger(m_chart_id,CHART_SCALE,FileReadInteger(file_handle,INT_VALUE))) + return(false); +//--- read value of the "ScaleFix" property + if(!ChartSetInteger(m_chart_id,CHART_SCALEFIX,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ScaleFix_11" property + if(!ChartSetInteger(m_chart_id,CHART_SCALEFIX_11,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "FixedMax" property + if(!ChartSetDouble(m_chart_id,CHART_FIXED_MAX,FileReadDatetime(file_handle))) + return(false); +//--- read value of the "FixedMin" property + if(!ChartSetDouble(m_chart_id,CHART_FIXED_MIN,FileReadDatetime(file_handle))) + return(false); +//--- read value of the "ScalePPB" property + if(!ChartSetInteger(m_chart_id,CHART_SCALE_PT_PER_BAR,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "PointsPerBar" property + if(!ChartSetDouble(m_chart_id,CHART_POINTS_PER_BAR,FileReadDatetime(file_handle))) + return(false); +//--- read value of the "ShowOHLC" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_OHLC,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowLineBid" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_BID_LINE,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowLineAsk" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_ASK_LINE,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowLastLine" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_LAST_LINE,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowPeriodSep" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_PERIOD_SEP,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowGrid" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_GRID,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- read value of the "ShowVolumes" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_VOLUMES,FileReadInteger(file_handle,INT_VALUE))) + return(false); +//--- read value of the "ShowObjectDescr" property + if(!ChartSetInteger(m_chart_id,CHART_SHOW_OBJECT_DESCR,FileReadInteger(file_handle,CHAR_VALUE))) + return(false); +//--- successful + return(resutl); + } +//+------------------------------------------------------------------+ diff --git a/Include/Controls/BmpButton.mqh b/Include/Controls/BmpButton.mqh new file mode 100644 index 0000000..2da4b7f --- /dev/null +++ b/Include/Controls/BmpButton.mqh @@ -0,0 +1,268 @@ +//+------------------------------------------------------------------+ +//| BmpButton.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "WndObj.mqh" +#include +//+------------------------------------------------------------------+ +//| Class CBmpButton | +//| Usage: control that is displayed by | +//| the CChartObjectBmpLabel object | +//+------------------------------------------------------------------+ +class CBmpButton : public CWndObj + { +private: + CChartObjectBmpLabel m_button; // chart object + //--- parameters of the chart object + int m_border; // border width + string m_bmp_off_name; // name of BMP file for the "OFF" state (default state) + string m_bmp_on_name; // name of BMP file for the "ON" state + string m_bmp_passive_name; + string m_bmp_active_name; + +public: + CBmpButton(void); + ~CBmpButton(void); + //--- create + virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); + //--- parameters of the chart object + int Border(void) const { return(m_border); } + bool Border(const int value); + bool BmpNames(const string off="",const string on=""); + string BmpOffName(void) const { return(m_bmp_off_name); } + bool BmpOffName(const string name); + string BmpOnName(void) const { return(m_bmp_on_name); } + bool BmpOnName(const string name); + string BmpPassiveName(void) const { return(m_bmp_passive_name); } + bool BmpPassiveName(const string name); + string BmpActiveName(void) const { return(m_bmp_active_name); } + bool BmpActiveName(const string name); + //--- state + bool Pressed(void) const { return(m_button.State()); } + bool Pressed(const bool pressed) { return(m_button.State(pressed)); } + //--- properties + bool Locking(void) const { return(IS_CAN_LOCK); } + void Locking(const bool locking); + +protected: + //--- handlers of object settings + virtual bool OnSetZOrder(void) { return(m_button.Z_Order(m_zorder)); } + //--- internal event handlers + virtual bool OnCreate(void); + virtual bool OnShow(void); + virtual bool OnHide(void); + virtual bool OnMove(void); + virtual bool OnChange(void); + //--- + virtual bool OnActivate(void); + virtual bool OnDeactivate(void); + virtual bool OnMouseDown(void); + virtual bool OnMouseUp(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CBmpButton::CBmpButton(void) : m_border(0), + m_bmp_off_name(NULL), + m_bmp_on_name(NULL), + m_bmp_passive_name(NULL), + m_bmp_active_name(NULL) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CBmpButton::~CBmpButton(void) + { + } +//+------------------------------------------------------------------+ +//| Create a control | +//+------------------------------------------------------------------+ +bool CBmpButton::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) + { +//--- call method of the parent class + if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) + return(false); +//--- create the chart object + if(!m_button.Create(chart,name,subwin,x1,y1)) + return(false); +//--- call the settings handler + return(OnChange()); + } +//+------------------------------------------------------------------+ +//| Set border width | +//+------------------------------------------------------------------+ +bool CBmpButton::Border(const int value) + { +//--- save new value of parameter + m_border=value; +//--- set up the chart object + return(m_button.Width(value)); + } +//+------------------------------------------------------------------+ +//| Set two images at once | +//+------------------------------------------------------------------+ +bool CBmpButton::BmpNames(const string off,const string on) + { +//--- save new values of parameters + m_bmp_off_name=off; + m_bmp_on_name =on; +//--- set up the chart object + if(!m_button.BmpFileOff(off)) + return(false); + if(!m_button.BmpFileOn(on)) + return(false); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Set image for the "OFF" state | +//+------------------------------------------------------------------+ +bool CBmpButton::BmpOffName(const string name) + { +//--- save new value of parameter + m_bmp_off_name=name; +//--- set up the chart object + if(!m_button.BmpFileOff(name)) + return(false); +//--- set size by image dimensions + Width(m_button.X_Size()); + Height(m_button.Y_Size()); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Set image for the "ON" state | +//+------------------------------------------------------------------+ +bool CBmpButton::BmpOnName(const string name) + { +//--- save new value of parameter + m_bmp_on_name=name; +//--- set up the chart object + if(!m_button.BmpFileOn(name)) + return(false); +//--- set size by image dimensions + Width(m_button.X_Size()); + Height(m_button.Y_Size()); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Set image for the "OFF" state (passive) | +//+------------------------------------------------------------------+ +bool CBmpButton::BmpPassiveName(const string name) + { +//--- save new value of parameter + m_bmp_passive_name=name; +//--- set up the chart object + if(!IS_ACTIVE) + return(BmpOffName(name)); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Set image for the "OFF" state (active) | +//+------------------------------------------------------------------+ +bool CBmpButton::BmpActiveName(const string name) + { +//--- save new value of parameter + m_bmp_active_name=name; +//--- set up the chart object + if(IS_ACTIVE) + return(BmpOffName(name)); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Locking flag | +//+------------------------------------------------------------------+ +void CBmpButton::Locking(const bool flag) + { + if(flag) + PropFlagsSet(WND_PROP_FLAG_CAN_LOCK); + else + PropFlagsReset(WND_PROP_FLAG_CAN_LOCK); + } +//+------------------------------------------------------------------+ +//| Create object on chart | +//+------------------------------------------------------------------+ +bool CBmpButton::OnCreate(void) + { +//--- create the chart object by previously set parameters + return(m_button.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top)); + } +//+------------------------------------------------------------------+ +//| Display object on chart | +//+------------------------------------------------------------------+ +bool CBmpButton::OnShow(void) + { + return(m_button.Timeframes(OBJ_ALL_PERIODS)); + } +//+------------------------------------------------------------------+ +//| Hide object from chart | +//+------------------------------------------------------------------+ +bool CBmpButton::OnHide(void) + { + return(m_button.Timeframes(OBJ_NO_PERIODS)); + } +//+------------------------------------------------------------------+ +//| Absolute movement of the chart object | +//+------------------------------------------------------------------+ +bool CBmpButton::OnMove(void) + { +//--- position the chart object + return(m_button.X_Distance(m_rect.left) && m_button.Y_Distance(m_rect.top)); + } +//+------------------------------------------------------------------+ +//| Set up the chart object | +//+------------------------------------------------------------------+ +bool CBmpButton::OnChange(void) + { +//--- set up the chart object + return(m_button.Width(m_border) && m_button.BmpFileOff(m_bmp_off_name) && m_button.BmpFileOn(m_bmp_on_name)); + } +//+------------------------------------------------------------------+ +//| Handler of activating the group of controls | +//+------------------------------------------------------------------+ +bool CBmpButton::OnActivate(void) + { + if(m_bmp_active_name!=NULL) + BmpOffName(m_bmp_active_name); +//--- handled + return(true); + } +//+------------------------------------------------------------------+ +//| Handler of deactivating the group of controls | +//+------------------------------------------------------------------+ +bool CBmpButton::OnDeactivate(void) + { + if(m_bmp_passive_name!=NULL) + BmpOffName(m_bmp_passive_name); + if(!IS_CAN_LOCK) + Pressed(false); +//--- handled + return(true); + } +//+------------------------------------------------------------------+ +//| Handler of click on the left mouse button | +//+------------------------------------------------------------------+ +bool CBmpButton::OnMouseDown(void) + { + if(!IS_CAN_LOCK) + Pressed(!Pressed()); +//--- call of the method of the parent class + return(CWnd::OnMouseDown()); + } +//+------------------------------------------------------------------+ +//| Handler of click on the left mouse button | +//+------------------------------------------------------------------+ +bool CBmpButton::OnMouseUp(void) + { +//--- depress the button if it is not fixed + if(m_button.State() && !IS_CAN_LOCK) + m_button.State(false); +//--- call of the method of the parent class + return(CWnd::OnMouseUp()); + } +//+------------------------------------------------------------------+ diff --git a/Include/Controls/Button.mqh b/Include/Controls/Button.mqh new file mode 100644 index 0000000..c826fd4 --- /dev/null +++ b/Include/Controls/Button.mqh @@ -0,0 +1,146 @@ +//+------------------------------------------------------------------+ +//| Button.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "WndObj.mqh" +#include +//+------------------------------------------------------------------+ +//| Class CButton | +//| Usage: control that is displayed by | +//| the CChartObjectButton object | +//+------------------------------------------------------------------+ +class CButton : public CWndObj + { +private: + CChartObjectButton m_button; // chart object + +public: + CButton(void); + ~CButton(void); + //--- create + virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); + //--- state + bool Pressed(void) const { return(m_button.State()); } + bool Pressed(const bool pressed) { return(m_button.State(pressed)); } + //--- properties + bool Locking(void) const { return(IS_CAN_LOCK); } + void Locking(const bool flag); + +protected: + //--- handlers of object settings + virtual bool OnSetText(void) { return(m_button.Description(m_text)); } + virtual bool OnSetColor(void) { return(m_button.Color(m_color)); } + virtual bool OnSetColorBackground(void) { return(m_button.BackColor(m_color_background)); } + virtual bool OnSetColorBorder(void) { return(m_button.BorderColor(m_color_border)); } + virtual bool OnSetFont(void) { return(m_button.Font(m_font)); } + virtual bool OnSetFontSize(void) { return(m_button.FontSize(m_font_size)); } + //--- internal event handlers + virtual bool OnCreate(void); + virtual bool OnShow(void); + virtual bool OnHide(void); + virtual bool OnMove(void); + virtual bool OnResize(void); + //--- + virtual bool OnMouseDown(void); + virtual bool OnMouseUp(void); + }; +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CButton::CButton(void) + { + m_color =CONTROLS_BUTTON_COLOR; + m_color_background=CONTROLS_BUTTON_COLOR_BG; + m_color_border =CONTROLS_BUTTON_COLOR_BORDER; + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CButton::~CButton(void) + { + } +//+------------------------------------------------------------------+ +//| Create a control | +//+------------------------------------------------------------------+ +bool CButton::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) + { +//--- call method of the parent class + if(!CWndObj::Create(chart,name,subwin,x1,y1,x2,y2)) + return(false); +//--- create the chart object + if(!m_button.Create(chart,name,subwin,x1,y1,Width(),Height())) + return(false); +//--- call the settings handler + return(OnChange()); + } +//+------------------------------------------------------------------+ +//| Locking flag | +//+------------------------------------------------------------------+ +void CButton::Locking(const bool flag) + { + if(flag) + PropFlagsSet(WND_PROP_FLAG_CAN_LOCK); + else + PropFlagsReset(WND_PROP_FLAG_CAN_LOCK); + } +//+------------------------------------------------------------------+ +//| Create object on chart | +//+------------------------------------------------------------------+ +bool CButton::OnCreate(void) + { +//--- create the chart object by previously set parameters + return(m_button.Create(m_chart_id,m_name,m_subwin,m_rect.left,m_rect.top,m_rect.Width(),m_rect.Height())); + } +//+------------------------------------------------------------------+ +//| Display object on chart | +//+------------------------------------------------------------------+ +bool CButton::OnShow(void) + { + return(m_button.Timeframes(OBJ_ALL_PERIODS)); + } +//+------------------------------------------------------------------+ +//| Hide object from chart | +//+------------------------------------------------------------------+ +bool CButton::OnHide(void) + { + return(m_button.Timeframes(OBJ_NO_PERIODS)); + } +//+------------------------------------------------------------------+ +//| Absolute movement of the chart object | +//+------------------------------------------------------------------+ +bool CButton::OnMove(void) + { +//--- position the chart object + return(m_button.X_Distance(m_rect.left) && m_button.Y_Distance(m_rect.top)); + } +//+------------------------------------------------------------------+ +//| Resize the chart object | +//+------------------------------------------------------------------+ +bool CButton::OnResize(void) + { +//--- resize the chart object + return(m_button.X_Size(m_rect.Width()) && m_button.Y_Size(m_rect.Height())); + } +//+------------------------------------------------------------------+ +//| Handler of click on the left mouse button | +//+------------------------------------------------------------------+ +bool CButton::OnMouseDown(void) + { + if(!IS_CAN_LOCK) + Pressed(!Pressed()); +//--- call of the method of the parent class + return(CWnd::OnMouseDown()); + } +//+------------------------------------------------------------------+ +//| Handler of click on the left mouse button | +//+------------------------------------------------------------------+ +bool CButton::OnMouseUp(void) + { +//--- depress the button if it is not fixed + if(m_button.State() && !IS_CAN_LOCK) + m_button.State(false); +//--- call of the method of the parent class + return(CWnd::OnMouseUp()); + } +//+------------------------------------------------------------------+ diff --git a/Include/Controls/CheckBox.mqh b/Include/Controls/CheckBox.mqh new file mode 100644 index 0000000..b0d6f89 --- /dev/null +++ b/Include/Controls/CheckBox.mqh @@ -0,0 +1,183 @@ +//+------------------------------------------------------------------+ +//| CheckBox.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "WndContainer.mqh" +#include "BmpButton.mqh" +#include "Edit.mqh" +//+------------------------------------------------------------------+ +//| Resources | +//+------------------------------------------------------------------+ +#resource "res\\CheckBoxOn.bmp" +#resource "res\\CheckBoxOff.bmp" +//+------------------------------------------------------------------+ +//| Class CCheckBox | +//| Usage: class that implements the "CheckBox" control | +//+------------------------------------------------------------------+ +class CCheckBox : public CWndContainer + { +private: + //--- dependent controls + CBmpButton m_button; // button object + CEdit m_label; // label object + //--- data + int m_value; // value + +public: + CCheckBox(void); + ~CCheckBox(void); + //--- create + virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); + //--- chart event handler + virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); + //--- settings + string Text(void) const { return(m_label.Text()); } + bool Text(const string value) { return(m_label.Text(value)); } + color Color(void) const { return(m_label.Color()); } + bool Color(const color value) { return(m_label.Color(value)); } + //--- state + bool Checked(void) const { return(m_button.Pressed()); } + bool Checked(const bool flag) { return(m_button.Pressed(flag)); } + //--- data + int Value(void) const { return(m_value); } + void Value(const int value) { m_value=value; } + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + +protected: + //--- create dependent controls + virtual bool CreateButton(void); + virtual bool CreateLabel(void); + //--- handlers of the dependent controls events + virtual bool OnClickButton(void); + virtual bool OnClickLabel(void); + }; +//+------------------------------------------------------------------+ +//| Common handler of chart events | +//+------------------------------------------------------------------+ +EVENT_MAP_BEGIN(CCheckBox) + ON_EVENT(ON_CLICK,m_button,OnClickButton) + ON_EVENT(ON_CLICK,m_label,OnClickLabel) +EVENT_MAP_END(CWndContainer) +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CCheckBox::CCheckBox(void) : m_value(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CCheckBox::~CCheckBox(void) + { + } +//+------------------------------------------------------------------+ +//| Create a control | +//+------------------------------------------------------------------+ +bool CCheckBox::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) + { +//--- call method of the parent class + if(!CWndContainer::Create(chart,name,subwin,x1,y1,x2,y2)) + return(false); +//--- create dependent controls + if(!CreateButton()) + return(false); + if(!CreateLabel()) + return(false); +//--- succeeded + return(true); + } +//+------------------------------------------------------------------+ +//| Create button | +//+------------------------------------------------------------------+ +bool CCheckBox::CreateButton(void) + { +//--- calculate coordinates + int x1=CONTROLS_CHECK_BUTTON_X_OFF; + int y1=CONTROLS_CHECK_BUTTON_Y_OFF; + int x2=x1+CONTROLS_BUTTON_SIZE; + int y2=y1+CONTROLS_BUTTON_SIZE-CONTROLS_BORDER_WIDTH; +//--- create + if(!m_button.Create(m_chart_id,m_name+"Button",m_subwin,x1,y1,x2,y2)) + return(false); + if(!m_button.BmpNames("::res\\CheckBoxOff.bmp","::res\\CheckBoxOn.bmp")) + return(false); + if(!Add(m_button)) + return(false); + m_button.Locking(true); +//--- succeeded + return(true); + } +//+------------------------------------------------------------------+ +//| Create label | +//+------------------------------------------------------------------+ +bool CCheckBox::CreateLabel(void) + { +//--- calculate coordinates + int x1=CONTROLS_CHECK_LABEL_X_OFF; + int y1=CONTROLS_CHECK_LABEL_Y_OFF; + int x2=Width(); + int y2=Height(); +//--- create + if(!m_label.Create(m_chart_id,m_name+"Label",m_subwin,x1,y1,x2,y2)) + return(false); + if(!m_label.Text(m_name)) + return(false); + if(!Add(m_label)) + return(false); + m_label.ReadOnly(true); + m_label.ColorBackground(CONTROLS_CHECKGROUP_COLOR_BG); + m_label.ColorBorder(CONTROLS_CHECKGROUP_COLOR_BG); +//--- succeeded + return(true); + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CCheckBox::Save(const int file_handle) + { +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- + FileWriteInteger(file_handle,Checked()); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CCheckBox::Load(const int file_handle) + { +//--- check + if(file_handle==INVALID_HANDLE) + return(false); +//--- + if(!FileIsEnding(file_handle)) + Checked(FileReadInteger(file_handle)); +//--- succeed + return(true); + } +//+------------------------------------------------------------------+ +//| Handler of click on button | +//+------------------------------------------------------------------+ +bool CCheckBox::OnClickButton(void) + { +//--- send the "changed state" event + EventChartCustom(CONTROLS_SELF_MESSAGE,ON_CHANGE,m_id,0.0,m_name); +//--- handled + return(true); + } +//+------------------------------------------------------------------+ +//| Handler of click on label | +//+------------------------------------------------------------------+ +bool CCheckBox::OnClickLabel(void) + { +//--- change button state + m_button.Pressed(!m_button.Pressed()); +//--- return the result of the button click handler + return(OnClickButton()); + } +//+------------------------------------------------------------------+ diff --git a/Include/Controls/CheckGroup.mqh b/Include/Controls/CheckGroup.mqh new file mode 100644 index 0000000..bef6325 --- /dev/null +++ b/Include/Controls/CheckGroup.mqh @@ -0,0 +1,377 @@ +//+------------------------------------------------------------------+ +//| CheckGroup.mqh | +//| Copyright 2000-2026, MetaQuotes Ltd. | +//| www.mql5.com | +//+------------------------------------------------------------------+ +#include "WndClient.mqh" +#include "CheckBox.mqh" +#include +#include +#include +//+------------------------------------------------------------------+ +//| Class CCheckGroup | +//| Usage: view and edit group of flags | +//+------------------------------------------------------------------+ +class CCheckGroup : public CWndClient + { +private: + //--- dependent controls + CCheckBox m_rows[]; // array of the row objects + //--- set up + int m_offset; // index of first visible row in array of rows + int m_total_view; // number of visible rows + int m_item_height; // height of visible row + //--- data + CArrayString m_strings; // array of rows + CArrayLong m_values; // array of values + CArrayInt m_states; // array of states + long m_value; // current value + int m_current; // index of current row in array of rows + +public: + CCheckGroup(void); + ~CCheckGroup(void); + //--- create + virtual bool Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2); + virtual void Destroy(const int reason=0); + //--- chart event handler + virtual bool OnEvent(const int id,const long &lparam,const double &dparam,const string &sparam); + //--- fill + virtual bool AddItem(const string item,const long value=0); + //--- data + long Value(void) const; + bool Value(const long value); + int Check(const int idx) const; + bool Check(const int idx,const int value); + //--- state + virtual bool Show(void); + //--- methods for working with files + virtual bool Save(const int file_handle); + virtual bool Load(const int file_handle); + +protected: + //--- create dependent controls + bool CreateButton(int index); + //--- handlers of the dependent controls events + virtual bool OnVScrollShow(void); + virtual bool OnVScrollHide(void); + virtual bool OnScrollLineDown(void); + virtual bool OnScrollLineUp(void); + virtual bool OnChangeItem(const int row_index); + //--- redraw + bool Redraw(void); + bool RowState(const int index,const bool select); + }; +//+------------------------------------------------------------------+ +//| Common handler of chart events | +//+------------------------------------------------------------------+ +EVENT_MAP_BEGIN(CCheckGroup) + ON_INDEXED_EVENT(ON_CHANGE,m_rows,OnChangeItem) +EVENT_MAP_END(CWndClient) +//+------------------------------------------------------------------+ +//| Constructor | +//+------------------------------------------------------------------+ +CCheckGroup::CCheckGroup(void) : m_offset(0), + m_total_view(0), + m_item_height(CONTROLS_LIST_ITEM_HEIGHT), + m_current(CONTROLS_INVALID_INDEX), + m_value(0) + { + } +//+------------------------------------------------------------------+ +//| Destructor | +//+------------------------------------------------------------------+ +CCheckGroup::~CCheckGroup(void) + { + } +//+------------------------------------------------------------------+ +//| Create a control | +//+------------------------------------------------------------------+ +bool CCheckGroup::Create(const long chart,const string name,const int subwin,const int x1,const int y1,const int x2,const int y2) + { +//--- determine the number of visible rows + m_total_view=(y2-y1)/m_item_height; +//--- check the number of visible rows + if(m_total_view<1) + return(false); +//--- call method of the parent class + if(!CWndClient::Create(chart,name,subwin,x1,y1,x2,y2)) + return(false); +//--- set up + if(!m_background.ColorBackground(CONTROLS_CHECKGROUP_COLOR_BG)) + return(false); + if(!m_background.ColorBorder(CONTROLS_CHECKGROUP_COLOR_BORDER)) + return(false); +//--- create dependent controls + ArrayResize(m_rows,m_total_view); + for(int i=0;i=m_values.Total()) + return(0); +//--- + return(m_states[idx]); + } +//+------------------------------------------------------------------+ +//| | +//+------------------------------------------------------------------+ +bool CCheckGroup::Check(const int idx,const int value) + { +//--- check + if(idx>=m_values.Total()) + return(false); +//--- + bool res=(m_states.Update(idx,value) && Redraw()); +//--- change value + if(res && idx<64) + { + if(m_rows[idx].Checked()) + Value(m_value|m_values.At(idx)); + else + Value(m_value&(~m_values.At(idx))); + } +//--- + return(res); + } +//+------------------------------------------------------------------+ +//| Makes the group visible | +//+------------------------------------------------------------------+ +bool CCheckGroup::Show(void) + { +//--- call of the method of the parent class + if(!CWndClient::Show()) + return(false); +//--- loop by rows + int total=m_values.Total(); + for(int i=total;i=ArraySize(m_rows)) + return(true); +//--- change state + return(m_rows[index].Checked(select)); + } +//+------------------------------------------------------------------+ +//| Handler of the "Show vertical scrollbar" event | +//+------------------------------------------------------------------+ +bool CCheckGroup::OnVScrollShow(void) + { +//--- loop by "rows" + for(int i=0;i