st-comp 0.0.276 → 0.0.278
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.argus/backups/.argus/20260727_072225.525_patch_klinebasic.py +339 -0
- package/.argus/backups/.argus/20260727_072458.864_fix_klinebasic.py +42 -0
- package/.argus/backups/.argus/20260727_072554.676_fix_klinebasic.py +45 -0
- package/.argus/backups/20260727_020847.846_package.json +48 -0
- package/.argus/backups/20260727_021018.627_vitest.config.ts +37 -0
- package/.argus/backups/20260727_021033.462_vitest.config.ts +37 -0
- package/.argus/backups/20260727_021041.704_vitest.config.ts +37 -0
- package/.argus/backups/20260727_021122.389_vitest.config.ts +36 -0
- package/.argus/backups/20260727_021159.250_vitest.config.ts +36 -0
- package/.argus/backups/20260727_065913.421_vitest.config.ts +38 -0
- package/.argus/backups/20260727_071318.099_vitest.config.ts +42 -0
- package/.argus/backups/20260728_005238.495_vitest.config.ts +53 -0
- package/.argus/backups/20260728_005424.767_vitest.config.ts +58 -0
- package/.argus/backups/20260728_005444.216_vitest.config.ts +58 -0
- package/.argus/backups/20260728_005613.603_vitest.config.ts +57 -0
- package/.argus/backups/docs/20260727_012301.521_tasks.md +22 -0
- package/.argus/backups/docs/20260727_013404.413_requirements.md +61 -0
- package/.argus/backups/docs/20260727_013916.866_tasks.md +22 -0
- package/.argus/backups/docs/20260727_015657.696_tasks.md +22 -0
- package/.argus/backups/docs/20260727_015737.998_tasks.md +22 -0
- package/.argus/backups/docs/20260727_021309.668_tasks.md +88 -0
- package/.argus/backups/docs/20260727_023430.573_tasks.md +88 -0
- package/.argus/backups/docs/20260727_031705.498_tasks.md +88 -0
- package/.argus/backups/docs/20260727_033858.862_tasks.md +88 -0
- package/.argus/backups/docs/20260727_033909.221_tasks.md +88 -0
- package/.argus/backups/docs/20260727_065645.168_tasks.md +90 -0
- package/.argus/backups/docs/20260728_005711.090_tasks.md +90 -0
- package/.argus/backups/docs/20260728_093519.026_tasks.md +96 -0
- package/.argus/backups/docs/20260728_093845.838_tasks.md +95 -0
- package/.argus/backups/docs/20260728_093956.037_tasks.md +95 -0
- package/.argus/backups/docs/20260728_094111.219_tasks.md +95 -0
- package/.argus/backups/docs/20260802_082651.083_tasks.md +95 -0
- package/.argus/backups/docs/20260802_082659.919_tasks.md +95 -0
- package/.argus/backups/docs/20260802_082817.843_test-plan.md +119 -0
- package/.argus/backups/docs/20260802_082825.291_test-plan.md +119 -0
- package/.argus/backups/tests/20260727_020937.620_smoke.spec.ts +12 -0
- package/.argus/backups/tests/20260727_021105.040_smoke.spec.ts +12 -0
- package/.argus/backups/tests/20260727_021129.257_smoke.spec.ts +12 -0
- package/.argus/backups/tests/components/20260727_021655.533_Pagination.spec.ts +59 -0
- package/.argus/backups/tests/components/20260727_021718.547_Pagination.spec.ts +59 -0
- package/.argus/backups/tests/components/20260727_021858.422_Table.spec.ts +71 -0
- package/.argus/backups/tests/components/20260727_022144.098_VirtualTable.spec.ts +90 -0
- package/.argus/backups/tests/components/20260727_022154.585_VirtualTable.spec.ts +88 -0
- package/.argus/backups/tests/components/20260727_022225.501_VirtualTable.spec.ts +89 -0
- package/.argus/backups/tests/components/20260727_022502.366_PasswordPrompt.spec.ts +72 -0
- package/.argus/backups/tests/components/20260727_022512.945_PasswordPrompt.spec.ts +72 -0
- package/.argus/backups/tests/components/20260727_023011.481_VarietyTextCopy.spec.ts +86 -0
- package/.argus/backups/tests/components/20260727_023108.189_VarietyTextCopy.spec.ts +89 -0
- package/.argus/backups/tests/components/20260727_024356.151_ConfigProvider.spec.ts +101 -0
- package/.argus/backups/tests/components/20260727_024430.348_ConfigProvider.spec.ts +100 -0
- package/.argus/backups/tests/components/20260727_024635.923_User.spec.ts +82 -0
- package/.argus/backups/tests/components/20260727_024836.707_VarSelectDialog.spec.ts +131 -0
- package/.argus/backups/tests/components/20260727_024852.191_VarSelectDialog.spec.ts +111 -0
- package/.argus/backups/tests/components/20260727_024858.884_VarSelectDialog.spec.ts +130 -0
- package/.argus/backups/tests/components/20260727_024905.552_VarSelectDialog.spec.ts +130 -0
- package/.argus/backups/tests/components/20260727_024935.877_VarSelectDialog.spec.ts +130 -0
- package/.argus/backups/tests/components/20260727_025017.207_VarSelectDialog.spec.ts +131 -0
- package/.argus/backups/tests/components/20260727_025222.178_CustomFunction.spec.ts +107 -0
- package/.argus/backups/tests/components/20260727_025533.944_VarietySearch.spec.ts +238 -0
- package/.argus/backups/tests/components/20260727_025738.354_VarietySearch.spec.ts +233 -0
- package/.argus/backups/tests/components/20260727_025853.506_VarietySearch.spec.ts +242 -0
- package/.argus/backups/tests/components/20260727_030036.628_VarietySearch.spec.ts +242 -0
- package/.argus/backups/tests/components/20260727_030236.640_VarietySearch.spec.ts +244 -0
- package/.argus/backups/tests/components/20260727_030355.487_VarietySearch.spec.ts +253 -0
- package/.argus/backups/tests/components/20260727_031231.694_VarietySearch.spec.ts +258 -0
- package/.argus/backups/tests/components/20260727_031334.734_VarietySearch.spec.ts +260 -0
- package/.argus/backups/tests/components/20260727_031351.660_VarietySearch.spec.ts +259 -0
- package/.argus/backups/tests/components/20260727_031410.450_VarietySearch.spec.ts +260 -0
- package/.argus/backups/tests/components/20260727_031425.598_VarietySearch.spec.ts +259 -0
- package/.argus/backups/tests/components/20260727_031456.187_VarietySearch.spec.ts +258 -0
- package/.argus/backups/tests/components/20260727_031508.243_VarietySearch.spec.ts +257 -0
- package/.argus/backups/tests/components/20260727_035911.421_FactorWarning.spec.ts +45 -0
- package/.argus/backups/tests/components/20260727_040217.402_FactorWarning.spec.ts +44 -0
- package/.argus/backups/tests/components/20260727_040313.577_FactorWarning.spec.ts +115 -0
- package/.argus/backups/tests/components/20260727_040443.842_FactorWarning.spec.ts +116 -0
- package/.argus/backups/tests/components/20260727_040543.676_FactorWarning.spec.ts +119 -0
- package/.argus/backups/tests/components/20260727_040552.567_FactorWarning.spec.ts +120 -0
- package/.argus/backups/tests/components/20260727_040615.706_FactorWarning.spec.ts +122 -0
- package/.argus/backups/tests/components/20260727_040626.121_FactorWarning.spec.ts +123 -0
- package/.argus/backups/tests/components/20260727_040717.265_MonacoEditor.spec.ts +83 -0
- package/.argus/backups/tests/components/20260727_040828.121_MonacoEditor.spec.ts +76 -0
- package/.argus/backups/tests/components/20260727_040904.972_MonacoEditor.spec.ts +78 -0
- package/.argus/backups/tests/components/20260727_041018.032_MonacoEditor.spec.ts +81 -0
- package/.argus/backups/tests/components/20260727_041314.621_VarietyAiHelper.spec.ts +163 -0
- package/.argus/backups/tests/components/20260727_041325.276_VarietyAiHelper.spec.ts +163 -0
- package/.argus/backups/tests/components/20260727_041334.762_VarietyAiHelper.spec.ts +165 -0
- package/.argus/backups/tests/components/20260727_041343.202_VarietyAiHelper.spec.ts +166 -0
- package/.argus/backups/tests/components/20260727_044733.056_KlineBasic.spec.ts +143 -0
- package/.argus/backups/tests/components/20260727_045002.981_KlineBasic.spec.ts +143 -0
- package/.argus/backups/tests/components/20260727_045140.574_KlineBasic.spec.ts +144 -0
- package/.argus/backups/tests/components/20260727_045425.611_KlineBasic.spec.ts +145 -0
- package/.argus/backups/tests/components/20260727_045715.412_Kline.spec.ts +113 -0
- package/.argus/backups/tests/components/20260727_045724.184_Kline.spec.ts +125 -0
- package/.argus/backups/tests/components/20260727_045825.069_Kline.spec.ts +126 -0
- package/.argus/backups/tests/components/20260727_045834.378_Kline.spec.ts +136 -0
- package/.argus/backups/tests/components/20260727_045841.348_Kline.spec.ts +137 -0
- package/.argus/backups/tests/components/20260727_045940.821_Kline.spec.ts +136 -0
- package/.argus/backups/tests/components/20260727_045949.967_Kline.spec.ts +137 -0
- package/.argus/backups/tests/components/20260727_050143.995_Kline.spec.ts +138 -0
- package/.argus/backups/tests/components/20260727_050209.308_Kline.spec.ts +142 -0
- package/.argus/backups/tests/components/20260727_050232.575_Kline.spec.ts +145 -0
- package/.argus/backups/tests/components/20260727_050520.150_Kline.spec.ts +149 -0
- package/.argus/backups/tests/components/20260727_050528.961_Kline.spec.ts +144 -0
- package/.argus/backups/tests/components/20260727_050539.132_Kline.spec.ts +140 -0
- package/.argus/backups/tests/components/20260727_050600.606_Kline.spec.ts +141 -0
- package/.argus/backups/tests/components/20260727_051012.712_Kline.spec.ts +145 -0
- package/.argus/backups/tests/components/20260727_051030.112_Kline.spec.ts +140 -0
- package/.argus/backups/tests/components/20260727_051039.759_Kline.spec.ts +140 -0
- package/.argus/backups/tests/components/20260727_064959.298_KlineNew.spec.ts +265 -0
- package/.argus/backups/tests/components/20260727_065038.479_KlineNew.spec.ts +278 -0
- package/.argus/backups/tests/components/20260727_065213.245_KlinePlus.spec.ts +151 -0
- package/.argus/backups/tests/components/20260727_065245.945_KlinePlus.spec.ts +151 -0
- package/.argus/backups/tests/components/20260727_070847.720_MonacoEditor.spec.ts +105 -0
- package/.argus/backups/tests/components/20260727_070916.945_MonacoEditor.spec.ts +473 -0
- package/.argus/backups/tests/components/20260727_070928.260_MonacoEditor.spec.ts +516 -0
- package/.argus/backups/tests/components/20260727_071228.326_MonacoEditor.spec.ts +517 -0
- package/.argus/backups/tests/components/20260727_071243.734_MonacoEditor.spec.ts +521 -0
- package/.argus/backups/tests/components/20260727_071305.582_MonacoEditor.spec.ts +521 -0
- package/.argus/backups/tests/components/20260727_071350.809_MonacoEditor.spec.ts +525 -0
- package/.argus/backups/tests/components/20260727_071407.272_MonacoEditor.spec.ts +525 -0
- package/.argus/backups/tests/components/20260727_071425.763_MonacoEditor.spec.ts +525 -0
- package/.argus/backups/tests/components/20260727_071444.203_MonacoEditor.spec.ts +525 -0
- package/.argus/backups/tests/components/20260727_071813.277_Kline2.spec.ts +269 -0
- package/.argus/backups/tests/components/20260727_071825.633_Kline2.spec.ts +275 -0
- package/.argus/backups/tests/components/20260727_071836.063_Kline2.spec.ts +275 -0
- package/.argus/backups/tests/components/20260727_071926.767_Kline2.spec.ts +275 -0
- package/.argus/backups/tests/components/20260727_072027.172_Kline2.spec.ts +285 -0
- package/.argus/backups/tests/components/20260727_123101.129_Kline2.spec.ts +292 -0
- package/.argus/backups/tests/components/20260727_135819.231_Kline2.spec.ts +293 -0
- package/.argus/backups/tests/components/20260727_135858.777_Kline2.spec.ts +304 -0
- package/.argus/backups/tests/components/20260727_141205.699_KlineBasic.spec.ts +429 -0
- package/.argus/backups/tests/components/20260727_141254.206_Kline2.spec.ts +304 -0
- package/.argus/backups/tests/components/20260727_141349.322_KlineBasic.spec.ts +437 -0
- package/.argus/backups/tests/components/20260727_141447.163_KlineBasic.spec.ts +439 -0
- package/.argus/backups/tests/components/20260727_141505.454_KlineBasic.spec.ts +427 -0
- package/.argus/backups/tests/components/20260728_010148.792_User.spec.ts +126 -0
- package/.argus/backups/tests/components/20260728_010436.092_TreeMap.spec.ts +100 -0
- package/.argus/backups/tests/components/20260728_010546.696_CustomFunction.spec.ts +185 -0
- package/.argus/backups/tests/components/20260728_010606.217_CustomFunction.spec.ts +185 -0
- package/.argus/backups/tests/components/20260728_010739.144_FactorWarning.spec.ts +285 -0
- package/.argus/backups/tests/components/20260728_050815.875_VirtualTable.spec.ts +234 -0
- package/.argus/backups/tests/components/20260728_092436.589_KlinePlus.spec.ts +151 -0
- package/.argus/backups/tests/components/20260728_092516.096_KlinePlus.spec.ts +273 -0
- package/.argus/backups/tests/components/20260728_092531.745_KlinePlus.spec.ts +274 -0
- package/.argus/backups/tests/components/20260728_092545.921_KlinePlus.spec.ts +275 -0
- package/.argus/backups/tests/components/20260728_092605.498_KlinePlus.spec.ts +276 -0
- package/.argus/backups/tests/components/20260728_092659.872_KlinePlus.spec.ts +277 -0
- package/.argus/backups/tests/components/20260728_092713.040_KlinePlus.spec.ts +278 -0
- package/.argus/backups/tests/components/20260728_092726.343_KlinePlus.spec.ts +279 -0
- package/.argus/backups/tests/components/20260728_092750.935_KlinePlus.spec.ts +281 -0
- package/.argus/backups/tests/components/20260728_092800.572_KlinePlus.spec.ts +283 -0
- package/.argus/backups/tests/components/20260728_092837.797_KlinePlus.spec.ts +284 -0
- package/.argus/backups/tests/mocks/20260727_023418.231_st-func.mock.ts +29 -0
- package/.argus/backups/tests/mocks/20260727_024337.409_st-func.mock.ts +37 -0
- package/.argus/backups/tests/mocks/20260727_024344.807_st-func.mock.ts +40 -0
- package/.argus/backups/tests/mocks/20260727_041047.900_monaco.mock.ts +27 -0
- package/.argus/backups/tests/mocks/20260727_041129.076_monaco.mock.ts +31 -0
- package/.argus/backups/tests/mocks/20260727_044924.603_st-func.mock.ts +44 -0
- package/.argus/backups/tests/mocks/20260727_045612.059_echarts.kline.mock.ts +33 -0
- package/.argus/backups/tests/mocks/20260727_050955.894_echarts.kline.mock.ts +35 -0
- package/.argus/backups/tests/mocks/20260727_065402.557_st-func.mock.ts +52 -0
- package/.argus/backups/tests/mocks/20260727_065525.410_st-func.mock.ts +52 -0
- package/.argus/backups/tests/mocks/20260727_140844.233_echarts.kline.mock.ts +40 -0
- package/.argus/backups/tests/mocks/20260727_140913.119_echarts.kline.mock.ts +42 -0
- package/.argus/backups/tests/mocks/20260727_140921.846_echarts.kline.mock.ts +42 -0
- package/.argus/backups/tests/mocks/20260727_140939.427_echarts.kline.mock.ts +41 -0
- package/.argus/bgtasks.json +229 -0
- package/.argus/coldstore/013c3c794e7b7c29.jsonl +2 -0
- package/.argus/coldstore/0e331b8a3b0d8880.jsonl +16 -0
- package/.argus/coldstore/5d9a9409cc3f63f2.jsonl +7 -0
- package/.argus/coldstore/5f928dc976ac6d8a.jsonl +1 -0
- package/.argus/coldstore/60094b3c17412a84.jsonl +3 -0
- package/.argus/coldstore/76b2ec805ca5837c.jsonl +1 -0
- package/.argus/coldstore/859e7008fa48fc1b.jsonl +1 -0
- package/.argus/coldstore/85f91d3daeab41ca.jsonl +1595 -0
- package/.argus/coldstore/9278244bf4eaf9c6.jsonl +2 -0
- package/.argus/coldstore/9bb5e3ec04a11742.jsonl +1 -0
- package/.argus/coldstore/9c00baba2fa5f259.jsonl +3 -0
- package/.argus/coldstore/b72af6a1286c4ff8.jsonl +3 -0
- package/.argus/coldstore/cf0c7a7843dd408d.jsonl +1 -0
- package/.argus/coldstore/d1638960d5dc3db4.jsonl +4 -0
- package/.argus/coldstore/e6432235b17215cb.jsonl +6 -0
- package/.argus/coldstore/fae1501de047b46f.jsonl +1 -0
- package/.argus/compress_context/20260727T013726.528Z_classifier_9c00baba2fa5f259.txt +16 -0
- package/.argus/compress_context/20260727T020603.082Z_classifier_85f91d3daeab41ca.txt +2339 -0
- package/.argus/compress_context/20260727T023245.640Z_classifier_fae1501de047b46f.txt +16 -0
- package/.argus/compress_context/20260727T030908.737Z_classifier_e6432235b17215cb.txt +16 -0
- package/.argus/compress_context/20260727T032428.553Z_classifier_cf0c7a7843dd408d.txt +16 -0
- package/.argus/compress_context/20260727T032636.912Z_classifier_85f91d3daeab41ca.txt +7419 -0
- package/.argus/compress_context/20260727T041443.286Z_classifier_5d9a9409cc3f63f2.txt +16 -0
- package/.argus/compress_context/20260727T050601.474Z_classifier_60094b3c17412a84.txt +16 -0
- package/.argus/compress_context/20260727T052133.391Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T052212.178Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T052324.111Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T054653.767Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T071236.629Z_classifier_85f91d3daeab41ca.txt +16527 -0
- package/.argus/compress_context/20260727T072148.598Z_classifier_0e331b8a3b0d8880.txt +16 -0
- package/.argus/compress_context/20260727T073105.913Z_classifier_76b2ec805ca5837c.txt +16 -0
- package/.argus/compress_context/20260727T073345.593Z_classifier_d1638960d5dc3db4.txt +16 -0
- package/.argus/compress_context/20260727T092505.960Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T113928.931Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260727T123224.070Z_classifier_85f91d3daeab41ca.txt +10993 -0
- package/.argus/compress_context/20260727T141545.843Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260728T010629.442Z_classifier_85f91d3daeab41ca.txt +24561 -0
- package/.argus/compress_context/20260728T010950.439Z_classifier_9278244bf4eaf9c6.txt +16 -0
- package/.argus/compress_context/20260728T011152.613Z_classifier_013c3c794e7b7c29.txt +16 -0
- package/.argus/compress_context/20260728T085939.340Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260728T091110.581Z_classifier_b72af6a1286c4ff8.txt +16 -0
- package/.argus/compress_context/20260728T091324.113Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260728T093934.076Z_classifier_85f91d3daeab41ca.txt +26262 -0
- package/.argus/compress_context/20260729T011307.637Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260802T081344.640Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260802T082844.660Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260802T083022.436Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260802T083336.095Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260903T053304.203Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260903T053851.653Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260903T054344.177Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260903T060025.617Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/compress_context/20260903T060858.200Z_classifier_FAILED_85f91d3daeab41ca.txt +30 -0
- package/.argus/config.toml +15 -0
- package/.argus/memory/.INDEX.md.lock +0 -0
- package/.argus/memory/INDEX.md +166 -0
- package/.argus/memory/dev_build_commands_build_targets_lib_vs_demo.md +14 -0
- package/.argus/memory/dev_build_commands_st_comp_build_scripts.md +13 -0
- package/.argus/memory/dev_coding_conventions_component_test_setupstate_override_pattern.md +14 -0
- package/.argus/memory/dev_coding_conventions_customfunction_ai_query_no_prefix.md +14 -0
- package/.argus/memory/dev_coding_conventions_fix_tests_not_packages_source.md +14 -0
- package/.argus/memory/dev_coding_conventions_git_workflow_branch_nds.md +14 -0
- package/.argus/memory/dev_coding_conventions_managedialog_ai_dialog_test_pattern.md +8 -0
- package/.argus/memory/dev_coding_conventions_no_packages_source_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_component_spec_conventions.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_component_test_mocking_pattern.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_coverage_scope_rules.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_exposed_methods_via_wrapper_vm.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_full_import_only.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_klinebasic_error_rethrow.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_monaco_mock_extend.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_no_modify_packages_constraint.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_no_packages_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_no_packages_code_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_no_packages_modification.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_no_packages_source_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_packages_read_only_for_test_work.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_packages_readonly_when_testing.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_seven_phase_workflow.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_spec_mock_patterns.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_spec_mount_pattern.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_component_patterns.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_constraint_no_packages_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_conventions.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_mock_conventions.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_phase_constraints.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_test_work_no_packages_changes.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_testing_exposed_methods.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_tests_must_not_touch_packages.md +14 -0
- package/.argus/memory/dev_coding_conventions_st_comp_varietysearch_setupstate.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_vi_mock_pattern.md +13 -0
- package/.argus/memory/dev_coding_conventions_st_comp_vitest_cache_handlers_false.md +14 -0
- package/.argus/memory/dev_coding_conventions_stcomp_props_emits_conventions.md +13 -0
- package/.argus/memory/dev_coding_conventions_stconfig_request_injection.md +14 -0
- package/.argus/memory/dev_coding_conventions_test_isolation_and_coverage_thresholds.md +14 -0
- package/.argus/memory/dev_coding_conventions_test_mock_strategy_layers.md +14 -0
- package/.argus/memory/dev_deps_project_ui_deps_echarts_monaco.md +14 -0
- package/.argus/memory/dev_deps_st_comp_tech_stack.md +13 -0
- package/.argus/memory/dev_deps_st_comp_test_stack.md +13 -0
- package/.argus/memory/dev_deps_st_comp_test_stack_versions.md +13 -0
- package/.argus/memory/dev_deps_stcomp_heavy_deps_per_component.md +13 -0
- package/.argus/memory/dev_deps_variety_ai_helper_api.md +14 -0
- package/.argus/memory/dev_deps_variety_ai_helper_endpoint.md +14 -0
- package/.argus/memory/dev_deps_vitest_034_coverage_threshold_format.md +14 -0
- package/.argus/memory/dev_key_modules_ai_helper_streaming_api.md +14 -0
- package/.argus/memory/dev_key_modules_ai_tools_sse_client.md +14 -0
- package/.argus/memory/dev_key_modules_custom_function_api_endpoints.md +14 -0
- package/.argus/memory/dev_key_modules_custom_function_manage_dialog.md +14 -0
- package/.argus/memory/dev_key_modules_custom_function_panel_structure.md +14 -0
- package/.argus/memory/dev_key_modules_customfunction_manage_dialog.md +14 -0
- package/.argus/memory/dev_key_modules_kline_echarts_mock_requirements.md +14 -0
- package/.argus/memory/dev_key_modules_shared_editor_varselect_components.md +14 -0
- package/.argus/memory/dev_key_modules_st_comp_extractvariables_syntax.md +13 -0
- package/.argus/memory/dev_key_modules_st_comp_kline_component_family.md +13 -0
- package/.argus/memory/dev_key_modules_st_comp_kline_components_test_mocks.md +14 -0
- package/.argus/memory/dev_key_modules_st_comp_l2_kline_components.md +14 -0
- package/.argus/memory/dev_key_modules_st_comp_packages_components.md +13 -0
- package/.argus/memory/dev_key_modules_st_comp_test_mocks_behavior.md +13 -0
- package/.argus/memory/dev_key_modules_st_comp_vite_plugins.md +13 -0
- package/.argus/memory/dev_key_modules_st_func_mock_helper.md +14 -0
- package/.argus/memory/dev_key_modules_stcomp_http_via_stfunc_no_axios.md +13 -0
- package/.argus/memory/dev_key_modules_stcomp_stconfig_provide_inject_contract.md +13 -0
- package/.argus/memory/dev_key_modules_test_shared_helpers.md +14 -0
- package/.argus/memory/dev_key_modules_vitest_vue_cachehandlers_false.md +14 -0
- package/.argus/memory/dev_known_issues_klinebasic_unhandled_rejection_noise.md +14 -0
- package/.argus/memory/dev_known_issues_low_func_coverage_components.md +14 -0
- package/.argus/memory/dev_known_issues_low_function_coverage_components.md +14 -0
- package/.argus/memory/dev_known_issues_missing_component_tests_backlog.md +14 -0
- package/.argus/memory/dev_known_issues_monaco_editor_baseline_test_failures.md +14 -0
- package/.argus/memory/dev_known_issues_pending_component_test_coverage.md +14 -0
- package/.argus/memory/dev_known_issues_st_comp_baseline_failing_specs.md +14 -0
- package/.argus/memory/dev_known_issues_st_comp_build_gaps.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_contextmenu_attachto_breaks.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_deferred_tests_factorwarning_monacoeditor_varietyaihelper.md +14 -0
- package/.argus/memory/dev_known_issues_st_comp_definemodel_setupstate_test_pitfall.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_elmessage_auto_import_tests.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_elmessage_missing_in_tests.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_eltable_render_timing.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_hardcoded_server_password.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_jsdom_resizeobserver_stub.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_klinebasic_resize_coverage_gap.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_klinebasic_unhandled_rejection_noise.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_known_gaps.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_preexisting_spec_failures.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_stfunc_mock_stmath_incomplete.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_test_elmessage_autoimport.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_test_env_pitfalls.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_test_teleport_cleanup.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_varietytextcopy_global_elmessage.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_vitest_cache_handlers_false.md +13 -0
- package/.argus/memory/dev_known_issues_st_comp_vitest_cachehandlers_coverage.md +13 -0
- package/.argus/memory/dev_known_issues_st_func_mock_addresizelistener_return_shape.md +14 -0
- package/.argus/memory/dev_known_issues_stcomp_klineconfig_defineemits_string_bug.md +13 -0
- package/.argus/memory/dev_known_issues_t1_uncovered_test_gaps.md +14 -0
- package/.argus/memory/dev_known_issues_vitest_034_windows_import_meta_url.md +14 -0
- package/.argus/memory/dev_known_issues_vitest_baseline_failing_specs.md +14 -0
- package/.argus/memory/dev_known_issues_vitest_vue_plugin_cache_handlers_false.md +14 -0
- package/.argus/memory/dev_project_layout_custom_function_manage_dialog.md +14 -0
- package/.argus/memory/dev_project_layout_docs_phase_artifacts.md +14 -0
- package/.argus/memory/dev_project_layout_docs_tasks_md_planning_file.md +14 -0
- package/.argus/memory/dev_project_layout_frontend_component_test_scope.md +14 -0
- package/.argus/memory/dev_project_layout_local_only_dirs_not_committed.md +14 -0
- package/.argus/memory/dev_project_layout_nds_local_tool_dirs_untracked.md +14 -0
- package/.argus/memory/dev_project_layout_packages_component_layout.md +14 -0
- package/.argus/memory/dev_project_layout_packages_dir_always_empty.md +14 -0
- package/.argus/memory/dev_project_layout_phased_workflow_docs.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_docs_phase_workflow.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_docs_task_tracking.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_layout_and_aliases.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_layout_and_docs_plan.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_layout_and_stack.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_layout_and_task_tracking.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_project_layout.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_project_overview.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_project_root_and_purpose.md +14 -0
- package/.argus/memory/dev_project_layout_st_comp_repo_layout.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_test_layout.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_test_layout_and_aliases.md +13 -0
- package/.argus/memory/dev_project_layout_st_comp_top_level_layout.md +13 -0
- package/.argus/memory/dev_project_layout_stcomp_packages_layout.md +13 -0
- package/.argus/memory/dev_project_layout_test_release_plugin_sftp_config.md +14 -0
- package/.argus/memory/dev_project_layout_tests_dir_and_vitest_config.md +14 -0
- package/.argus/memory/dev_test_commands_component_test_baseline_and_layout.md +14 -0
- package/.argus/memory/dev_test_commands_coverage_gate_80_baseline_exclusions.md +14 -0
- package/.argus/memory/dev_test_commands_coverage_thresholds.md +14 -0
- package/.argus/memory/dev_test_commands_coverage_thresholds_enforced.md +14 -0
- package/.argus/memory/dev_test_commands_func_coverage_threshold_80.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_component_test_command.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_coverage_commands.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_coverage_scope_and_threshold.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_coverage_thresholds.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_run_component_tests.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_commands.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_component_suite.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_component_tests.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_components.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_coverage_config.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_coverage_thresholds.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_must_use_local_bin.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run_and_coverage.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run_command.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run_commands.md +13 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run_coverage.md +14 -0
- package/.argus/memory/dev_test_commands_st_comp_vitest_run_spec.md +13 -0
- package/.argus/memory/dev_test_commands_test_stack_vitest_vue.md +14 -0
- package/.argus/memory/dev_test_commands_test_suite_baseline.md +14 -0
- package/.argus/memory/dev_test_commands_vitest_cache_handlers_disabled_for_coverage.md +14 -0
- package/.argus/memory/dev_test_commands_vitest_component_test_plan_tiers.md +14 -0
- package/.argus/memory/dev_test_commands_vitest_component_testing_setup.md +14 -0
- package/.argus/memory/dev_test_commands_vitest_run_and_coverage.md +14 -0
- package/.argus/memory/dev_test_commands_vitest_test_infra_setup.md +14 -0
- package/.argus/memory/ops_credentials_locations_sftp_cred_testrelese_ts.md +14 -0
- package/.argus/memory/ops_credentials_locations_st_comp_test_server_root_password_hardcoded.md +13 -0
- package/.argus/memory/ops_deploy_workflow_build_outputs_lib_vs_demo.md +14 -0
- package/.argus/memory/ops_deploy_workflow_sftp_deploy_via_vite_plugin.md +14 -0
- package/.argus/memory/ops_deploy_workflow_st_comp_test_deploy_via_testrelese_plugin.md +13 -0
- package/.argus/memory/ops_servers_test_server_192_168_12_49.md +14 -0
- package/.argus/scheduled_tasks.json +467 -0
- package/.cliworks/.cliworks/input_history.jsonl +44 -0
- package/.cliworks/chat/acp/39525acf-0832-40e4-90e5-f058f1bccb8d/2026-07-27.json +17623 -0
- package/.cliworks/chat/acp/39525acf-0832-40e4-90e5-f058f1bccb8d/2026-07-28.json +6349 -0
- package/.cliworks/chat/acp/39525acf-0832-40e4-90e5-f058f1bccb8d/2026-07-29.json +33 -0
- package/.cliworks/chat/acp/39525acf-0832-40e4-90e5-f058f1bccb8d/2026-08-02.json +1174 -0
- package/.cliworks/chat/acp/39525acf-0832-40e4-90e5-f058f1bccb8d/2026-09-03.json +2354 -0
- package/.cliworks/hooks/on_create.md +6 -0
- package/.cliworks/hooks/on_open.md +11 -0
- package/.cliworks/project.json +5 -0
- package/coverage/ChartLayout/index.html +116 -0
- package/coverage/ChartLayout/index.vue.html +274 -0
- package/coverage/ConfigProvider/index.html +116 -0
- package/coverage/ConfigProvider/index.vue.html +283 -0
- package/coverage/Contextmenu/index.html +116 -0
- package/coverage/Contextmenu/index.vue.html +487 -0
- package/coverage/CustomFunction/index.html +116 -0
- package/coverage/CustomFunction/index.vue.html +685 -0
- package/coverage/FactorWarning/index.html +116 -0
- package/coverage/FactorWarning/index.vue.html +2347 -0
- package/coverage/HeatMap/index.html +116 -0
- package/coverage/HeatMap/index.vue.html +508 -0
- package/coverage/Kline/index.html +116 -0
- package/coverage/Kline/index.vue.html +2758 -0
- package/coverage/KlineBasic/index.html +116 -0
- package/coverage/KlineBasic/index.vue.html +2602 -0
- package/coverage/KlineConfig/index.html +116 -0
- package/coverage/KlineConfig/index.vue.html +1153 -0
- package/coverage/KlinePlus/index.html +116 -0
- package/coverage/KlinePlus/index.vue.html +3286 -0
- package/coverage/LinearLegend/index.html +116 -0
- package/coverage/LinearLegend/index.vue.html +418 -0
- package/coverage/Map/index.html +116 -0
- package/coverage/Map/index.vue.html +526 -0
- package/coverage/MonacoEditor/index.html +116 -0
- package/coverage/MonacoEditor/index.vue.html +3508 -0
- package/coverage/Pagination/index.html +116 -0
- package/coverage/Pagination/index.vue.html +346 -0
- package/coverage/PasswordPrompt/index.html +116 -0
- package/coverage/PasswordPrompt/index.vue.html +886 -0
- package/coverage/Pie/index.html +116 -0
- package/coverage/Pie/index.vue.html +529 -0
- package/coverage/Table/index.html +116 -0
- package/coverage/Table/index.vue.html +484 -0
- package/coverage/TreeMap/index.html +116 -0
- package/coverage/TreeMap/index.vue.html +643 -0
- package/coverage/User/index.html +116 -0
- package/coverage/User/index.vue.html +436 -0
- package/coverage/VarSelectDialog/index.html +116 -0
- package/coverage/VarSelectDialog/index.vue.html +1054 -0
- package/coverage/VarietyAiHelper/index.html +116 -0
- package/coverage/VarietyAiHelper/index.vue.html +2815 -0
- package/coverage/VarietyAutoComplete/index.html +116 -0
- package/coverage/VarietyAutoComplete/index.vue.html +352 -0
- package/coverage/VarietySearch/index.html +116 -0
- package/coverage/VarietySearch/index.vue.html +2149 -0
- package/coverage/VarietyTextCopy/index.html +116 -0
- package/coverage/VarietyTextCopy/index.vue.html +493 -0
- package/coverage/VirtualTable/index.html +116 -0
- package/coverage/VirtualTable/index.vue.html +808 -0
- package/coverage/base.css +224 -0
- package/coverage/block-navigation.js +87 -0
- package/coverage/clover.xml +9392 -0
- package/coverage/coverage-final.json +26 -0
- package/coverage/favicon.png +0 -0
- package/coverage/index.html +476 -0
- package/coverage/prettify.css +1 -0
- package/coverage/prettify.js +2 -0
- package/coverage/sort-arrow-sprite.png +0 -0
- package/coverage/sorter.js +210 -0
- package/docs/api-design.md +120 -0
- package/docs/architecture.md +110 -0
- package/docs/bugs.md +13 -0
- package/docs/requirements.md +63 -0
- package/docs/review.md +80 -0
- package/docs/security.md +83 -0
- package/docs/tasks.md +112 -0
- package/docs/test-plan.md +119 -0
- package/es/CustomFunction.cjs +2 -2
- package/es/CustomFunction.js +514 -308
- package/es/FactorWarning.cjs +1 -1
- package/es/FactorWarning.js +25 -25
- package/es/Kline.cjs +1 -1
- package/es/Kline.js +10 -10
- package/es/KlineBasic.cjs +1 -1
- package/es/KlineBasic.js +18 -18
- package/es/KlineConfig.cjs +1 -1
- package/es/KlineConfig.js +15 -15
- package/es/KlineNew.cjs +1 -1
- package/es/KlineNew.js +9 -9
- package/es/KlinePlus.cjs +1 -1
- package/es/KlinePlus.js +11 -11
- package/es/MonacoEditor.cjs +1 -1
- package/es/MonacoEditor.js +17 -17
- package/es/Pagination.cjs +1 -1
- package/es/Pagination.js +76 -76
- package/es/PasswordPrompt.cjs +1 -1
- package/es/PasswordPrompt.js +2 -2
- package/es/Table.cjs +1 -1
- package/es/Table.js +22 -22
- package/es/User.cjs +1 -1
- package/es/User.js +18 -18
- package/es/VarSelectDialog.cjs +1 -1
- package/es/VarSelectDialog.js +18 -18
- package/es/VarietyAiHelper.cjs +7 -7
- package/es/VarietyAiHelper.js +306 -279
- package/es/VarietyAutoComplete.cjs +1 -1
- package/es/VarietyAutoComplete.js +7 -7
- package/es/VarietySearch.cjs +1 -1
- package/es/VarietySearch.js +30 -30
- package/es/{VarietySelect-610903f0.cjs → VarietySelect-5fee5520.cjs} +1 -1
- package/es/{VarietySelect-6a166293.js → VarietySelect-e563b7a6.js} +3 -3
- package/es/VarietyTextCopy.cjs +1 -1
- package/es/VarietyTextCopy.js +10 -10
- package/es/VirtualTable.cjs +1 -1
- package/es/VirtualTable.js +5 -5
- package/es/{_initCloneObject-152b4f4e.js → _initCloneObject-5e73c9c8.js} +2 -2
- package/es/{_initCloneObject-76e116d0.cjs → _initCloneObject-a5832933.cjs} +1 -1
- package/es/{aiTools-d7bd47c3.js → aiTools-a3b095f6.js} +19 -19
- package/es/aiTools-f36136a4.cjs +2 -0
- package/es/aiTools.js +4 -0
- package/es/{config-provider-4c784baf.cjs → config-provider-06ea6b42.cjs} +1 -1
- package/es/{config-provider-7a3aca55.js → config-provider-88b637a8.js} +3 -3
- package/es/{dropdown-987c47fb.cjs → dropdown-4ce4c3e2.cjs} +1 -1
- package/es/{dropdown-01e09205.js → dropdown-72fbc15d.js} +2 -2
- package/es/{el-autocomplete-342ae823.js → el-autocomplete-6cafcdf1.js} +5 -5
- package/es/{el-autocomplete-16f66b34.cjs → el-autocomplete-e0857c5b.cjs} +1 -1
- package/es/{el-button-13dc915f.cjs → el-button-613e0bb2.cjs} +1 -1
- package/es/{el-button-314171a0.js → el-button-94d3de38.js} +3 -3
- package/es/{el-checkbox-5899681f.js → el-checkbox-6d4cb393.js} +3 -3
- package/es/{el-checkbox-bdf64663.cjs → el-checkbox-e90762df.cjs} +1 -1
- package/es/{el-dialog-0ef95df6.cjs → el-dialog-02942e22.cjs} +1 -1
- package/es/{el-dialog-86a3eec2.js → el-dialog-5e30161f.js} +4 -4
- package/es/{el-form-item-77ef6784.cjs → el-form-item-44f060d8.cjs} +1 -1
- package/es/{el-form-item-cda4d93c.js → el-form-item-9e8969ea.js} +5 -5
- package/es/{el-input-fa1445ad.js → el-input-342b2def.js} +13 -13
- package/es/{el-input-89222943.cjs → el-input-9a9a7f7f.cjs} +1 -1
- package/es/{el-input-number-d3338e5e.cjs → el-input-number-65c847d6.cjs} +1 -1
- package/es/{el-input-number-8f6dbf74.js → el-input-number-c22b803a.js} +35 -35
- package/es/{el-loading-84dde1a8.cjs → el-loading-8179b497.cjs} +1 -1
- package/es/{el-loading-9b671203.js → el-loading-bcc51e3d.js} +1 -1
- package/es/{el-menu-item-535fef90.js → el-menu-item-339baf6d.js} +21 -21
- package/es/{el-menu-item-4531fd0e.cjs → el-menu-item-83186444.cjs} +1 -1
- package/es/{el-message-box-621ed2f1.js → el-message-box-035b699c.js} +9 -9
- package/es/{el-message-box-f5717070.cjs → el-message-box-c62d3217.cjs} +1 -1
- package/es/{el-overlay-23de4324.js → el-overlay-3e17604b.js} +3 -3
- package/es/{el-overlay-151708cb.cjs → el-overlay-8f1da9e8.cjs} +1 -1
- package/es/{el-popconfirm-b8aa490e.cjs → el-popconfirm-4e32ec1b.cjs} +1 -1
- package/es/{el-popconfirm-bcff0aa5.js → el-popconfirm-f8a86e2c.js} +4 -4
- package/es/{el-popper-43ec37f4.cjs → el-popper-a4f3ae4d.cjs} +1 -1
- package/es/{el-popper-03988662.js → el-popper-fbd0dcea.js} +1 -1
- package/es/{el-segmented-db3a86be.js → el-segmented-56bd58a8.js} +2 -2
- package/es/{el-segmented-79a1367c.cjs → el-segmented-833ac177.cjs} +1 -1
- package/es/{el-select-4bd0010d.js → el-select-44fa547d.js} +8 -8
- package/es/{el-select-ec04e7ce.cjs → el-select-70806166.cjs} +1 -1
- package/es/{el-table-column-35e47229.cjs → el-table-column-3e2cdfca.cjs} +1 -1
- package/es/{el-table-column-7af2bec7.js → el-table-column-b74a857d.js} +9 -9
- package/es/{el-tag-bef9e83d.js → el-tag-66c83156.js} +2 -2
- package/es/{el-tag-5d5608e0.cjs → el-tag-9c2f329f.cjs} +1 -1
- package/es/{el-text-f998902c.js → el-text-034bd3f4.js} +1 -1
- package/es/{el-text-abf10fdf.cjs → el-text-6b2374bb.cjs} +1 -1
- package/es/index-00f6ca75.cjs +1 -0
- package/es/{index-a00e0534.cjs → index-185792a7.cjs} +1 -1
- package/es/{index-d1b554ac.js → index-1e2a38ab.js} +2 -2
- package/es/{index-f3f6ee27.js → index-27416486.js} +9 -9
- package/es/{index-2d7ac156.cjs → index-47c9eb63.cjs} +2 -2
- package/es/{index-0986e373.cjs → index-4cd74df9.cjs} +1 -1
- package/es/{index-ce21e2ce.js → index-5216d5f5.js} +5 -5
- package/es/{index-cfcd60ca.cjs → index-5c0dd943.cjs} +1 -1
- package/es/{index-cfd00899.cjs → index-870b6c9d.cjs} +1 -1
- package/es/{index-a83ec4b7.js → index-8c2d7e05.js} +1 -1
- package/es/{index-db846633.cjs → index-9d16cea8.cjs} +1 -1
- package/es/{index-936e7dec.js → index-a9785a4f.js} +1 -1
- package/es/{index-60c69dd1.cjs → index-ac8dc53d.cjs} +1 -1
- package/es/{index-102edf6c.js → index-c5f2d372.js} +2 -2
- package/es/{index-6290ae0f.js → index-c6584658.js} +2 -2
- package/es/{index-5c44d71a.js → index-eb96e074.js} +149 -119
- package/es/{index-5ed42e9c.js → index-f034b910.js} +2 -2
- package/es/{index-c58e90e9.cjs → index-f58b2920.cjs} +1 -1
- package/es/{python-71f59893.cjs → python-2623e545.cjs} +1 -1
- package/es/{python-6cc6ec1b.js → python-85a62852.js} +17 -17
- package/es/style.css +1 -1
- package/es/{use-form-common-props-02be4928.cjs → use-form-common-props-ac666630.cjs} +1 -1
- package/es/{use-form-common-props-76a09d89.js → use-form-common-props-ed72e914.js} +11 -11
- package/es/{use-global-config-31b3ec57.cjs → use-global-config-23472471.cjs} +1 -1
- package/es/{use-global-config-0f43458b.js → use-global-config-ee406d58.js} +2 -2
- package/es/{validator-ea292506.cjs → validator-366dbf39.cjs} +1 -1
- package/es/{validator-0e037618.js → validator-c60a8956.js} +1 -1
- package/es/{zh-cn-1227eb8a.js → zh-cn-b59a0910.js} +1 -1
- package/es/{zh-cn-0edc7234.cjs → zh-cn-c21d12d8.cjs} +1 -1
- package/introduce.md +41 -0
- package/lib/aiTools.js +4 -0
- package/lib/bundle.js +1 -1
- package/lib/bundle.umd.cjs +189 -189
- package/lib/{index-8b55d8c8.js → index-72506c43.js} +20695 -20434
- package/lib/{python-f164f783.js → python-d3119b5a.js} +1 -1
- package/lib/style.css +1 -1
- package/package.json +9 -2
- package/packages/CustomFunction/components/ManageDialog.vue +300 -3
- package/packages/VarietyAiHelper/index.vue +42 -6
- package/public/aiTools.js +4 -0
- package/src/App.vue +16 -16
- package/tests/components/ChartLayout.spec.ts +67 -0
- package/tests/components/ConfigProvider.spec.ts +99 -0
- package/tests/components/Contextmenu.spec.ts +79 -0
- package/tests/components/CustomFunction.spec.ts +185 -0
- package/tests/components/FactorWarning.spec.ts +287 -0
- package/tests/components/HeatMap.spec.ts +83 -0
- package/tests/components/Kline.spec.ts +140 -0
- package/tests/components/Kline2.spec.ts +311 -0
- package/tests/components/KlineBasic.spec.ts +429 -0
- package/tests/components/KlineConfig.spec.ts +278 -0
- package/tests/components/KlineNew.spec.ts +278 -0
- package/tests/components/KlinePlus.spec.ts +284 -0
- package/tests/components/LinearLegend.spec.ts +69 -0
- package/tests/components/ManageDialog.spec.ts +253 -0
- package/tests/components/Map.spec.ts +71 -0
- package/tests/components/MonacoEditor.spec.ts +530 -0
- package/tests/components/Pagination.spec.ts +59 -0
- package/tests/components/PasswordPrompt.spec.ts +72 -0
- package/tests/components/Pie.spec.ts +96 -0
- package/tests/components/Table.spec.ts +186 -0
- package/tests/components/TreeMap.spec.ts +100 -0
- package/tests/components/User.spec.ts +130 -0
- package/tests/components/VarSelectDialog.spec.ts +131 -0
- package/tests/components/VarietyAiHelper.spec.ts +164 -0
- package/tests/components/VarietyAutoComplete.spec.ts +110 -0
- package/tests/components/VarietySearch.spec.ts +526 -0
- package/tests/components/VarietyTextCopy.spec.ts +86 -0
- package/tests/components/VirtualTable.spec.ts +285 -0
- package/tests/helpers/mountWithPlugins.ts +19 -0
- package/tests/mocks/echarts.kline.mock.ts +41 -0
- package/tests/mocks/echarts.mock.ts +38 -0
- package/tests/mocks/monaco.mock.ts +37 -0
- package/tests/mocks/st-func.mock.ts +52 -0
- package/tests/mocks/talib.mock.ts +13 -0
- package/tests/setup.ts +65 -0
- package/tests/smoke.spec.ts +12 -0
- package/vitest.config.ts +56 -0
- package/workflow.md +33 -0
- package/es/aiTools-9fd42de8.cjs +0 -2
- package/es/index-476c4459.cjs +0 -1
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
{"id":"t1.tool9","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/index.vue lines=1-400/1068 bytes=35463]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/index.vue\", offset=401, limit=400)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 1\t\u003c!-- 单品种单周期K线业务组件 --\u003e\r\n 2\t\u003cscript setup\u003e\r\n 3\timport dayjs from \"dayjs\";\r\n 4\timport * as echarts from \"echarts\";\r\n 5\timport { stMath, debounce, addResizeListener } from \"st-func\";\r\n 6\timport { ref, watch, computed, onMounted, onUnmounted, inject } from \"vue\";\r\n 7\timport { loadKlineConfig, checkTimeInterval, getSubOptions, mergeklineData, handleMarkPointTradeLog, handleNetPositionLine, handleTradeIncomeRateLine, normalizeToKlineTimeByMatch } from \"./utils.js\";\r\n 8\timport Tips from \"./components/Tips.vue\";\r\n 9\timport SliderChart from \"./components/SliderChart.vue\";\r\n 10\t\r\n 11\tconst { round, formatValue } = stMath;\r\n 12\tconst { request } = inject(\"stConfig\"); // 组件库全局配置\r\n 13\t\r\n 14\tconst loading = ref(false);\r\n 15\tconst isHover = ref(false);\r\n 16\tconst props = defineProps({\r\n 17\t varietyCode: { type: String, required: true },\r\n 18\t varietyStock: { type: Number, required: true }, // 品种市场类型 0-期货, 1-股票, 2-期权\r\n 19\t indicatorStore: { type: Object, required: true }, // 指标配置Store\r\n 20\t userKlineConfig: { type: Object, required: true }, // 用户自定义的K线配置\r\n 21\t\r\n 22\t // 交互选项\r\n 23\t cycle: { type: String, required: true },\r\n 24\t sellBuy: { type: Number, default: 1 },\r\n 25\t rightType: { type: [Number, null], default: null }, // 复权类型\r\n 26\t klineType: { type: [Number, null], default: null }, // 合约类型\r\n 27\t incomeType: { type: Number, default: 0 }, // 收益计算类型 0-单利, 1-复利\r\n 28\t initTimeRange: { type: Array, required: true },\r\n 29\t mainIndicator: { type: String, required: true },\r\n 30\t\r\n 31\t // 绘制数据\r\n 32\t tradeLog: { type: Array, default: () =\u003e [] }, // 成交数据\r\n 33\t netPositionData: { type: Array, default: () =\u003e [] }, // 净值数据\r\n 34\t tradeIncomeRateData: { type: Array, default: () =\u003e [] }, // 成交收益率数据\r\n 35\t\r\n 36\t // 强制定位高亮(非必填)\r\n 37\t positionTime: { type: [String, null], default: null },\r\n 38\t // 同步刷选范围(非必填)\r\n 39\t syncBrushRange: { type: Object, default: () =\u003e ({ cycleList: [], startTime: null, endTime: null }) },\r\n 40\t\r\n 41\t // 业务系统相关\r\n 42\t deleteFirstNumber: { type: Number, default: 1 }, // 绩效标记\r\n 43\t withoutPermission: { type: Number, default: 0 }, // 三方标记(不需要token授权)\r\n 44\t});\r\n 45\t\r\n 46\t// 加载标记\r\n 47\tlet isLoadNew = false;\r\n 48\tlet isloadAllNew = false;\r\n 49\tlet isLoadHistory = false;\r\n 50\tlet isloadAllHistory = false;\r\n 51\t\r\n 52\tlet resizeRo = null;\r\n 53\tlet subChartIns = null;\r\n 54\tlet mainChartIns = null;\r\n 55\tconst mainChartRef = ref(null);\r\n 56\tconst subChartRef = ref(null);\r\n 57\tconst sliderChartRef = ref(null);\r\n 58\t\r\n 59\t// K线数据\r\n 60\tconst klineData = ref({\r\n 61\t data: [],\r\n 62\t mainIndicator: [],\r\n 63\t subIndicator: [],\r\n 64\t time: [],\r\n 65\t});\r\n 66\tconst activeIndex = ref(0);\r\n 67\tconst screenTimeRange = ref([]); // 当屏首末时间\r\n 68\tconst contextmenuKlineTime = ref(null); // 右键点击的那一根K线时间\r\n 69\t\r\n 70\t// 指标数据\r\n 71\tconst subIndicator = ref(\"VOL\");\r\n 72\tconst mainTips = computed(() =\u003e {\r\n 73\t const itemData = klineData.value.data[activeIndex.value];\r\n 74\t if (!itemData) {\r\n 75\t return [\r\n 76\t { label: \"开\", value: \"-\" },\r\n 77\t { label: \"高\", value: \"-\" },\r\n 78\t { label: \"低\", value: \"-\" },\r\n 79\t { label: \"收\", value: \"-\" },\r\n 80\t { label: \"额\", value: \"-\" },\r\n 81\t { label: \"涨跌\", value: \"-\" },\r\n 82\t ];\r\n 83\t }\r\n 84\t const result = [\r\n 85\t { label: \"开\", value: itemData[0] },\r\n 86\t { label: \"高\", value: itemData[3] },\r\n 87\t { label: \"低\", value: itemData[2] },\r\n 88\t { label: \"收\", value: itemData[1] },\r\n 89\t ];\r\n 90\t // 特殊: 交易总额\r\n 91\t itemData[4] !== null \u0026\u0026 result.push({ label: \"额\", value: formatValue(itemData[4]) });\r\n 92\t // 特殊: 涨跌幅 公式:收盘价 - 昨收 / (昨收绝对值)\r\n 93\t let diffColor;\r\n 94\t if (itemData[6] \u003e 0) {\r\n 95\t diffColor = \"red\";\r\n 96\t } else if (itemData[6] \u003c 0) {\r\n 97\t diffColor = \"green\";\r\n 98\t }\r\n 99\t result.push({ label: \"涨跌\", value: `${round(itemData[6])}%`, color: diffColor });\r\n 100\t return result;\r\n 101\t});\r\n 102\tconst mainIndicatorTips = computed(() =\u003e {\r\n 103\t return (\r\n 104\t klineData.value?.mainIndicator?.map((item) =\u003e {\r\n 105\t return { label: item.key, value: round(item.data[activeIndex.value]), color: item.color };\r\n 106\t }) || []\r\n 107\t );\r\n 108\t});\r\n 109\tconst subIndicatorTips = computed(() =\u003e {\r\n 110\t return klineData.value?.subIndicator?.map((item) =\u003e ({ label: item.key, color: item.color, value: item.data[activeIndex.value] || \"-\" })) || [];\r\n 111\t});\r\n 112\t\r\n 113\t// 图表: 初始化\r\n 114\tconst initChart = () =\u003e {\r\n 115\t // 检查DOM元素是否存在\r\n 116\t if (!mainChartRef.value) {\r\n 117\t console.warn(\"[K线] 主图DOM元素不存在,取消初始化\");\r\n 118\t return false;\r\n 119\t }\r\n 120\t if (mainChartIns) return true;\r\n 121\t\r\n 122\t try {\r\n 123\t // 主图\r\n 124\t mainChartIns = echarts.init(mainChartRef.value);\r\n 125\t mainChartIns.on(\r\n 126\t \"highlight\",\r\n 127\t debounce((params) =\u003e {\r\n 128\t if (params.dataIndex) {\r\n 129\t activeIndex.value = params.dataIndex;\r\n 130\t } else {\r\n 131\t activeIndex.value = params.batch?.[0].dataIndex ?? -1;\r\n 132\t }\r\n 133\t }, 10),\r\n 134\t );\r\n 135\t mainChartIns.on(\r\n 136\t \"datazoom\",\r\n 137\t debounce(async () =\u003e {\r\n 138\t const { loadCheckCount } = loadKlineConfig;\r\n 139\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 140\t // 当前开始索引 \u003c 阈值边界, 触发加载更多数据\r\n 141\t if (isLoadHistory === false \u0026\u0026 isloadAllHistory === false \u0026\u0026 startValue \u003c loadCheckCount) await getMoreData(\"history\");\r\n 142\t // 当前结束索引 \u003e 阈值边界, 触发加载更多数据\r\n 143\t if (isLoadNew === false \u0026\u0026 isloadAllNew === false \u0026\u0026 endValue \u003e klineData.value.time.length - loadCheckCount) await getMoreData(\"new\");\r\n 144\t getScreenTimeRange();\r\n 145\t drawScreenMaxPrice();\r\n 146\t }),\r\n 147\t );\r\n 148\t mainChartIns.on(\"globalout\", () =\u003e {\r\n 149\t if (mainChartIns) {\r\n 150\t const option = mainChartIns.getOption();\r\n 151\t activeIndex.value = option.dataZoom[0].endValue;\r\n 152\t }\r\n 153\t });\r\n 154\t mainChartIns.getZr().on(\"contextmenu\", () =\u003e {\r\n 155\t contextmenuKlineTime.value = klineData.value?.time[activeIndex.value];\r\n 156\t });\r\n 157\t window.addEventListener(\"keydown\", handleKeyDownEvent);\r\n 158\t\r\n 159\t // 副图\r\n 160\t if (props.userKlineConfig.enable_subChart \u0026\u0026 subChartRef.value) {\r\n 161\t subChartIns = echarts.init(subChartRef.value);\r\n 162\t echarts.connect([mainChartIns, subChartIns]);\r\n 163\t }\r\n 164\t\r\n 165\t // DOM缩放\r\n 166\t if (mainChartRef.value) {\r\n 167\t resizeRo = addResizeListener(mainChartRef.value);\r\n 168\t resizeRo.listen(() =\u003e {\r\n 169\t requestAnimationFrame(() =\u003e {\r\n 170\t mainChartIns?.resize();\r\n 171\t subChartIns?.resize();\r\n 172\t sliderChartRef.value?.resize();\r\n 173\t });\r\n 174\t });\r\n 175\t }\r\n 176\t\r\n 177\t return true;\r\n 178\t } catch (error) {\r\n 179\t console.error(\"[st-klinePlus]: 初始化异常:\", error);\r\n 180\t return false;\r\n 181\t }\r\n 182\t};\r\n 183\t// 图表: 获取数据 [首屏]\r\n 184\tconst getMainData = async ({ startTime, endTime }) =\u003e {\r\n 185\t // 加载状态初始化\r\n 186\t isLoadHistory = false;\r\n 187\t isloadAllHistory = false;\r\n 188\t isLoadNew = false;\r\n 189\t isloadAllNew = false;\r\n 190\t try {\r\n 191\t loading.value = true;\r\n 192\t const params = {\r\n 193\t varietyCode: props.varietyCode,\r\n 194\t cycle: props.cycle, // 周期\r\n 195\t right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式\r\n 196\t contractType: props.varietyStock ? null : props.klineType, // 合约类型\r\n 197\t mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),\r\n 198\t subIndicator: subIndicator.value,\r\n 199\t deleteFirstNumber: props.deleteFirstNumber,\r\n 200\t withoutPermission: props.withoutPermission,\r\n 201\t };\r\n 202\t // Echarts如果一屏如果正好展示了所有数据, 是无法缩放触发加载更多的\r\n 203\t // 所以在请求中需要对开始和结束时间进行延伸, 但是首屏的展示时间定位还是会以传入的开始结束时间为准\r\n 204\t if ([\"1\", \"2\", \"3\", \"4\", \"5\"].includes(props.cycle)) {\r\n 205\t Object.assign(params, {\r\n 206\t startTime: dayjs(startTime).subtract(1, \"day\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 207\t endTime: dayjs(endTime).add(1, \"day\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 208\t });\r\n 209\t } else if ([\"6\", \"7\"].includes(props.cycle)) {\r\n 210\t Object.assign(params, {\r\n 211\t startTime: dayjs(startTime).subtract(50, \"day\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 212\t endTime: dayjs(endTime).add(50, \"day\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 213\t });\r\n 214\t } else {\r\n 215\t Object.assign(params, {\r\n 216\t startTime: dayjs(startTime).subtract(10, \"month\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 217\t endTime: dayjs(endTime).add(10, \"month\").format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 218\t });\r\n 219\t }\r\n 220\t const { body } = await request.post(\"/middleLayer/kline/getKline\", params);\r\n 221\t klineData.value = body ?? { data: [], mainIndicator: [], subIndicator: [], time: [] };\r\n 222\t\r\n 223\t // 找到匹配对应传入的开始, 结束时间的数据索引, 绘制图表\r\n 224\t let startValue = -1;\r\n 225\t let endValue = -1;\r\n 226\t for (let index = 0; index \u003c klineData.value.time.length; index++) {\r\n 227\t const day = dayjs(klineData.value.time[index]).format(\"YYYY-MM-DD\");\r\n 228\t if (startValue !== -1 \u0026\u0026 endValue !== -1) break;\r\n 229\t if (startValue === -1) {\r\n 230\t if (new Date(day) === new Date(startTime)) startValue = index;\r\n 231\t else if (new Date(day) \u003e new Date(startTime)) startValue = index - 1;\r\n 232\t }\r\n 233\t if (endValue === -1) {\r\n 234\t if (new Date(day) === new Date(endTime)) endValue = index;\r\n 235\t else if (new Date(day) \u003e new Date(endTime)) endValue = index - 1;\r\n 236\t }\r\n 237\t }\r\n 238\t if (endValue === -1) endValue = klineData.value.time.length - 1;\r\n 239\t // const startValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(startTime));\r\n 240\t // const endValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(endTime));\r\n 241\t draw({\r\n 242\t startValue: startValue === -1 ? 0 : startValue,\r\n 243\t endValue: endValue === -1 ? klineData.value.time.length - 1 : endValue,\r\n 244\t });\r\n 245\t } finally {\r\n 246\t loading.value = false;\r\n 247\t }\r\n 248\t};\r\n 249\t// 图表: 获取数据 [更多]\r\n 250\tconst getMoreData = async (type) =\u003e {\r\n 251\t const { loadAddCount } = loadKlineConfig;\r\n 252\t switch (type) {\r\n 253\t case \"history\": {\r\n 254\t isLoadHistory = true;\r\n 255\t const params = {\r\n 256\t varietyCode: props.varietyCode,\r\n 257\t cycle: props.cycle,\r\n 258\t right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式\r\n 259\t contractType: props.varietyStock ? null : props.klineType,\r\n 260\t endTime: klineData.value.time[0],\r\n 261\t limit: loadAddCount,\r\n 262\t mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),\r\n 263\t subIndicator: subIndicator.value,\r\n 264\t deleteFirstNumber: props.deleteFirstNumber,\r\n 265\t withoutPermission: props.withoutPermission,\r\n 266\t };\r\n 267\t const { body } = await request.post(\"/middleLayer/kline/getKline\", params);\r\n 268\t // 合并数据\r\n 269\t klineData.value = mergeklineData(body, klineData.value);\r\n 270\t // 判断是否加载完全部的历史数据\r\n 271\t if (body.data.length \u003c loadAddCount) {\r\n 272\t console.log(\"[K线] 获取更多数据: 左侧已全部获取完毕, 关闭\");\r\n 273\t isloadAllHistory = true;\r\n 274\t }\r\n 275\t isLoadHistory = false;\r\n 276\t break;\r\n 277\t }\r\n 278\t case \"new\": {\r\n 279\t isLoadNew = true;\r\n 280\t const params = {\r\n 281\t varietyCode: props.varietyCode,\r\n 282\t cycle: props.cycle, // 周期\r\n 283\t right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式\r\n 284\t contractType: props.varietyStock ? null : props.klineType, // 合约类型\r\n 285\t startTime: klineData.value.time[klineData.value.time.length - 1], // 开始时间\r\n 286\t limit: loadAddCount, // 查询K线数量\r\n 287\t mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),\r\n 288\t subIndicator: subIndicator.value,\r\n 289\t deleteFirstNumber: props.deleteFirstNumber,\r\n 290\t withoutPermission: props.withoutPermission,\r\n 291\t };\r\n 292\t const { body } = await request.post(\"/middleLayer/kline/getKline\", params);\r\n 293\t // 合并数据\r\n 294\t klineData.value = mergeklineData(klineData.value, body);\r\n 295\t // 判断是否加载完全部的新数据\r\n 296\t if (body.data.length \u003c loadAddCount) {\r\n 297\t console.log(\"[K线] 获取更多数据: 右侧已全部获取完毕, 关闭\");\r\n 298\t isloadAllNew = true;\r\n 299\t }\r\n 300\t isLoadNew = false;\r\n 301\t break;\r\n 302\t }\r\n 303\t }\r\n 304\t\r\n 305\t // 绘制图表 (更新载入更多数据后的定位索引)\r\n 306\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 307\t const klineDataTime = mainChartIns.getOption()?.xAxis[0].data;\r\n 308\t const newStartValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[startValue]));\r\n 309\t const newEndValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[endValue]));\r\n 310\t draw({\r\n 311\t startValue: newStartValue === -1 ? 0 : newStartValue,\r\n 312\t endValue: newEndValue === -1 ? klineData.value.time.length - 1 : newEndValue,\r\n 313\t });\r\n 314\t};\r\n 315\t// 图表: 获取数据 [当屏首末时间范围]\r\n 316\tconst getScreenTimeRange = () =\u003e {\r\n 317\t if (!klineData.value.time.length) return (screenTimeRange.value = []);\r\n 318\t const { time } = klineData.value;\r\n 319\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 320\t const startTime = time[startValue];\r\n 321\t const endTime = time[endValue];\r\n 322\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 323\t screenTimeRange.value = [dayjs(startTime).format(\"YYYY-MM-DD\"), dayjs(endTime).format(\"YYYY-MM-DD\")];\r\n 324\t } else {\r\n 325\t screenTimeRange.value = [startTime, endTime];\r\n 326\t }\r\n 327\t};\r\n 328\t\r\n 329\t// 图表: 绘制(主流程)\r\n 330\tconst draw = (params = { startValue: 0, endValue: 0 }) =\u003e {\r\n 331\t const isInitialized = initChart();\r\n 332\t // 确保图表存在\r\n 333\t if (!isInitialized || !mainChartIns) {\r\n 334\t return console.warn(\"[st-klinePlus]: 图表未准备就绪, 跳过绘制\");\r\n 335\t }\r\n 336\t // 检查数据是否存在\r\n 337\t if (!klineData.value.time.length) {\r\n 338\t return console.warn(\"[st-klinePlus]: 图表无数据, 跳过绘制\");\r\n 339\t }\r\n 340\t\r\n 341\t const { maxValueSpan } = loadKlineConfig;\r\n 342\t const { time, data, mainIndicator } = klineData.value;\r\n 343\t\r\n 344\t // 如果超过了最大根数限制, 要求endValue不变, startValue进行变换\r\n 345\t const startValue = params.endValue - params.startValue \u003e maxValueSpan ? params.endValue - maxValueSpan : params.startValue;\r\n 346\t const endValue = params.endValue;\r\n 347\t\r\n 348\t // 配置: 指标线\r\n 349\t const indicatorSeries = mainIndicator.map((item) =\u003e {\r\n 350\t return {\r\n 351\t name: item.key,\r\n 352\t type: \"line\",\r\n 353\t silent: true,\r\n 354\t symbol: \"none\",\r\n 355\t data: item.data,\r\n 356\t lineStyle: {\r\n 357\t width: item.width || 1,\r\n 358\t },\r\n 359\t itemStyle: {\r\n 360\t color: item.color,\r\n 361\t },\r\n 362\t };\r\n 363\t });\r\n 364\t // 配置数据: 净值曲线\r\n 365\t const { netPositionLineData } = handleNetPositionLine(props.netPositionData, props.cycle);\r\n 366\t // 配置数据: 成交收益率曲线\r\n 367\t const { tradeIncomeRateLineData } = handleTradeIncomeRateLine(props.tradeIncomeRateData, props.cycle, props.incomeType, time);\r\n 368\t // 配置数据: 成交点位 + 连线\r\n 369\t const { tradePointData, tradeLineData } = handleMarkPointTradeLog(props.tradeLog, props.cycle, props.sellBuy, time, data, props.userKlineConfig);\r\n 370\t\r\n 371\t /**\r\n 372\t * @description: 渲染图表\r\n 373\t */\r\n 374\t mainChartIns.setOption(\r\n 375\t {\r\n 376\t animation: false,\r\n 377\t grid: {\r\n 378\t top: \"40px\",\r\n 379\t left: \"60px\",\r\n 380\t right: \"20px\",\r\n 381\t bottom: \"20px\",\r\n 382\t },\r\n 383\t dataZoom: [\r\n 384\t {\r\n 385\t type: \"inside\",\r\n 386\t startValue,\r\n 387\t endValue,\r\n 388\t minValueSpan: loadKlineConfig.minValueSpan,\r\n 389\t maxValueSpan: loadKlineConfig.maxValueSpan,\r\n 390\t },\r\n 391\t ],\r\n 392\t tooltip: {\r\n 393\t trigger: \"axis\",\r\n 394\t appendToBody: true,\r\n 395\t confine: true,\r\n 396\t axisPointer: {\r\n 397\t type: \"cross\",\r\n 398\t label: {\r\n 399\t rich: {},\r\n 400\t formatter: (data) =\u003e {\r\n","preview":"1\t\u003c!-- 单品种单周期K线业务组件 --\u003e\n2\t\u003cscript setup\u003e\n3\timport dayjs from \"dayjs\";","bytes":18317,"is_error":false,"stored_at":"2026-07-27T07:03:47.2538846Z"}
|
|
2
|
+
{"id":"t1.tool11","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/index.vue lines=271-670/1068 bytes=35463]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/index.vue\", offset=671, limit=400)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 271\t if (body.data.length \u003c loadAddCount) {\r\n 272\t console.log(\"[K线] 获取更多数据: 左侧已全部获取完毕, 关闭\");\r\n 273\t isloadAllHistory = true;\r\n 274\t }\r\n 275\t isLoadHistory = false;\r\n 276\t break;\r\n 277\t }\r\n 278\t case \"new\": {\r\n 279\t isLoadNew = true;\r\n 280\t const params = {\r\n 281\t varietyCode: props.varietyCode,\r\n 282\t cycle: props.cycle, // 周期\r\n 283\t right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式\r\n 284\t contractType: props.varietyStock ? null : props.klineType, // 合约类型\r\n 285\t startTime: klineData.value.time[klineData.value.time.length - 1], // 开始时间\r\n 286\t limit: loadAddCount, // 查询K线数量\r\n 287\t mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),\r\n 288\t subIndicator: subIndicator.value,\r\n 289\t deleteFirstNumber: props.deleteFirstNumber,\r\n 290\t withoutPermission: props.withoutPermission,\r\n 291\t };\r\n 292\t const { body } = await request.post(\"/middleLayer/kline/getKline\", params);\r\n 293\t // 合并数据\r\n 294\t klineData.value = mergeklineData(klineData.value, body);\r\n 295\t // 判断是否加载完全部的新数据\r\n 296\t if (body.data.length \u003c loadAddCount) {\r\n 297\t console.log(\"[K线] 获取更多数据: 右侧已全部获取完毕, 关闭\");\r\n 298\t isloadAllNew = true;\r\n 299\t }\r\n 300\t isLoadNew = false;\r\n 301\t break;\r\n 302\t }\r\n 303\t }\r\n 304\t\r\n 305\t // 绘制图表 (更新载入更多数据后的定位索引)\r\n 306\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 307\t const klineDataTime = mainChartIns.getOption()?.xAxis[0].data;\r\n 308\t const newStartValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[startValue]));\r\n 309\t const newEndValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[endValue]));\r\n 310\t draw({\r\n 311\t startValue: newStartValue === -1 ? 0 : newStartValue,\r\n 312\t endValue: newEndValue === -1 ? klineData.value.time.length - 1 : newEndValue,\r\n 313\t });\r\n 314\t};\r\n 315\t// 图表: 获取数据 [当屏首末时间范围]\r\n 316\tconst getScreenTimeRange = () =\u003e {\r\n 317\t if (!klineData.value.time.length) return (screenTimeRange.value = []);\r\n 318\t const { time } = klineData.value;\r\n 319\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 320\t const startTime = time[startValue];\r\n 321\t const endTime = time[endValue];\r\n 322\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 323\t screenTimeRange.value = [dayjs(startTime).format(\"YYYY-MM-DD\"), dayjs(endTime).format(\"YYYY-MM-DD\")];\r\n 324\t } else {\r\n 325\t screenTimeRange.value = [startTime, endTime];\r\n 326\t }\r\n 327\t};\r\n 328\t\r\n 329\t// 图表: 绘制(主流程)\r\n 330\tconst draw = (params = { startValue: 0, endValue: 0 }) =\u003e {\r\n 331\t const isInitialized = initChart();\r\n 332\t // 确保图表存在\r\n 333\t if (!isInitialized || !mainChartIns) {\r\n 334\t return console.warn(\"[st-klinePlus]: 图表未准备就绪, 跳过绘制\");\r\n 335\t }\r\n 336\t // 检查数据是否存在\r\n 337\t if (!klineData.value.time.length) {\r\n 338\t return console.warn(\"[st-klinePlus]: 图表无数据, 跳过绘制\");\r\n 339\t }\r\n 340\t\r\n 341\t const { maxValueSpan } = loadKlineConfig;\r\n 342\t const { time, data, mainIndicator } = klineData.value;\r\n 343\t\r\n 344\t // 如果超过了最大根数限制, 要求endValue不变, startValue进行变换\r\n 345\t const startValue = params.endValue - params.startValue \u003e maxValueSpan ? params.endValue - maxValueSpan : params.startValue;\r\n 346\t const endValue = params.endValue;\r\n 347\t\r\n 348\t // 配置: 指标线\r\n 349\t const indicatorSeries = mainIndicator.map((item) =\u003e {\r\n 350\t return {\r\n 351\t name: item.key,\r\n 352\t type: \"line\",\r\n 353\t silent: true,\r\n 354\t symbol: \"none\",\r\n 355\t data: item.data,\r\n 356\t lineStyle: {\r\n 357\t width: item.width || 1,\r\n 358\t },\r\n 359\t itemStyle: {\r\n 360\t color: item.color,\r\n 361\t },\r\n 362\t };\r\n 363\t });\r\n 364\t // 配置数据: 净值曲线\r\n 365\t const { netPositionLineData } = handleNetPositionLine(props.netPositionData, props.cycle);\r\n 366\t // 配置数据: 成交收益率曲线\r\n 367\t const { tradeIncomeRateLineData } = handleTradeIncomeRateLine(props.tradeIncomeRateData, props.cycle, props.incomeType, time);\r\n 368\t // 配置数据: 成交点位 + 连线\r\n 369\t const { tradePointData, tradeLineData } = handleMarkPointTradeLog(props.tradeLog, props.cycle, props.sellBuy, time, data, props.userKlineConfig);\r\n 370\t\r\n 371\t /**\r\n 372\t * @description: 渲染图表\r\n 373\t */\r\n 374\t mainChartIns.setOption(\r\n 375\t {\r\n 376\t animation: false,\r\n 377\t grid: {\r\n 378\t top: \"40px\",\r\n 379\t left: \"60px\",\r\n 380\t right: \"20px\",\r\n 381\t bottom: \"20px\",\r\n 382\t },\r\n 383\t dataZoom: [\r\n 384\t {\r\n 385\t type: \"inside\",\r\n 386\t startValue,\r\n 387\t endValue,\r\n 388\t minValueSpan: loadKlineConfig.minValueSpan,\r\n 389\t maxValueSpan: loadKlineConfig.maxValueSpan,\r\n 390\t },\r\n 391\t ],\r\n 392\t tooltip: {\r\n 393\t trigger: \"axis\",\r\n 394\t appendToBody: true,\r\n 395\t confine: true,\r\n 396\t axisPointer: {\r\n 397\t type: \"cross\",\r\n 398\t label: {\r\n 399\t rich: {},\r\n 400\t formatter: (data) =\u003e {\r\n 401\t const { axisDimension, value } = data;\r\n 402\t if (axisDimension === \"x\") {\r\n 403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 404\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 405\t } else {\r\n 406\t return value;\r\n 407\t }\r\n 408\t } else {\r\n 409\t return String(round(value));\r\n 410\t }\r\n 411\t },\r\n 412\t },\r\n 413\t },\r\n 414\t formatter: (params) =\u003e {\r\n 415\t if (!params?.length) return null;\r\n 416\t let body = \"\";\r\n 417\t params.forEach((item) =\u003e {\r\n 418\t // 资产持仓净值\r\n 419\t if (item.seriesName === \"netPosition\" \u0026\u0026 item.data !== null) {\r\n 420\t body += `\u003cdiv\u003e资产持仓净值: ${item.data[1]}\u003c/div\u003e`;\r\n 421\t }\r\n 422\t // 成交数据\r\n 423\t if (item.componentSubType === \"candlestick\") {\r\n 424\t // 买卖点\r\n 425\t if (props.sellBuy === 0) {\r\n 426\t // 买卖点渲染逻辑\r\n 427\t const sellBuy = tradePointData.filter((i) =\u003e i?.coord[0] === item.axisValue);\r\n 428\t let buy = 0;\r\n 429\t let sell = 0;\r\n 430\t sellBuy.forEach((i) =\u003e {\r\n 431\t const { tradeType, amount } = i.customData;\r\n 432\t if (tradeType === \"买\") {\r\n 433\t buy += amount;\r\n 434\t }\r\n 435\t if (tradeType === \"卖\") {\r\n 436\t sell += amount;\r\n 437\t }\r\n 438\t });\r\n 439\t if (buy) body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 440\t if (sell) body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 441\t }\r\n 442\t // 开平点\r\n 443\t else {\r\n 444\t // 开平点渲染逻辑\r\n 445\t tradePointData.forEach((i) =\u003e {\r\n 446\t if (i?.coord[0] === item.axisValue) {\r\n 447\t const { tradeType, amount, profitAndLoss, openPriceAll, closePriceAll } = i.customData;\r\n 448\t let tooltip = `\u003cdiv\u003e${tradeType}: ${amount}手\u003c/div\u003e`;\r\n 449\t // 开仓-tooltip: 开仓价\r\n 450\t if (tradeType.includes(\"开\")) {\r\n 451\t tooltip += `\u003cdiv\u003e开仓价: ${(openPriceAll / amount)?.toFixed(2)}\u003c/div\u003e`;\r\n 452\t }\r\n 453\t // 平仓-tooltip: 平仓价, 盈亏, 盈亏比率\r\n 454\t if (tradeType.includes(\"平\")) {\r\n 455\t const openPrice = (openPriceAll / amount)?.toFixed(2);\r\n 456\t const closePrice = (closePriceAll / amount)?.toFixed(2);\r\n 457\t tooltip += `\u003cdiv\u003e平仓价: ${closePrice}\u003c/div\u003e`;\r\n 458\t tooltip += `\u003cdiv\u003e盈亏: ${profitAndLoss?.toFixed(2)}\u003c/div\u003e`;\r\n 459\t tooltip += `\u003cdiv\u003e盈亏比率: ${(((closePrice - openPrice) / openPrice) * 100)?.toFixed(2)} %\u003c/div\u003e`;\r\n 460\t }\r\n 461\t body += tooltip;\r\n 462\t }\r\n 463\t });\r\n 464\t }\r\n 465\t }\r\n 466\t });\r\n 467\t if (!body) return null;\r\n 468\t let time = params[0].axisValue;\r\n 469\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) time = dayjs(time).format(\"YYYY-MM-DD\");\r\n 470\t return `\r\n 471\t \u003cdiv\u003e\r\n 472\t \u003cspan style=\"font-weight: bold;\"\u003e${time}\u003c/span\u003e\r\n 473\t ${body}\r\n 474\t \u003c/div\u003e\r\n 475\t `;\r\n 476\t },\r\n 477\t },\r\n 478\t xAxis: {\r\n 479\t show: true,\r\n 480\t type: \"category\",\r\n 481\t data: time,\r\n 482\t splitLine: {\r\n 483\t show: false,\r\n 484\t },\r\n 485\t axisLabel: {\r\n 486\t formatter: (value) =\u003e {\r\n 487\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 488\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 489\t } else {\r\n 490\t return value;\r\n 491\t }\r\n 492\t },\r\n 493\t },\r\n 494\t },\r\n 495\t yAxis: [\r\n 496\t {\r\n 497\t type: \"value\",\r\n 498\t axisLine: {\r\n 499\t show: true,\r\n 500\t },\r\n 501\t splitLine: {\r\n 502\t show: true,\r\n 503\t lineStyle: {\r\n 504\t type: \"dotted\",\r\n 505\t color: \"#333\",\r\n 506\t },\r\n 507\t },\r\n 508\t min: (value) =\u003e {\r\n 509\t const { min, max } = value;\r\n 510\t const interval = Math.abs((max - min) / 10);\r\n 511\t return round(min - interval);\r\n 512\t },\r\n 513\t max: (value) =\u003e {\r\n 514\t const { min, max } = value;\r\n 515\t const interval = Math.abs((max - min) / 10);\r\n 516\t return round(max + interval);\r\n 517\t },\r\n 518\t },\r\n 519\t {\r\n 520\t show: false,\r\n 521\t min: (value) =\u003e {\r\n 522\t const { min, max } = value;\r\n 523\t const interval = Math.abs((max - min) / 10);\r\n 524\t return min - interval;\r\n 525\t },\r\n 526\t max: (value) =\u003e {\r\n 527\t const { min, max } = value;\r\n 528\t const interval = Math.abs((max - min) / 10);\r\n 529\t return max + interval;\r\n 530\t },\r\n 531\t },\r\n 532\t ],\r\n 533\t series: [\r\n 534\t // K线\r\n 535\t {\r\n 536\t type: \"candlestick\",\r\n 537\t name: \"kLine\",\r\n 538\t data,\r\n 539\t markPoint: { data: [...tradePointData] },\r\n 540\t markLine: { data: [...tradeLineData] },\r\n 541\t itemStyle: {\r\n 542\t color: \"transparent\",\r\n 543\t color0: \"#00FFFF\",\r\n 544\t borderColor: \"#FF0000\",\r\n 545\t borderColor0: \"#00FFFF\",\r\n 546\t borderWidth: 1,\r\n 547\t },\r\n 548\t },\r\n 549\t // 净值曲线\r\n 550\t {\r\n 551\t type: \"line\",\r\n 552\t name: \"netPosition\",\r\n 553\t data: netPositionLineData,\r\n 554\t symbol: \"none\",\r\n 555\t yAxisIndex: 1,\r\n 556\t connectNulls: true,\r\n 557\t itemStyle: {\r\n 558\t color: \"#666666\",\r\n 559\t },\r\n 560\t lineStyle: {\r\n 561\t width: 2,\r\n 562\t },\r\n 563\t },\r\n 564\t // 交易收益率曲线\r\n 565\t {\r\n 566\t type: \"line\",\r\n 567\t name: \"tradeIncomeRate\",\r\n 568\t data: tradeIncomeRateLineData,\r\n 569\t symbol: \"none\",\r\n 570\t yAxisIndex: 1,\r\n 571\t connectNulls: false,\r\n 572\t itemStyle: {\r\n 573\t color: \"#666666\",\r\n 574\t },\r\n 575\t lineStyle: {\r\n 576\t width: 2,\r\n 577\t },\r\n 578\t },\r\n 579\t // 指标线\r\n 580\t ...indicatorSeries,\r\n 581\t ],\r\n 582\t toolbox: {\r\n 583\t show: false,\r\n 584\t },\r\n 585\t brush: {\r\n 586\t xAxisIndex: \"all\",\r\n 587\t brushLink: \"all\",\r\n 588\t transformable: false,\r\n 589\t outOfBrush: {\r\n 590\t colorAlpha: 2,\r\n 591\t },\r\n 592\t brushStyle: {\r\n 593\t color: \"rgba(255,255,255,0.1)\",\r\n 594\t borderColor: \"rgba(255,255,255,0.4)\",\r\n 595\t },\r\n 596\t },\r\n 597\t },\r\n 598\t true,\r\n 599\t );\r\n 600\t if (props.userKlineConfig.enable_subChart) {\r\n 601\t subChartIns.setOption(getSubOptions(klineData.value, startValue, endValue), true);\r\n 602\t }\r\n 603\t\r\n 604\t /**\r\n 605\t * @description: 善后处理\r\n 606\t */\r\n 607\t {\r\n 608\t getScreenTimeRange();\r\n 609\t drawScreenMaxPrice();\r\n 610\t activeIndex.value = endValue;\r\n 611\t }\r\n 612\t\r\n 613\t /**\r\n 614\t * @description: 数据框选: brush\r\n 615\t * 由于brush配置中, 如果有一边的X轴时间不在已载入数据中, 整个都会失效, 所以这里使用的是匹配算法\r\n 616\t */\r\n 617\t {\r\n 618\t const brushAreas = [];\r\n 619\t // 1. 成交范围 (灰白色)\r\n 620\t if (props.userKlineConfig.enable_tradeLogBrush \u0026\u0026 tradePointData.length) {\r\n 621\t const [startTime, endTime] = [tradePointData[0].coord[0], tradePointData.at(-1).coord[0]];\r\n 622\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 623\t brushAreas.push({\r\n 624\t brushId: \"开平仓范围\",\r\n 625\t brushType: \"lineX\",\r\n 626\t xAxisIndex: 0,\r\n 627\t coordRange,\r\n 628\t });\r\n 629\t }\r\n 630\t // 2. 同步刷选范围 (蓝色)\r\n 631\t if (props.syncBrushRange.cycleList.includes(props.cycle)) {\r\n 632\t const { startTime, endTime } = props.syncBrushRange;\r\n 633\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 634\t brushAreas.push({\r\n 635\t brushId: \"同步刷选范围\",\r\n 636\t brushType: \"lineX\",\r\n 637\t xAxisIndex: 0,\r\n 638\t coordRange,\r\n 639\t brushStyle: {\r\n 640\t fill: \"rgba(64, 158, 255, 0.2)\",\r\n 641\t stroke: \"rgba(64, 158, 255, 0.8)\",\r\n 642\t lineWidth: 1,\r\n 643\t },\r\n 644\t });\r\n 645\t }\r\n 646\t // 3. 强制定位高亮 (紫色)\r\n 647\t if (props.positionTime) {\r\n 648\t const [startTime, endTime] = [dayjs(props.positionTime).format(\"YYYY-MM-DD 00:00:00\"), dayjs(props.positionTime).format(\"YYYY-MM-DD 23:59:59\")];\r\n 649\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 650\t brushAreas.push({\r\n 651\t brushId: \"强制定位高亮\",\r\n 652\t brushType: \"lineX\",\r\n 653\t xAxisIndex: 0,\r\n 654\t coordRange,\r\n 655\t brushStyle: {\r\n 656\t fill: \"rgba(217, 179, 255, 0.2)\",\r\n 657\t stroke: \"rgba(217, 179, 255, 0.8)\",\r\n 658\t lineWidth: 1,\r\n 659\t },\r\n 660\t });\r\n 661\t }\r\n 662\t mainChartIns.dispatchAction({\r\n 663\t type: \"brush\",\r\n 664\t areas: brushAreas,\r\n 665\t });\r\n 666\t }\r\n 667\t};\r\n 668\t// 图表: 绘制(当屏最高价)\r\n 669\tconst drawScreenMaxPrice = () =\u003e {\r\n 670\t if (!props.userKlineConfig.enable_showScreenMaxPrice || !klineData.value.time.length) return;\r\n","preview":"271\t if (body.data.length \u003c loadAddCount) {\n272\t console.log(\"[K线] 获取更多数据: 左侧已全部获取完毕, 关闭\");\n273\t isloadAllHistory = true;","bytes":16430,"is_error":false,"stored_at":"2026-07-27T07:03:56.4062125Z"}
|
|
3
|
+
{"id":"t1.tool12","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/index.vue lines=271-670/1068 bytes=35463]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/index.vue\", offset=671, limit=400)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 271\t if (body.data.length \u003c loadAddCount) {\r\n 272\t console.log(\"[K线] 获取更多数据: 左侧已全部获取完毕, 关闭\");\r\n 273\t isloadAllHistory = true;\r\n 274\t }\r\n 275\t isLoadHistory = false;\r\n 276\t break;\r\n 277\t }\r\n 278\t case \"new\": {\r\n 279\t isLoadNew = true;\r\n 280\t const params = {\r\n 281\t varietyCode: props.varietyCode,\r\n 282\t cycle: props.cycle, // 周期\r\n 283\t right: props.varietyStock === 1 ? props.rightType : 0, // 复权方式\r\n 284\t contractType: props.varietyStock ? null : props.klineType, // 合约类型\r\n 285\t startTime: klineData.value.time[klineData.value.time.length - 1], // 开始时间\r\n 286\t limit: loadAddCount, // 查询K线数量\r\n 287\t mainIndicatorList: props.indicatorStore.getIndicatorParams(props.mainIndicator),\r\n 288\t subIndicator: subIndicator.value,\r\n 289\t deleteFirstNumber: props.deleteFirstNumber,\r\n 290\t withoutPermission: props.withoutPermission,\r\n 291\t };\r\n 292\t const { body } = await request.post(\"/middleLayer/kline/getKline\", params);\r\n 293\t // 合并数据\r\n 294\t klineData.value = mergeklineData(klineData.value, body);\r\n 295\t // 判断是否加载完全部的新数据\r\n 296\t if (body.data.length \u003c loadAddCount) {\r\n 297\t console.log(\"[K线] 获取更多数据: 右侧已全部获取完毕, 关闭\");\r\n 298\t isloadAllNew = true;\r\n 299\t }\r\n 300\t isLoadNew = false;\r\n 301\t break;\r\n 302\t }\r\n 303\t }\r\n 304\t\r\n 305\t // 绘制图表 (更新载入更多数据后的定位索引)\r\n 306\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 307\t const klineDataTime = mainChartIns.getOption()?.xAxis[0].data;\r\n 308\t const newStartValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[startValue]));\r\n 309\t const newEndValue = klineData.value.time.findIndex((item) =\u003e new Date(item) \u003e= new Date(klineDataTime[endValue]));\r\n 310\t draw({\r\n 311\t startValue: newStartValue === -1 ? 0 : newStartValue,\r\n 312\t endValue: newEndValue === -1 ? klineData.value.time.length - 1 : newEndValue,\r\n 313\t });\r\n 314\t};\r\n 315\t// 图表: 获取数据 [当屏首末时间范围]\r\n 316\tconst getScreenTimeRange = () =\u003e {\r\n 317\t if (!klineData.value.time.length) return (screenTimeRange.value = []);\r\n 318\t const { time } = klineData.value;\r\n 319\t const { startValue, endValue } = mainChartIns.getOption()?.dataZoom[0] ?? {};\r\n 320\t const startTime = time[startValue];\r\n 321\t const endTime = time[endValue];\r\n 322\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 323\t screenTimeRange.value = [dayjs(startTime).format(\"YYYY-MM-DD\"), dayjs(endTime).format(\"YYYY-MM-DD\")];\r\n 324\t } else {\r\n 325\t screenTimeRange.value = [startTime, endTime];\r\n 326\t }\r\n 327\t};\r\n 328\t\r\n 329\t// 图表: 绘制(主流程)\r\n 330\tconst draw = (params = { startValue: 0, endValue: 0 }) =\u003e {\r\n 331\t const isInitialized = initChart();\r\n 332\t // 确保图表存在\r\n 333\t if (!isInitialized || !mainChartIns) {\r\n 334\t return console.warn(\"[st-klinePlus]: 图表未准备就绪, 跳过绘制\");\r\n 335\t }\r\n 336\t // 检查数据是否存在\r\n 337\t if (!klineData.value.time.length) {\r\n 338\t return console.warn(\"[st-klinePlus]: 图表无数据, 跳过绘制\");\r\n 339\t }\r\n 340\t\r\n 341\t const { maxValueSpan } = loadKlineConfig;\r\n 342\t const { time, data, mainIndicator } = klineData.value;\r\n 343\t\r\n 344\t // 如果超过了最大根数限制, 要求endValue不变, startValue进行变换\r\n 345\t const startValue = params.endValue - params.startValue \u003e maxValueSpan ? params.endValue - maxValueSpan : params.startValue;\r\n 346\t const endValue = params.endValue;\r\n 347\t\r\n 348\t // 配置: 指标线\r\n 349\t const indicatorSeries = mainIndicator.map((item) =\u003e {\r\n 350\t return {\r\n 351\t name: item.key,\r\n 352\t type: \"line\",\r\n 353\t silent: true,\r\n 354\t symbol: \"none\",\r\n 355\t data: item.data,\r\n 356\t lineStyle: {\r\n 357\t width: item.width || 1,\r\n 358\t },\r\n 359\t itemStyle: {\r\n 360\t color: item.color,\r\n 361\t },\r\n 362\t };\r\n 363\t });\r\n 364\t // 配置数据: 净值曲线\r\n 365\t const { netPositionLineData } = handleNetPositionLine(props.netPositionData, props.cycle);\r\n 366\t // 配置数据: 成交收益率曲线\r\n 367\t const { tradeIncomeRateLineData } = handleTradeIncomeRateLine(props.tradeIncomeRateData, props.cycle, props.incomeType, time);\r\n 368\t // 配置数据: 成交点位 + 连线\r\n 369\t const { tradePointData, tradeLineData } = handleMarkPointTradeLog(props.tradeLog, props.cycle, props.sellBuy, time, data, props.userKlineConfig);\r\n 370\t\r\n 371\t /**\r\n 372\t * @description: 渲染图表\r\n 373\t */\r\n 374\t mainChartIns.setOption(\r\n 375\t {\r\n 376\t animation: false,\r\n 377\t grid: {\r\n 378\t top: \"40px\",\r\n 379\t left: \"60px\",\r\n 380\t right: \"20px\",\r\n 381\t bottom: \"20px\",\r\n 382\t },\r\n 383\t dataZoom: [\r\n 384\t {\r\n 385\t type: \"inside\",\r\n 386\t startValue,\r\n 387\t endValue,\r\n 388\t minValueSpan: loadKlineConfig.minValueSpan,\r\n 389\t maxValueSpan: loadKlineConfig.maxValueSpan,\r\n 390\t },\r\n 391\t ],\r\n 392\t tooltip: {\r\n 393\t trigger: \"axis\",\r\n 394\t appendToBody: true,\r\n 395\t confine: true,\r\n 396\t axisPointer: {\r\n 397\t type: \"cross\",\r\n 398\t label: {\r\n 399\t rich: {},\r\n 400\t formatter: (data) =\u003e {\r\n 401\t const { axisDimension, value } = data;\r\n 402\t if (axisDimension === \"x\") {\r\n 403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 404\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 405\t } else {\r\n 406\t return value;\r\n 407\t }\r\n 408\t } else {\r\n 409\t return String(round(value));\r\n 410\t }\r\n 411\t },\r\n 412\t },\r\n 413\t },\r\n 414\t formatter: (params) =\u003e {\r\n 415\t if (!params?.length) return null;\r\n 416\t let body = \"\";\r\n 417\t params.forEach((item) =\u003e {\r\n 418\t // 资产持仓净值\r\n 419\t if (item.seriesName === \"netPosition\" \u0026\u0026 item.data !== null) {\r\n 420\t body += `\u003cdiv\u003e资产持仓净值: ${item.data[1]}\u003c/div\u003e`;\r\n 421\t }\r\n 422\t // 成交数据\r\n 423\t if (item.componentSubType === \"candlestick\") {\r\n 424\t // 买卖点\r\n 425\t if (props.sellBuy === 0) {\r\n 426\t // 买卖点渲染逻辑\r\n 427\t const sellBuy = tradePointData.filter((i) =\u003e i?.coord[0] === item.axisValue);\r\n 428\t let buy = 0;\r\n 429\t let sell = 0;\r\n 430\t sellBuy.forEach((i) =\u003e {\r\n 431\t const { tradeType, amount } = i.customData;\r\n 432\t if (tradeType === \"买\") {\r\n 433\t buy += amount;\r\n 434\t }\r\n 435\t if (tradeType === \"卖\") {\r\n 436\t sell += amount;\r\n 437\t }\r\n 438\t });\r\n 439\t if (buy) body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 440\t if (sell) body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 441\t }\r\n 442\t // 开平点\r\n 443\t else {\r\n 444\t // 开平点渲染逻辑\r\n 445\t tradePointData.forEach((i) =\u003e {\r\n 446\t if (i?.coord[0] === item.axisValue) {\r\n 447\t const { tradeType, amount, profitAndLoss, openPriceAll, closePriceAll } = i.customData;\r\n 448\t let tooltip = `\u003cdiv\u003e${tradeType}: ${amount}手\u003c/div\u003e`;\r\n 449\t // 开仓-tooltip: 开仓价\r\n 450\t if (tradeType.includes(\"开\")) {\r\n 451\t tooltip += `\u003cdiv\u003e开仓价: ${(openPriceAll / amount)?.toFixed(2)}\u003c/div\u003e`;\r\n 452\t }\r\n 453\t // 平仓-tooltip: 平仓价, 盈亏, 盈亏比率\r\n 454\t if (tradeType.includes(\"平\")) {\r\n 455\t const openPrice = (openPriceAll / amount)?.toFixed(2);\r\n 456\t const closePrice = (closePriceAll / amount)?.toFixed(2);\r\n 457\t tooltip += `\u003cdiv\u003e平仓价: ${closePrice}\u003c/div\u003e`;\r\n 458\t tooltip += `\u003cdiv\u003e盈亏: ${profitAndLoss?.toFixed(2)}\u003c/div\u003e`;\r\n 459\t tooltip += `\u003cdiv\u003e盈亏比率: ${(((closePrice - openPrice) / openPrice) * 100)?.toFixed(2)} %\u003c/div\u003e`;\r\n 460\t }\r\n 461\t body += tooltip;\r\n 462\t }\r\n 463\t });\r\n 464\t }\r\n 465\t }\r\n 466\t });\r\n 467\t if (!body) return null;\r\n 468\t let time = params[0].axisValue;\r\n 469\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) time = dayjs(time).format(\"YYYY-MM-DD\");\r\n 470\t return `\r\n 471\t \u003cdiv\u003e\r\n 472\t \u003cspan style=\"font-weight: bold;\"\u003e${time}\u003c/span\u003e\r\n 473\t ${body}\r\n 474\t \u003c/div\u003e\r\n 475\t `;\r\n 476\t },\r\n 477\t },\r\n 478\t xAxis: {\r\n 479\t show: true,\r\n 480\t type: \"category\",\r\n 481\t data: time,\r\n 482\t splitLine: {\r\n 483\t show: false,\r\n 484\t },\r\n 485\t axisLabel: {\r\n 486\t formatter: (value) =\u003e {\r\n 487\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 488\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 489\t } else {\r\n 490\t return value;\r\n 491\t }\r\n 492\t },\r\n 493\t },\r\n 494\t },\r\n 495\t yAxis: [\r\n 496\t {\r\n 497\t type: \"value\",\r\n 498\t axisLine: {\r\n 499\t show: true,\r\n 500\t },\r\n 501\t splitLine: {\r\n 502\t show: true,\r\n 503\t lineStyle: {\r\n 504\t type: \"dotted\",\r\n 505\t color: \"#333\",\r\n 506\t },\r\n 507\t },\r\n 508\t min: (value) =\u003e {\r\n 509\t const { min, max } = value;\r\n 510\t const interval = Math.abs((max - min) / 10);\r\n 511\t return round(min - interval);\r\n 512\t },\r\n 513\t max: (value) =\u003e {\r\n 514\t const { min, max } = value;\r\n 515\t const interval = Math.abs((max - min) / 10);\r\n 516\t return round(max + interval);\r\n 517\t },\r\n 518\t },\r\n 519\t {\r\n 520\t show: false,\r\n 521\t min: (value) =\u003e {\r\n 522\t const { min, max } = value;\r\n 523\t const interval = Math.abs((max - min) / 10);\r\n 524\t return min - interval;\r\n 525\t },\r\n 526\t max: (value) =\u003e {\r\n 527\t const { min, max } = value;\r\n 528\t const interval = Math.abs((max - min) / 10);\r\n 529\t return max + interval;\r\n 530\t },\r\n 531\t },\r\n 532\t ],\r\n 533\t series: [\r\n 534\t // K线\r\n 535\t {\r\n 536\t type: \"candlestick\",\r\n 537\t name: \"kLine\",\r\n 538\t data,\r\n 539\t markPoint: { data: [...tradePointData] },\r\n 540\t markLine: { data: [...tradeLineData] },\r\n 541\t itemStyle: {\r\n 542\t color: \"transparent\",\r\n 543\t color0: \"#00FFFF\",\r\n 544\t borderColor: \"#FF0000\",\r\n 545\t borderColor0: \"#00FFFF\",\r\n 546\t borderWidth: 1,\r\n 547\t },\r\n 548\t },\r\n 549\t // 净值曲线\r\n 550\t {\r\n 551\t type: \"line\",\r\n 552\t name: \"netPosition\",\r\n 553\t data: netPositionLineData,\r\n 554\t symbol: \"none\",\r\n 555\t yAxisIndex: 1,\r\n 556\t connectNulls: true,\r\n 557\t itemStyle: {\r\n 558\t color: \"#666666\",\r\n 559\t },\r\n 560\t lineStyle: {\r\n 561\t width: 2,\r\n 562\t },\r\n 563\t },\r\n 564\t // 交易收益率曲线\r\n 565\t {\r\n 566\t type: \"line\",\r\n 567\t name: \"tradeIncomeRate\",\r\n 568\t data: tradeIncomeRateLineData,\r\n 569\t symbol: \"none\",\r\n 570\t yAxisIndex: 1,\r\n 571\t connectNulls: false,\r\n 572\t itemStyle: {\r\n 573\t color: \"#666666\",\r\n 574\t },\r\n 575\t lineStyle: {\r\n 576\t width: 2,\r\n 577\t },\r\n 578\t },\r\n 579\t // 指标线\r\n 580\t ...indicatorSeries,\r\n 581\t ],\r\n 582\t toolbox: {\r\n 583\t show: false,\r\n 584\t },\r\n 585\t brush: {\r\n 586\t xAxisIndex: \"all\",\r\n 587\t brushLink: \"all\",\r\n 588\t transformable: false,\r\n 589\t outOfBrush: {\r\n 590\t colorAlpha: 2,\r\n 591\t },\r\n 592\t brushStyle: {\r\n 593\t color: \"rgba(255,255,255,0.1)\",\r\n 594\t borderColor: \"rgba(255,255,255,0.4)\",\r\n 595\t },\r\n 596\t },\r\n 597\t },\r\n 598\t true,\r\n 599\t );\r\n 600\t if (props.userKlineConfig.enable_subChart) {\r\n 601\t subChartIns.setOption(getSubOptions(klineData.value, startValue, endValue), true);\r\n 602\t }\r\n 603\t\r\n 604\t /**\r\n 605\t * @description: 善后处理\r\n 606\t */\r\n 607\t {\r\n 608\t getScreenTimeRange();\r\n 609\t drawScreenMaxPrice();\r\n 610\t activeIndex.value = endValue;\r\n 611\t }\r\n 612\t\r\n 613\t /**\r\n 614\t * @description: 数据框选: brush\r\n 615\t * 由于brush配置中, 如果有一边的X轴时间不在已载入数据中, 整个都会失效, 所以这里使用的是匹配算法\r\n 616\t */\r\n 617\t {\r\n 618\t const brushAreas = [];\r\n 619\t // 1. 成交范围 (灰白色)\r\n 620\t if (props.userKlineConfig.enable_tradeLogBrush \u0026\u0026 tradePointData.length) {\r\n 621\t const [startTime, endTime] = [tradePointData[0].coord[0], tradePointData.at(-1).coord[0]];\r\n 622\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 623\t brushAreas.push({\r\n 624\t brushId: \"开平仓范围\",\r\n 625\t brushType: \"lineX\",\r\n 626\t xAxisIndex: 0,\r\n 627\t coordRange,\r\n 628\t });\r\n 629\t }\r\n 630\t // 2. 同步刷选范围 (蓝色)\r\n 631\t if (props.syncBrushRange.cycleList.includes(props.cycle)) {\r\n 632\t const { startTime, endTime } = props.syncBrushRange;\r\n 633\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 634\t brushAreas.push({\r\n 635\t brushId: \"同步刷选范围\",\r\n 636\t brushType: \"lineX\",\r\n 637\t xAxisIndex: 0,\r\n 638\t coordRange,\r\n 639\t brushStyle: {\r\n 640\t fill: \"rgba(64, 158, 255, 0.2)\",\r\n 641\t stroke: \"rgba(64, 158, 255, 0.8)\",\r\n 642\t lineWidth: 1,\r\n 643\t },\r\n 644\t });\r\n 645\t }\r\n 646\t // 3. 强制定位高亮 (紫色)\r\n 647\t if (props.positionTime) {\r\n 648\t const [startTime, endTime] = [dayjs(props.positionTime).format(\"YYYY-MM-DD 00:00:00\"), dayjs(props.positionTime).format(\"YYYY-MM-DD 23:59:59\")];\r\n 649\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 650\t brushAreas.push({\r\n 651\t brushId: \"强制定位高亮\",\r\n 652\t brushType: \"lineX\",\r\n 653\t xAxisIndex: 0,\r\n 654\t coordRange,\r\n 655\t brushStyle: {\r\n 656\t fill: \"rgba(217, 179, 255, 0.2)\",\r\n 657\t stroke: \"rgba(217, 179, 255, 0.8)\",\r\n 658\t lineWidth: 1,\r\n 659\t },\r\n 660\t });\r\n 661\t }\r\n 662\t mainChartIns.dispatchAction({\r\n 663\t type: \"brush\",\r\n 664\t areas: brushAreas,\r\n 665\t });\r\n 666\t }\r\n 667\t};\r\n 668\t// 图表: 绘制(当屏最高价)\r\n 669\tconst drawScreenMaxPrice = () =\u003e {\r\n 670\t if (!props.userKlineConfig.enable_showScreenMaxPrice || !klineData.value.time.length) return;\r\n","preview":"271\t if (body.data.length \u003c loadAddCount) {\n272\t console.log(\"[K线] 获取更多数据: 左侧已全部获取完毕, 关闭\");\n273\t isloadAllHistory = true;","bytes":16430,"is_error":false,"stored_at":"2026-07-27T07:04:02.4050177Z"}
|
|
4
|
+
{"id":"t1.tool14","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/index.vue lines=401-800/1068 bytes=35463]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/index.vue\", offset=801, limit=400)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t const { axisDimension, value } = data;\r\n 402\t if (axisDimension === \"x\") {\r\n 403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 404\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 405\t } else {\r\n 406\t return value;\r\n 407\t }\r\n 408\t } else {\r\n 409\t return String(round(value));\r\n 410\t }\r\n 411\t },\r\n 412\t },\r\n 413\t },\r\n 414\t formatter: (params) =\u003e {\r\n 415\t if (!params?.length) return null;\r\n 416\t let body = \"\";\r\n 417\t params.forEach((item) =\u003e {\r\n 418\t // 资产持仓净值\r\n 419\t if (item.seriesName === \"netPosition\" \u0026\u0026 item.data !== null) {\r\n 420\t body += `\u003cdiv\u003e资产持仓净值: ${item.data[1]}\u003c/div\u003e`;\r\n 421\t }\r\n 422\t // 成交数据\r\n 423\t if (item.componentSubType === \"candlestick\") {\r\n 424\t // 买卖点\r\n 425\t if (props.sellBuy === 0) {\r\n 426\t // 买卖点渲染逻辑\r\n 427\t const sellBuy = tradePointData.filter((i) =\u003e i?.coord[0] === item.axisValue);\r\n 428\t let buy = 0;\r\n 429\t let sell = 0;\r\n 430\t sellBuy.forEach((i) =\u003e {\r\n 431\t const { tradeType, amount } = i.customData;\r\n 432\t if (tradeType === \"买\") {\r\n 433\t buy += amount;\r\n 434\t }\r\n 435\t if (tradeType === \"卖\") {\r\n 436\t sell += amount;\r\n 437\t }\r\n 438\t });\r\n 439\t if (buy) body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 440\t if (sell) body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 441\t }\r\n 442\t // 开平点\r\n 443\t else {\r\n 444\t // 开平点渲染逻辑\r\n 445\t tradePointData.forEach((i) =\u003e {\r\n 446\t if (i?.coord[0] === item.axisValue) {\r\n 447\t const { tradeType, amount, profitAndLoss, openPriceAll, closePriceAll } = i.customData;\r\n 448\t let tooltip = `\u003cdiv\u003e${tradeType}: ${amount}手\u003c/div\u003e`;\r\n 449\t // 开仓-tooltip: 开仓价\r\n 450\t if (tradeType.includes(\"开\")) {\r\n 451\t tooltip += `\u003cdiv\u003e开仓价: ${(openPriceAll / amount)?.toFixed(2)}\u003c/div\u003e`;\r\n 452\t }\r\n 453\t // 平仓-tooltip: 平仓价, 盈亏, 盈亏比率\r\n 454\t if (tradeType.includes(\"平\")) {\r\n 455\t const openPrice = (openPriceAll / amount)?.toFixed(2);\r\n 456\t const closePrice = (closePriceAll / amount)?.toFixed(2);\r\n 457\t tooltip += `\u003cdiv\u003e平仓价: ${closePrice}\u003c/div\u003e`;\r\n 458\t tooltip += `\u003cdiv\u003e盈亏: ${profitAndLoss?.toFixed(2)}\u003c/div\u003e`;\r\n 459\t tooltip += `\u003cdiv\u003e盈亏比率: ${(((closePrice - openPrice) / openPrice) * 100)?.toFixed(2)} %\u003c/div\u003e`;\r\n 460\t }\r\n 461\t body += tooltip;\r\n 462\t }\r\n 463\t });\r\n 464\t }\r\n 465\t }\r\n 466\t });\r\n 467\t if (!body) return null;\r\n 468\t let time = params[0].axisValue;\r\n 469\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) time = dayjs(time).format(\"YYYY-MM-DD\");\r\n 470\t return `\r\n 471\t \u003cdiv\u003e\r\n 472\t \u003cspan style=\"font-weight: bold;\"\u003e${time}\u003c/span\u003e\r\n 473\t ${body}\r\n 474\t \u003c/div\u003e\r\n 475\t `;\r\n 476\t },\r\n 477\t },\r\n 478\t xAxis: {\r\n 479\t show: true,\r\n 480\t type: \"category\",\r\n 481\t data: time,\r\n 482\t splitLine: {\r\n 483\t show: false,\r\n 484\t },\r\n 485\t axisLabel: {\r\n 486\t formatter: (value) =\u003e {\r\n 487\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 488\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 489\t } else {\r\n 490\t return value;\r\n 491\t }\r\n 492\t },\r\n 493\t },\r\n 494\t },\r\n 495\t yAxis: [\r\n 496\t {\r\n 497\t type: \"value\",\r\n 498\t axisLine: {\r\n 499\t show: true,\r\n 500\t },\r\n 501\t splitLine: {\r\n 502\t show: true,\r\n 503\t lineStyle: {\r\n 504\t type: \"dotted\",\r\n 505\t color: \"#333\",\r\n 506\t },\r\n 507\t },\r\n 508\t min: (value) =\u003e {\r\n 509\t const { min, max } = value;\r\n 510\t const interval = Math.abs((max - min) / 10);\r\n 511\t return round(min - interval);\r\n 512\t },\r\n 513\t max: (value) =\u003e {\r\n 514\t const { min, max } = value;\r\n 515\t const interval = Math.abs((max - min) / 10);\r\n 516\t return round(max + interval);\r\n 517\t },\r\n 518\t },\r\n 519\t {\r\n 520\t show: false,\r\n 521\t min: (value) =\u003e {\r\n 522\t const { min, max } = value;\r\n 523\t const interval = Math.abs((max - min) / 10);\r\n 524\t return min - interval;\r\n 525\t },\r\n 526\t max: (value) =\u003e {\r\n 527\t const { min, max } = value;\r\n 528\t const interval = Math.abs((max - min) / 10);\r\n 529\t return max + interval;\r\n 530\t },\r\n 531\t },\r\n 532\t ],\r\n 533\t series: [\r\n 534\t // K线\r\n 535\t {\r\n 536\t type: \"candlestick\",\r\n 537\t name: \"kLine\",\r\n 538\t data,\r\n 539\t markPoint: { data: [...tradePointData] },\r\n 540\t markLine: { data: [...tradeLineData] },\r\n 541\t itemStyle: {\r\n 542\t color: \"transparent\",\r\n 543\t color0: \"#00FFFF\",\r\n 544\t borderColor: \"#FF0000\",\r\n 545\t borderColor0: \"#00FFFF\",\r\n 546\t borderWidth: 1,\r\n 547\t },\r\n 548\t },\r\n 549\t // 净值曲线\r\n 550\t {\r\n 551\t type: \"line\",\r\n 552\t name: \"netPosition\",\r\n 553\t data: netPositionLineData,\r\n 554\t symbol: \"none\",\r\n 555\t yAxisIndex: 1,\r\n 556\t connectNulls: true,\r\n 557\t itemStyle: {\r\n 558\t color: \"#666666\",\r\n 559\t },\r\n 560\t lineStyle: {\r\n 561\t width: 2,\r\n 562\t },\r\n 563\t },\r\n 564\t // 交易收益率曲线\r\n 565\t {\r\n 566\t type: \"line\",\r\n 567\t name: \"tradeIncomeRate\",\r\n 568\t data: tradeIncomeRateLineData,\r\n 569\t symbol: \"none\",\r\n 570\t yAxisIndex: 1,\r\n 571\t connectNulls: false,\r\n 572\t itemStyle: {\r\n 573\t color: \"#666666\",\r\n 574\t },\r\n 575\t lineStyle: {\r\n 576\t width: 2,\r\n 577\t },\r\n 578\t },\r\n 579\t // 指标线\r\n 580\t ...indicatorSeries,\r\n 581\t ],\r\n 582\t toolbox: {\r\n 583\t show: false,\r\n 584\t },\r\n 585\t brush: {\r\n 586\t xAxisIndex: \"all\",\r\n 587\t brushLink: \"all\",\r\n 588\t transformable: false,\r\n 589\t outOfBrush: {\r\n 590\t colorAlpha: 2,\r\n 591\t },\r\n 592\t brushStyle: {\r\n 593\t color: \"rgba(255,255,255,0.1)\",\r\n 594\t borderColor: \"rgba(255,255,255,0.4)\",\r\n 595\t },\r\n 596\t },\r\n 597\t },\r\n 598\t true,\r\n 599\t );\r\n 600\t if (props.userKlineConfig.enable_subChart) {\r\n 601\t subChartIns.setOption(getSubOptions(klineData.value, startValue, endValue), true);\r\n 602\t }\r\n 603\t\r\n 604\t /**\r\n 605\t * @description: 善后处理\r\n 606\t */\r\n 607\t {\r\n 608\t getScreenTimeRange();\r\n 609\t drawScreenMaxPrice();\r\n 610\t activeIndex.value = endValue;\r\n 611\t }\r\n 612\t\r\n 613\t /**\r\n 614\t * @description: 数据框选: brush\r\n 615\t * 由于brush配置中, 如果有一边的X轴时间不在已载入数据中, 整个都会失效, 所以这里使用的是匹配算法\r\n 616\t */\r\n 617\t {\r\n 618\t const brushAreas = [];\r\n 619\t // 1. 成交范围 (灰白色)\r\n 620\t if (props.userKlineConfig.enable_tradeLogBrush \u0026\u0026 tradePointData.length) {\r\n 621\t const [startTime, endTime] = [tradePointData[0].coord[0], tradePointData.at(-1).coord[0]];\r\n 622\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 623\t brushAreas.push({\r\n 624\t brushId: \"开平仓范围\",\r\n 625\t brushType: \"lineX\",\r\n 626\t xAxisIndex: 0,\r\n 627\t coordRange,\r\n 628\t });\r\n 629\t }\r\n 630\t // 2. 同步刷选范围 (蓝色)\r\n 631\t if (props.syncBrushRange.cycleList.includes(props.cycle)) {\r\n 632\t const { startTime, endTime } = props.syncBrushRange;\r\n 633\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 634\t brushAreas.push({\r\n 635\t brushId: \"同步刷选范围\",\r\n 636\t brushType: \"lineX\",\r\n 637\t xAxisIndex: 0,\r\n 638\t coordRange,\r\n 639\t brushStyle: {\r\n 640\t fill: \"rgba(64, 158, 255, 0.2)\",\r\n 641\t stroke: \"rgba(64, 158, 255, 0.8)\",\r\n 642\t lineWidth: 1,\r\n 643\t },\r\n 644\t });\r\n 645\t }\r\n 646\t // 3. 强制定位高亮 (紫色)\r\n 647\t if (props.positionTime) {\r\n 648\t const [startTime, endTime] = [dayjs(props.positionTime).format(\"YYYY-MM-DD 00:00:00\"), dayjs(props.positionTime).format(\"YYYY-MM-DD 23:59:59\")];\r\n 649\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 650\t brushAreas.push({\r\n 651\t brushId: \"强制定位高亮\",\r\n 652\t brushType: \"lineX\",\r\n 653\t xAxisIndex: 0,\r\n 654\t coordRange,\r\n 655\t brushStyle: {\r\n 656\t fill: \"rgba(217, 179, 255, 0.2)\",\r\n 657\t stroke: \"rgba(217, 179, 255, 0.8)\",\r\n 658\t lineWidth: 1,\r\n 659\t },\r\n 660\t });\r\n 661\t }\r\n 662\t mainChartIns.dispatchAction({\r\n 663\t type: \"brush\",\r\n 664\t areas: brushAreas,\r\n 665\t });\r\n 666\t }\r\n 667\t};\r\n 668\t// 图表: 绘制(当屏最高价)\r\n 669\tconst drawScreenMaxPrice = () =\u003e {\r\n 670\t if (!props.userKlineConfig.enable_showScreenMaxPrice || !klineData.value.time.length) return;\r\n 671\t\r\n 672\t const { data } = klineData.value;\r\n 673\t const originOption = mainChartIns.getOption();\r\n 674\t const { startValue, endValue } = originOption?.dataZoom[0] ?? {};\r\n 675\t\r\n 676\t let maxPrice = 0;\r\n 677\t let maxPriceIndex = startValue;\r\n 678\t\r\n 679\t for (let i = startValue; i \u003c= endValue; i++) {\r\n 680\t if (Number(data[i][3]) \u003e= maxPrice) {\r\n 681\t maxPrice = Number(data[i][3]);\r\n 682\t maxPriceIndex = i;\r\n 683\t }\r\n 684\t }\r\n 685\t\r\n 686\t const filterSeries = originOption.series?.filter((item) =\u003e item.name !== \"maxPrice\") || [];\r\n 687\t const dataLength = endValue - startValue;\r\n 688\t const positionInView = (maxPriceIndex - startValue) / dataLength;\r\n 689\t let position = \"right\";\r\n 690\t let formatter = `←${maxPrice}`;\r\n 691\t\r\n 692\t if (positionInView \u003e 0.7) {\r\n 693\t position = \"left\";\r\n 694\t formatter = `${round(maxPrice)}→`;\r\n 695\t } else {\r\n 696\t position = \"right\";\r\n 697\t formatter = `←${round(maxPrice)}`;\r\n 698\t }\r\n 699\t\r\n 700\t // 关键:只更新 series,不更新其他配置\r\n 701\t mainChartIns?.setOption(\r\n 702\t {\r\n 703\t series: [\r\n 704\t ...filterSeries,\r\n 705\t {\r\n 706\t type: \"line\",\r\n 707\t name: \"maxPrice\",\r\n 708\t data: [],\r\n 709\t lineStyle: { opacity: 0 },\r\n 710\t markPoint: {\r\n 711\t symbol: \"circle\",\r\n 712\t symbolSize: 1,\r\n 713\t itemStyle: {\r\n 714\t color: \"transparent\",\r\n 715\t borderColor: \"transparent\",\r\n 716\t },\r\n 717\t z: 100,\r\n 718\t zlevel: 10,\r\n 719\t label: {\r\n 720\t show: true,\r\n 721\t position,\r\n 722\t formatter,\r\n 723\t color: \"#fff\",\r\n 724\t fontSize: 12,\r\n 725\t padding: [4, -4],\r\n 726\t },\r\n 727\t data: [\r\n 728\t {\r\n 729\t name: \"最高点\",\r\n 730\t coord: [maxPriceIndex, maxPrice],\r\n 731\t },\r\n 732\t ],\r\n 733\t },\r\n 734\t },\r\n 735\t ],\r\n 736\t },\r\n 737\t {\r\n 738\t notMerge: false, // 合并模式\r\n 739\t replaceMerge: [], // 不替换任何配置\r\n 740\t },\r\n 741\t );\r\n 742\t};\r\n 743\t\r\n 744\t// 拖拽轴: 拖拽回调\r\n 745\tconst handleSliderChange = (params) =\u003e {\r\n 746\t const [startTime, endTime] = checkTimeInterval({\r\n 747\t startTime: params.startTime,\r\n 748\t endTime: params.endTime,\r\n 749\t cycle: props.cycle,\r\n 750\t });\r\n 751\t getMainData({ startTime, endTime });\r\n 752\t};\r\n 753\t// 键盘控制 [↑↓←→缩放平移]\r\n 754\tconst handleKeyDownEvent = ({ code, ctrlKey }) =\u003e {\r\n 755\t if (!mainChartIns) return;\r\n 756\t if (!(ctrlKey || isHover.value)) return;\r\n 757\t const { xAxis, dataZoom } = mainChartIns.getOption();\r\n 758\t const { data: xAxisData } = xAxis?.[0] ?? { data: [] };\r\n 759\t let { startValue, endValue } = dataZoom?.[0] ?? {};\r\n 760\t if (!xAxisData?.length) return;\r\n 761\t // 键位cb\r\n 762\t switch (code) {\r\n 763\t // ↑ 放大\r\n 764\t case \"ArrowUp\": {\r\n 765\t if (endValue - startValue \u003c 5) return;\r\n 766\t const diff = Math.floor((endValue - startValue) / 2) + 1;\r\n 767\t startValue = startValue + diff;\r\n 768\t if (endValue - startValue \u003c 5) startValue = endValue - 4;\r\n 769\t break;\r\n 770\t }\r\n 771\t // ↓ 缩小\r\n 772\t case \"ArrowDown\": {\r\n 773\t const diff = Math.min(500, endValue - startValue);\r\n 774\t startValue = startValue - diff - 1;\r\n 775\t break;\r\n 776\t }\r\n 777\t // ← 左移\r\n 778\t case \"ArrowLeft\": {\r\n 779\t if (startValue \u003e 0) {\r\n 780\t startValue -= 1;\r\n 781\t endValue -= 1;\r\n 782\t }\r\n 783\t if (activeIndex.value \u003e 0) {\r\n 784\t activeIndex.value -= 1;\r\n 785\t }\r\n 786\t break;\r\n 787\t }\r\n 788\t // → 右移\r\n 789\t case \"ArrowRight\": {\r\n 790\t if (endValue \u003c xAxisData.length - 1) {\r\n 791\t startValue += 1;\r\n 792\t endValue += 1;\r\n 793\t }\r\n 794\t if (isHover.value \u0026\u0026 activeIndex.value \u003c xAxisData.length - 1) {\r\n 795\t activeIndex.value += 1;\r\n 796\t }\r\n 797\t break;\r\n 798\t }\r\n 799\t }\r\n 800\t // 1. 更新图表当屏位置\r\n","preview":"401\t const { axisDimension, value } = data;\n402\t if (axisDimension === \"x\") {\n403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {","bytes":15102,"is_error":false,"stored_at":"2026-07-27T07:04:15.2329212Z"}
|
|
5
|
+
{"id":"t1.tool15","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/index.vue lines=401-800/1068 bytes=35463]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/index.vue\", offset=801, limit=400)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t const { axisDimension, value } = data;\r\n 402\t if (axisDimension === \"x\") {\r\n 403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 404\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 405\t } else {\r\n 406\t return value;\r\n 407\t }\r\n 408\t } else {\r\n 409\t return String(round(value));\r\n 410\t }\r\n 411\t },\r\n 412\t },\r\n 413\t },\r\n 414\t formatter: (params) =\u003e {\r\n 415\t if (!params?.length) return null;\r\n 416\t let body = \"\";\r\n 417\t params.forEach((item) =\u003e {\r\n 418\t // 资产持仓净值\r\n 419\t if (item.seriesName === \"netPosition\" \u0026\u0026 item.data !== null) {\r\n 420\t body += `\u003cdiv\u003e资产持仓净值: ${item.data[1]}\u003c/div\u003e`;\r\n 421\t }\r\n 422\t // 成交数据\r\n 423\t if (item.componentSubType === \"candlestick\") {\r\n 424\t // 买卖点\r\n 425\t if (props.sellBuy === 0) {\r\n 426\t // 买卖点渲染逻辑\r\n 427\t const sellBuy = tradePointData.filter((i) =\u003e i?.coord[0] === item.axisValue);\r\n 428\t let buy = 0;\r\n 429\t let sell = 0;\r\n 430\t sellBuy.forEach((i) =\u003e {\r\n 431\t const { tradeType, amount } = i.customData;\r\n 432\t if (tradeType === \"买\") {\r\n 433\t buy += amount;\r\n 434\t }\r\n 435\t if (tradeType === \"卖\") {\r\n 436\t sell += amount;\r\n 437\t }\r\n 438\t });\r\n 439\t if (buy) body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 440\t if (sell) body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 441\t }\r\n 442\t // 开平点\r\n 443\t else {\r\n 444\t // 开平点渲染逻辑\r\n 445\t tradePointData.forEach((i) =\u003e {\r\n 446\t if (i?.coord[0] === item.axisValue) {\r\n 447\t const { tradeType, amount, profitAndLoss, openPriceAll, closePriceAll } = i.customData;\r\n 448\t let tooltip = `\u003cdiv\u003e${tradeType}: ${amount}手\u003c/div\u003e`;\r\n 449\t // 开仓-tooltip: 开仓价\r\n 450\t if (tradeType.includes(\"开\")) {\r\n 451\t tooltip += `\u003cdiv\u003e开仓价: ${(openPriceAll / amount)?.toFixed(2)}\u003c/div\u003e`;\r\n 452\t }\r\n 453\t // 平仓-tooltip: 平仓价, 盈亏, 盈亏比率\r\n 454\t if (tradeType.includes(\"平\")) {\r\n 455\t const openPrice = (openPriceAll / amount)?.toFixed(2);\r\n 456\t const closePrice = (closePriceAll / amount)?.toFixed(2);\r\n 457\t tooltip += `\u003cdiv\u003e平仓价: ${closePrice}\u003c/div\u003e`;\r\n 458\t tooltip += `\u003cdiv\u003e盈亏: ${profitAndLoss?.toFixed(2)}\u003c/div\u003e`;\r\n 459\t tooltip += `\u003cdiv\u003e盈亏比率: ${(((closePrice - openPrice) / openPrice) * 100)?.toFixed(2)} %\u003c/div\u003e`;\r\n 460\t }\r\n 461\t body += tooltip;\r\n 462\t }\r\n 463\t });\r\n 464\t }\r\n 465\t }\r\n 466\t });\r\n 467\t if (!body) return null;\r\n 468\t let time = params[0].axisValue;\r\n 469\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) time = dayjs(time).format(\"YYYY-MM-DD\");\r\n 470\t return `\r\n 471\t \u003cdiv\u003e\r\n 472\t \u003cspan style=\"font-weight: bold;\"\u003e${time}\u003c/span\u003e\r\n 473\t ${body}\r\n 474\t \u003c/div\u003e\r\n 475\t `;\r\n 476\t },\r\n 477\t },\r\n 478\t xAxis: {\r\n 479\t show: true,\r\n 480\t type: \"category\",\r\n 481\t data: time,\r\n 482\t splitLine: {\r\n 483\t show: false,\r\n 484\t },\r\n 485\t axisLabel: {\r\n 486\t formatter: (value) =\u003e {\r\n 487\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {\r\n 488\t return dayjs(value).format(\"YYYY-MM-DD\");\r\n 489\t } else {\r\n 490\t return value;\r\n 491\t }\r\n 492\t },\r\n 493\t },\r\n 494\t },\r\n 495\t yAxis: [\r\n 496\t {\r\n 497\t type: \"value\",\r\n 498\t axisLine: {\r\n 499\t show: true,\r\n 500\t },\r\n 501\t splitLine: {\r\n 502\t show: true,\r\n 503\t lineStyle: {\r\n 504\t type: \"dotted\",\r\n 505\t color: \"#333\",\r\n 506\t },\r\n 507\t },\r\n 508\t min: (value) =\u003e {\r\n 509\t const { min, max } = value;\r\n 510\t const interval = Math.abs((max - min) / 10);\r\n 511\t return round(min - interval);\r\n 512\t },\r\n 513\t max: (value) =\u003e {\r\n 514\t const { min, max } = value;\r\n 515\t const interval = Math.abs((max - min) / 10);\r\n 516\t return round(max + interval);\r\n 517\t },\r\n 518\t },\r\n 519\t {\r\n 520\t show: false,\r\n 521\t min: (value) =\u003e {\r\n 522\t const { min, max } = value;\r\n 523\t const interval = Math.abs((max - min) / 10);\r\n 524\t return min - interval;\r\n 525\t },\r\n 526\t max: (value) =\u003e {\r\n 527\t const { min, max } = value;\r\n 528\t const interval = Math.abs((max - min) / 10);\r\n 529\t return max + interval;\r\n 530\t },\r\n 531\t },\r\n 532\t ],\r\n 533\t series: [\r\n 534\t // K线\r\n 535\t {\r\n 536\t type: \"candlestick\",\r\n 537\t name: \"kLine\",\r\n 538\t data,\r\n 539\t markPoint: { data: [...tradePointData] },\r\n 540\t markLine: { data: [...tradeLineData] },\r\n 541\t itemStyle: {\r\n 542\t color: \"transparent\",\r\n 543\t color0: \"#00FFFF\",\r\n 544\t borderColor: \"#FF0000\",\r\n 545\t borderColor0: \"#00FFFF\",\r\n 546\t borderWidth: 1,\r\n 547\t },\r\n 548\t },\r\n 549\t // 净值曲线\r\n 550\t {\r\n 551\t type: \"line\",\r\n 552\t name: \"netPosition\",\r\n 553\t data: netPositionLineData,\r\n 554\t symbol: \"none\",\r\n 555\t yAxisIndex: 1,\r\n 556\t connectNulls: true,\r\n 557\t itemStyle: {\r\n 558\t color: \"#666666\",\r\n 559\t },\r\n 560\t lineStyle: {\r\n 561\t width: 2,\r\n 562\t },\r\n 563\t },\r\n 564\t // 交易收益率曲线\r\n 565\t {\r\n 566\t type: \"line\",\r\n 567\t name: \"tradeIncomeRate\",\r\n 568\t data: tradeIncomeRateLineData,\r\n 569\t symbol: \"none\",\r\n 570\t yAxisIndex: 1,\r\n 571\t connectNulls: false,\r\n 572\t itemStyle: {\r\n 573\t color: \"#666666\",\r\n 574\t },\r\n 575\t lineStyle: {\r\n 576\t width: 2,\r\n 577\t },\r\n 578\t },\r\n 579\t // 指标线\r\n 580\t ...indicatorSeries,\r\n 581\t ],\r\n 582\t toolbox: {\r\n 583\t show: false,\r\n 584\t },\r\n 585\t brush: {\r\n 586\t xAxisIndex: \"all\",\r\n 587\t brushLink: \"all\",\r\n 588\t transformable: false,\r\n 589\t outOfBrush: {\r\n 590\t colorAlpha: 2,\r\n 591\t },\r\n 592\t brushStyle: {\r\n 593\t color: \"rgba(255,255,255,0.1)\",\r\n 594\t borderColor: \"rgba(255,255,255,0.4)\",\r\n 595\t },\r\n 596\t },\r\n 597\t },\r\n 598\t true,\r\n 599\t );\r\n 600\t if (props.userKlineConfig.enable_subChart) {\r\n 601\t subChartIns.setOption(getSubOptions(klineData.value, startValue, endValue), true);\r\n 602\t }\r\n 603\t\r\n 604\t /**\r\n 605\t * @description: 善后处理\r\n 606\t */\r\n 607\t {\r\n 608\t getScreenTimeRange();\r\n 609\t drawScreenMaxPrice();\r\n 610\t activeIndex.value = endValue;\r\n 611\t }\r\n 612\t\r\n 613\t /**\r\n 614\t * @description: 数据框选: brush\r\n 615\t * 由于brush配置中, 如果有一边的X轴时间不在已载入数据中, 整个都会失效, 所以这里使用的是匹配算法\r\n 616\t */\r\n 617\t {\r\n 618\t const brushAreas = [];\r\n 619\t // 1. 成交范围 (灰白色)\r\n 620\t if (props.userKlineConfig.enable_tradeLogBrush \u0026\u0026 tradePointData.length) {\r\n 621\t const [startTime, endTime] = [tradePointData[0].coord[0], tradePointData.at(-1).coord[0]];\r\n 622\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 623\t brushAreas.push({\r\n 624\t brushId: \"开平仓范围\",\r\n 625\t brushType: \"lineX\",\r\n 626\t xAxisIndex: 0,\r\n 627\t coordRange,\r\n 628\t });\r\n 629\t }\r\n 630\t // 2. 同步刷选范围 (蓝色)\r\n 631\t if (props.syncBrushRange.cycleList.includes(props.cycle)) {\r\n 632\t const { startTime, endTime } = props.syncBrushRange;\r\n 633\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 634\t brushAreas.push({\r\n 635\t brushId: \"同步刷选范围\",\r\n 636\t brushType: \"lineX\",\r\n 637\t xAxisIndex: 0,\r\n 638\t coordRange,\r\n 639\t brushStyle: {\r\n 640\t fill: \"rgba(64, 158, 255, 0.2)\",\r\n 641\t stroke: \"rgba(64, 158, 255, 0.8)\",\r\n 642\t lineWidth: 1,\r\n 643\t },\r\n 644\t });\r\n 645\t }\r\n 646\t // 3. 强制定位高亮 (紫色)\r\n 647\t if (props.positionTime) {\r\n 648\t const [startTime, endTime] = [dayjs(props.positionTime).format(\"YYYY-MM-DD 00:00:00\"), dayjs(props.positionTime).format(\"YYYY-MM-DD 23:59:59\")];\r\n 649\t const coordRange = normalizeToKlineTimeByMatch(time, [startTime, endTime], props.cycle);\r\n 650\t brushAreas.push({\r\n 651\t brushId: \"强制定位高亮\",\r\n 652\t brushType: \"lineX\",\r\n 653\t xAxisIndex: 0,\r\n 654\t coordRange,\r\n 655\t brushStyle: {\r\n 656\t fill: \"rgba(217, 179, 255, 0.2)\",\r\n 657\t stroke: \"rgba(217, 179, 255, 0.8)\",\r\n 658\t lineWidth: 1,\r\n 659\t },\r\n 660\t });\r\n 661\t }\r\n 662\t mainChartIns.dispatchAction({\r\n 663\t type: \"brush\",\r\n 664\t areas: brushAreas,\r\n 665\t });\r\n 666\t }\r\n 667\t};\r\n 668\t// 图表: 绘制(当屏最高价)\r\n 669\tconst drawScreenMaxPrice = () =\u003e {\r\n 670\t if (!props.userKlineConfig.enable_showScreenMaxPrice || !klineData.value.time.length) return;\r\n 671\t\r\n 672\t const { data } = klineData.value;\r\n 673\t const originOption = mainChartIns.getOption();\r\n 674\t const { startValue, endValue } = originOption?.dataZoom[0] ?? {};\r\n 675\t\r\n 676\t let maxPrice = 0;\r\n 677\t let maxPriceIndex = startValue;\r\n 678\t\r\n 679\t for (let i = startValue; i \u003c= endValue; i++) {\r\n 680\t if (Number(data[i][3]) \u003e= maxPrice) {\r\n 681\t maxPrice = Number(data[i][3]);\r\n 682\t maxPriceIndex = i;\r\n 683\t }\r\n 684\t }\r\n 685\t\r\n 686\t const filterSeries = originOption.series?.filter((item) =\u003e item.name !== \"maxPrice\") || [];\r\n 687\t const dataLength = endValue - startValue;\r\n 688\t const positionInView = (maxPriceIndex - startValue) / dataLength;\r\n 689\t let position = \"right\";\r\n 690\t let formatter = `←${maxPrice}`;\r\n 691\t\r\n 692\t if (positionInView \u003e 0.7) {\r\n 693\t position = \"left\";\r\n 694\t formatter = `${round(maxPrice)}→`;\r\n 695\t } else {\r\n 696\t position = \"right\";\r\n 697\t formatter = `←${round(maxPrice)}`;\r\n 698\t }\r\n 699\t\r\n 700\t // 关键:只更新 series,不更新其他配置\r\n 701\t mainChartIns?.setOption(\r\n 702\t {\r\n 703\t series: [\r\n 704\t ...filterSeries,\r\n 705\t {\r\n 706\t type: \"line\",\r\n 707\t name: \"maxPrice\",\r\n 708\t data: [],\r\n 709\t lineStyle: { opacity: 0 },\r\n 710\t markPoint: {\r\n 711\t symbol: \"circle\",\r\n 712\t symbolSize: 1,\r\n 713\t itemStyle: {\r\n 714\t color: \"transparent\",\r\n 715\t borderColor: \"transparent\",\r\n 716\t },\r\n 717\t z: 100,\r\n 718\t zlevel: 10,\r\n 719\t label: {\r\n 720\t show: true,\r\n 721\t position,\r\n 722\t formatter,\r\n 723\t color: \"#fff\",\r\n 724\t fontSize: 12,\r\n 725\t padding: [4, -4],\r\n 726\t },\r\n 727\t data: [\r\n 728\t {\r\n 729\t name: \"最高点\",\r\n 730\t coord: [maxPriceIndex, maxPrice],\r\n 731\t },\r\n 732\t ],\r\n 733\t },\r\n 734\t },\r\n 735\t ],\r\n 736\t },\r\n 737\t {\r\n 738\t notMerge: false, // 合并模式\r\n 739\t replaceMerge: [], // 不替换任何配置\r\n 740\t },\r\n 741\t );\r\n 742\t};\r\n 743\t\r\n 744\t// 拖拽轴: 拖拽回调\r\n 745\tconst handleSliderChange = (params) =\u003e {\r\n 746\t const [startTime, endTime] = checkTimeInterval({\r\n 747\t startTime: params.startTime,\r\n 748\t endTime: params.endTime,\r\n 749\t cycle: props.cycle,\r\n 750\t });\r\n 751\t getMainData({ startTime, endTime });\r\n 752\t};\r\n 753\t// 键盘控制 [↑↓←→缩放平移]\r\n 754\tconst handleKeyDownEvent = ({ code, ctrlKey }) =\u003e {\r\n 755\t if (!mainChartIns) return;\r\n 756\t if (!(ctrlKey || isHover.value)) return;\r\n 757\t const { xAxis, dataZoom } = mainChartIns.getOption();\r\n 758\t const { data: xAxisData } = xAxis?.[0] ?? { data: [] };\r\n 759\t let { startValue, endValue } = dataZoom?.[0] ?? {};\r\n 760\t if (!xAxisData?.length) return;\r\n 761\t // 键位cb\r\n 762\t switch (code) {\r\n 763\t // ↑ 放大\r\n 764\t case \"ArrowUp\": {\r\n 765\t if (endValue - startValue \u003c 5) return;\r\n 766\t const diff = Math.floor((endValue - startValue) / 2) + 1;\r\n 767\t startValue = startValue + diff;\r\n 768\t if (endValue - startValue \u003c 5) startValue = endValue - 4;\r\n 769\t break;\r\n 770\t }\r\n 771\t // ↓ 缩小\r\n 772\t case \"ArrowDown\": {\r\n 773\t const diff = Math.min(500, endValue - startValue);\r\n 774\t startValue = startValue - diff - 1;\r\n 775\t break;\r\n 776\t }\r\n 777\t // ← 左移\r\n 778\t case \"ArrowLeft\": {\r\n 779\t if (startValue \u003e 0) {\r\n 780\t startValue -= 1;\r\n 781\t endValue -= 1;\r\n 782\t }\r\n 783\t if (activeIndex.value \u003e 0) {\r\n 784\t activeIndex.value -= 1;\r\n 785\t }\r\n 786\t break;\r\n 787\t }\r\n 788\t // → 右移\r\n 789\t case \"ArrowRight\": {\r\n 790\t if (endValue \u003c xAxisData.length - 1) {\r\n 791\t startValue += 1;\r\n 792\t endValue += 1;\r\n 793\t }\r\n 794\t if (isHover.value \u0026\u0026 activeIndex.value \u003c xAxisData.length - 1) {\r\n 795\t activeIndex.value += 1;\r\n 796\t }\r\n 797\t break;\r\n 798\t }\r\n 799\t }\r\n 800\t // 1. 更新图表当屏位置\r\n","preview":"401\t const { axisDimension, value } = data;\n402\t if (axisDimension === \"x\") {\n403\t if ([\"6\", \"7\", \"8\"].includes(props.cycle)) {","bytes":15102,"is_error":false,"stored_at":"2026-07-27T07:04:20.5892019Z"}
|
|
6
|
+
{"id":"t1.tool21","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/utils.js lines=1-772/772 bytes=28356]\n 1\timport dayjs from \"dayjs\";\r\n 2\timport { stMath } from \"st-func\";\r\n 3\t\r\n 4\tconst { round } = stMath;\r\n 5\t\r\n 6\t// 获取K线展示根数上限配置\r\n 7\texport const loadKlineConfig = {\r\n 8\t // 单屏展示条目上限\r\n 9\t minValueSpan: 5,\r\n 10\t maxValueSpan: 2000,\r\n 11\t // 加载更多: 触发阈值\r\n 12\t loadCheckCount: 500,\r\n 13\t // 加载更多: 载入条目\r\n 14\t loadAddCount: 2000,\r\n 15\t};\r\n 16\t\r\n 17\t// 前端估算K线根数的周期分布\r\n 18\texport const dayBarsNumByCycle = {\r\n 19\t 1: 240, // 每天240根(4小时×60分钟)\r\n 20\t 2: 48, // 每天48根(4小时×12根)\r\n 21\t 3: 16, // 每天16根(4小时×4根)\r\n 22\t 4: 8, // 每天8根(4小时×2根)\r\n 23\t 5: 4, // 每天4根(4小时×1根)\r\n 24\t 6: 1, // 每天1根\r\n 25\t 7: 0.2, // 每周1根(按5个交易日算)\r\n 26\t 8: 0.033, // 每月1根(按21个交易日算)\r\n 27\t};\r\n 28\t\r\n 29\t/**\r\n 30\t * @description: 校验时间间隔, 避免开始和结束时间过大, 超过一屏根数上限\r\n 31\t */\r\n 32\texport const checkTimeInterval = ({ startTime, endTime, cycle, type = \"end\" }) =\u003e {\r\n 33\t // 大切小时可能会出现请求超过一屏上限的数据量, 进行时间间隔校验, 由于是估算, 所以计算时可以比一屏上限根数多一些\r\n 34\t const { maxValueSpan } = loadKlineConfig;\r\n 35\t const N = Math.ceil((maxValueSpan + 1000) / dayBarsNumByCycle[cycle]);\r\n 36\t const d1 = dayjs(startTime);\r\n 37\t const d2 = dayjs(endTime);\r\n 38\t const diff = d2.diff(d1, \"day\");\r\n 39\t // 未超出间隔, 返回原本的开始, 结束时间\r\n 40\t if (diff \u003c= N) return [startTime, endTime];\r\n 41\t // 超出间隔, 按照期望以结束时间, 还是中间时间去计算\r\n 42\t if (type === \"end\") return [dayjs(endTime).subtract(N, \"day\").format(\"YYYY-MM-DD HH:mm:ss\"), endTime];\r\n 43\t if (type === \"middle\") {\r\n 44\t const middleTime = dayjs((dayjs(startTime).valueOf() + dayjs(endTime).valueOf()) / 2).format(\"YYYY-MM-DD HH:mm:ss\");\r\n 45\t console.log(\"超过一屏数量上限, 进行中间时间计算\", middleTime, N / 2);\r\n 46\t return [\r\n 47\t dayjs(middleTime)\r\n 48\t .subtract(N / 2, \"day\")\r\n 49\t .format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 50\t dayjs(middleTime)\r\n 51\t .add(N / 2, \"day\")\r\n 52\t .format(\"YYYY-MM-DD HH:mm:ss\"),\r\n 53\t ];\r\n 54\t }\r\n 55\t};\r\n 56\t\r\n 57\t// 获取副图配置\r\n 58\tconst getSubBarStyle = (data, index) =\u003e {\r\n 59\t const itemData = data[index];\r\n 60\t const preItemData = index === 0 ? data[index] : data[index - 1];\r\n 61\t if (itemData[0] === itemData[1]) {\r\n 62\t return itemData[0] \u003e= preItemData[1]\r\n 63\t ? {\r\n 64\t color: \"transparent\",\r\n 65\t borderColor: \"#FF0000\",\r\n 66\t }\r\n 67\t : {\r\n 68\t color: \"#00FFFF\",\r\n 69\t };\r\n 70\t } else {\r\n 71\t return itemData[1] \u003e itemData[0]\r\n 72\t ? {\r\n 73\t color: \"transparent\",\r\n 74\t borderColor: \"#FF0000\",\r\n 75\t }\r\n 76\t : {\r\n 77\t color: \"#00FFFF\",\r\n 78\t };\r\n 79\t }\r\n 80\t};\r\n 81\texport const getSubOptions = (klineData, startValue, endValue) =\u003e {\r\n 82\t const series = klineData.subIndicator.map((item) =\u003e {\r\n 83\t if (item.series === \"bar\") {\r\n 84\t return {\r\n 85\t name: \"subMain\",\r\n 86\t xAxisIndex: 0,\r\n 87\t yAxisIndex: 1,\r\n 88\t type: \"bar\",\r\n 89\t silent: true,\r\n 90\t symbol: \"none\",\r\n 91\t data: item.data.map((val, index) =\u003e {\r\n 92\t if (item.seriesColor === \"kline\") {\r\n 93\t return {\r\n 94\t value: val,\r\n 95\t itemStyle: getSubBarStyle(klineData.data, index),\r\n 96\t };\r\n 97\t } else if (item.seriesColor === \"value\") {\r\n 98\t return {\r\n 99\t value: val,\r\n 100\t itemStyle: {\r\n 101\t color: val \u003e= 0 ? \"#FF0000\" : \"#00FFFF\",\r\n 102\t },\r\n 103\t };\r\n 104\t } else {\r\n 105\t return {\r\n 106\t value: val,\r\n 107\t itemStyle: {\r\n 108\t color: item.seriesColor,\r\n 109\t },\r\n 110\t };\r\n 111\t }\r\n 112\t }),\r\n 113\t };\r\n 114\t } else if (item.series === \"line\") {\r\n 115\t return {\r\n 116\t xAxisIndex: 0,\r\n 117\t yAxisIndex: item.yAxis === \"right\" ? 2 : 1,\r\n 118\t name: item.key,\r\n 119\t type: \"line\",\r\n 120\t silent: true,\r\n 121\t symbol: \"none\",\r\n 122\t data: item.data,\r\n 123\t lineStyle: {\r\n 124\t width: 1,\r\n 125\t },\r\n 126\t itemStyle: {\r\n 127\t color: item.color,\r\n 128\t },\r\n 129\t };\r\n 130\t }\r\n 131\t });\r\n 132\t return {\r\n 133\t animation: false,\r\n 134\t grid: {\r\n 135\t top: \"10px\",\r\n 136\t left: \"60px\",\r\n 137\t right: \"20px\",\r\n 138\t bottom: \"10px\",\r\n 139\t },\r\n 140\t dataZoom: [\r\n 141\t {\r\n 142\t type: \"inside\",\r\n 143\t startValue,\r\n 144\t endValue,\r\n 145\t maxValueSpan: loadKlineConfig.maxValueSpan,\r\n 146\t },\r\n 147\t ],\r\n 148\t tooltip: {\r\n 149\t show: false,\r\n 150\t },\r\n 151\t axisPointer: {\r\n 152\t show: true,\r\n 153\t triggerTooltip: false,\r\n 154\t label: {\r\n 155\t rich: {},\r\n 156\t formatter: (data) =\u003e {\r\n 157\t const { axisDimension, value } = data;\r\n 158\t if (axisDimension === \"x\") {\r\n 159\t return null;\r\n 160\t } else if (data.axisIndex === 1) {\r\n 161\t return String(round(value));\r\n 162\t }\r\n 163\t },\r\n 164\t },\r\n 165\t },\r\n 166\t xAxis: {\r\n 167\t type: \"category\",\r\n 168\t data: klineData.time,\r\n 169\t axisLine: {\r\n 170\t show: true,\r\n 171\t },\r\n 172\t splitLine: {\r\n 173\t show: false,\r\n 174\t },\r\n 175\t axisLabel: {\r\n 176\t show: false,\r\n 177\t },\r\n 178\t },\r\n 179\t yAxis: [\r\n 180\t {\r\n 181\t position: \"right\",\r\n 182\t },\r\n 183\t {\r\n 184\t position: \"left\",\r\n 185\t min: klineData.subIndicator[0]?.leftYAxisRange === \"cover\" ? (value) =\u003e value.min : null,\r\n 186\t max: klineData.subIndicator[0]?.leftYAxisRange === \"cover\" ? (value) =\u003e value.max : null,\r\n 187\t splitNumber: 1,\r\n 188\t axisLine: {\r\n 189\t show: true,\r\n 190\t },\r\n 191\t splitLine: {\r\n 192\t show: true,\r\n 193\t lineStyle: {\r\n 194\t type: \"dotted\",\r\n 195\t color: \"#333\",\r\n 196\t },\r\n 197\t },\r\n 198\t },\r\n 199\t {\r\n 200\t position: \"right\",\r\n 201\t min: klineData.subIndicator[0]?.rightYAxisRange === \"cover\" ? (value) =\u003e value.min : null,\r\n 202\t max: klineData.subIndicator[0]?.rightYAxisRange === \"cover\" ? (value) =\u003e value.max : null,\r\n 203\t splitNumber: 1,\r\n 204\t axisLine: {\r\n 205\t show: false,\r\n 206\t },\r\n 207\t splitLine: {\r\n 208\t show: false,\r\n 209\t },\r\n 210\t axisLabel: {\r\n 211\t show: false,\r\n 212\t },\r\n 213\t },\r\n 214\t ],\r\n 215\t series,\r\n 216\t };\r\n 217\t};\r\n 218\t\r\n 219\t/**\r\n 220\t * @description: 二分查找法\r\n 221\t * @param {Array}: 值数组\r\n 222\t * @param {any}: 值\r\n 223\t * @param {string}: 查找模式\r\n 224\t * - \"strict\": 精确查找(默认)\r\n 225\t * - \"gte\": 查找第一个大于等于目标值的元素\r\n 226\t * @returns {Number}: 索引 (-1表示未找到)\r\n 227\t */\r\n 228\texport const binarySearch = (arr, target, type = \"strict\") =\u003e {\r\n 229\t let left = 0;\r\n 230\t let right = arr.length - 1;\r\n 231\t let result = -1; // 仅用于 gte 模式\r\n 232\t\r\n 233\t while (left \u003c= right) {\r\n 234\t let mid = Math.floor((left + right) / 2);\r\n 235\t if (type === \"strict\") {\r\n 236\t // 精确查找模式\r\n 237\t if (arr[mid] === target) {\r\n 238\t return mid;\r\n 239\t } else if (arr[mid] \u003c target) {\r\n 240\t // 左半区\r\n 241\t left = mid + 1;\r\n 242\t } else {\r\n 243\t // 右半区\r\n 244\t right = mid - 1;\r\n 245\t }\r\n 246\t }\r\n 247\t // 大于等于模式\r\n 248\t else if (type === \"gte\") {\r\n 249\t if (arr[mid] \u003e= target) {\r\n 250\t result = mid; // 记录可能的结果\r\n 251\t right = mid - 1; // 继续向左尝试找到更小的匹配\r\n 252\t } else {\r\n 253\t left = mid + 1;\r\n 254\t }\r\n 255\t }\r\n 256\t }\r\n 257\t\r\n 258\t return type === \"strict\" ? -1 : result;\r\n 259\t};\r\n 260\t\r\n 261\t/**\r\n 262\t * @description: 统一兼容处理标记点Y轴偏移量问题\r\n 263\t */\r\n 264\texport const handleMarkPointOffset = (markPointData) =\u003e {\r\n 265\t const data = [];\r\n 266\t markPointData.reduce((result, item) =\u003e {\r\n 267\t // 1. 找到当前标记的位置\r\n 268\t const key = `${item.coord[0]}+${item.label.position}`;\r\n 269\t const baseOffset = item.label.position === \"top\" ? -16 : 16;\r\n 270\t // 2. 找到先前处于相同位置 [key] 标记的数量\r\n 271\t const sameNum = result.get(key);\r\n 272\t if (sameNum) {\r\n 273\t result.set(key, sameNum + 1);\r\n 274\t item.symbolOffset[1] = baseOffset * (sameNum + 1);\r\n 275\t } else {\r\n 276\t result.set(key, 1);\r\n 277\t item.symbolOffset[1] = item.label.position === \"top\" ? -7 : 7;\r\n 278\t }\r\n 279\t data.push(item);\r\n 280\t return result;\r\n 281\t }, new Map());\r\n 282\t return data;\r\n 283\t};\r\n 284\t\r\n 285\t// K线数据合并\r\n 286\texport const mergeklineData = (leftData, rightData) =\u003e {\r\n 287\t return {\r\n 288\t data: [...leftData.data, ...rightData.data.slice(1)],\r\n 289\t mainIndicator: leftData.mainIndicator.map((item, index) =\u003e ({\r\n 290\t ...item,\r\n 291\t data: [...item.data, ...rightData.mainIndicator[index].data.slice(1)],\r\n 292\t })),\r\n 293\t subIndicator: leftData.subIndicator.map((item, index) =\u003e ({\r\n 294\t ...item,\r\n 295\t data: [...item.data, ...rightData.subIndicator[index].data.slice(1)],\r\n 296\t })),\r\n 297\t time: [...leftData.time, ...rightData.time.slice(1)],\r\n 298\t };\r\n 299\t};\r\n 300\t\r\n 301\t// 将时间标准化为K线时间(精准计算)\r\n 302\texport const normalizeToKlineTime = (klineTimeArray, time, cycle) =\u003e {\r\n 303\t // 5, 6, 7, 8特殊处理\r\n 304\t switch (cycle) {\r\n 305\t /**\r\n 306\t * @description: 60min\r\n 307\t * 按照最新的20根K线时分秒做匹配\r\n 308\t * 不进行步阶计算的原因是无法确认首根K线的时间, 例如真实时间 09:35:00, 该品种首根可能是 10:00:00 也可能是 10:30:00\r\n 309\t */\r\n 310\t case \"5\": {\r\n 311\t let HmsArray = [\r\n 312\t ...new Set(\r\n 313\t klineTimeArray.slice(-20).map((item) =\u003e {\r\n 314\t const date = new Date(item);\r\n 315\t const hour = String(date.getHours()).padStart(2, \"0\");\r\n 316\t const minute = String(date.getMinutes()).padStart(2, \"0\");\r\n 317\t const second = String(date.getSeconds()).padStart(2, \"0\");\r\n 318\t return `${hour}:${minute}:${second}`;\r\n 319\t })\r\n 320\t ),\r\n 321\t ];\r\n 322\t if (HmsArray.includes(\"00:00:00\")) {\r\n 323\t HmsArray.splice(HmsArray.indexOf(\"00:00:00\"), 1);\r\n 324\t HmsArray.push(\"24:00:00\");\r\n 325\t }\r\n 326\t HmsArray = HmsArray.sort((a, b) =\u003e new Date(`2000-01-30 ${a}`).getTime() - new Date(`2000-01-30 ${b}`).getTime());\r\n 327\t\r\n 328\t const date = new Date(time);\r\n 329\t const year = date.getFullYear();\r\n 330\t const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n 331\t const day = String(date.getDate()).padStart(2, \"0\");\r\n 332\t const hour = String(date.getHours()).padStart(2, \"0\");\r\n 333\t const minute = String(date.getMinutes()).padStart(2, \"0\");\r\n 334\t const second = String(date.getSeconds()).padStart(2, \"0\");\r\n 335\t\r\n 336\t // 该类为数据中台补全的数据, 不管它\r\n 337\t if (`${hour}:${minute}:${second}` === \"00:00:00\") {\r\n 338\t return `${year}-${month}-${day} 01:00:00`;\r\n 339\t }\r\n 340\t // 进行处理\r\n 341\t else {\r\n 342\t let Hms = null;\r\n 343\t for (let index = 0; index \u003c HmsArray.length; index++) {\r\n 344\t const item = HmsArray[index].split(\":\");\r\n 345\t if (Number(`${item[0]}${item[1]}${item[2]}`) \u003e= Number(`${hour}${minute}${second}`)) {\r\n 346\t Hms = HmsArray[index];\r\n 347\t break;\r\n 348\t }\r\n 349\t }\r\n 350\t if (Hms === \"24:00:00\") {\r\n 351\t const date = new Date(time);\r\n 352\t date.setDate(date.getDate() + 1);\r\n 353\t const year = date.getFullYear();\r\n 354\t const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n 355\t const day = String(date.getDate()).padStart(2, \"0\");\r\n 356\t return `${year}-${month}-${day} 00:00:00`;\r\n 357\t }\r\n 358\t return `${year}-${month}-${day} ${Hms}`;\r\n 359\t }\r\n 360\t }\r\n 361\t /**\r\n 362\t * @description: 1d\r\n 363\t * 因为考虑到期货的操作, 并且日线不像周月线那样直接统一成周五,或月末, 如果直接+1天会面临可能存在的非交易日的情况\r\n 364\t * 所以针对 [21:00:00 ~ 04:00:00)的数据, 都要进行匹配的算法, 即去klineTimeArray找到当前time的下一个日期\r\n 365\t */\r\n 366\t case \"6\": {\r\n 367\t const date = new Date(time);\r\n 368\t const hours = date.getHours();\r\n 369\t // 常规处理的 [04:00:00 ~ 21:00:00) =\u003e 直接格式化\r\n 370\t if (hours \u003e= 4 \u0026\u0026 hours \u003c 21) {\r\n 371\t const year = date.getFullYear();\r\n 372\t const month = String(date.getMonth() + 1).padStart(2, \"0\");\r\n 373\t const day = String(date.getDate()).padStart(2, \"0\");\r\n 374\t return `${year}-${month}-${day} 09:00:00`;\r\n 375\t }\r\n 376\t // 特殊处理的 [21:00:00 ~ 04:00:00) =\u003e 匹配算法\r\n 377\t else {\r\n 378\t const currentDate = new Date(time);\r\n 379\t // 如果是21:00:00之后, 需要将日期+1天\r\n 380\t if (currentDate.getHours() \u003e= 21) {\r\n 381\t currentDate.setDate(currentDate.getDate() + 1);\r\n 382\t }\r\n 383\t const year = currentDate.getFullYear();\r\n 384\t const month = String(currentDate.getMonth() + 1).padStart(2, \"0\");\r\n 385\t const day = String(currentDate.getDate()).padStart(2, \"0\");\r\n 386\t const currentDateStr = `${year}-${month}-${day} 09:00:00`;\r\n 387\t\r\n 388\t // 在klineTimeArray中找到当前日期或之后的第一个交易日 (二分优化)\r\n 389\t const idx = binarySearch(klineTimeArray, currentDateStr, \"gte\");\r\n 390\t return idx === -1 ? currentDateStr : klineTimeArray[idx];\r\n 391\t }\r\n 392\t }\r\n 393\t /**\r\n 394\t * @description: 1w\r\n 395\t * 周五 || 下周五\r\n 396\t */\r\n 397\t case \"7\": {\r\n 398\t const date = new Date(time);\r\n 399\t const startOfWeek = new Date(date);\r\n 400\t startOfWeek.setDate(date.getDate() - date.getDay());\r\n 401\t startOfWeek.setHours(0, 0, 0, 0);\r\n 402\t const nowWeek5 = new Date(startOfWeek);\r\n 403\t nowWeek5.setDate(startOfWeek.getDate() + 5);\r\n 404\t nowWeek5.setHours(23, 59, 59, 0);\r\n 405\t const nextWeek5 = new Date(startOfWeek);\r\n 406\t nextWeek5.setDate(startOfWeek.getDate() + 12);\r\n 407\t nextWeek5.setHours(9, 0, 0, 0);\r\n 408\t const result = date \u003c= nowWeek5 ? nowWeek5 : nextWeek5;\r\n 409\t const year = result.getFullYear();\r\n 410\t const month = String(result.getMonth() + 1).padStart(2, \"0\");\r\n 411\t const day = String(result.getDate()).padStart(2, \"0\");\r\n 412\t return `${year}-${month}-${day} 09:00:00`;\r\n 413\t }\r\n 414\t /**\r\n 415\t * @description: 1mon\r\n 416\t * 本月最后一天\r\n 417\t */\r\n 418\t case \"8\": {\r\n 419\t const date = new Date(time);\r\n 420\t const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);\r\n 421\t const endOfMonth = new Date(nextMonth);\r\n 422\t endOfMonth.setDate(nextMonth.getDate() - 1);\r\n 423\t endOfMonth.setHours(9, 0, 0, 0);\r\n 424\t const year = endOfMonth.getFullYear();\r\n 425\t const month = String(endOfMonth.getMonth() + 1).padStart(2, \"0\");\r\n 426\t const day = String(endOfMonth.getDate()).padStart(2, \"0\");\r\n 427\t return `${year}-${month}-${day} 09:00:00`;\r\n 428\t }\r\n 429\t }\r\n 430\t // 1, 2, 3, 4步阶计算\r\n 431\t const date = new Date(time);\r\n 432\t const year = date.getFullYear();\r\n 433\t const month = date.getMonth(); // 0-based\r\n 434\t const day = date.getDate();\r\n 435\t const hour = date.getHours();\r\n 436\t const minute = date.getMinutes();\r\n 437\t const second = date.getSeconds();\r\n 438\t const computeSmallCycle = (periodMinutes) =\u003e {\r\n 439\t let targetMinute = null;\r\n 440\t // 计算是否需要进位\r\n 441\t const currentPeriod = Math.floor(minute / periodMinutes);\r\n 442\t if (minute % periodMinutes === 0 \u0026\u0026 second === 0) {\r\n 443\t // 正好在周期边界上且秒为0,不调整\r\n 444\t targetMinute = minute;\r\n 445\t } else {\r\n 446\t // 计算下一个周期点\r\n 447\t targetMinute = (currentPeriod + 1) * periodMinutes;\r\n 448\t }\r\n 449\t\r\n 450\t // 处理小时, 分钟的进位\r\n 451\t const minuteCarry = Math.floor(targetMinute / 60);\r\n 452\t const adjustedHour = hour + minuteCarry;\r\n 453\t const adjustedMinute = targetMinute % 60;\r\n 454\t\r\n 455\t // 创建新日期(自动处理日/月/年进位)\r\n 456\t const resultDate = new Date(year, month, day, adjustedHour, adjustedMinute, 0);\r\n 457\t\r\n 458\t // 格式化输出\r\n 459\t const formatPart = (num) =\u003e String(num).padStart(2, \"0\");\r\n 460\t const formattedYear = resultDate.getFullYear();\r\n 461\t const formattedMonth = formatPart(resultDate.getMonth() + 1);\r\n 462\t const formattedDay = formatPart(resultDate.getDate());\r\n 463\t const formattedHour = formatPart(resultDate.getHours());\r\n 464\t const formattedMinute = formatPart(resultDate.getMinutes());\r\n 465\t\r\n 466\t return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHour}:${formattedMinute}:00`;\r\n 467\t };\r\n 468\t switch (cycle) {\r\n 469\t case \"1\":\r\n 470\t return computeSmallCycle(1); // 1m周期\r\n 471\t case \"2\":\r\n 472\t return computeSmallCycle(5); // 5m周期\r\n 473\t case \"3\":\r\n 474\t return computeSmallCycle(15); // 15m周期\r\n 475\t case \"4\":\r\n 476\t return computeSmallCycle(30); // 30m周期\r\n 477\t default:\r\n 478\t throw new Error(`Unsupported cycle: ${time} ${cycle}`);\r\n 479\t }\r\n 480\t};\r\n 481\t\r\n 482\t// 将时间标准化为K线时间范围(匹配算法)\r\n 483\texport const normalizeToKlineTimeByMatch = (klineTimeArray, timeRange, cycle) =\u003e {\r\n 484\t let klineStart = null;\r\n 485\t let klineEnd = null;\r\n 486\t const [start, end] = timeRange;\r\n 487\t switch (cycle) {\r\n 488\t // 日线\r\n 489\t case \"6\": {\r\n 490\t klineStart = klineTimeArray.find((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 23:59:59\")).getTime() \u003e= new Date(start).getTime());\r\n 491\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 00:00:00\")).getTime() \u003c= new Date(end).getTime());\r\n 492\t break;\r\n 493\t }\r\n 494\t // 周线\r\n 495\t case \"7\": {\r\n 496\t const format5 = dayjs(end).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD 23:59:59\");\r\n 497\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\") === dayjs(start).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\"));\r\n 498\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(format5).getTime());\r\n 499\t break;\r\n 500\t }\r\n 501\t // 月线 (仅判断是否同属本月即可)\r\n 502\t case \"8\": {\r\n 503\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(start).format(\"YYYY-MM\"));\r\n 504\t klineEnd = klineTimeArray.findLast((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(end).format(\"YYYY-MM\"));\r\n 505\t break;\r\n 506\t }\r\n 507\t // 常规匹配\r\n 508\t default: {\r\n 509\t klineStart = klineTimeArray.find((t) =\u003e new Date(t).getTime() \u003e= new Date(start).getTime());\r\n 510\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(end).getTime());\r\n 511\t }\r\n 512\t }\r\n 513\t return [klineStart, klineEnd];\r\n 514\t};\r\n 515\t\r\n 516\t// 生成图表配置所需数据: 成交点位, 成交点位连线\r\n 517\texport const handleMarkPointTradeLog = (tradeLog, cycle, sellBuy, klineTimeArray, klineDataArray, userKlineConfig) =\u003e {\r\n 518\t // 获取交易类型\r\n 519\t const handleTradeType = (data, type) =\u003e {\r\n 520\t const { direction = \"\", tradeAction = \"\", tradeType = \"\" } = data;\r\n 521\t const key = tradeType ?? direction + tradeAction;\r\n 522\t switch (type) {\r\n 523\t // 买卖\r\n 524\t case 0: {\r\n 525\t const keyMap = new Map([\r\n 526\t [\"开多\", \"买\"],\r\n 527\t [\"平多\", \"卖\"],\r\n 528\t [\"开空\", \"卖\"],\r\n 529\t [\"平空\", \"买\"],\r\n 530\t ]);\r\n 531\t return keyMap.get(key);\r\n 532\t }\r\n 533\t // 开平\r\n 534\t case 1: {\r\n 535\t return tradeType ?? direction + tradeAction;\r\n 536\t }\r\n 537\t }\r\n 538\t };\r\n 539\t // 交易点: 买卖配置数据\r\n 540\t const handleSellBuyConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 541\t return data.reduce((result, next) =\u003e {\r\n 542\t const tradeType = handleTradeType(next, 0);\r\n 543\t // 找寻同一根K线上是否有其它点位类型\r\n 544\t const timeTradeList = data.filter((item) =\u003e item.klineTime === next.klineTime);\r\n 545\t const timeTradeTypes = [...new Set(timeTradeList.map((item) =\u003e handleTradeType(item, 0)))];\r\n 546\t let symbol = null;\r\n 547\t if (timeTradeTypes.length \u003e 1) {\r\n 548\t symbol = \"image://\" + new URL(\"./images/t.svg\", import.meta.url).href;\r\n 549\t } else {\r\n 550\t symbol = \"image://\" + new URL(`./images/${tradeType === \"买\" ? \"buy\" : \"sell\"}.svg`, import.meta.url).href;\r\n 551\t }\r\n 552\t // 获取对应K线索引, 图标Y值永远为: 高, 连线起始Y值为: 低, 末端Y值为: 高\r\n 553\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 554\t const yAxisValue = klineDataAry[klineIndex]?.[3];\r\n 555\t baseLineData.forEach((item) =\u003e {\r\n 556\t const rangeIndex = tradeType === \"买\" ? 0 : 1;\r\n 557\t if (item.range[rangeIndex] === next.klineTime) {\r\n 558\t item.rangeValue[rangeIndex] = klineDataAry[klineIndex]?.[tradeType === \"买\" ? 2 : 3];\r\n 559\t }\r\n 560\t });\r\n 561\t result.push({\r\n 562\t symbol,\r\n 563\t symbolSize: 25,\r\n 564\t symbolRotate: 0,\r\n 565\t symbolOffset: [0, 0],\r\n 566\t label: {\r\n 567\t show: false,\r\n 568\t position: \"top\",\r\n 569\t },\r\n 570\t coord: [next.klineTime, yAxisValue],\r\n 571\t silent: true,\r\n 572\t animation: false,\r\n 573\t customData: {\r\n 574\t tradeType,\r\n 575\t amount: next.amount, // 手数\r\n 576\t part: next.part, // 份数\r\n 577\t profitAndLoss: next.profitAndLoss, // 盈亏\r\n 578\t openPriceAll: next.openPriceAll, // 开仓价\r\n 579\t closePriceAll: next.closePriceAll, // 收仓价\r\n 580\t },\r\n 581\t });\r\n 582\t return result;\r\n 583\t }, []);\r\n 584\t };\r\n 585\t // 交易点: 开平配置数据 (如果是价格, 则无需偏移, 如果是K线高低价, 则需要偏移)\r\n 586\t const handleOpenCloseConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 587\t const { sellBuyByPrice } = userKlineConfig;\r\n 588\t const result = data.reduce((result, next, index) =\u003e {\r\n 589\t // 1.交易类型\r\n 590\t const tradeType = handleTradeType(next, 1);\r\n 591\t // 2.图标位置 [开在K线上方,平在K线下方]\r\n 592\t const position = [\"开多\", \"开空\"].includes(tradeType) ? \"top\" : \"bottom\";\r\n 593\t // 3.获取对应Y轴值 [根据自定义配置判断, 是按价格还是直接用K线的最高低价]\r\n 594\t let yAxisValue = null;\r\n 595\t if (sellBuyByPrice) {\r\n 596\t yAxisValue = position === \"top\" ? (next.openPriceAll / next.amount)?.toFixed(2) : (next.closePriceAll / next.amount)?.toFixed(2);\r\n 597\t } else {\r\n 598\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 599\t yAxisValue = position === \"top\" ? klineDataAry[klineIndex]?.[3] : klineDataAry[klineIndex]?.[2];\r\n 600\t }\r\n 601\t // 4.将Y轴值存到连线的数据中, 提供给连线配置使用\r\n 602\t baseLineData.forEach((item) =\u003e {\r\n 603\t const rangeTime = position === \"top\" ? item.range[0] : item.range[1];\r\n 604\t if (rangeTime === next.klineTime) item.rangeValue[position === \"top\" ? 0 : 1] = yAxisValue;\r\n 605\t });\r\n 606\t result.push({\r\n 607\t symbol: \"triangle\",\r\n 608\t symbolSize: [10, 12],\r\n 609\t symbolRotate: position === \"top\" ? 180 : 0,\r\n 610\t symbolOffset: [0, 0],\r\n 611\t label: {\r\n 612\t show: true,\r\n 613\t position,\r\n 614\t color: \"#fff\",\r\n 615\t formatter: `${tradeType} ${position === \"top\" ? \"+\" : \"-\"} ${next.amount}手 ${next.part ? `(${next.part}份)` : \"\"}`,\r\n 616\t },\r\n 617\t itemStyle: { color: position === \"top\" ? \"#FF0000\" : \"#389e0d\" },\r\n 618\t coord: [next.klineTime, yAxisValue],\r\n 619\t silent: true,\r\n 620\t animation: false,\r\n 621\t customData: {\r\n 622\t tradeType,\r\n 623\t amount: next.amount, // 手数\r\n 624\t part: next.part, // 份数\r\n 625\t profitAndLoss: next.profitAndLoss, // 盈亏\r\n 626\t openPriceAll: next.openPriceAll, // 开仓价\r\n 627\t closePriceAll: next.closePriceAll, // 收仓价\r\n 628\t },\r\n 629\t });\r\n 630\t return result;\r\n 631\t }, []);\r\n 632\t if (sellBuyByPrice) {\r\n 633\t return result;\r\n 634\t } else {\r\n 635\t return handleMarkPointOffset(result);\r\n 636\t }\r\n 637\t };\r\n 638\t // 交易点: 生成基础点位, 连线数据\r\n 639\t const handleTradePointLineData = (tradeLog, cycle, klineTimeArray) =\u003e {\r\n 640\t // 1. 生成基础数据 { 点位数据, 连线数据 }\r\n 641\t const { basePointData, baseLineData } = tradeLog.reduce(\r\n 642\t (result, item) =\u003e {\r\n 643\t // 开仓点\r\n 644\t const startPoint = {\r\n 645\t pointTime: item.openTime, // 交易时间\r\n 646\t tradeAction: \"开\", // 交易行为\r\n 647\t tradeDirection: item.tradeDirection ? \"空\" : \"多\", // 交易方向 1:空, 0:多\r\n 648\t tradeType: `开${item.tradeDirection ? \"空\" : \"多\"}`,\r\n 649\t part: null, // 份数,\r\n 650\t amount: item.tradeVolume, // 手数\r\n 651\t profitAndLoss: item.profitAndLoss, // 盈亏\r\n 652\t openPriceAll: item.openPrice * item.tradeVolume, // 开仓总价\r\n 653\t closePriceAll: item.closePrice * item.tradeVolume, // 平仓总价\r\n 654\t\r\n 655\t klineTime: normalizeToKlineTime(klineTimeArray, item.openTime, cycle), // K线时间\r\n 656\t };\r\n 657\t // 平仓点\r\n 658\t const endPoint = {\r\n 659\t pointTime: item.closeTime, // 交易时间\r\n 660\t tradeAction: \"平\", // 交易行为\r\n 661\t tradeDirection: item.tradeDirection ? \"空\" : \"多\", // 交易方向 1:空, 0:多\r\n 662\t tradeType: `平${item.tradeDirection ? \"空\" : \"多\"}`,\r\n 663\t part: null, // 份数,\r\n 664\t amount: item.tradeVolume, // 手数\r\n 665\t profitAndLoss: item.profitAndLoss, // 盈亏\r\n 666\t openPriceAll: item.openPrice * item.tradeVolume, // 开仓总价\r\n 667\t closePriceAll: item.closePrice * item.tradeVolume, // 平仓总价\r\n 668\t\r\n 669\t klineTime: normalizeToKlineTime(klineTimeArray, item.closeTime, cycle), // K线时间\r\n 670\t };\r\n 671\t // 连线\r\n 672\t const line = {\r\n 673\t range: [startPoint.klineTime, endPoint.klineTime],\r\n 674\t rangeValue: [null, null],\r\n 675\t color: endPoint.profitAndLoss \u003e 0 ? \"#FF0000\" : \"#389e0d\",\r\n 676\t };\r\n 677\t result.basePointData.push(startPoint, endPoint);\r\n 678\t result.baseLineData.push(line);\r\n 679\t return result;\r\n 680\t },\r\n 681\t {\r\n 682\t basePointData: [], // 点位数据\r\n 683\t baseLineData: [], // 连线数据\r\n 684\t }\r\n 685\t );\r\n 686\t // 2. 合并同一根K线上的数据, 整体按照时间排序\r\n 687\t const basePointDataMergeMap = basePointData.reduce((result, item) =\u003e {\r\n 688\t const newItem = JSON.parse(JSON.stringify(item));\r\n 689\t // 判断这个节点是否已存在数据, 若存在说明重复, 进行数据合并\r\n 690\t const key = newItem.klineTime + newItem.tradeType;\r\n 691\t if (result.has(key)) {\r\n 692\t const oldItem = result.get(key);\r\n 693\t newItem.amount += oldItem.amount;\r\n 694\t newItem.part += oldItem.part;\r\n 695\t newItem.profitAndLoss += oldItem.profitAndLoss;\r\n 696\t newItem.openPriceAll += oldItem.openPriceAll;\r\n 697\t newItem.closePriceAll += oldItem.closePriceAll;\r\n 698\t }\r\n 699\t result.set(key, newItem);\r\n 700\t return result;\r\n 701\t }, new Map([]));\r\n 702\t const pointData = [...basePointDataMergeMap.values()].sort((a, b) =\u003e new Date(a.klineTime) - new Date(b.klineTime));\r\n 703\t // 3. 过滤掉开始和结束都在一根K线上的连线\r\n 704\t const lineData = baseLineData.filter(({ range }) =\u003e range[0] !== range[1]);\r\n 705\t return { pointData, lineData };\r\n 706\t };\r\n 707\t const { pointData, lineData } = handleTradePointLineData(tradeLog, cycle, klineTimeArray);\r\n 708\t const handler = sellBuy === 0 ? handleSellBuyConfig : handleOpenCloseConfig;\r\n 709\t return {\r\n 710\t tradePointData: handler(pointData, lineData, klineTimeArray, klineDataArray),\r\n 711\t tradeLineData: lineData.map(({ range, rangeValue, color }) =\u003e {\r\n 712\t return [\r\n 713\t {\r\n 714\t symbol: \"none\",\r\n 715\t coord: [range[0], rangeValue[0]],\r\n 716\t lineStyle: { color, width: 2, type: \"solid\" },\r\n 717\t silent: true,\r\n 718\t animation: false,\r\n 719\t },\r\n 720\t {\r\n 721\t symbol: \"none\",\r\n 722\t coord: [range[1], rangeValue[1]],\r\n 723\t silent: true,\r\n 724\t animation: false,\r\n 725\t },\r\n 726\t ];\r\n 727\t }),\r\n 728\t };\r\n 729\t};\r\n 730\t\r\n 731\t// 生成图表配置所需数据: 净值曲线 (周, 月不展示)\r\n 732\texport const handleNetPositionLine = (netPositionData, cycle) =\u003e {\r\n 733\t let netPositionLineData = [];\r\n 734\t // 分钟线: 时间格式化\r\n 735\t if (Number(cycle) \u003c= 5) {\r\n 736\t netPositionLineData = netPositionData.map(({ tradeDate, netPositionValue }) =\u003e {\r\n 737\t return [dayjs(tradeDate).format(\"YYYY-MM-DD HH:mm:ss\"), netPositionValue];\r\n 738\t });\r\n 739\t }\r\n 740\t // 日线: 取每天最后一条净值数据, 且时分秒转换成固定09:00:00\r\n 741\t else if (Number(cycle) === 6) {\r\n 742\t const netPositionMap = netPositionData.reduce((result, item) =\u003e {\r\n 743\t const { tradeDate, netPositionValue } = item;\r\n 744\t result.set(dayjs(tradeDate).format(\"YYYY-MM-DD 09:00:00\"), netPositionValue);\r\n 745\t return result;\r\n 746\t }, new Map());\r\n 747\t netPositionLineData = Array.from(netPositionMap, ([key, value]) =\u003e [key, value]);\r\n 748\t }\r\n 749\t // 其它: 不展示\r\n 750\t else {\r\n 751\t netPositionLineData = [];\r\n 752\t }\r\n 753\t return { netPositionLineData };\r\n 754\t};\r\n 755\t\r\n 756\t// 生成图表配置所需数据: 成交收益率曲线 (仅在日, 周, 月展示)\r\n 757\texport const handleTradeIncomeRateLine = (tradeIncomeRateData, cycle, incomeType, klineTimeArray) =\u003e {\r\n 758\t let tradeIncomeRateLineData = [];\r\n 759\t if ([\"6\", \"7\", \"8\"].includes(cycle)) {\r\n 760\t const data = tradeIncomeRateData.reduce((result, item) =\u003e {\r\n 761\t const key = normalizeToKlineTime(klineTimeArray, item.date, cycle);\r\n 762\t const value = item[incomeType === 0 ? \"simple\" : \"compound\"];\r\n 763\t result.set(key, value);\r\n 764\t return result;\r\n 765\t }, new Map());\r\n 766\t tradeIncomeRateLineData = Array.from(data, ([key, value]) =\u003e [key, value]);\r\n 767\t } else {\r\n 768\t tradeIncomeRateLineData = [];\r\n 769\t }\r\n 770\t return { tradeIncomeRateLineData };\r\n 771\t};\r\n 772\t\n","preview":"1\timport dayjs from \"dayjs\";\n2\timport { stMath } from \"st-func\";\n3","bytes":33062,"is_error":false,"stored_at":"2026-07-27T07:04:58.5520392Z"}
|
|
7
|
+
{"id":"t1.tool25","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/utils.js lines=401-600/772 bytes=28356]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/utils.js\", offset=601, limit=200)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t startOfWeek.setHours(0, 0, 0, 0);\r\n 402\t const nowWeek5 = new Date(startOfWeek);\r\n 403\t nowWeek5.setDate(startOfWeek.getDate() + 5);\r\n 404\t nowWeek5.setHours(23, 59, 59, 0);\r\n 405\t const nextWeek5 = new Date(startOfWeek);\r\n 406\t nextWeek5.setDate(startOfWeek.getDate() + 12);\r\n 407\t nextWeek5.setHours(9, 0, 0, 0);\r\n 408\t const result = date \u003c= nowWeek5 ? nowWeek5 : nextWeek5;\r\n 409\t const year = result.getFullYear();\r\n 410\t const month = String(result.getMonth() + 1).padStart(2, \"0\");\r\n 411\t const day = String(result.getDate()).padStart(2, \"0\");\r\n 412\t return `${year}-${month}-${day} 09:00:00`;\r\n 413\t }\r\n 414\t /**\r\n 415\t * @description: 1mon\r\n 416\t * 本月最后一天\r\n 417\t */\r\n 418\t case \"8\": {\r\n 419\t const date = new Date(time);\r\n 420\t const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);\r\n 421\t const endOfMonth = new Date(nextMonth);\r\n 422\t endOfMonth.setDate(nextMonth.getDate() - 1);\r\n 423\t endOfMonth.setHours(9, 0, 0, 0);\r\n 424\t const year = endOfMonth.getFullYear();\r\n 425\t const month = String(endOfMonth.getMonth() + 1).padStart(2, \"0\");\r\n 426\t const day = String(endOfMonth.getDate()).padStart(2, \"0\");\r\n 427\t return `${year}-${month}-${day} 09:00:00`;\r\n 428\t }\r\n 429\t }\r\n 430\t // 1, 2, 3, 4步阶计算\r\n 431\t const date = new Date(time);\r\n 432\t const year = date.getFullYear();\r\n 433\t const month = date.getMonth(); // 0-based\r\n 434\t const day = date.getDate();\r\n 435\t const hour = date.getHours();\r\n 436\t const minute = date.getMinutes();\r\n 437\t const second = date.getSeconds();\r\n 438\t const computeSmallCycle = (periodMinutes) =\u003e {\r\n 439\t let targetMinute = null;\r\n 440\t // 计算是否需要进位\r\n 441\t const currentPeriod = Math.floor(minute / periodMinutes);\r\n 442\t if (minute % periodMinutes === 0 \u0026\u0026 second === 0) {\r\n 443\t // 正好在周期边界上且秒为0,不调整\r\n 444\t targetMinute = minute;\r\n 445\t } else {\r\n 446\t // 计算下一个周期点\r\n 447\t targetMinute = (currentPeriod + 1) * periodMinutes;\r\n 448\t }\r\n 449\t\r\n 450\t // 处理小时, 分钟的进位\r\n 451\t const minuteCarry = Math.floor(targetMinute / 60);\r\n 452\t const adjustedHour = hour + minuteCarry;\r\n 453\t const adjustedMinute = targetMinute % 60;\r\n 454\t\r\n 455\t // 创建新日期(自动处理日/月/年进位)\r\n 456\t const resultDate = new Date(year, month, day, adjustedHour, adjustedMinute, 0);\r\n 457\t\r\n 458\t // 格式化输出\r\n 459\t const formatPart = (num) =\u003e String(num).padStart(2, \"0\");\r\n 460\t const formattedYear = resultDate.getFullYear();\r\n 461\t const formattedMonth = formatPart(resultDate.getMonth() + 1);\r\n 462\t const formattedDay = formatPart(resultDate.getDate());\r\n 463\t const formattedHour = formatPart(resultDate.getHours());\r\n 464\t const formattedMinute = formatPart(resultDate.getMinutes());\r\n 465\t\r\n 466\t return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHour}:${formattedMinute}:00`;\r\n 467\t };\r\n 468\t switch (cycle) {\r\n 469\t case \"1\":\r\n 470\t return computeSmallCycle(1); // 1m周期\r\n 471\t case \"2\":\r\n 472\t return computeSmallCycle(5); // 5m周期\r\n 473\t case \"3\":\r\n 474\t return computeSmallCycle(15); // 15m周期\r\n 475\t case \"4\":\r\n 476\t return computeSmallCycle(30); // 30m周期\r\n 477\t default:\r\n 478\t throw new Error(`Unsupported cycle: ${time} ${cycle}`);\r\n 479\t }\r\n 480\t};\r\n 481\t\r\n 482\t// 将时间标准化为K线时间范围(匹配算法)\r\n 483\texport const normalizeToKlineTimeByMatch = (klineTimeArray, timeRange, cycle) =\u003e {\r\n 484\t let klineStart = null;\r\n 485\t let klineEnd = null;\r\n 486\t const [start, end] = timeRange;\r\n 487\t switch (cycle) {\r\n 488\t // 日线\r\n 489\t case \"6\": {\r\n 490\t klineStart = klineTimeArray.find((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 23:59:59\")).getTime() \u003e= new Date(start).getTime());\r\n 491\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 00:00:00\")).getTime() \u003c= new Date(end).getTime());\r\n 492\t break;\r\n 493\t }\r\n 494\t // 周线\r\n 495\t case \"7\": {\r\n 496\t const format5 = dayjs(end).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD 23:59:59\");\r\n 497\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\") === dayjs(start).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\"));\r\n 498\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(format5).getTime());\r\n 499\t break;\r\n 500\t }\r\n 501\t // 月线 (仅判断是否同属本月即可)\r\n 502\t case \"8\": {\r\n 503\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(start).format(\"YYYY-MM\"));\r\n 504\t klineEnd = klineTimeArray.findLast((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(end).format(\"YYYY-MM\"));\r\n 505\t break;\r\n 506\t }\r\n 507\t // 常规匹配\r\n 508\t default: {\r\n 509\t klineStart = klineTimeArray.find((t) =\u003e new Date(t).getTime() \u003e= new Date(start).getTime());\r\n 510\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(end).getTime());\r\n 511\t }\r\n 512\t }\r\n 513\t return [klineStart, klineEnd];\r\n 514\t};\r\n 515\t\r\n 516\t// 生成图表配置所需数据: 成交点位, 成交点位连线\r\n 517\texport const handleMarkPointTradeLog = (tradeLog, cycle, sellBuy, klineTimeArray, klineDataArray, userKlineConfig) =\u003e {\r\n 518\t // 获取交易类型\r\n 519\t const handleTradeType = (data, type) =\u003e {\r\n 520\t const { direction = \"\", tradeAction = \"\", tradeType = \"\" } = data;\r\n 521\t const key = tradeType ?? direction + tradeAction;\r\n 522\t switch (type) {\r\n 523\t // 买卖\r\n 524\t case 0: {\r\n 525\t const keyMap = new Map([\r\n 526\t [\"开多\", \"买\"],\r\n 527\t [\"平多\", \"卖\"],\r\n 528\t [\"开空\", \"卖\"],\r\n 529\t [\"平空\", \"买\"],\r\n 530\t ]);\r\n 531\t return keyMap.get(key);\r\n 532\t }\r\n 533\t // 开平\r\n 534\t case 1: {\r\n 535\t return tradeType ?? direction + tradeAction;\r\n 536\t }\r\n 537\t }\r\n 538\t };\r\n 539\t // 交易点: 买卖配置数据\r\n 540\t const handleSellBuyConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 541\t return data.reduce((result, next) =\u003e {\r\n 542\t const tradeType = handleTradeType(next, 0);\r\n 543\t // 找寻同一根K线上是否有其它点位类型\r\n 544\t const timeTradeList = data.filter((item) =\u003e item.klineTime === next.klineTime);\r\n 545\t const timeTradeTypes = [...new Set(timeTradeList.map((item) =\u003e handleTradeType(item, 0)))];\r\n 546\t let symbol = null;\r\n 547\t if (timeTradeTypes.length \u003e 1) {\r\n 548\t symbol = \"image://\" + new URL(\"./images/t.svg\", import.meta.url).href;\r\n 549\t } else {\r\n 550\t symbol = \"image://\" + new URL(`./images/${tradeType === \"买\" ? \"buy\" : \"sell\"}.svg`, import.meta.url).href;\r\n 551\t }\r\n 552\t // 获取对应K线索引, 图标Y值永远为: 高, 连线起始Y值为: 低, 末端Y值为: 高\r\n 553\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 554\t const yAxisValue = klineDataAry[klineIndex]?.[3];\r\n 555\t baseLineData.forEach((item) =\u003e {\r\n 556\t const rangeIndex = tradeType === \"买\" ? 0 : 1;\r\n 557\t if (item.range[rangeIndex] === next.klineTime) {\r\n 558\t item.rangeValue[rangeIndex] = klineDataAry[klineIndex]?.[tradeType === \"买\" ? 2 : 3];\r\n 559\t }\r\n 560\t });\r\n 561\t result.push({\r\n 562\t symbol,\r\n 563\t symbolSize: 25,\r\n 564\t symbolRotate: 0,\r\n 565\t symbolOffset: [0, 0],\r\n 566\t label: {\r\n 567\t show: false,\r\n 568\t position: \"top\",\r\n 569\t },\r\n 570\t coord: [next.klineTime, yAxisValue],\r\n 571\t silent: true,\r\n 572\t animation: false,\r\n 573\t customData: {\r\n 574\t tradeType,\r\n 575\t amount: next.amount, // 手数\r\n 576\t part: next.part, // 份数\r\n 577\t profitAndLoss: next.profitAndLoss, // 盈亏\r\n 578\t openPriceAll: next.openPriceAll, // 开仓价\r\n 579\t closePriceAll: next.closePriceAll, // 收仓价\r\n 580\t },\r\n 581\t });\r\n 582\t return result;\r\n 583\t }, []);\r\n 584\t };\r\n 585\t // 交易点: 开平配置数据 (如果是价格, 则无需偏移, 如果是K线高低价, 则需要偏移)\r\n 586\t const handleOpenCloseConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 587\t const { sellBuyByPrice } = userKlineConfig;\r\n 588\t const result = data.reduce((result, next, index) =\u003e {\r\n 589\t // 1.交易类型\r\n 590\t const tradeType = handleTradeType(next, 1);\r\n 591\t // 2.图标位置 [开在K线上方,平在K线下方]\r\n 592\t const position = [\"开多\", \"开空\"].includes(tradeType) ? \"top\" : \"bottom\";\r\n 593\t // 3.获取对应Y轴值 [根据自定义配置判断, 是按价格还是直接用K线的最高低价]\r\n 594\t let yAxisValue = null;\r\n 595\t if (sellBuyByPrice) {\r\n 596\t yAxisValue = position === \"top\" ? (next.openPriceAll / next.amount)?.toFixed(2) : (next.closePriceAll / next.amount)?.toFixed(2);\r\n 597\t } else {\r\n 598\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 599\t yAxisValue = position === \"top\" ? klineDataAry[klineIndex]?.[3] : klineDataAry[klineIndex]?.[2];\r\n 600\t }\r\n","preview":"401\t startOfWeek.setHours(0, 0, 0, 0);\n402\t const nowWeek5 = new Date(startOfWeek);\n403\t nowWeek5.setDate(startOfWeek.getDate() + 5);","bytes":9945,"is_error":false,"stored_at":"2026-07-27T07:05:26.620218Z"}
|
|
8
|
+
{"id":"t1.tool26","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/utils.js lines=401-600/772 bytes=28356]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/utils.js\", offset=601, limit=200)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t startOfWeek.setHours(0, 0, 0, 0);\r\n 402\t const nowWeek5 = new Date(startOfWeek);\r\n 403\t nowWeek5.setDate(startOfWeek.getDate() + 5);\r\n 404\t nowWeek5.setHours(23, 59, 59, 0);\r\n 405\t const nextWeek5 = new Date(startOfWeek);\r\n 406\t nextWeek5.setDate(startOfWeek.getDate() + 12);\r\n 407\t nextWeek5.setHours(9, 0, 0, 0);\r\n 408\t const result = date \u003c= nowWeek5 ? nowWeek5 : nextWeek5;\r\n 409\t const year = result.getFullYear();\r\n 410\t const month = String(result.getMonth() + 1).padStart(2, \"0\");\r\n 411\t const day = String(result.getDate()).padStart(2, \"0\");\r\n 412\t return `${year}-${month}-${day} 09:00:00`;\r\n 413\t }\r\n 414\t /**\r\n 415\t * @description: 1mon\r\n 416\t * 本月最后一天\r\n 417\t */\r\n 418\t case \"8\": {\r\n 419\t const date = new Date(time);\r\n 420\t const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);\r\n 421\t const endOfMonth = new Date(nextMonth);\r\n 422\t endOfMonth.setDate(nextMonth.getDate() - 1);\r\n 423\t endOfMonth.setHours(9, 0, 0, 0);\r\n 424\t const year = endOfMonth.getFullYear();\r\n 425\t const month = String(endOfMonth.getMonth() + 1).padStart(2, \"0\");\r\n 426\t const day = String(endOfMonth.getDate()).padStart(2, \"0\");\r\n 427\t return `${year}-${month}-${day} 09:00:00`;\r\n 428\t }\r\n 429\t }\r\n 430\t // 1, 2, 3, 4步阶计算\r\n 431\t const date = new Date(time);\r\n 432\t const year = date.getFullYear();\r\n 433\t const month = date.getMonth(); // 0-based\r\n 434\t const day = date.getDate();\r\n 435\t const hour = date.getHours();\r\n 436\t const minute = date.getMinutes();\r\n 437\t const second = date.getSeconds();\r\n 438\t const computeSmallCycle = (periodMinutes) =\u003e {\r\n 439\t let targetMinute = null;\r\n 440\t // 计算是否需要进位\r\n 441\t const currentPeriod = Math.floor(minute / periodMinutes);\r\n 442\t if (minute % periodMinutes === 0 \u0026\u0026 second === 0) {\r\n 443\t // 正好在周期边界上且秒为0,不调整\r\n 444\t targetMinute = minute;\r\n 445\t } else {\r\n 446\t // 计算下一个周期点\r\n 447\t targetMinute = (currentPeriod + 1) * periodMinutes;\r\n 448\t }\r\n 449\t\r\n 450\t // 处理小时, 分钟的进位\r\n 451\t const minuteCarry = Math.floor(targetMinute / 60);\r\n 452\t const adjustedHour = hour + minuteCarry;\r\n 453\t const adjustedMinute = targetMinute % 60;\r\n 454\t\r\n 455\t // 创建新日期(自动处理日/月/年进位)\r\n 456\t const resultDate = new Date(year, month, day, adjustedHour, adjustedMinute, 0);\r\n 457\t\r\n 458\t // 格式化输出\r\n 459\t const formatPart = (num) =\u003e String(num).padStart(2, \"0\");\r\n 460\t const formattedYear = resultDate.getFullYear();\r\n 461\t const formattedMonth = formatPart(resultDate.getMonth() + 1);\r\n 462\t const formattedDay = formatPart(resultDate.getDate());\r\n 463\t const formattedHour = formatPart(resultDate.getHours());\r\n 464\t const formattedMinute = formatPart(resultDate.getMinutes());\r\n 465\t\r\n 466\t return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHour}:${formattedMinute}:00`;\r\n 467\t };\r\n 468\t switch (cycle) {\r\n 469\t case \"1\":\r\n 470\t return computeSmallCycle(1); // 1m周期\r\n 471\t case \"2\":\r\n 472\t return computeSmallCycle(5); // 5m周期\r\n 473\t case \"3\":\r\n 474\t return computeSmallCycle(15); // 15m周期\r\n 475\t case \"4\":\r\n 476\t return computeSmallCycle(30); // 30m周期\r\n 477\t default:\r\n 478\t throw new Error(`Unsupported cycle: ${time} ${cycle}`);\r\n 479\t }\r\n 480\t};\r\n 481\t\r\n 482\t// 将时间标准化为K线时间范围(匹配算法)\r\n 483\texport const normalizeToKlineTimeByMatch = (klineTimeArray, timeRange, cycle) =\u003e {\r\n 484\t let klineStart = null;\r\n 485\t let klineEnd = null;\r\n 486\t const [start, end] = timeRange;\r\n 487\t switch (cycle) {\r\n 488\t // 日线\r\n 489\t case \"6\": {\r\n 490\t klineStart = klineTimeArray.find((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 23:59:59\")).getTime() \u003e= new Date(start).getTime());\r\n 491\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 00:00:00\")).getTime() \u003c= new Date(end).getTime());\r\n 492\t break;\r\n 493\t }\r\n 494\t // 周线\r\n 495\t case \"7\": {\r\n 496\t const format5 = dayjs(end).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD 23:59:59\");\r\n 497\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\") === dayjs(start).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\"));\r\n 498\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(format5).getTime());\r\n 499\t break;\r\n 500\t }\r\n 501\t // 月线 (仅判断是否同属本月即可)\r\n 502\t case \"8\": {\r\n 503\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(start).format(\"YYYY-MM\"));\r\n 504\t klineEnd = klineTimeArray.findLast((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(end).format(\"YYYY-MM\"));\r\n 505\t break;\r\n 506\t }\r\n 507\t // 常规匹配\r\n 508\t default: {\r\n 509\t klineStart = klineTimeArray.find((t) =\u003e new Date(t).getTime() \u003e= new Date(start).getTime());\r\n 510\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(end).getTime());\r\n 511\t }\r\n 512\t }\r\n 513\t return [klineStart, klineEnd];\r\n 514\t};\r\n 515\t\r\n 516\t// 生成图表配置所需数据: 成交点位, 成交点位连线\r\n 517\texport const handleMarkPointTradeLog = (tradeLog, cycle, sellBuy, klineTimeArray, klineDataArray, userKlineConfig) =\u003e {\r\n 518\t // 获取交易类型\r\n 519\t const handleTradeType = (data, type) =\u003e {\r\n 520\t const { direction = \"\", tradeAction = \"\", tradeType = \"\" } = data;\r\n 521\t const key = tradeType ?? direction + tradeAction;\r\n 522\t switch (type) {\r\n 523\t // 买卖\r\n 524\t case 0: {\r\n 525\t const keyMap = new Map([\r\n 526\t [\"开多\", \"买\"],\r\n 527\t [\"平多\", \"卖\"],\r\n 528\t [\"开空\", \"卖\"],\r\n 529\t [\"平空\", \"买\"],\r\n 530\t ]);\r\n 531\t return keyMap.get(key);\r\n 532\t }\r\n 533\t // 开平\r\n 534\t case 1: {\r\n 535\t return tradeType ?? direction + tradeAction;\r\n 536\t }\r\n 537\t }\r\n 538\t };\r\n 539\t // 交易点: 买卖配置数据\r\n 540\t const handleSellBuyConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 541\t return data.reduce((result, next) =\u003e {\r\n 542\t const tradeType = handleTradeType(next, 0);\r\n 543\t // 找寻同一根K线上是否有其它点位类型\r\n 544\t const timeTradeList = data.filter((item) =\u003e item.klineTime === next.klineTime);\r\n 545\t const timeTradeTypes = [...new Set(timeTradeList.map((item) =\u003e handleTradeType(item, 0)))];\r\n 546\t let symbol = null;\r\n 547\t if (timeTradeTypes.length \u003e 1) {\r\n 548\t symbol = \"image://\" + new URL(\"./images/t.svg\", import.meta.url).href;\r\n 549\t } else {\r\n 550\t symbol = \"image://\" + new URL(`./images/${tradeType === \"买\" ? \"buy\" : \"sell\"}.svg`, import.meta.url).href;\r\n 551\t }\r\n 552\t // 获取对应K线索引, 图标Y值永远为: 高, 连线起始Y值为: 低, 末端Y值为: 高\r\n 553\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 554\t const yAxisValue = klineDataAry[klineIndex]?.[3];\r\n 555\t baseLineData.forEach((item) =\u003e {\r\n 556\t const rangeIndex = tradeType === \"买\" ? 0 : 1;\r\n 557\t if (item.range[rangeIndex] === next.klineTime) {\r\n 558\t item.rangeValue[rangeIndex] = klineDataAry[klineIndex]?.[tradeType === \"买\" ? 2 : 3];\r\n 559\t }\r\n 560\t });\r\n 561\t result.push({\r\n 562\t symbol,\r\n 563\t symbolSize: 25,\r\n 564\t symbolRotate: 0,\r\n 565\t symbolOffset: [0, 0],\r\n 566\t label: {\r\n 567\t show: false,\r\n 568\t position: \"top\",\r\n 569\t },\r\n 570\t coord: [next.klineTime, yAxisValue],\r\n 571\t silent: true,\r\n 572\t animation: false,\r\n 573\t customData: {\r\n 574\t tradeType,\r\n 575\t amount: next.amount, // 手数\r\n 576\t part: next.part, // 份数\r\n 577\t profitAndLoss: next.profitAndLoss, // 盈亏\r\n 578\t openPriceAll: next.openPriceAll, // 开仓价\r\n 579\t closePriceAll: next.closePriceAll, // 收仓价\r\n 580\t },\r\n 581\t });\r\n 582\t return result;\r\n 583\t }, []);\r\n 584\t };\r\n 585\t // 交易点: 开平配置数据 (如果是价格, 则无需偏移, 如果是K线高低价, 则需要偏移)\r\n 586\t const handleOpenCloseConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 587\t const { sellBuyByPrice } = userKlineConfig;\r\n 588\t const result = data.reduce((result, next, index) =\u003e {\r\n 589\t // 1.交易类型\r\n 590\t const tradeType = handleTradeType(next, 1);\r\n 591\t // 2.图标位置 [开在K线上方,平在K线下方]\r\n 592\t const position = [\"开多\", \"开空\"].includes(tradeType) ? \"top\" : \"bottom\";\r\n 593\t // 3.获取对应Y轴值 [根据自定义配置判断, 是按价格还是直接用K线的最高低价]\r\n 594\t let yAxisValue = null;\r\n 595\t if (sellBuyByPrice) {\r\n 596\t yAxisValue = position === \"top\" ? (next.openPriceAll / next.amount)?.toFixed(2) : (next.closePriceAll / next.amount)?.toFixed(2);\r\n 597\t } else {\r\n 598\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 599\t yAxisValue = position === \"top\" ? klineDataAry[klineIndex]?.[3] : klineDataAry[klineIndex]?.[2];\r\n 600\t }\r\n","preview":"401\t startOfWeek.setHours(0, 0, 0, 0);\n402\t const nowWeek5 = new Date(startOfWeek);\n403\t nowWeek5.setDate(startOfWeek.getDate() + 5);","bytes":9945,"is_error":false,"stored_at":"2026-07-27T07:05:32.2774062Z"}
|
|
9
|
+
{"id":"t1.tool27","tool_name":"read_file","full_text":"[read_file path=packages/KlinePlus/utils.js lines=401-580/772 bytes=28356]\n[hint] more remains; next page: read_file(path=\"packages/KlinePlus/utils.js\", offset=581, limit=180)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t startOfWeek.setHours(0, 0, 0, 0);\r\n 402\t const nowWeek5 = new Date(startOfWeek);\r\n 403\t nowWeek5.setDate(startOfWeek.getDate() + 5);\r\n 404\t nowWeek5.setHours(23, 59, 59, 0);\r\n 405\t const nextWeek5 = new Date(startOfWeek);\r\n 406\t nextWeek5.setDate(startOfWeek.getDate() + 12);\r\n 407\t nextWeek5.setHours(9, 0, 0, 0);\r\n 408\t const result = date \u003c= nowWeek5 ? nowWeek5 : nextWeek5;\r\n 409\t const year = result.getFullYear();\r\n 410\t const month = String(result.getMonth() + 1).padStart(2, \"0\");\r\n 411\t const day = String(result.getDate()).padStart(2, \"0\");\r\n 412\t return `${year}-${month}-${day} 09:00:00`;\r\n 413\t }\r\n 414\t /**\r\n 415\t * @description: 1mon\r\n 416\t * 本月最后一天\r\n 417\t */\r\n 418\t case \"8\": {\r\n 419\t const date = new Date(time);\r\n 420\t const nextMonth = new Date(date.getFullYear(), date.getMonth() + 1, 1);\r\n 421\t const endOfMonth = new Date(nextMonth);\r\n 422\t endOfMonth.setDate(nextMonth.getDate() - 1);\r\n 423\t endOfMonth.setHours(9, 0, 0, 0);\r\n 424\t const year = endOfMonth.getFullYear();\r\n 425\t const month = String(endOfMonth.getMonth() + 1).padStart(2, \"0\");\r\n 426\t const day = String(endOfMonth.getDate()).padStart(2, \"0\");\r\n 427\t return `${year}-${month}-${day} 09:00:00`;\r\n 428\t }\r\n 429\t }\r\n 430\t // 1, 2, 3, 4步阶计算\r\n 431\t const date = new Date(time);\r\n 432\t const year = date.getFullYear();\r\n 433\t const month = date.getMonth(); // 0-based\r\n 434\t const day = date.getDate();\r\n 435\t const hour = date.getHours();\r\n 436\t const minute = date.getMinutes();\r\n 437\t const second = date.getSeconds();\r\n 438\t const computeSmallCycle = (periodMinutes) =\u003e {\r\n 439\t let targetMinute = null;\r\n 440\t // 计算是否需要进位\r\n 441\t const currentPeriod = Math.floor(minute / periodMinutes);\r\n 442\t if (minute % periodMinutes === 0 \u0026\u0026 second === 0) {\r\n 443\t // 正好在周期边界上且秒为0,不调整\r\n 444\t targetMinute = minute;\r\n 445\t } else {\r\n 446\t // 计算下一个周期点\r\n 447\t targetMinute = (currentPeriod + 1) * periodMinutes;\r\n 448\t }\r\n 449\t\r\n 450\t // 处理小时, 分钟的进位\r\n 451\t const minuteCarry = Math.floor(targetMinute / 60);\r\n 452\t const adjustedHour = hour + minuteCarry;\r\n 453\t const adjustedMinute = targetMinute % 60;\r\n 454\t\r\n 455\t // 创建新日期(自动处理日/月/年进位)\r\n 456\t const resultDate = new Date(year, month, day, adjustedHour, adjustedMinute, 0);\r\n 457\t\r\n 458\t // 格式化输出\r\n 459\t const formatPart = (num) =\u003e String(num).padStart(2, \"0\");\r\n 460\t const formattedYear = resultDate.getFullYear();\r\n 461\t const formattedMonth = formatPart(resultDate.getMonth() + 1);\r\n 462\t const formattedDay = formatPart(resultDate.getDate());\r\n 463\t const formattedHour = formatPart(resultDate.getHours());\r\n 464\t const formattedMinute = formatPart(resultDate.getMinutes());\r\n 465\t\r\n 466\t return `${formattedYear}-${formattedMonth}-${formattedDay} ${formattedHour}:${formattedMinute}:00`;\r\n 467\t };\r\n 468\t switch (cycle) {\r\n 469\t case \"1\":\r\n 470\t return computeSmallCycle(1); // 1m周期\r\n 471\t case \"2\":\r\n 472\t return computeSmallCycle(5); // 5m周期\r\n 473\t case \"3\":\r\n 474\t return computeSmallCycle(15); // 15m周期\r\n 475\t case \"4\":\r\n 476\t return computeSmallCycle(30); // 30m周期\r\n 477\t default:\r\n 478\t throw new Error(`Unsupported cycle: ${time} ${cycle}`);\r\n 479\t }\r\n 480\t};\r\n 481\t\r\n 482\t// 将时间标准化为K线时间范围(匹配算法)\r\n 483\texport const normalizeToKlineTimeByMatch = (klineTimeArray, timeRange, cycle) =\u003e {\r\n 484\t let klineStart = null;\r\n 485\t let klineEnd = null;\r\n 486\t const [start, end] = timeRange;\r\n 487\t switch (cycle) {\r\n 488\t // 日线\r\n 489\t case \"6\": {\r\n 490\t klineStart = klineTimeArray.find((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 23:59:59\")).getTime() \u003e= new Date(start).getTime());\r\n 491\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(dayjs(t).format(\"YYYY-MM-DD 00:00:00\")).getTime() \u003c= new Date(end).getTime());\r\n 492\t break;\r\n 493\t }\r\n 494\t // 周线\r\n 495\t case \"7\": {\r\n 496\t const format5 = dayjs(end).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD 23:59:59\");\r\n 497\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\") === dayjs(start).endOf(\"week\").add(1, \"day\").format(\"YYYY-MM-DD\"));\r\n 498\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(format5).getTime());\r\n 499\t break;\r\n 500\t }\r\n 501\t // 月线 (仅判断是否同属本月即可)\r\n 502\t case \"8\": {\r\n 503\t klineStart = klineTimeArray.find((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(start).format(\"YYYY-MM\"));\r\n 504\t klineEnd = klineTimeArray.findLast((t) =\u003e dayjs(t).format(\"YYYY-MM\") === dayjs(end).format(\"YYYY-MM\"));\r\n 505\t break;\r\n 506\t }\r\n 507\t // 常规匹配\r\n 508\t default: {\r\n 509\t klineStart = klineTimeArray.find((t) =\u003e new Date(t).getTime() \u003e= new Date(start).getTime());\r\n 510\t klineEnd = klineTimeArray.findLast((t) =\u003e new Date(t).getTime() \u003c= new Date(end).getTime());\r\n 511\t }\r\n 512\t }\r\n 513\t return [klineStart, klineEnd];\r\n 514\t};\r\n 515\t\r\n 516\t// 生成图表配置所需数据: 成交点位, 成交点位连线\r\n 517\texport const handleMarkPointTradeLog = (tradeLog, cycle, sellBuy, klineTimeArray, klineDataArray, userKlineConfig) =\u003e {\r\n 518\t // 获取交易类型\r\n 519\t const handleTradeType = (data, type) =\u003e {\r\n 520\t const { direction = \"\", tradeAction = \"\", tradeType = \"\" } = data;\r\n 521\t const key = tradeType ?? direction + tradeAction;\r\n 522\t switch (type) {\r\n 523\t // 买卖\r\n 524\t case 0: {\r\n 525\t const keyMap = new Map([\r\n 526\t [\"开多\", \"买\"],\r\n 527\t [\"平多\", \"卖\"],\r\n 528\t [\"开空\", \"卖\"],\r\n 529\t [\"平空\", \"买\"],\r\n 530\t ]);\r\n 531\t return keyMap.get(key);\r\n 532\t }\r\n 533\t // 开平\r\n 534\t case 1: {\r\n 535\t return tradeType ?? direction + tradeAction;\r\n 536\t }\r\n 537\t }\r\n 538\t };\r\n 539\t // 交易点: 买卖配置数据\r\n 540\t const handleSellBuyConfig = (data, baseLineData, klineTimeAry, klineDataAry) =\u003e {\r\n 541\t return data.reduce((result, next) =\u003e {\r\n 542\t const tradeType = handleTradeType(next, 0);\r\n 543\t // 找寻同一根K线上是否有其它点位类型\r\n 544\t const timeTradeList = data.filter((item) =\u003e item.klineTime === next.klineTime);\r\n 545\t const timeTradeTypes = [...new Set(timeTradeList.map((item) =\u003e handleTradeType(item, 0)))];\r\n 546\t let symbol = null;\r\n 547\t if (timeTradeTypes.length \u003e 1) {\r\n 548\t symbol = \"image://\" + new URL(\"./images/t.svg\", import.meta.url).href;\r\n 549\t } else {\r\n 550\t symbol = \"image://\" + new URL(`./images/${tradeType === \"买\" ? \"buy\" : \"sell\"}.svg`, import.meta.url).href;\r\n 551\t }\r\n 552\t // 获取对应K线索引, 图标Y值永远为: 高, 连线起始Y值为: 低, 末端Y值为: 高\r\n 553\t const klineIndex = binarySearch(klineTimeAry, next.klineTime);\r\n 554\t const yAxisValue = klineDataAry[klineIndex]?.[3];\r\n 555\t baseLineData.forEach((item) =\u003e {\r\n 556\t const rangeIndex = tradeType === \"买\" ? 0 : 1;\r\n 557\t if (item.range[rangeIndex] === next.klineTime) {\r\n 558\t item.rangeValue[rangeIndex] = klineDataAry[klineIndex]?.[tradeType === \"买\" ? 2 : 3];\r\n 559\t }\r\n 560\t });\r\n 561\t result.push({\r\n 562\t symbol,\r\n 563\t symbolSize: 25,\r\n 564\t symbolRotate: 0,\r\n 565\t symbolOffset: [0, 0],\r\n 566\t label: {\r\n 567\t show: false,\r\n 568\t position: \"top\",\r\n 569\t },\r\n 570\t coord: [next.klineTime, yAxisValue],\r\n 571\t silent: true,\r\n 572\t animation: false,\r\n 573\t customData: {\r\n 574\t tradeType,\r\n 575\t amount: next.amount, // 手数\r\n 576\t part: next.part, // 份数\r\n 577\t profitAndLoss: next.profitAndLoss, // 盈亏\r\n 578\t openPriceAll: next.openPriceAll, // 开仓价\r\n 579\t closePriceAll: next.closePriceAll, // 收仓价\r\n 580\t },\r\n","preview":"401\t startOfWeek.setHours(0, 0, 0, 0);\n402\t const nowWeek5 = new Date(startOfWeek);\n403\t nowWeek5.setDate(startOfWeek.getDate() + 5);","bytes":8733,"is_error":false,"stored_at":"2026-07-27T07:05:40.0990652Z"}
|
|
10
|
+
{"id":"t1.tool31","tool_name":"read_file","full_text":"[read_file path=packages/Kline/index.vue lines=1-892/892 bytes=27214]\n 1\t\u003cscript setup lang=\"ts\"\u003e\r\n 2\timport * as echarts from \"echarts\";\r\n 3\timport { ref, computed, watch, onMounted, onUnmounted } from \"vue\";\r\n 4\timport Tips from \"./components/Tips/index.vue\";\r\n 5\timport Contextmenu from \"./components/Contextmenu/index.vue\";\r\n 6\timport { getOption, getLineOption } from \"./option\";\r\n 7\timport { printConsole, formatValue, formatPrice } from \"./utils\";\r\n 8\timport type { EChartsType, EChartsOption, ECElementEvent, DataZoomComponentOption } from \"echarts\";\r\n 9\timport type { KlineDataType, LineDataType, InConfig, MenuDataType } from \"./type.d.ts\";\r\n 10\t\r\n 11\t/**\r\n 12\t * 组件接收参数\r\n 13\t * @param {Object} indicator 指标线\r\n 14\t * @param {KlineDataType} klineData K线数据\r\n 15\t * @param {Array} markData 标注点位数据 [开平点, 买卖点, 信号点,...]\r\n 16\t * @param {Array} lineData K线额外画线数据 [预警线, 持仓线, 条件单, ...]\r\n 17\t * @param {Array} brushRange 区域刷选范围数据\r\n 18\t * @param {Array} defaultMenuData 菜单项数据\r\n 19\t * @param {Boolean} isSelect 组件是否被选中\r\n 20\t *\r\n 21\t * @param {InConfig} config 其他配置项数据\r\n 22\t */\r\n 23\tconst props = defineProps({\r\n 24\t indicator: {\r\n 25\t type: Object,\r\n 26\t default: () =\u003e ({}),\r\n 27\t },\r\n 28\t klineData: {\r\n 29\t type: Array,\r\n 30\t default: () =\u003e [],\r\n 31\t },\r\n 32\t markData: {\r\n 33\t type: Array,\r\n 34\t default: () =\u003e [],\r\n 35\t },\r\n 36\t lineData: {\r\n 37\t type: Array,\r\n 38\t default: () =\u003e [],\r\n 39\t },\r\n 40\t netPositionData: {\r\n 41\t type: Array,\r\n 42\t default: () =\u003e [],\r\n 43\t },\r\n 44\t relevanceData: {\r\n 45\t type: Array,\r\n 46\t default: () =\u003e [],\r\n 47\t },\r\n 48\t priceTrendData: {\r\n 49\t type: Array,\r\n 50\t default: () =\u003e [],\r\n 51\t },\r\n 52\t priceTrendPercentData: {\r\n 53\t type: Array,\r\n 54\t default: () =\u003e [],\r\n 55\t },\r\n 56\t brushRange: {\r\n 57\t type: Array,\r\n 58\t default: () =\u003e [],\r\n 59\t },\r\n 60\t defaultMenuData: {\r\n 61\t type: Array,\r\n 62\t default: () =\u003e [],\r\n 63\t },\r\n 64\t isSelect: {\r\n 65\t type: Boolean,\r\n 66\t default: () =\u003e false,\r\n 67\t },\r\n 68\t config: {\r\n 69\t type: Object,\r\n 70\t default: () =\u003e ({}),\r\n 71\t },\r\n 72\t});\r\n 73\t\r\n 74\t/**\r\n 75\t * K线功能默认配置\r\n 76\t * @param {Number} totalBarCount k线总条数\r\n 77\t * @param {Number} defaultShowBarCount k线默认展示条数\r\n 78\t * @param {Number} preBarCount k线预加载条数,用于计算指标线\r\n 79\t * ---------------------------------------------------------\r\n 80\t * @param {Number} gridLeft k线组件grid: left\r\n 81\t * @param {Number} gridTop k线组件grid: top\r\n 82\t * @param {Number} gridRight k线组件grid: right\r\n 83\t * @param {Number} gridBottom k线组件grid: bottom\r\n 84\t * ---------------------------------------------------------\r\n 85\t * @param {Object} warningConfig 预警线配置\r\n 86\t * @param {Object} positionConfig 持仓线配置\r\n 87\t * @param {Object} conditionConfig 条件单配置\r\n 88\t */\r\n 89\tconst defaultConfig: InConfig = {\r\n 90\t totalBarCount: 2000,\r\n 91\t defaultShowBarCount: 200,\r\n 92\t preBarCount: 800,\r\n 93\t maxValueSpan: 10000,\r\n 94\t gridLeft: 60,\r\n 95\t gridTop: 0,\r\n 96\t gridRight: 60,\r\n 97\t gridBottom: 30,\r\n 98\t // 预警线配置\r\n 99\t warningConfig: {\r\n 100\t draggable: false,\r\n 101\t lineColor: \"#fff\",\r\n 102\t textColor: \"#fff\",\r\n 103\t },\r\n 104\t // 持仓线配置\r\n 105\t positionConfig: {\r\n 106\t draggable: false,\r\n 107\t lineColor: \"#e45d07\",\r\n 108\t textColor: \"#fff\",\r\n 109\t },\r\n 110\t // 条件单配置\r\n 111\t conditionConfig: {\r\n 112\t draggable: false,\r\n 113\t lineColor: \"#fff\",\r\n 114\t textColor: \"#fff\",\r\n 115\t profitLineColor: \"#b71e44\",\r\n 116\t profitTextColor: \"#fff\",\r\n 117\t lossLineColor: \"#749b66\",\r\n 118\t lossTextColor: \"#fff\",\r\n 119\t },\r\n 120\t // Tips配置\r\n 121\t tipsConfig: {\r\n 122\t open: true,\r\n 123\t heigh: true,\r\n 124\t low: true,\r\n 125\t close: true,\r\n 126\t business: true,\r\n 127\t riseAndFall: true,\r\n 128\t },\r\n 129\t // 动态加载配置\r\n 130\t dynamicLoadConfig: {\r\n 131\t historyVisible: false,\r\n 132\t historyLoadCallBack: () =\u003e {},\r\n 133\t futureVisible: false,\r\n 134\t futureLoadCallBack: () =\u003e {},\r\n 135\t },\r\n 136\t // 是否禁用鼠标滚轮缩放K线,在开启动态后续数据加载时,建议开启,否则会引起datazoom内部缩放拖拽触发后续加载冲突\r\n 137\t zoomLock: false,\r\n 138\t // 是否启用计算收盘价分布统计功能\r\n 139\t isOpenDS: false,\r\n 140\t};\r\n 141\t\r\n 142\t/**\r\n 143\t * @description: 合并后的功能配置项\r\n 144\t */\r\n 145\tconst config: { value: InConfig } = computed(() =\u003e {\r\n 146\t return { ...defaultConfig, ...props.config };\r\n 147\t});\r\n 148\t\r\n 149\tconst option = ref\u003cany\u003e(null); // 处理后的echarts配置\r\n 150\tconst activeIndex = ref(-1); // 当前鼠标激活的数据索引\r\n 151\t\r\n 152\t// Tips: [开, 收, 低, 高, 成交额, 昨收]\r\n 153\tconst kLineTips = computed(() =\u003e {\r\n 154\t if (option.value \u0026\u0026 option.value.dataset[0]?.source?.klineData[activeIndex.value]) {\r\n 155\t const klineItem = option.value.dataset[0].source.klineData[activeIndex.value];\r\n 156\t // 处理结果\r\n 157\t const result = [];\r\n 158\t const { open, heigh, low, close, business, riseAndFall } = config.value.tipsConfig;\r\n 159\t open \u0026\u0026 result.push({ label: \"开\", value: klineItem[0], color: \"rgb(153, 153, 153)\" });\r\n 160\t heigh \u0026\u0026 result.push({ label: \"高\", value: klineItem[3], color: \"rgb(153, 153, 153)\" });\r\n 161\t low \u0026\u0026 result.push({ label: \"低\", value: klineItem[2], color: \"rgb(153, 153, 153)\" });\r\n 162\t close \u0026\u0026 result.push({ label: \"收\", value: klineItem[1], color: \"rgb(153, 153, 153)\" });\r\n 163\t business \u0026\u0026 result.push({ label: \"额\", value: formatPrice(klineItem[4]), color: \"rgb(153, 153, 153)\" });\r\n 164\t if (riseAndFall) {\r\n 165\t let ratio = (((klineItem[1] - klineItem[5]) / klineItem[1]) * 100).toFixed(2);\r\n 166\t let ratioColor = +ratio === 0.0 ? \"white\" : +ratio \u003e 0 ? \"red\" : \"#00ff00\";\r\n 167\t result.push({ label: \"涨跌\", value: `${ratio}%`, color: ratioColor });\r\n 168\t }\r\n 169\t return result;\r\n 170\t }\r\n 171\t return [];\r\n 172\t});\r\n 173\t// Tips: 指标\r\n 174\tconst indicatorTips = computed(() =\u003e {\r\n 175\t if (option.value \u0026\u0026 option.value.dataset[0]?.source?.indicatorData) {\r\n 176\t return option.value.dataset[0].source.indicatorData.reduce((result: any, next: any) =\u003e {\r\n 177\t next.data[activeIndex.value] \u0026\u0026\r\n 178\t result.push({\r\n 179\t label: next.key,\r\n 180\t value: formatValue(next.data[activeIndex.value]),\r\n 181\t color: next.color,\r\n 182\t });\r\n 183\t return result;\r\n 184\t }, []);\r\n 185\t }\r\n 186\t return [];\r\n 187\t});\r\n 188\t// Tips: 价格趋势[价差定制]\r\n 189\tconst priceTrendTips = computed(() =\u003e {\r\n 190\t // 颜色配置\r\n 191\t const colorList = [\r\n 192\t \"#FFFFFF\",\r\n 193\t \"#FFDD00\",\r\n 194\t \"#FF00FF\",\r\n 195\t \"#00FF00\",\r\n 196\t \"#FF6000\",\r\n 197\t \"#1677FF\",\r\n 198\t \"#7C3CC9\",\r\n 199\t \"#FF0000\",\r\n 200\t \"#FB9A0E\",\r\n 201\t \"#00B7FF\",\r\n 202\t ];\r\n 203\t // 价格趋势\r\n 204\t if (props.priceTrendData.length \u003e 0) {\r\n 205\t const result = [];\r\n 206\t props.priceTrendData.forEach((item: any, index: number) =\u003e {\r\n 207\t const value = item.data[activeIndex.value] ? formatValue(item.data[activeIndex.value][1]) : null;\r\n 208\t if (value !== null) {\r\n 209\t result.push({\r\n 210\t label: item.name,\r\n 211\t value: formatValue(item.data[activeIndex.value]?.[1]),\r\n 212\t color: colorList[index],\r\n 213\t });\r\n 214\t }\r\n 215\t });\r\n 216\t return result;\r\n 217\t }\r\n 218\t // 价格趋势百分比\r\n 219\t if (props.priceTrendPercentData.length \u003e 0) {\r\n 220\t const result = [];\r\n 221\t const chartOption = echartsInstance.getOption()\r\n 222\t chartOption.series.filter(line =\u003e line.name.includes('二腿价格走势百分比图')).forEach((item: any, index: number) =\u003e {\r\n 223\t const value = item.data[activeIndex.value] ? formatValue(item.data[activeIndex.value][1]) : null;\r\n 224\t if (value !== null) {\r\n 225\t result.push({\r\n 226\t label: item.name.split('-')[1],\r\n 227\t value: `${formatValue(item.data[activeIndex.value]?.[1])}%`,\r\n 228\t color: colorList[index],\r\n 229\t });\r\n 230\t }\r\n 231\t });\r\n 232\t return result;\r\n 233\t }\r\n 234\t return [];\r\n 235\t});\r\n 236\t\r\n 237\t// 监视: props[klineData, indicator, markData, netPositionData, relevanceData] -\u003e 重绘全部\r\n 238\twatch(\r\n 239\t () =\u003e [props.klineData, props.indicator, props.markData, props.netPositionData, props.relevanceData],\r\n 240\t () =\u003e {\r\n 241\t draw();\r\n 242\t },\r\n 243\t {\r\n 244\t deep: true,\r\n 245\t }\r\n 246\t);\r\n 247\t// 监视: props.lineData - 额外画线数据 -\u003e 重新绘制额外线\r\n 248\twatch(\r\n 249\t () =\u003e props.lineData,\r\n 250\t async () =\u003e {\r\n 251\t printConsole(\"st-kline组件消息:props.lineData监控,额外画线数据发生改变,重绘-\u003eline\", {\r\n 252\t color: \"red\",\r\n 253\t });\r\n 254\t draw(\"line\");\r\n 255\t },\r\n 256\t {\r\n 257\t deep: true,\r\n 258\t }\r\n 259\t);\r\n 260\t// 监视: props.brushRange - 区域刷选数据 -\u003e 重新刷选\r\n 261\twatch(\r\n 262\t () =\u003e props.brushRange,\r\n 263\t async () =\u003e {\r\n 264\t printConsole(\"st-kline组件消息:props.brushRange监控,区域刷选数据发生改变,重绘-\u003ebrush\", {\r\n 265\t color: \"red\",\r\n 266\t });\r\n 267\t draw(\"brush\");\r\n 268\t },\r\n 269\t {\r\n 270\t deep: true,\r\n 271\t }\r\n 272\t);\r\n 273\t// 监视: props.priceTrendData - 二腿价格趋势数据 -\u003e 重绘趋势\r\n 274\twatch(\r\n 275\t () =\u003e props.priceTrendData,\r\n 276\t async () =\u003e {\r\n 277\t draw(\"priceTrend\");\r\n 278\t },\r\n 279\t {\r\n 280\t deep: true,\r\n 281\t }\r\n 282\t);\r\n 283\t// 监视: props.priceTrendPercentData - 二腿价格趋势百分比数据 -\u003e 重绘趋势百分比\r\n 284\twatch(\r\n 285\t () =\u003e props.priceTrendPercentData,\r\n 286\t async () =\u003e {\r\n 287\t draw(\"priceTrendPercent\");\r\n 288\t },\r\n 289\t {\r\n 290\t deep: true,\r\n 291\t }\r\n 292\t);\r\n 293\t\r\n 294\t/**\r\n 295\t * @description: 图表绘制函数\r\n 296\t * @param {?String} type 绘制类型[Kline-K线,line-额外线条,brush-区域刷选,不传-全部]\r\n 297\t */\r\n 298\tconst draw = async (type?: string) =\u003e {\r\n 299\t const callBackMap = new Map([\r\n 300\t // kline-K线绘制\r\n 301\t [\r\n 302\t \"kline\",\r\n 303\t async () =\u003e {\r\n 304\t const newOption = await getOption(\r\n 305\t props.klineData as KlineDataType,\r\n 306\t props.markData,\r\n 307\t props.indicator,\r\n 308\t config.value,\r\n 309\t props.netPositionData,\r\n 310\t props.relevanceData\r\n 311\t );\r\n 312\t const { graphic } = echartsInstance.getOption() ?? { graphic: [] };\r\n 313\t option.value = { ...newOption, graphic };\r\n 314\t echartsInstance.setOption(option.value, true);\r\n 315\t printConsole(\"st-kline组件消息:K线绘制完毕-draw\", { color: \"green\" });\r\n 316\t },\r\n 317\t ],\r\n 318\t // line-额外线条绘制\r\n 319\t [\r\n 320\t \"line\",\r\n 321\t () =\u003e {\r\n 322\t const echartsOptions = echartsInstance.getOption();\r\n 323\t const graphic = getLineOption(props.lineData as LineDataType, config.value, echartsInstance);\r\n 324\t option.value = { ...echartsOptions, graphic };\r\n 325\t echartsInstance.setOption(option.value, {\r\n 326\t replaceMerge: [\"graphic\"],\r\n 327\t });\r\n 328\t printConsole(\"st-kline组件消息:额外画线绘制完毕-draw\", { color: \"green\" });\r\n 329\t },\r\n 330\t ],\r\n 331\t // brush-区域刷选\r\n 332\t [\r\n 333\t \"brush\",\r\n 334\t () =\u003e {\r\n 335\t const { brushRange } = props;\r\n 336\t if (brushRange instanceof Array \u0026\u0026 brushRange.length \u003e 0) {\r\n 337\t echartsInstance.dispatchAction({\r\n 338\t type: \"brush\",\r\n 339\t areas: [\r\n 340\t {\r\n 341\t brushType: \"lineX\",\r\n 342\t coordRange: [brushRange[0] + \"\", brushRange[1] + \"\"],\r\n 343\t xAxisIndex: 0,\r\n 344\t },\r\n 345\t ],\r\n 346\t });\r\n 347\t printConsole(\"st-kline组件消息:区域刷选绘制完毕-draw\", { color: \"green\" });\r\n 348\t }\r\n 349\t },\r\n 350\t ],\r\n 351\t // 全部绘制\r\n 352\t [\r\n 353\t undefined,\r\n 354\t async () =\u003e {\r\n 355\t await draw(\"kline\");\r\n 356\t await draw(\"line\");\r\n 357\t await draw(\"priceTrend\");\r\n 358\t await draw(\"priceTrendPercent\");\r\n 359\t await draw(\"brush\");\r\n 360\t },\r\n 361\t ],\r\n 362\t // history-K线历史数据绘制\r\n 363\t [\r\n 364\t \"history\",\r\n 365\t async () =\u003e {\r\n 366\t const chartOption = echartsInstance.getOption();\r\n 367\t const newOption = await getOption(\r\n 368\t props.klineData as KlineDataType,\r\n 369\t props.markData,\r\n 370\t props.indicator,\r\n 371\t config.value,\r\n 372\t props.netPositionData,\r\n 373\t props.relevanceData\r\n 374\t );\r\n 375\t const addDataLength =\r\n 376\t newOption.dataset.source.klineData.length - chartOption.dataset[0].source.klineData.length;\r\n 377\t echartsInstance.setOption(\r\n 378\t {\r\n 379\t ...newOption,\r\n 380\t dataZoom: [\r\n 381\t {\r\n 382\t type: \"inside\",\r\n 383\t xAxisIndex: [0, 0],\r\n 384\t zoomLock: config.value.zoomLock,\r\n 385\t maxValueSpan: config.value.maxValueSpan,\r\n 386\t startValue: chartOption.dataZoom[0].startValue + addDataLength,\r\n 387\t endValue: chartOption.dataZoom[0].endValue + addDataLength,\r\n 388\t },\r\n 389\t ],\r\n 390\t },\r\n 391\t true\r\n 392\t );\r\n 393\t await draw(\"priceTrend\");\r\n 394\t },\r\n 395\t ],\r\n 396\t // future-K线后续数据绘制\r\n 397\t [\r\n 398\t \"future\",\r\n 399\t async () =\u003e {\r\n 400\t const chartOption = echartsInstance.getOption();\r\n 401\t const newOption = await getOption(\r\n 402\t props.klineData as KlineDataType,\r\n 403\t props.markData,\r\n 404\t props.indicator,\r\n 405\t config.value,\r\n 406\t props.netPositionData,\r\n 407\t props.relevanceData\r\n 408\t );\r\n 409\t newOption.dataZoom[0].start =\r\n 410\t ((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /\r\n 411\t props.klineData.length) *\r\n 412\t 100;\r\n 413\t echartsInstance.setOption(newOption, true);\r\n 414\t await draw(\"priceTrend\");\r\n 415\t },\r\n 416\t ],\r\n 417\t // priceTrend-二腿价格趋势绘制\r\n 418\t [\r\n 419\t \"priceTrend\",\r\n 420\t () =\u003e {\r\n 421\t if (props.priceTrendData.length === 0) return;\r\n 422\t const echartsOptions = echartsInstance.getOption();\r\n 423\t // 颜色配置\r\n 424\t const colorList = [\r\n 425\t \"#FFFFFF\",\r\n 426\t \"#FFDD00\",\r\n 427\t \"#FF00FF\",\r\n 428\t \"#00FF00\",\r\n 429\t \"#FF6000\",\r\n 430\t \"#1677FF\",\r\n 431\t \"#7C3CC9\",\r\n 432\t \"#FF0000\",\r\n 433\t \"#FB9A0E\",\r\n 434\t \"#00B7FF\",\r\n 435\t ];\r\n 436\t // 格式化series配置\r\n 437\t const priceTrendLineSeries = props.priceTrendData.reduce((result, next, index) =\u003e {\r\n 438\t const { name, data } = next;\r\n 439\t const line = {\r\n 440\t name: `二腿价格走势图-${name}`,\r\n 441\t type: \"line\",\r\n 442\t data,\r\n 443\t symbol: \"none\",\r\n 444\t yAxisIndex: 1,\r\n 445\t connectNulls: true,\r\n 446\t itemStyle: {\r\n 447\t color: colorList[index],\r\n 448\t },\r\n 449\t lineStyle: {\r\n 450\t type: \"dashed\", // 虚线\r\n 451\t width: 1,\r\n 452\t },\r\n 453\t };\r\n 454\t result.push(line);\r\n 455\t return result;\r\n 456\t }, []);\r\n 457\t option.value = { ...echartsOptions, series: [...echartsOptions.series, ...priceTrendLineSeries] };\r\n 458\t echartsInstance.setOption(option.value, {\r\n 459\t replaceMerge: [\"series\"],\r\n 460\t });\r\n 461\t },\r\n 462\t ],\r\n 463\t // priceTrendPercent-二腿价格趋势百分比绘制-初始绘制\r\n 464\t [\r\n 465\t \"priceTrendPercent\",\r\n 466\t async () =\u003e {\r\n 467\t if (props.priceTrendPercentData.length === 0) return;\r\n 468\t const chartOption = echartsInstance.getOption();\r\n 469\t // 颜色配置\r\n 470\t const colorList = [\r\n 471\t \"#FFFFFF\",\r\n 472\t \"#FFDD00\",\r\n 473\t \"#FF00FF\",\r\n 474\t \"#00FF00\",\r\n 475\t \"#FF6000\",\r\n 476\t \"#1677FF\",\r\n 477\t \"#7C3CC9\",\r\n 478\t \"#FF0000\",\r\n 479\t \"#FB9A0E\",\r\n 480\t \"#00B7FF\",\r\n 481\t ];\r\n 482\t // 获取当屏首根K线索引\r\n 483\t const startIndex = chartOption.dataZoom[0].startValue;\r\n 484\t // 处理趋势数据(百分比)\r\n 485\t const handlePriceTrendSeries = props.priceTrendPercentData.reduce((result, next, index) =\u003e {\r\n 486\t const { name, data } = next;\r\n 487\t const handleData = data.length \u003e config.value.totalBarCount ? data.slice(data.length - config.value.totalBarCount) : data\r\n 488\t const priceTrendBase = handleData[startIndex][1];\r\n 489\t const lineData = handleData.map((item) =\u003e {\r\n 490\t return [item[0], ((item[1] - priceTrendBase) / priceTrendBase) * 100];\r\n 491\t });\r\n 492\t const line = {\r\n 493\t name: `二腿价格走势百分比图-${name}`,\r\n 494\t type: \"line\",\r\n 495\t data: lineData,\r\n 496\t symbol: \"none\",\r\n 497\t yAxisIndex: 1,\r\n 498\t connectNulls: true,\r\n 499\t itemStyle: {\r\n 500\t color: colorList[index],\r\n 501\t },\r\n 502\t lineStyle: {\r\n 503\t type: \"dashed\", // 虚线\r\n 504\t width: 1,\r\n 505\t },\r\n 506\t };\r\n 507\t result.push(line);\r\n 508\t return result;\r\n 509\t }, []);\r\n 510\t // 填充价格趋势线(避免重复)\r\n 511\t chartOption.series = [...chartOption.series.filter(line =\u003e {\r\n 512\t return !line.name.includes('二腿价格走势百分比图')\r\n 513\t }), ...handlePriceTrendSeries];\r\n 514\t echartsInstance.setOption(chartOption, true);\r\n 515\t },\r\n 516\t ],\r\n 517\t ]);\r\n 518\t const callBack = callBackMap.get(type);\r\n 519\t if (callBack instanceof Function) {\r\n 520\t await callBack();\r\n 521\t }\r\n 522\t};\r\n 523\t\r\n 524\t//----------------------------右键菜单功能相关----------------------------------\r\n 525\tconst cursorPenVisible = ref(false); // 画笔模式开关\r\n 526\t\r\n 527\tconst menuData = ref\u003cMenuDataType\u003e([]);\r\n 528\t\r\n 529\t/**\r\n 530\t * @description: 点击菜单项\r\n 531\t * @param {Object} item 菜单项的数据\r\n 532\t */\r\n 533\tconst menuClick = (item: any) =\u003e {\r\n 534\t const { callBack } = item;\r\n 535\t callBack instanceof Function \u0026\u0026 callBack(echartsInstance, cursorPenVisible);\r\n 536\t};\r\n 537\t\r\n 538\t/**\r\n 539\t * @description: 菜单关闭的回调\r\n 540\t * @todo: 进行菜单内容的初始化\r\n 541\t */\r\n 542\tconst closeContextMenuCallBack = () =\u003e {\r\n 543\t menuData.value = props.defaultMenuData as MenuDataType;\r\n 544\t};\r\n 545\t\r\n 546\t//----------------------------Echarts基座-------------------------------\r\n 547\tconst echartsRef = ref\u003cHTMLElement\u003e();\r\n 548\tlet echartsInstance: EChartsType; // echarts实例\r\n 549\tlet chartDomObserver: any; // 监视图表DOM变化\r\n 550\t\r\n 551\t/**\r\n 552\t * @description: echarts数据高亮回调\r\n 553\t * @param {any} data echarts数据\r\n 554\t * @param {EChartsType} chart echarts实例\r\n 555\t */\r\n 556\tconst highlight = (data: any, chart: EChartsType) =\u003e {\r\n 557\t if (data) {\r\n 558\t // 图表内部移动\r\n 559\t activeIndex.value = typeof data?.batch[0]?.dataIndex === \"number\" ? data?.batch[0]?.dataIndex : -1;\r\n 560\t } else {\r\n 561\t // 移出图表\r\n 562\t const chartOptions: EChartsOption = chart.getOption() as EChartsOption;\r\n 563\t if (chartOptions.dataZoom instanceof Array) {\r\n 564\t activeIndex.value = chartOptions.dataZoom[0].endValue as number;\r\n 565\t }\r\n 566\t }\r\n 567\t};\r\n 568\t\r\n 569\t/**\r\n 570\t * @description: echarts数据缩放的相关参数\r\n 571\t * @param {any} datazoomTimer 缩放回调函数的延时器\r\n 572\t * @param {Number} datazoomTime 缩放回调函数的延时器的时间\r\n 573\t * @param {Function} datazoom 数据缩放的回调函数\r\n 574\t *\r\n 575\t * 判断具体是拖拽,还是缩放的逻辑\r\n 576\t * 根据start~end的差值前后是否变动\r\n 577\t * 因为拖拽时,他们的差值是恒定的,缩放时是会变动\r\n 578\t */\r\n 579\tlet datazoomTimer: any = null;\r\n 580\tconst datazoomTime: number = 300;\r\n 581\tlet historyIsLoading = false;\r\n 582\tlet historyIsAllLoad = ref(false);\r\n 583\tlet futureIsLoading = false;\r\n 584\tlet futureIsAllLoad = ref(false);\r\n 585\tconst datazoom = async (params: any) =\u003e {\r\n 586\t // 执行[方向键]时,返回的是startValue和endValue\r\n 587\t // 执行[鼠标]时,返回的是batch\r\n 588\t /**\r\n 589\t * @description: 历史数据动态加载渲染逻辑\r\n 590\t * 前置条件:\r\n 591\t * 1.允许开启历史动态加载\r\n 592\t * 2.如果剩余K线根数不足100根\r\n 593\t * 3.没有正在加载历史数据\r\n 594\t * 4.历史数据并未已全部获取\r\n 595\t */\r\n 596\t // 获取左侧剩余K线根数\r\n 597\t const historyLength = params.batch ? params.batch[0].start * config.value.totalBarCount : params.startValue;\r\n 598\t if (\r\n 599\t config.value.dynamicLoadConfig.historyVisible \u0026\u0026\r\n 600\t historyLength \u003c= 100 \u0026\u0026\r\n 601\t !historyIsLoading \u0026\u0026\r\n 602\t !historyIsAllLoad.value\r\n 603\t ) {\r\n 604\t historyIsLoading = true;\r\n 605\t await config.value.dynamicLoadConfig.historyLoadCallBack(historyIsAllLoad);\r\n 606\t await draw(\"history\");\r\n 607\t historyIsLoading = false;\r\n 608\t }\r\n 609\t /**\r\n 610\t * @description: 后续数据动态加载渲染逻辑\r\n 611\t * 前置条件:\r\n 612\t * 1.允许开启后续动态加载\r\n 613\t * 2.右侧已为图表尽头\r\n 614\t * 3.没有正在加载后续数据\r\n 615\t * 4.后续数据并未已全部获取\r\n 616\t * 5.仅仅在[鼠标拖拽,方向键左右]时\r\n 617\t */\r\n 618\t // 获取右侧K线是否最后一根\r\n 619\t const nowIsEnd = params.batch\r\n 620\t ? params.batch[0].end === 100\r\n 621\t : params.endValue \u003e= props.klineData.length - config.value.preBarCount - 1;\r\n 622\t const actionIsWheel = params.actionIsWheel;\r\n 623\t if (\r\n 624\t config.value.dynamicLoadConfig.futureVisible \u0026\u0026\r\n 625\t nowIsEnd \u0026\u0026\r\n 626\t !futureIsLoading \u0026\u0026\r\n 627\t !futureIsAllLoad.value \u0026\u0026\r\n 628\t !actionIsWheel\r\n 629\t ) {\r\n 630\t futureIsLoading = true;\r\n 631\t await config.value.dynamicLoadConfig.futureLoadCallBack(futureIsAllLoad);\r\n 632\t await draw(\"future\");\r\n 633\t futureIsLoading = false;\r\n 634\t }\r\n 635\t clearTimeout(datazoomTimer);\r\n 636\t datazoomTimer = setTimeout(() =\u003e {\r\n 637\t draw(\"line\");\r\n 638\t draw(\"priceTrendPercent\");\r\n 639\t clearTimeout(datazoomTimer);\r\n 640\t datazoomTimer = null;\r\n 641\t }, datazoomTime);\r\n 642\t};\r\n 643\t/**\r\n 644\t * @description: echarts鼠标右击事件回调\r\n 645\t */\r\n 646\tlet echartsContextMenuTimer: any = null;\r\n 647\tconst echartsContextMenu = (params: ECElementEvent) =\u003e {\r\n 648\t echartsContextMenuTimer = setTimeout(() =\u003e {\r\n 649\t // 判定是否点击到的元素为额外画线\r\n 650\t if (params.componentType === \"graphic\") {\r\n 651\t const { oncontextmenu } = params.info?.event ?? {};\r\n 652\t oncontextmenu instanceof Function \u0026\u0026 oncontextmenu(params, params.info, menuData);\r\n 653\t }\r\n 654\t clearTimeout(echartsContextMenuTimer);\r\n 655\t echartsContextMenuTimer = null;\r\n 656\t });\r\n 657\t};\r\n 658\t\r\n 659\t// 绑定事件\r\n 660\tconst addEventListener = () =\u003e {\r\n 661\t echartsInstance.on(\"highlight\", (data: any) =\u003e {\r\n 662\t highlight(data, echartsInstance);\r\n 663\t });\r\n 664\t echartsInstance.on(\"globalout\", () =\u003e {\r\n 665\t highlight(null, echartsInstance);\r\n 666\t });\r\n 667\t echartsInstance.on(\"contextmenu\", (params: ECElementEvent) =\u003e {\r\n 668\t echartsContextMenu(params);\r\n 669\t });\r\n 670\t echartsInstance.on(\"datazoom\", (params: any) =\u003e {\r\n 671\t datazoom(params);\r\n 672\t });\r\n 673\t};\r\n 674\t\r\n 675\t// 解绑事件\r\n 676\tconst removeEventListener = () =\u003e {\r\n 677\t echartsInstance.off(\"highlight\");\r\n 678\t echartsInstance.off(\"globalout\");\r\n 679\t echartsInstance.off(\"contextmenu\");\r\n 680\t echartsInstance.off(\"datazoom\");\r\n 681\t};\r\n 682\t\r\n 683\t// 键盘事件\r\n 684\tconst keyDownEvent = (e: KeyboardEvent) =\u003e {\r\n 685\t if (!echartsInstance) return;\r\n 686\t // 只有选中或者按ctrl才激活按键\r\n 687\t if (!(e.ctrlKey || props.isSelect)) return;\r\n 688\t const option = echartsInstance.getOption() as EChartsOption;\r\n 689\t let { startValue, endValue, start, end } = (option.dataZoom as DataZoomComponentOption[])[0] as {\r\n 690\t startValue: number;\r\n 691\t endValue: number;\r\n 692\t };\r\n 693\t const handleLeft = () =\u003e {\r\n 694\t // 左按键\r\n 695\t if (startValue === 0) {\r\n 696\t return;\r\n 697\t }\r\n 698\t startValue = startValue - 1;\r\n 699\t endValue = endValue - 1;\r\n 700\t echartsInstance.dispatchAction({\r\n 701\t type: \"dataZoom\",\r\n 702\t startValue,\r\n 703\t endValue,\r\n 704\t maxValueSpan: config.value.maxValueSpan,\r\n 705\t });\r\n 706\t };\r\n 707\t const handleRight = () =\u003e {\r\n 708\t // 右按键\r\n 709\t // 如果开启了动态后续数据加载\r\n 710\t if (config.value.dynamicLoadConfig.futureVisible \u0026\u0026 !futureIsLoading \u0026\u0026 !futureIsAllLoad.value) {\r\n 711\t startValue = startValue + 1;\r\n 712\t endValue = endValue + 1;\r\n 713\t echartsInstance.dispatchAction({\r\n 714\t type: \"dataZoom\",\r\n 715\t startValue,\r\n 716\t endValue,\r\n 717\t maxValueSpan: config.value.maxValueSpan,\r\n 718\t });\r\n 719\t } else {\r\n 720\t if (endValue === option.xAxis[0].data.length - 1) {\r\n 721\t return;\r\n 722\t }\r\n 723\t startValue = startValue + 1;\r\n 724\t endValue = endValue + 1;\r\n 725\t echartsInstance.dispatchAction({\r\n 726\t type: \"dataZoom\",\r\n 727\t startValue,\r\n 728\t endValue,\r\n 729\t maxValueSpan: config.value.maxValueSpan,\r\n 730\t });\r\n 731\t }\r\n 732\t };\r\n 733\t const handleUp = () =\u003e {\r\n 734\t // 上按键-放大 最少保持5条数据\r\n 735\t if (endValue - startValue \u003c 5) {\r\n 736\t return;\r\n 737\t }\r\n 738\t // 以鼠标指定的K线为中心点进行放大\r\n 739\t const startDiff = Math.floor((activeIndex.value - startValue) / 2) + 1;\r\n 740\t const endDiff = Math.floor((endValue - activeIndex.value) / 2) + 1;\r\n 741\t startValue = startValue + startDiff;\r\n 742\t endValue = endValue - endDiff;\r\n 743\t echartsInstance.dispatchAction({\r\n 744\t type: \"dataZoom\",\r\n 745\t startValue,\r\n 746\t endValue,\r\n 747\t actionIsWheel: true, // 标记为缩放\r\n 748\t maxValueSpan: config.value.maxValueSpan,\r\n 749\t });\r\n 750\t };\r\n 751\t const handleDown = () =\u003e {\r\n 752\t // 下按键-缩小\r\n 753\t // const diff = Math.min(50, activeIndex.value - startValue);\r\n 754\t // startValue = startValue - diff - 1;\r\n 755\t start = start - (end - start) / 4;\r\n 756\t echartsInstance.dispatchAction({\r\n 757\t type: \"dataZoom\",\r\n 758\t start,\r\n 759\t end,\r\n 760\t actionIsWheel: true, // 标记为缩放\r\n 761\t maxValueSpan: config.value.maxValueSpan,\r\n 762\t });\r\n 763\t };\r\n 764\t const callBackMap = new Map([\r\n 765\t [\"ArrowLeft\", handleLeft],\r\n 766\t [\"ArrowRight\", handleRight],\r\n 767\t [\"ArrowUp\", handleUp],\r\n 768\t [\"ArrowDown\", handleDown],\r\n 769\t ]);\r\n 770\t const callBack = callBackMap.get(e.code);\r\n 771\t callBack instanceof Function \u0026\u0026 callBack();\r\n 772\t};\r\n 773\t\r\n 774\t/**\r\n 775\t * @description: K线组件初始化\r\n 776\t */\r\n 777\tconst init = async () =\u003e {\r\n 778\t let Initializing: boolean | null = true; // 正在初始化\r\n 779\t // 1.初始化图表\r\n 780\t echartsInstance = echarts.init(echartsRef.value) as EChartsType;\r\n 781\t // 2.绘制\r\n 782\t await draw();\r\n 783\t // 3.初始化右键菜单\r\n 784\t menuData.value = props.defaultMenuData as MenuDataType;\r\n 785\t // 4.初始化数据激活索引\r\n 786\t activeIndex.value = ((echartsInstance.getOption().dataZoom as DataZoomComponentOption[])[0].endValue as number) ?? -1;\r\n 787\t // 5.进行图表事件绑定\r\n 788\t addEventListener();\r\n 789\t // 6.进行DOM监控,图表重加载\r\n 790\t chartDomObserver = new ResizeObserver(() =\u003e {\r\n 791\t if (Initializing) {\r\n 792\t Initializing = null;\r\n 793\t return;\r\n 794\t }\r\n 795\t echartsInstance.resize();\r\n 796\t draw(\"line\");\r\n 797\t });\r\n 798\t chartDomObserver.observe(echartsRef.value);\r\n 799\t // 7.绑定键盘事件\r\n 800\t window.addEventListener(\"keydown\", keyDownEvent);\r\n 801\t};\r\n 802\t\r\n 803\tonMounted(() =\u003e {\r\n 804\t init();\r\n 805\t});\r\n 806\t\r\n 807\tonUnmounted(() =\u003e {\r\n 808\t // 1.解绑\r\n 809\t removeEventListener();\r\n 810\t window.removeEventListener(\"keydown\", keyDownEvent);\r\n 811\t // 2.销毁实例\r\n 812\t echartsInstance.dispose();\r\n 813\t // 3.取消监听图表DOM\r\n 814\t chartDomObserver.disconnect();\r\n 815\t chartDomObserver = null;\r\n 816\t});\r\n 817\t\u003c/script\u003e\r\n 818\t\r\n 819\t\u003ctemplate\u003e\r\n 820\t \u003c!-- 神兔-K线组件 --\u003e\r\n 821\t \u003cdiv class=\"st-kline\"\u003e\r\n 822\t \u003c!-- Tips --\u003e\r\n 823\t \u003cdiv class=\"st-kline-header\"\u003e\r\n 824\t \u003c!-- 开, 高, 低, 收 --\u003e\r\n 825\t \u003cTips :data=\"kLineTips\" /\u003e\r\n 826\t \u003c!-- 指标 --\u003e\r\n 827\t \u003cTips :data=\"indicatorTips\" /\u003e\r\n 828\t \u003c!-- 价格趋势[价差定制] --\u003e\r\n 829\t \u003cTips :data=\"priceTrendTips\" /\u003e\r\n 830\t \u003c/div\u003e\r\n 831\t \u003c!-- 图表 + 菜单 --\u003e\r\n 832\t \u003cContextmenu class=\"st-kline-body\" @closeContextMenuCallBack=\"closeContextMenuCallBack\"\u003e\r\n 833\t \u003c!-- 图表 --\u003e\r\n 834\t \u003cdiv ref=\"echartsRef\" :class=\"cursorPenVisible ? 'st-kline-chart cursorPen' : 'st-kline-chart'\" /\u003e\r\n 835\t \u003c!-- 菜单 --\u003e\r\n 836\t \u003ctemplate #popover\u003e\r\n 837\t \u003cel-menu class=\"menu\"\u003e\r\n 838\t \u003cel-menu-item\r\n 839\t v-for=\"item in menuData\"\r\n 840\t :key=\"item.label\"\r\n 841\t :index=\"item.label\"\r\n 842\t class=\"menuItem\"\r\n 843\t @click=\"menuClick(item)\"\r\n 844\t \u003e\r\n 845\t {{ item.label }}\r\n 846\t \u003c/el-menu-item\u003e\r\n 847\t \u003c/el-menu\u003e\r\n 848\t \u003c/template\u003e\r\n 849\t \u003c/Contextmenu\u003e\r\n 850\t \u003c/div\u003e\r\n 851\t\u003c/template\u003e\r\n 852\t\r\n 853\t\u003cstyle lang=\"scss\"\u003e\r\n 854\t// 需要写在外面,不然会被Vue唯一标识符顶掉导致样式不生效\r\n 855\t.cursorPen canvas {\r\n 856\t cursor: url(./images/pen.png) -30 30, auto !important;\r\n 857\t}\r\n 858\t\u003c/style\u003e\r\n 859\t\u003cstyle lang=\"scss\" scoped\u003e\r\n 860\t.st-kline {\r\n 861\t width: 100%;\r\n 862\t height: 100%;\r\n 863\t background: #000;\r\n 864\t overflow-x: hidden;\r\n 865\t position: relative; // 配合提供给菜单定位\r\n 866\t // K线头部区域: Tips\r\n 867\t \u0026-header {\r\n 868\t height: 34px;\r\n 869\t position: absolute;\r\n 870\t left: 66px;\r\n 871\t }\r\n 872\t \u0026-body {\r\n 873\t width: 100%;\r\n 874\t height: 100%;\r\n 875\t }\r\n 876\t \u0026-chart {\r\n 877\t width: 100%;\r\n 878\t height: 100%;\r\n 879\t }\r\n 880\t .menu {\r\n 881\t border-radius: 4px;\r\n 882\t overflow: hidden;\r\n 883\t background-color: white;\r\n 884\t border-right: 0;\r\n 885\t .menuItem {\r\n 886\t font-size: 16px;\r\n 887\t height: 32px;\r\n 888\t }\r\n 889\t }\r\n 890\t}\r\n 891\t\u003c/style\u003e\r\n 892\t\n","preview":"1\t\u003cscript setup lang=\"ts\"\u003e\n2\timport * as echarts from \"echarts\";\n3\timport { ref, computed, watch, onMounted, onUnmounted } from \"vue\";","bytes":32637,"is_error":false,"stored_at":"2026-07-27T07:06:02.6131463Z"}
|
|
11
|
+
{"id":"t1.tool34","tool_name":"read_file","full_text":"[read_file path=packages/Kline/index.vue lines=401-600/892 bytes=27214]\n[hint] more remains; next page: read_file(path=\"packages/Kline/index.vue\", offset=601, limit=200)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t const newOption = await getOption(\r\n 402\t props.klineData as KlineDataType,\r\n 403\t props.markData,\r\n 404\t props.indicator,\r\n 405\t config.value,\r\n 406\t props.netPositionData,\r\n 407\t props.relevanceData\r\n 408\t );\r\n 409\t newOption.dataZoom[0].start =\r\n 410\t ((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /\r\n 411\t props.klineData.length) *\r\n 412\t 100;\r\n 413\t echartsInstance.setOption(newOption, true);\r\n 414\t await draw(\"priceTrend\");\r\n 415\t },\r\n 416\t ],\r\n 417\t // priceTrend-二腿价格趋势绘制\r\n 418\t [\r\n 419\t \"priceTrend\",\r\n 420\t () =\u003e {\r\n 421\t if (props.priceTrendData.length === 0) return;\r\n 422\t const echartsOptions = echartsInstance.getOption();\r\n 423\t // 颜色配置\r\n 424\t const colorList = [\r\n 425\t \"#FFFFFF\",\r\n 426\t \"#FFDD00\",\r\n 427\t \"#FF00FF\",\r\n 428\t \"#00FF00\",\r\n 429\t \"#FF6000\",\r\n 430\t \"#1677FF\",\r\n 431\t \"#7C3CC9\",\r\n 432\t \"#FF0000\",\r\n 433\t \"#FB9A0E\",\r\n 434\t \"#00B7FF\",\r\n 435\t ];\r\n 436\t // 格式化series配置\r\n 437\t const priceTrendLineSeries = props.priceTrendData.reduce((result, next, index) =\u003e {\r\n 438\t const { name, data } = next;\r\n 439\t const line = {\r\n 440\t name: `二腿价格走势图-${name}`,\r\n 441\t type: \"line\",\r\n 442\t data,\r\n 443\t symbol: \"none\",\r\n 444\t yAxisIndex: 1,\r\n 445\t connectNulls: true,\r\n 446\t itemStyle: {\r\n 447\t color: colorList[index],\r\n 448\t },\r\n 449\t lineStyle: {\r\n 450\t type: \"dashed\", // 虚线\r\n 451\t width: 1,\r\n 452\t },\r\n 453\t };\r\n 454\t result.push(line);\r\n 455\t return result;\r\n 456\t }, []);\r\n 457\t option.value = { ...echartsOptions, series: [...echartsOptions.series, ...priceTrendLineSeries] };\r\n 458\t echartsInstance.setOption(option.value, {\r\n 459\t replaceMerge: [\"series\"],\r\n 460\t });\r\n 461\t },\r\n 462\t ],\r\n 463\t // priceTrendPercent-二腿价格趋势百分比绘制-初始绘制\r\n 464\t [\r\n 465\t \"priceTrendPercent\",\r\n 466\t async () =\u003e {\r\n 467\t if (props.priceTrendPercentData.length === 0) return;\r\n 468\t const chartOption = echartsInstance.getOption();\r\n 469\t // 颜色配置\r\n 470\t const colorList = [\r\n 471\t \"#FFFFFF\",\r\n 472\t \"#FFDD00\",\r\n 473\t \"#FF00FF\",\r\n 474\t \"#00FF00\",\r\n 475\t \"#FF6000\",\r\n 476\t \"#1677FF\",\r\n 477\t \"#7C3CC9\",\r\n 478\t \"#FF0000\",\r\n 479\t \"#FB9A0E\",\r\n 480\t \"#00B7FF\",\r\n 481\t ];\r\n 482\t // 获取当屏首根K线索引\r\n 483\t const startIndex = chartOption.dataZoom[0].startValue;\r\n 484\t // 处理趋势数据(百分比)\r\n 485\t const handlePriceTrendSeries = props.priceTrendPercentData.reduce((result, next, index) =\u003e {\r\n 486\t const { name, data } = next;\r\n 487\t const handleData = data.length \u003e config.value.totalBarCount ? data.slice(data.length - config.value.totalBarCount) : data\r\n 488\t const priceTrendBase = handleData[startIndex][1];\r\n 489\t const lineData = handleData.map((item) =\u003e {\r\n 490\t return [item[0], ((item[1] - priceTrendBase) / priceTrendBase) * 100];\r\n 491\t });\r\n 492\t const line = {\r\n 493\t name: `二腿价格走势百分比图-${name}`,\r\n 494\t type: \"line\",\r\n 495\t data: lineData,\r\n 496\t symbol: \"none\",\r\n 497\t yAxisIndex: 1,\r\n 498\t connectNulls: true,\r\n 499\t itemStyle: {\r\n 500\t color: colorList[index],\r\n 501\t },\r\n 502\t lineStyle: {\r\n 503\t type: \"dashed\", // 虚线\r\n 504\t width: 1,\r\n 505\t },\r\n 506\t };\r\n 507\t result.push(line);\r\n 508\t return result;\r\n 509\t }, []);\r\n 510\t // 填充价格趋势线(避免重复)\r\n 511\t chartOption.series = [...chartOption.series.filter(line =\u003e {\r\n 512\t return !line.name.includes('二腿价格走势百分比图')\r\n 513\t }), ...handlePriceTrendSeries];\r\n 514\t echartsInstance.setOption(chartOption, true);\r\n 515\t },\r\n 516\t ],\r\n 517\t ]);\r\n 518\t const callBack = callBackMap.get(type);\r\n 519\t if (callBack instanceof Function) {\r\n 520\t await callBack();\r\n 521\t }\r\n 522\t};\r\n 523\t\r\n 524\t//----------------------------右键菜单功能相关----------------------------------\r\n 525\tconst cursorPenVisible = ref(false); // 画笔模式开关\r\n 526\t\r\n 527\tconst menuData = ref\u003cMenuDataType\u003e([]);\r\n 528\t\r\n 529\t/**\r\n 530\t * @description: 点击菜单项\r\n 531\t * @param {Object} item 菜单项的数据\r\n 532\t */\r\n 533\tconst menuClick = (item: any) =\u003e {\r\n 534\t const { callBack } = item;\r\n 535\t callBack instanceof Function \u0026\u0026 callBack(echartsInstance, cursorPenVisible);\r\n 536\t};\r\n 537\t\r\n 538\t/**\r\n 539\t * @description: 菜单关闭的回调\r\n 540\t * @todo: 进行菜单内容的初始化\r\n 541\t */\r\n 542\tconst closeContextMenuCallBack = () =\u003e {\r\n 543\t menuData.value = props.defaultMenuData as MenuDataType;\r\n 544\t};\r\n 545\t\r\n 546\t//----------------------------Echarts基座-------------------------------\r\n 547\tconst echartsRef = ref\u003cHTMLElement\u003e();\r\n 548\tlet echartsInstance: EChartsType; // echarts实例\r\n 549\tlet chartDomObserver: any; // 监视图表DOM变化\r\n 550\t\r\n 551\t/**\r\n 552\t * @description: echarts数据高亮回调\r\n 553\t * @param {any} data echarts数据\r\n 554\t * @param {EChartsType} chart echarts实例\r\n 555\t */\r\n 556\tconst highlight = (data: any, chart: EChartsType) =\u003e {\r\n 557\t if (data) {\r\n 558\t // 图表内部移动\r\n 559\t activeIndex.value = typeof data?.batch[0]?.dataIndex === \"number\" ? data?.batch[0]?.dataIndex : -1;\r\n 560\t } else {\r\n 561\t // 移出图表\r\n 562\t const chartOptions: EChartsOption = chart.getOption() as EChartsOption;\r\n 563\t if (chartOptions.dataZoom instanceof Array) {\r\n 564\t activeIndex.value = chartOptions.dataZoom[0].endValue as number;\r\n 565\t }\r\n 566\t }\r\n 567\t};\r\n 568\t\r\n 569\t/**\r\n 570\t * @description: echarts数据缩放的相关参数\r\n 571\t * @param {any} datazoomTimer 缩放回调函数的延时器\r\n 572\t * @param {Number} datazoomTime 缩放回调函数的延时器的时间\r\n 573\t * @param {Function} datazoom 数据缩放的回调函数\r\n 574\t *\r\n 575\t * 判断具体是拖拽,还是缩放的逻辑\r\n 576\t * 根据start~end的差值前后是否变动\r\n 577\t * 因为拖拽时,他们的差值是恒定的,缩放时是会变动\r\n 578\t */\r\n 579\tlet datazoomTimer: any = null;\r\n 580\tconst datazoomTime: number = 300;\r\n 581\tlet historyIsLoading = false;\r\n 582\tlet historyIsAllLoad = ref(false);\r\n 583\tlet futureIsLoading = false;\r\n 584\tlet futureIsAllLoad = ref(false);\r\n 585\tconst datazoom = async (params: any) =\u003e {\r\n 586\t // 执行[方向键]时,返回的是startValue和endValue\r\n 587\t // 执行[鼠标]时,返回的是batch\r\n 588\t /**\r\n 589\t * @description: 历史数据动态加载渲染逻辑\r\n 590\t * 前置条件:\r\n 591\t * 1.允许开启历史动态加载\r\n 592\t * 2.如果剩余K线根数不足100根\r\n 593\t * 3.没有正在加载历史数据\r\n 594\t * 4.历史数据并未已全部获取\r\n 595\t */\r\n 596\t // 获取左侧剩余K线根数\r\n 597\t const historyLength = params.batch ? params.batch[0].start * config.value.totalBarCount : params.startValue;\r\n 598\t if (\r\n 599\t config.value.dynamicLoadConfig.historyVisible \u0026\u0026\r\n 600\t historyLength \u003c= 100 \u0026\u0026\r\n","preview":"401\t const newOption = await getOption(\n402\t props.klineData as KlineDataType,\n403\t props.markData,","bytes":8248,"is_error":false,"stored_at":"2026-07-27T07:06:19.2761298Z"}
|
|
12
|
+
{"id":"t1.tool35","tool_name":"read_file","full_text":"[read_file path=packages/Kline/index.vue lines=401-600/892 bytes=27214]\n[hint] more remains; next page: read_file(path=\"packages/Kline/index.vue\", offset=601, limit=200)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 401\t const newOption = await getOption(\r\n 402\t props.klineData as KlineDataType,\r\n 403\t props.markData,\r\n 404\t props.indicator,\r\n 405\t config.value,\r\n 406\t props.netPositionData,\r\n 407\t props.relevanceData\r\n 408\t );\r\n 409\t newOption.dataZoom[0].start =\r\n 410\t ((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /\r\n 411\t props.klineData.length) *\r\n 412\t 100;\r\n 413\t echartsInstance.setOption(newOption, true);\r\n 414\t await draw(\"priceTrend\");\r\n 415\t },\r\n 416\t ],\r\n 417\t // priceTrend-二腿价格趋势绘制\r\n 418\t [\r\n 419\t \"priceTrend\",\r\n 420\t () =\u003e {\r\n 421\t if (props.priceTrendData.length === 0) return;\r\n 422\t const echartsOptions = echartsInstance.getOption();\r\n 423\t // 颜色配置\r\n 424\t const colorList = [\r\n 425\t \"#FFFFFF\",\r\n 426\t \"#FFDD00\",\r\n 427\t \"#FF00FF\",\r\n 428\t \"#00FF00\",\r\n 429\t \"#FF6000\",\r\n 430\t \"#1677FF\",\r\n 431\t \"#7C3CC9\",\r\n 432\t \"#FF0000\",\r\n 433\t \"#FB9A0E\",\r\n 434\t \"#00B7FF\",\r\n 435\t ];\r\n 436\t // 格式化series配置\r\n 437\t const priceTrendLineSeries = props.priceTrendData.reduce((result, next, index) =\u003e {\r\n 438\t const { name, data } = next;\r\n 439\t const line = {\r\n 440\t name: `二腿价格走势图-${name}`,\r\n 441\t type: \"line\",\r\n 442\t data,\r\n 443\t symbol: \"none\",\r\n 444\t yAxisIndex: 1,\r\n 445\t connectNulls: true,\r\n 446\t itemStyle: {\r\n 447\t color: colorList[index],\r\n 448\t },\r\n 449\t lineStyle: {\r\n 450\t type: \"dashed\", // 虚线\r\n 451\t width: 1,\r\n 452\t },\r\n 453\t };\r\n 454\t result.push(line);\r\n 455\t return result;\r\n 456\t }, []);\r\n 457\t option.value = { ...echartsOptions, series: [...echartsOptions.series, ...priceTrendLineSeries] };\r\n 458\t echartsInstance.setOption(option.value, {\r\n 459\t replaceMerge: [\"series\"],\r\n 460\t });\r\n 461\t },\r\n 462\t ],\r\n 463\t // priceTrendPercent-二腿价格趋势百分比绘制-初始绘制\r\n 464\t [\r\n 465\t \"priceTrendPercent\",\r\n 466\t async () =\u003e {\r\n 467\t if (props.priceTrendPercentData.length === 0) return;\r\n 468\t const chartOption = echartsInstance.getOption();\r\n 469\t // 颜色配置\r\n 470\t const colorList = [\r\n 471\t \"#FFFFFF\",\r\n 472\t \"#FFDD00\",\r\n 473\t \"#FF00FF\",\r\n 474\t \"#00FF00\",\r\n 475\t \"#FF6000\",\r\n 476\t \"#1677FF\",\r\n 477\t \"#7C3CC9\",\r\n 478\t \"#FF0000\",\r\n 479\t \"#FB9A0E\",\r\n 480\t \"#00B7FF\",\r\n 481\t ];\r\n 482\t // 获取当屏首根K线索引\r\n 483\t const startIndex = chartOption.dataZoom[0].startValue;\r\n 484\t // 处理趋势数据(百分比)\r\n 485\t const handlePriceTrendSeries = props.priceTrendPercentData.reduce((result, next, index) =\u003e {\r\n 486\t const { name, data } = next;\r\n 487\t const handleData = data.length \u003e config.value.totalBarCount ? data.slice(data.length - config.value.totalBarCount) : data\r\n 488\t const priceTrendBase = handleData[startIndex][1];\r\n 489\t const lineData = handleData.map((item) =\u003e {\r\n 490\t return [item[0], ((item[1] - priceTrendBase) / priceTrendBase) * 100];\r\n 491\t });\r\n 492\t const line = {\r\n 493\t name: `二腿价格走势百分比图-${name}`,\r\n 494\t type: \"line\",\r\n 495\t data: lineData,\r\n 496\t symbol: \"none\",\r\n 497\t yAxisIndex: 1,\r\n 498\t connectNulls: true,\r\n 499\t itemStyle: {\r\n 500\t color: colorList[index],\r\n 501\t },\r\n 502\t lineStyle: {\r\n 503\t type: \"dashed\", // 虚线\r\n 504\t width: 1,\r\n 505\t },\r\n 506\t };\r\n 507\t result.push(line);\r\n 508\t return result;\r\n 509\t }, []);\r\n 510\t // 填充价格趋势线(避免重复)\r\n 511\t chartOption.series = [...chartOption.series.filter(line =\u003e {\r\n 512\t return !line.name.includes('二腿价格走势百分比图')\r\n 513\t }), ...handlePriceTrendSeries];\r\n 514\t echartsInstance.setOption(chartOption, true);\r\n 515\t },\r\n 516\t ],\r\n 517\t ]);\r\n 518\t const callBack = callBackMap.get(type);\r\n 519\t if (callBack instanceof Function) {\r\n 520\t await callBack();\r\n 521\t }\r\n 522\t};\r\n 523\t\r\n 524\t//----------------------------右键菜单功能相关----------------------------------\r\n 525\tconst cursorPenVisible = ref(false); // 画笔模式开关\r\n 526\t\r\n 527\tconst menuData = ref\u003cMenuDataType\u003e([]);\r\n 528\t\r\n 529\t/**\r\n 530\t * @description: 点击菜单项\r\n 531\t * @param {Object} item 菜单项的数据\r\n 532\t */\r\n 533\tconst menuClick = (item: any) =\u003e {\r\n 534\t const { callBack } = item;\r\n 535\t callBack instanceof Function \u0026\u0026 callBack(echartsInstance, cursorPenVisible);\r\n 536\t};\r\n 537\t\r\n 538\t/**\r\n 539\t * @description: 菜单关闭的回调\r\n 540\t * @todo: 进行菜单内容的初始化\r\n 541\t */\r\n 542\tconst closeContextMenuCallBack = () =\u003e {\r\n 543\t menuData.value = props.defaultMenuData as MenuDataType;\r\n 544\t};\r\n 545\t\r\n 546\t//----------------------------Echarts基座-------------------------------\r\n 547\tconst echartsRef = ref\u003cHTMLElement\u003e();\r\n 548\tlet echartsInstance: EChartsType; // echarts实例\r\n 549\tlet chartDomObserver: any; // 监视图表DOM变化\r\n 550\t\r\n 551\t/**\r\n 552\t * @description: echarts数据高亮回调\r\n 553\t * @param {any} data echarts数据\r\n 554\t * @param {EChartsType} chart echarts实例\r\n 555\t */\r\n 556\tconst highlight = (data: any, chart: EChartsType) =\u003e {\r\n 557\t if (data) {\r\n 558\t // 图表内部移动\r\n 559\t activeIndex.value = typeof data?.batch[0]?.dataIndex === \"number\" ? data?.batch[0]?.dataIndex : -1;\r\n 560\t } else {\r\n 561\t // 移出图表\r\n 562\t const chartOptions: EChartsOption = chart.getOption() as EChartsOption;\r\n 563\t if (chartOptions.dataZoom instanceof Array) {\r\n 564\t activeIndex.value = chartOptions.dataZoom[0].endValue as number;\r\n 565\t }\r\n 566\t }\r\n 567\t};\r\n 568\t\r\n 569\t/**\r\n 570\t * @description: echarts数据缩放的相关参数\r\n 571\t * @param {any} datazoomTimer 缩放回调函数的延时器\r\n 572\t * @param {Number} datazoomTime 缩放回调函数的延时器的时间\r\n 573\t * @param {Function} datazoom 数据缩放的回调函数\r\n 574\t *\r\n 575\t * 判断具体是拖拽,还是缩放的逻辑\r\n 576\t * 根据start~end的差值前后是否变动\r\n 577\t * 因为拖拽时,他们的差值是恒定的,缩放时是会变动\r\n 578\t */\r\n 579\tlet datazoomTimer: any = null;\r\n 580\tconst datazoomTime: number = 300;\r\n 581\tlet historyIsLoading = false;\r\n 582\tlet historyIsAllLoad = ref(false);\r\n 583\tlet futureIsLoading = false;\r\n 584\tlet futureIsAllLoad = ref(false);\r\n 585\tconst datazoom = async (params: any) =\u003e {\r\n 586\t // 执行[方向键]时,返回的是startValue和endValue\r\n 587\t // 执行[鼠标]时,返回的是batch\r\n 588\t /**\r\n 589\t * @description: 历史数据动态加载渲染逻辑\r\n 590\t * 前置条件:\r\n 591\t * 1.允许开启历史动态加载\r\n 592\t * 2.如果剩余K线根数不足100根\r\n 593\t * 3.没有正在加载历史数据\r\n 594\t * 4.历史数据并未已全部获取\r\n 595\t */\r\n 596\t // 获取左侧剩余K线根数\r\n 597\t const historyLength = params.batch ? params.batch[0].start * config.value.totalBarCount : params.startValue;\r\n 598\t if (\r\n 599\t config.value.dynamicLoadConfig.historyVisible \u0026\u0026\r\n 600\t historyLength \u003c= 100 \u0026\u0026\r\n","preview":"401\t const newOption = await getOption(\n402\t props.klineData as KlineDataType,\n403\t props.markData,","bytes":8248,"is_error":false,"stored_at":"2026-07-27T07:06:23.397676Z"}
|
|
13
|
+
{"id":"t1.tool36","tool_name":"read_file","full_text":"[read_file path=packages/Kline/index.vue lines=400-599/892 bytes=27214]\n[hint] more remains; next page: read_file(path=\"packages/Kline/index.vue\", offset=600, limit=200)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 400\t const chartOption = echartsInstance.getOption();\r\n 401\t const newOption = await getOption(\r\n 402\t props.klineData as KlineDataType,\r\n 403\t props.markData,\r\n 404\t props.indicator,\r\n 405\t config.value,\r\n 406\t props.netPositionData,\r\n 407\t props.relevanceData\r\n 408\t );\r\n 409\t newOption.dataZoom[0].start =\r\n 410\t ((props.klineData.length - chartOption.dataZoom[0].endValue + chartOption.dataZoom[0].startValue) /\r\n 411\t props.klineData.length) *\r\n 412\t 100;\r\n 413\t echartsInstance.setOption(newOption, true);\r\n 414\t await draw(\"priceTrend\");\r\n 415\t },\r\n 416\t ],\r\n 417\t // priceTrend-二腿价格趋势绘制\r\n 418\t [\r\n 419\t \"priceTrend\",\r\n 420\t () =\u003e {\r\n 421\t if (props.priceTrendData.length === 0) return;\r\n 422\t const echartsOptions = echartsInstance.getOption();\r\n 423\t // 颜色配置\r\n 424\t const colorList = [\r\n 425\t \"#FFFFFF\",\r\n 426\t \"#FFDD00\",\r\n 427\t \"#FF00FF\",\r\n 428\t \"#00FF00\",\r\n 429\t \"#FF6000\",\r\n 430\t \"#1677FF\",\r\n 431\t \"#7C3CC9\",\r\n 432\t \"#FF0000\",\r\n 433\t \"#FB9A0E\",\r\n 434\t \"#00B7FF\",\r\n 435\t ];\r\n 436\t // 格式化series配置\r\n 437\t const priceTrendLineSeries = props.priceTrendData.reduce((result, next, index) =\u003e {\r\n 438\t const { name, data } = next;\r\n 439\t const line = {\r\n 440\t name: `二腿价格走势图-${name}`,\r\n 441\t type: \"line\",\r\n 442\t data,\r\n 443\t symbol: \"none\",\r\n 444\t yAxisIndex: 1,\r\n 445\t connectNulls: true,\r\n 446\t itemStyle: {\r\n 447\t color: colorList[index],\r\n 448\t },\r\n 449\t lineStyle: {\r\n 450\t type: \"dashed\", // 虚线\r\n 451\t width: 1,\r\n 452\t },\r\n 453\t };\r\n 454\t result.push(line);\r\n 455\t return result;\r\n 456\t }, []);\r\n 457\t option.value = { ...echartsOptions, series: [...echartsOptions.series, ...priceTrendLineSeries] };\r\n 458\t echartsInstance.setOption(option.value, {\r\n 459\t replaceMerge: [\"series\"],\r\n 460\t });\r\n 461\t },\r\n 462\t ],\r\n 463\t // priceTrendPercent-二腿价格趋势百分比绘制-初始绘制\r\n 464\t [\r\n 465\t \"priceTrendPercent\",\r\n 466\t async () =\u003e {\r\n 467\t if (props.priceTrendPercentData.length === 0) return;\r\n 468\t const chartOption = echartsInstance.getOption();\r\n 469\t // 颜色配置\r\n 470\t const colorList = [\r\n 471\t \"#FFFFFF\",\r\n 472\t \"#FFDD00\",\r\n 473\t \"#FF00FF\",\r\n 474\t \"#00FF00\",\r\n 475\t \"#FF6000\",\r\n 476\t \"#1677FF\",\r\n 477\t \"#7C3CC9\",\r\n 478\t \"#FF0000\",\r\n 479\t \"#FB9A0E\",\r\n 480\t \"#00B7FF\",\r\n 481\t ];\r\n 482\t // 获取当屏首根K线索引\r\n 483\t const startIndex = chartOption.dataZoom[0].startValue;\r\n 484\t // 处理趋势数据(百分比)\r\n 485\t const handlePriceTrendSeries = props.priceTrendPercentData.reduce((result, next, index) =\u003e {\r\n 486\t const { name, data } = next;\r\n 487\t const handleData = data.length \u003e config.value.totalBarCount ? data.slice(data.length - config.value.totalBarCount) : data\r\n 488\t const priceTrendBase = handleData[startIndex][1];\r\n 489\t const lineData = handleData.map((item) =\u003e {\r\n 490\t return [item[0], ((item[1] - priceTrendBase) / priceTrendBase) * 100];\r\n 491\t });\r\n 492\t const line = {\r\n 493\t name: `二腿价格走势百分比图-${name}`,\r\n 494\t type: \"line\",\r\n 495\t data: lineData,\r\n 496\t symbol: \"none\",\r\n 497\t yAxisIndex: 1,\r\n 498\t connectNulls: true,\r\n 499\t itemStyle: {\r\n 500\t color: colorList[index],\r\n 501\t },\r\n 502\t lineStyle: {\r\n 503\t type: \"dashed\", // 虚线\r\n 504\t width: 1,\r\n 505\t },\r\n 506\t };\r\n 507\t result.push(line);\r\n 508\t return result;\r\n 509\t }, []);\r\n 510\t // 填充价格趋势线(避免重复)\r\n 511\t chartOption.series = [...chartOption.series.filter(line =\u003e {\r\n 512\t return !line.name.includes('二腿价格走势百分比图')\r\n 513\t }), ...handlePriceTrendSeries];\r\n 514\t echartsInstance.setOption(chartOption, true);\r\n 515\t },\r\n 516\t ],\r\n 517\t ]);\r\n 518\t const callBack = callBackMap.get(type);\r\n 519\t if (callBack instanceof Function) {\r\n 520\t await callBack();\r\n 521\t }\r\n 522\t};\r\n 523\t\r\n 524\t//----------------------------右键菜单功能相关----------------------------------\r\n 525\tconst cursorPenVisible = ref(false); // 画笔模式开关\r\n 526\t\r\n 527\tconst menuData = ref\u003cMenuDataType\u003e([]);\r\n 528\t\r\n 529\t/**\r\n 530\t * @description: 点击菜单项\r\n 531\t * @param {Object} item 菜单项的数据\r\n 532\t */\r\n 533\tconst menuClick = (item: any) =\u003e {\r\n 534\t const { callBack } = item;\r\n 535\t callBack instanceof Function \u0026\u0026 callBack(echartsInstance, cursorPenVisible);\r\n 536\t};\r\n 537\t\r\n 538\t/**\r\n 539\t * @description: 菜单关闭的回调\r\n 540\t * @todo: 进行菜单内容的初始化\r\n 541\t */\r\n 542\tconst closeContextMenuCallBack = () =\u003e {\r\n 543\t menuData.value = props.defaultMenuData as MenuDataType;\r\n 544\t};\r\n 545\t\r\n 546\t//----------------------------Echarts基座-------------------------------\r\n 547\tconst echartsRef = ref\u003cHTMLElement\u003e();\r\n 548\tlet echartsInstance: EChartsType; // echarts实例\r\n 549\tlet chartDomObserver: any; // 监视图表DOM变化\r\n 550\t\r\n 551\t/**\r\n 552\t * @description: echarts数据高亮回调\r\n 553\t * @param {any} data echarts数据\r\n 554\t * @param {EChartsType} chart echarts实例\r\n 555\t */\r\n 556\tconst highlight = (data: any, chart: EChartsType) =\u003e {\r\n 557\t if (data) {\r\n 558\t // 图表内部移动\r\n 559\t activeIndex.value = typeof data?.batch[0]?.dataIndex === \"number\" ? data?.batch[0]?.dataIndex : -1;\r\n 560\t } else {\r\n 561\t // 移出图表\r\n 562\t const chartOptions: EChartsOption = chart.getOption() as EChartsOption;\r\n 563\t if (chartOptions.dataZoom instanceof Array) {\r\n 564\t activeIndex.value = chartOptions.dataZoom[0].endValue as number;\r\n 565\t }\r\n 566\t }\r\n 567\t};\r\n 568\t\r\n 569\t/**\r\n 570\t * @description: echarts数据缩放的相关参数\r\n 571\t * @param {any} datazoomTimer 缩放回调函数的延时器\r\n 572\t * @param {Number} datazoomTime 缩放回调函数的延时器的时间\r\n 573\t * @param {Function} datazoom 数据缩放的回调函数\r\n 574\t *\r\n 575\t * 判断具体是拖拽,还是缩放的逻辑\r\n 576\t * 根据start~end的差值前后是否变动\r\n 577\t * 因为拖拽时,他们的差值是恒定的,缩放时是会变动\r\n 578\t */\r\n 579\tlet datazoomTimer: any = null;\r\n 580\tconst datazoomTime: number = 300;\r\n 581\tlet historyIsLoading = false;\r\n 582\tlet historyIsAllLoad = ref(false);\r\n 583\tlet futureIsLoading = false;\r\n 584\tlet futureIsAllLoad = ref(false);\r\n 585\tconst datazoom = async (params: any) =\u003e {\r\n 586\t // 执行[方向键]时,返回的是startValue和endValue\r\n 587\t // 执行[鼠标]时,返回的是batch\r\n 588\t /**\r\n 589\t * @description: 历史数据动态加载渲染逻辑\r\n 590\t * 前置条件:\r\n 591\t * 1.允许开启历史动态加载\r\n 592\t * 2.如果剩余K线根数不足100根\r\n 593\t * 3.没有正在加载历史数据\r\n 594\t * 4.历史数据并未已全部获取\r\n 595\t */\r\n 596\t // 获取左侧剩余K线根数\r\n 597\t const historyLength = params.batch ? params.batch[0].start * config.value.totalBarCount : params.startValue;\r\n 598\t if (\r\n 599\t config.value.dynamicLoadConfig.historyVisible \u0026\u0026\r\n","preview":"400\t const chartOption = echartsInstance.getOption();\n401\t const newOption = await getOption(\n402\t props.klineData as KlineDataType,","bytes":8277,"is_error":false,"stored_at":"2026-07-27T07:06:27.4624929Z"}
|
|
14
|
+
{"id":"t1.tool42","tool_name":"read_file","full_text":"[read_file path=packages/Kline/option.ts lines=1-540/540 bytes=17011]\n 1\timport { stMath } from \"st-func\";\r\n 2\timport formatKlineData from \"./formatKlineData\";\r\n 3\timport type { EChartsType } from \"echarts\";\r\n 4\timport type {\r\n 5\t KlineDataType,\r\n 6\t IndicatorConfigType,\r\n 7\t InConfig,\r\n 8\t LineDataType,\r\n 9\t WarningDataItem,\r\n 10\t PositionDataItem,\r\n 11\t ConditionDataItem,\r\n 12\t GraphicEvent,\r\n 13\t} from \"./type.d.ts\";\r\n 14\timport { formatValue, getWarningItem, getPositionItem, getConditionItem, handlePoint } from \"./utils\";\r\n 15\t\r\n 16\t/**\r\n 17\t * @description: 格式化K线图配置项\r\n 18\t * @param {KlineDataType} klineData\r\n 19\t * @param {IndicatorConfigType} indicatorConfig\r\n 20\t * @param {any} markData\r\n 21\t * @param {InConfig} config\r\n 22\t *\r\n 23\t * @return {option} echarts基础配置\r\n 24\t */\r\n 25\texport const getOption = async (\r\n 26\t klineData: KlineDataType,\r\n 27\t markData: any,\r\n 28\t indicatorConfig: IndicatorConfigType,\r\n 29\t config: InConfig,\r\n 30\t netPositionData: any[],\r\n 31\t relevanceData: any[],\r\n 32\t) =\u003e {\r\n 33\t const { totalBarCount, defaultShowBarCount, maxValueSpan, gridLeft, gridTop, gridRight, gridBottom, zoomLock } =\r\n 34\t config;\r\n 35\t /**\r\n 36\t * @todo: 1.数据处理\r\n 37\t * @returns {Array\u003c'YYYY-MM-DD HH:mm:ss'\u003e} time 用于X轴的时间数组\r\n 38\t * @returns {Array\u003c{开,高,低,收}\u003e} KLine 用于渲染candlestick[K线]\r\n 39\t * @returns {Array\u003c{时间,开,高,低,收,成交量,成交额}\u003e} originData 源数据K线\r\n 40\t * @returns {Array\u003c{key:'DKX',color:'#FFFFFF',data:[开]}\u003e} indicator 用于渲染line[指标线]\r\n 41\t */\r\n 42\t const { time, kLine, originData, indicator } = await formatKlineData(klineData, indicatorConfig, totalBarCount);\r\n 43\t /**\r\n 44\t * @todo: 2.指标线渲染项处理\r\n 45\t */\r\n 46\t const lineSeries = indicator.map((item) =\u003e {\r\n 47\t const { key, data, color } = item;\r\n 48\t return {\r\n 49\t name: key,\r\n 50\t type: \"line\",\r\n 51\t silent: true,\r\n 52\t symbol: \"none\",\r\n 53\t data,\r\n 54\t lineStyle: {\r\n 55\t width: 1,\r\n 56\t },\r\n 57\t itemStyle: {\r\n 58\t color,\r\n 59\t },\r\n 60\t };\r\n 61\t });\r\n 62\t /**\r\n 63\t * @todo: 3.标注点位渲染项处理\r\n 64\t */\r\n 65\t const markPointData = handlePoint(markData, originData);\r\n 66\t /**\r\n 67\t * @todo: 4.标注点位连线渲染处理\r\n 68\t */\r\n 69\t let markLineData = [];\r\n 70\t markPointData.forEach((item) =\u003e {\r\n 71\t if (item.markLineTarget) {\r\n 72\t markLineData = [...markLineData, ...item.markLineTarget];\r\n 73\t }\r\n 74\t });\r\n 75\t // -----return------\r\n 76\t return {\r\n 77\t animation: false,\r\n 78\t dataset: {\r\n 79\t id: \"data\",\r\n 80\t source: {\r\n 81\t klineData: kLine,\r\n 82\t indicatorData: indicator,\r\n 83\t },\r\n 84\t },\r\n 85\t grid: {\r\n 86\t left: `${gridLeft}px`,\r\n 87\t top: `${gridTop}px`,\r\n 88\t right: `${gridRight}px`,\r\n 89\t bottom: `${gridBottom}px`,\r\n 90\t },\r\n 91\t tooltip: {\r\n 92\t trigger: \"axis\",\r\n 93\t axisPointer: {\r\n 94\t type: \"cross\",\r\n 95\t label: {\r\n 96\t formatter: (data: any) =\u003e {\r\n 97\t const { axisDimension, value } = data;\r\n 98\t if (axisDimension === \"x\") {\r\n 99\t return value;\r\n 100\t } else {\r\n 101\t return String(formatValue(value));\r\n 102\t }\r\n 103\t },\r\n 104\t },\r\n 105\t },\r\n 106\t formatter: (params) =\u003e {\r\n 107\t let html: null | string = null;\r\n 108\t let body: string = \"\";\r\n 109\t params.forEach((item) =\u003e {\r\n 110\t // 资产持仓净值tooltip渲染逻辑\r\n 111\t if (item.seriesName === \"资产持仓净值\" \u0026\u0026 item.data !== null) {\r\n 112\t body += `\u003cdiv\u003e资产持仓净值: ${item.data}\u003c/div\u003e`;\r\n 113\t }\r\n 114\t if (item.componentSubType === \"candlestick\") {\r\n 115\t // 买卖/开平tooltip渲染逻辑\r\n 116\t markData.forEach((mark) =\u003e {\r\n 117\t const { key, data } = mark;\r\n 118\t if (key === \"sellBuy\") {\r\n 119\t // 买卖点渲染逻辑\r\n 120\t const sellBuy = data.filter((i) =\u003e i.time === item.axisValue);\r\n 121\t let buy = 0;\r\n 122\t let sell = 0;\r\n 123\t sellBuy.forEach((i) =\u003e {\r\n 124\t if (i.tradeType === \"开多\" || i.tradeType === \"平空\") {\r\n 125\t // 买\r\n 126\t buy += i.amount;\r\n 127\t } else {\r\n 128\t // 卖\r\n 129\t sell += i.amount;\r\n 130\t }\r\n 131\t });\r\n 132\t if (buy) {\r\n 133\t body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 134\t }\r\n 135\t if (sell) {\r\n 136\t body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 137\t }\r\n 138\t }\r\n 139\t if (key === \"openClose\") {\r\n 140\t // 开平点渲染逻辑\r\n 141\t data.forEach((i) =\u003e {\r\n 142\t if (i.time === item.axisValue) {\r\n 143\t body += i.tooltip;\r\n 144\t }\r\n 145\t });\r\n 146\t }\r\n 147\t });\r\n 148\t }\r\n 149\t if (body) {\r\n 150\t html = `\u003cdiv\u003e\u003cspan style=\"font-weight: bold;\"\u003e${item.axisValue}\u003c/span\u003e${body}\u003c/div\u003e`;\r\n 151\t }\r\n 152\t });\r\n 153\t return html;\r\n 154\t },\r\n 155\t },\r\n 156\t xAxis: {\r\n 157\t type: \"category\",\r\n 158\t data: time,\r\n 159\t axisLine: {\r\n 160\t show: true,\r\n 161\t },\r\n 162\t splitLine: {\r\n 163\t show: true,\r\n 164\t lineStyle: {\r\n 165\t type: \"dotted\",\r\n 166\t color: \"#333\",\r\n 167\t },\r\n 168\t },\r\n 169\t axisLabel: {\r\n 170\t show: true,\r\n 171\t formatter: (data: string) =\u003e data,\r\n 172\t },\r\n 173\t },\r\n 174\t yAxis: [\r\n 175\t {\r\n 176\t index: 0,\r\n 177\t splitLine: {\r\n 178\t show: true,\r\n 179\t lineStyle: {\r\n 180\t type: \"dotted\",\r\n 181\t color: \"#333\",\r\n 182\t },\r\n 183\t },\r\n 184\t min: (value: any) =\u003e {\r\n 185\t const { min, max } = value;\r\n 186\t const interval = Math.abs((max - min) / 10);\r\n 187\t return min - interval;\r\n 188\t },\r\n 189\t max: (value: any) =\u003e {\r\n 190\t const { min, max } = value;\r\n 191\t const interval = Math.abs((max - min) / 10);\r\n 192\t return max + interval;\r\n 193\t },\r\n 194\t axisLine: {\r\n 195\t show: true,\r\n 196\t },\r\n 197\t axisLabel: {\r\n 198\t formatter: (value: number) =\u003e formatValue(value),\r\n 199\t showMaxLabel: false,\r\n 200\t },\r\n 201\t },\r\n 202\t {\r\n 203\t show: false,\r\n 204\t min: \"dataMin\",\r\n 205\t },\r\n 206\t ],\r\n 207\t dataZoom: [\r\n 208\t {\r\n 209\t type: \"inside\",\r\n 210\t xAxisIndex: [0, 0],\r\n 211\t zoomLock,\r\n 212\t maxValueSpan,\r\n 213\t start: kLine.length \u003e= defaultShowBarCount ? ((kLine.length - defaultShowBarCount) / kLine.length) * 100 : 0,\r\n 214\t end: 99.99,\r\n 215\t // startValue: kLine.length \u003e= defaultShowBarCount ? kLine.length - defaultShowBarCount : 0,\r\n 216\t // endValue: kLine.length - 1,\r\n 217\t },\r\n 218\t ],\r\n 219\t series: [\r\n 220\t {\r\n 221\t name: \"k线\",\r\n 222\t type: \"candlestick\",\r\n 223\t data: kLine,\r\n 224\t markPoint: {\r\n 225\t data: markPointData,\r\n 226\t },\r\n 227\t markLine: {\r\n 228\t position: \"middle\",\r\n 229\t textStyle: { color: \"blue\", fontSize: 15 },\r\n 230\t animation: false,\r\n 231\t data: markLineData,\r\n 232\t lineStyle: {\r\n 233\t width: 3,\r\n 234\t type: \"solid\",\r\n 235\t },\r\n 236\t symbol: [\"none\", \"none\"],\r\n 237\t },\r\n 238\t itemStyle: {\r\n 239\t color: \"transparent\",\r\n 240\t color0: \"#00FFFF\",\r\n 241\t borderColor: \"#FF0000\",\r\n 242\t borderColor0: \"#00FFFF\",\r\n 243\t borderWidth: 1,\r\n 244\t },\r\n 245\t z: 1,\r\n 246\t },\r\n 247\t ...lineSeries,\r\n 248\t {\r\n 249\t name: \"资产持仓净值\",\r\n 250\t type: \"line\",\r\n 251\t data: netPositionData ?? [],\r\n 252\t symbol: \"none\",\r\n 253\t yAxisIndex: 1,\r\n 254\t connectNulls: true,\r\n 255\t itemStyle: {\r\n 256\t color: \"#666\",\r\n 257\t },\r\n 258\t },\r\n 259\t {\r\n 260\t name: \"二腿相关度\",\r\n 261\t type: \"line\",\r\n 262\t data: relevanceData ?? [],\r\n 263\t symbol: \"none\",\r\n 264\t yAxisIndex: 1,\r\n 265\t connectNulls: true,\r\n 266\t itemStyle: {\r\n 267\t color: \"#FFF\",\r\n 268\t },\r\n 269\t lineStyle: {\r\n 270\t // type: \"dashed\", // 虚线\r\n 271\t width: 2,\r\n 272\t },\r\n 273\t label: {\r\n 274\t show: true, // 显示文字\r\n 275\t position: \"top\", // 文字位置\r\n 276\t formatter: (params: any) =\u003e {\r\n 277\t return `相关度: ${params.data[1]}`;\r\n 278\t },\r\n 279\t textStyle: {\r\n 280\t color: \"#FFF\", // 文字颜色\r\n 281\t fontSize: 14, // 文字字体大小\r\n 282\t },\r\n 283\t },\r\n 284\t }\r\n 285\t ],\r\n 286\t toolbox: {\r\n 287\t show: false,\r\n 288\t },\r\n 289\t brush: {\r\n 290\t xAxisIndex: \"all\",\r\n 291\t brushLink: \"all\",\r\n 292\t transformable: false,\r\n 293\t outOfBrush: {\r\n 294\t colorAlpha: 1,\r\n 295\t },\r\n 296\t brushStyle: {\r\n 297\t color: \"rgba(120,140,180,0)\",\r\n 298\t borderColor: \"rgba(255,255,255,0.4)\",\r\n 299\t },\r\n 300\t },\r\n 301\t };\r\n 302\t};\r\n 303\t\r\n 304\t/**\r\n 305\t * @description: 格式化其他画线配置项\r\n 306\t * @param {LineDataType} data\r\n 307\t * @param {InConfig} config\r\n 308\t * @param {EChartsType} echartsInstance\r\n 309\t *\r\n 310\t * @return {graphicType} graphic绘线配置\r\n 311\t */\r\n 312\texport const getLineOption = (data: LineDataType, config: InConfig, echartsInstance: EChartsType) =\u003e {\r\n 313\t const { gridLeft, gridRight, gridBottom, warningConfig, positionConfig, conditionConfig, isOpenDS } = config;\r\n 314\t let elements: any = [];\r\n 315\t\r\n 316\t if ((echartsInstance as any)?.getModel()?.getComponent) {\r\n 317\t const [minValue, maxValue] = (echartsInstance as any).getModel().getComponent(\"yAxis\").axis.scale._extent;\r\n 318\t // -----数据初始化----\r\n 319\t let warningData: WarningDataItem[] = [];\r\n 320\t let warningEvent: GraphicEvent = {};\r\n 321\t let positionData: PositionDataItem[] = [];\r\n 322\t let positionEvent: GraphicEvent = {};\r\n 323\t let conditionData: ConditionDataItem[] = [];\r\n 324\t let conditionEvent: GraphicEvent = {};\r\n 325\t data.forEach(({ key, data, ...events }) =\u003e {\r\n 326\t if (key === \"warning\") {\r\n 327\t warningData = data as WarningDataItem[];\r\n 328\t warningEvent = events;\r\n 329\t } else if (key === \"position\") {\r\n 330\t positionData = data as PositionDataItem[];\r\n 331\t positionEvent = events;\r\n 332\t } else if (key === \"condition\") {\r\n 333\t conditionData = data as ConditionDataItem[];\r\n 334\t conditionEvent = events;\r\n 335\t }\r\n 336\t });\r\n 337\t\r\n 338\t // -----处理预警线-----\r\n 339\t if (warningData.length \u003e 0) {\r\n 340\t elements = warningData.reduce((res, item: WarningDataItem) =\u003e {\r\n 341\t const { value, text, info, config } = item;\r\n 342\t if (value \u003e maxValue || value \u003c minValue) {\r\n 343\t // 当前数值超出图表范围,不绘制\r\n 344\t return res;\r\n 345\t }\r\n 346\t const params = {\r\n 347\t y: echartsInstance.convertToPixel({ yAxisIndex: 0 }, value),\r\n 348\t text,\r\n 349\t info: { info, config: { ...warningConfig, ...config }, event: { ...warningEvent } },\r\n 350\t gridLeft,\r\n 351\t gridRight,\r\n 352\t echartsWidth: echartsInstance.getWidth(),\r\n 353\t echartsInstance,\r\n 354\t };\r\n 355\t return [...res, getWarningItem(params)];\r\n 356\t }, elements);\r\n 357\t }\r\n 358\t // -----处理持仓线-----\r\n 359\t if (positionData.length \u003e 0) {\r\n 360\t elements = positionData.reduce((res, item: PositionDataItem) =\u003e {\r\n 361\t const { value, text, info, config } = item;\r\n 362\t // 数值超出图表范围的不绘制, 直接return\r\n 363\t if (value \u003e maxValue || value \u003c minValue) {\r\n 364\t // 当前数值超出图表范围,不绘制\r\n 365\t return res;\r\n 366\t }\r\n 367\t const params = {\r\n 368\t y: echartsInstance.convertToPixel({ yAxisIndex: 0 }, value),\r\n 369\t text,\r\n 370\t info: { info, config: { ...positionConfig, ...config }, event: { ...positionEvent } },\r\n 371\t gridLeft,\r\n 372\t gridRight,\r\n 373\t echartsWidth: echartsInstance.getWidth(),\r\n 374\t echartsInstance,\r\n 375\t };\r\n 376\t return [...res, getPositionItem(params)];\r\n 377\t }, elements);\r\n 378\t }\r\n 379\t // -----处理条件单-----\r\n 380\t if (conditionData.length \u003e 0) {\r\n 381\t elements = conditionData.reduce((res, item: ConditionDataItem) =\u003e {\r\n 382\t const { value, text, profitValue, profitText, lossValue, lossText, info, config } = item;\r\n 383\t // 数值超出图表范围的不绘制, 直接return\r\n 384\t if (value \u003e maxValue || value \u003c minValue) {\r\n 385\t return res;\r\n 386\t }\r\n 387\t const params = {\r\n 388\t y: echartsInstance.convertToPixel({ yAxisIndex: 0 }, value),\r\n 389\t text,\r\n 390\t profitY: echartsInstance.convertToPixel({ yAxisIndex: 0 }, profitValue),\r\n 391\t profitText,\r\n 392\t lossY: echartsInstance.convertToPixel({ yAxisIndex: 0 }, lossValue),\r\n 393\t lossText,\r\n 394\t info: { info, config: { ...conditionConfig, ...config }, event: { ...conditionEvent } },\r\n 395\t gridLeft,\r\n 396\t gridRight,\r\n 397\t echartsWidth: echartsInstance.getWidth(),\r\n 398\t echartsInstance,\r\n 399\t };\r\n 400\t return [...res, getConditionItem(params)];\r\n 401\t }, elements);\r\n 402\t }\r\n 403\t // -----处理分布图-----\r\n 404\t if (isOpenDS) {\r\n 405\t const option = echartsInstance.getOption() as any;\r\n 406\t const countKline = option.dataset[0].source.klineData.slice(\r\n 407\t option.dataZoom[0].startValue,\r\n 408\t option.dataZoom[0].endValue + 1\r\n 409\t );\r\n 410\t // 1.统计它们的收盘价出现的次数\r\n 411\t const countMap = {};\r\n 412\t countKline.forEach((item: any) =\u003e {\r\n 413\t if (countMap[item[1]]) {\r\n 414\t countMap[item[1]] += 1;\r\n 415\t } else {\r\n 416\t countMap[item[1]] = 1;\r\n 417\t }\r\n 418\t });\r\n 419\t const lastKlineValue = countKline[countKline.length - 1][1];\r\n 420\t const meanValue =\r\n 421\t countKline.reduce((res: number, item: any) =\u003e {\r\n 422\t return res + item[1];\r\n 423\t }, 0) / countKline.length;\r\n 424\t const disMeanValue = lastKlineValue - meanValue;\r\n 425\t const medianValue = percentToValue(countKline, 1, 50);\r\n 426\t const standardDeviationValue = calculateStandardDeviation(countKline);\r\n 427\t const disMeanSDValue = standardDeviationValue ? disMeanValue / standardDeviationValue : 0;\r\n 428\t const percentileValue = valueToPercent(countKline, 1, lastKlineValue);\r\n 429\t // 2.生成对应的分布图配置样式\r\n 430\t elements = [\r\n 431\t ...elements,\r\n 432\t {\r\n 433\t type: \"group\",\r\n 434\t draggable: false,\r\n 435\t children: Object.keys(countMap).map((key: any) =\u003e {\r\n 436\t return {\r\n 437\t type: \"line\",\r\n 438\t shape: {\r\n 439\t x1: echartsInstance.getWidth() - gridRight - 100 * countMap[key],\r\n 440\t y1: echartsInstance.convertToPixel({ yAxisIndex: 0 }, key),\r\n 441\t x2: echartsInstance.getWidth() - gridRight,\r\n 442\t y2: echartsInstance.convertToPixel({ yAxisIndex: 0 }, key),\r\n 443\t },\r\n 444\t style: {\r\n 445\t stroke: \"rgb(124,124,124)\",\r\n 446\t lineWidth: 1,\r\n 447\t },\r\n 448\t z: 10,\r\n 449\t };\r\n 450\t }),\r\n 451\t },\r\n 452\t {\r\n 453\t type: \"text\",\r\n 454\t x: gridLeft + 6,\r\n 455\t y: echartsInstance.getHeight() - gridBottom - 20,\r\n 456\t style: {\r\n 457\t fill: \"#FFF\",\r\n 458\t text: `均值: ${formatValue(meanValue)}\\xa0\\xa0\\xa0\\xa0距均值: ${formatValue(\r\n 459\t disMeanValue\r\n 460\t )}\\xa0\\xa0\\xa0\\xa0中值: ${formatValue(medianValue)}\\xa0\\xa0\\xa0\\xa0标准差: ${formatValue(\r\n 461\t standardDeviationValue\r\n 462\t )}\\xa0\\xa0\\xa0\\xa0距均值/标准差: ${formatValue(disMeanSDValue)}\\xa0\\xa0\\xa0\\xa0百分位: ${formatValue(\r\n 463\t percentileValue\r\n 464\t )}%`,\r\n 465\t lineWidth: 1,\r\n 466\t opacity: 1,\r\n 467\t },\r\n 468\t z: 10,\r\n 469\t },\r\n 470\t ];\r\n 471\t }\r\n 472\t }\r\n 473\t\r\n 474\t // -----return------\r\n 475\t return { elements };\r\n 476\t};\r\n 477\t\r\n 478\t// 根据值计算分位\r\n 479\tconst valueToPercent = (data, dataIndex, value) =\u003e {\r\n 480\t const { round, add, subtract, multiply, divide } = stMath;\r\n 481\t const sortData = data.map((i) =\u003e i[dataIndex]).sort((a, b) =\u003e a - b);\r\n 482\t let position = 0;\r\n 483\t for (let i = 0; i \u003c sortData.length; i++) {\r\n 484\t if (sortData[i] === value) {\r\n 485\t // 位置为整数\r\n 486\t position = i;\r\n 487\t break;\r\n 488\t } else if (sortData[i] \u003e= value) {\r\n 489\t // 位置为小数\r\n 490\t position = add(i, divide(subtract(sortData[i + 1], sortData[i]), sortData[i]));\r\n 491\t break;\r\n 492\t }\r\n 493\t }\r\n 494\t return round(divide(multiply(position, 100), data.length + 1));\r\n 495\t};\r\n 496\t// 根据分位计算值\r\n 497\tconst percentToValue = (data, dataIndex, percent) =\u003e {\r\n 498\t const { round, add, subtract, multiply, divide } = stMath;\r\n 499\t const sortData = data.map((i) =\u003e i[dataIndex]).sort((a, b) =\u003e a - b);\r\n 500\t const position = multiply(data.length + 1, divide(percent, 100));\r\n 501\t const positionInterger = Math.floor(position);\r\n 502\t const positionDecimal = subtract(position, positionInterger);\r\n 503\t if (positionDecimal === 0) {\r\n 504\t // 位置为整数\r\n 505\t return round(sortData[position]);\r\n 506\t } else {\r\n 507\t // 位置为小数\r\n 508\t return round(\r\n 509\t add(\r\n 510\t sortData[positionInterger],\r\n 511\t multiply(\r\n 512\t subtract(\r\n 513\t sortData[positionInterger \u003e= data.length - 1 ? positionInterger : positionInterger + 1],\r\n 514\t sortData[positionInterger]\r\n 515\t ),\r\n 516\t positionDecimal\r\n 517\t )\r\n 518\t )\r\n 519\t );\r\n 520\t }\r\n 521\t};\r\n 522\t// 计算K线数组中数值的标准差\r\n 523\tfunction calculateStandardDeviation(data) {\r\n 524\t const numbers = data.map((i) =\u003e i[1]);\r\n 525\t const n = numbers.length;\r\n 526\t let sum = 0;\r\n 527\t let squaredSum = 0;\r\n 528\t // 计算平均值(均值)\r\n 529\t for (const num of numbers) {\r\n 530\t sum += num;\r\n 531\t }\r\n 532\t const mean = sum / n;\r\n 533\t // 计算平方和\r\n 534\t for (const num of numbers) {\r\n 535\t squaredSum += Math.pow(num - mean, 2);\r\n 536\t }\r\n 537\t // 计算并返回标准差\r\n 538\t return Math.sqrt(squaredSum / n);\r\n 539\t}\r\n 540\t\n","preview":"1\timport { stMath } from \"st-func\";\n2\timport formatKlineData from \"./formatKlineData\";\n3\timport type { EChartsType } from \"echarts\";","bytes":20322,"is_error":false,"stored_at":"2026-07-27T07:07:07.3481819Z"}
|
|
15
|
+
{"id":"t1.tool43","tool_name":"read_file","full_text":"[read_file path=packages/Kline/option.ts lines=1-280/540 bytes=17011]\n[hint] more remains; next page: read_file(path=\"packages/Kline/option.ts\", offset=281, limit=280)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 1\timport { stMath } from \"st-func\";\r\n 2\timport formatKlineData from \"./formatKlineData\";\r\n 3\timport type { EChartsType } from \"echarts\";\r\n 4\timport type {\r\n 5\t KlineDataType,\r\n 6\t IndicatorConfigType,\r\n 7\t InConfig,\r\n 8\t LineDataType,\r\n 9\t WarningDataItem,\r\n 10\t PositionDataItem,\r\n 11\t ConditionDataItem,\r\n 12\t GraphicEvent,\r\n 13\t} from \"./type.d.ts\";\r\n 14\timport { formatValue, getWarningItem, getPositionItem, getConditionItem, handlePoint } from \"./utils\";\r\n 15\t\r\n 16\t/**\r\n 17\t * @description: 格式化K线图配置项\r\n 18\t * @param {KlineDataType} klineData\r\n 19\t * @param {IndicatorConfigType} indicatorConfig\r\n 20\t * @param {any} markData\r\n 21\t * @param {InConfig} config\r\n 22\t *\r\n 23\t * @return {option} echarts基础配置\r\n 24\t */\r\n 25\texport const getOption = async (\r\n 26\t klineData: KlineDataType,\r\n 27\t markData: any,\r\n 28\t indicatorConfig: IndicatorConfigType,\r\n 29\t config: InConfig,\r\n 30\t netPositionData: any[],\r\n 31\t relevanceData: any[],\r\n 32\t) =\u003e {\r\n 33\t const { totalBarCount, defaultShowBarCount, maxValueSpan, gridLeft, gridTop, gridRight, gridBottom, zoomLock } =\r\n 34\t config;\r\n 35\t /**\r\n 36\t * @todo: 1.数据处理\r\n 37\t * @returns {Array\u003c'YYYY-MM-DD HH:mm:ss'\u003e} time 用于X轴的时间数组\r\n 38\t * @returns {Array\u003c{开,高,低,收}\u003e} KLine 用于渲染candlestick[K线]\r\n 39\t * @returns {Array\u003c{时间,开,高,低,收,成交量,成交额}\u003e} originData 源数据K线\r\n 40\t * @returns {Array\u003c{key:'DKX',color:'#FFFFFF',data:[开]}\u003e} indicator 用于渲染line[指标线]\r\n 41\t */\r\n 42\t const { time, kLine, originData, indicator } = await formatKlineData(klineData, indicatorConfig, totalBarCount);\r\n 43\t /**\r\n 44\t * @todo: 2.指标线渲染项处理\r\n 45\t */\r\n 46\t const lineSeries = indicator.map((item) =\u003e {\r\n 47\t const { key, data, color } = item;\r\n 48\t return {\r\n 49\t name: key,\r\n 50\t type: \"line\",\r\n 51\t silent: true,\r\n 52\t symbol: \"none\",\r\n 53\t data,\r\n 54\t lineStyle: {\r\n 55\t width: 1,\r\n 56\t },\r\n 57\t itemStyle: {\r\n 58\t color,\r\n 59\t },\r\n 60\t };\r\n 61\t });\r\n 62\t /**\r\n 63\t * @todo: 3.标注点位渲染项处理\r\n 64\t */\r\n 65\t const markPointData = handlePoint(markData, originData);\r\n 66\t /**\r\n 67\t * @todo: 4.标注点位连线渲染处理\r\n 68\t */\r\n 69\t let markLineData = [];\r\n 70\t markPointData.forEach((item) =\u003e {\r\n 71\t if (item.markLineTarget) {\r\n 72\t markLineData = [...markLineData, ...item.markLineTarget];\r\n 73\t }\r\n 74\t });\r\n 75\t // -----return------\r\n 76\t return {\r\n 77\t animation: false,\r\n 78\t dataset: {\r\n 79\t id: \"data\",\r\n 80\t source: {\r\n 81\t klineData: kLine,\r\n 82\t indicatorData: indicator,\r\n 83\t },\r\n 84\t },\r\n 85\t grid: {\r\n 86\t left: `${gridLeft}px`,\r\n 87\t top: `${gridTop}px`,\r\n 88\t right: `${gridRight}px`,\r\n 89\t bottom: `${gridBottom}px`,\r\n 90\t },\r\n 91\t tooltip: {\r\n 92\t trigger: \"axis\",\r\n 93\t axisPointer: {\r\n 94\t type: \"cross\",\r\n 95\t label: {\r\n 96\t formatter: (data: any) =\u003e {\r\n 97\t const { axisDimension, value } = data;\r\n 98\t if (axisDimension === \"x\") {\r\n 99\t return value;\r\n 100\t } else {\r\n 101\t return String(formatValue(value));\r\n 102\t }\r\n 103\t },\r\n 104\t },\r\n 105\t },\r\n 106\t formatter: (params) =\u003e {\r\n 107\t let html: null | string = null;\r\n 108\t let body: string = \"\";\r\n 109\t params.forEach((item) =\u003e {\r\n 110\t // 资产持仓净值tooltip渲染逻辑\r\n 111\t if (item.seriesName === \"资产持仓净值\" \u0026\u0026 item.data !== null) {\r\n 112\t body += `\u003cdiv\u003e资产持仓净值: ${item.data}\u003c/div\u003e`;\r\n 113\t }\r\n 114\t if (item.componentSubType === \"candlestick\") {\r\n 115\t // 买卖/开平tooltip渲染逻辑\r\n 116\t markData.forEach((mark) =\u003e {\r\n 117\t const { key, data } = mark;\r\n 118\t if (key === \"sellBuy\") {\r\n 119\t // 买卖点渲染逻辑\r\n 120\t const sellBuy = data.filter((i) =\u003e i.time === item.axisValue);\r\n 121\t let buy = 0;\r\n 122\t let sell = 0;\r\n 123\t sellBuy.forEach((i) =\u003e {\r\n 124\t if (i.tradeType === \"开多\" || i.tradeType === \"平空\") {\r\n 125\t // 买\r\n 126\t buy += i.amount;\r\n 127\t } else {\r\n 128\t // 卖\r\n 129\t sell += i.amount;\r\n 130\t }\r\n 131\t });\r\n 132\t if (buy) {\r\n 133\t body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 134\t }\r\n 135\t if (sell) {\r\n 136\t body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 137\t }\r\n 138\t }\r\n 139\t if (key === \"openClose\") {\r\n 140\t // 开平点渲染逻辑\r\n 141\t data.forEach((i) =\u003e {\r\n 142\t if (i.time === item.axisValue) {\r\n 143\t body += i.tooltip;\r\n 144\t }\r\n 145\t });\r\n 146\t }\r\n 147\t });\r\n 148\t }\r\n 149\t if (body) {\r\n 150\t html = `\u003cdiv\u003e\u003cspan style=\"font-weight: bold;\"\u003e${item.axisValue}\u003c/span\u003e${body}\u003c/div\u003e`;\r\n 151\t }\r\n 152\t });\r\n 153\t return html;\r\n 154\t },\r\n 155\t },\r\n 156\t xAxis: {\r\n 157\t type: \"category\",\r\n 158\t data: time,\r\n 159\t axisLine: {\r\n 160\t show: true,\r\n 161\t },\r\n 162\t splitLine: {\r\n 163\t show: true,\r\n 164\t lineStyle: {\r\n 165\t type: \"dotted\",\r\n 166\t color: \"#333\",\r\n 167\t },\r\n 168\t },\r\n 169\t axisLabel: {\r\n 170\t show: true,\r\n 171\t formatter: (data: string) =\u003e data,\r\n 172\t },\r\n 173\t },\r\n 174\t yAxis: [\r\n 175\t {\r\n 176\t index: 0,\r\n 177\t splitLine: {\r\n 178\t show: true,\r\n 179\t lineStyle: {\r\n 180\t type: \"dotted\",\r\n 181\t color: \"#333\",\r\n 182\t },\r\n 183\t },\r\n 184\t min: (value: any) =\u003e {\r\n 185\t const { min, max } = value;\r\n 186\t const interval = Math.abs((max - min) / 10);\r\n 187\t return min - interval;\r\n 188\t },\r\n 189\t max: (value: any) =\u003e {\r\n 190\t const { min, max } = value;\r\n 191\t const interval = Math.abs((max - min) / 10);\r\n 192\t return max + interval;\r\n 193\t },\r\n 194\t axisLine: {\r\n 195\t show: true,\r\n 196\t },\r\n 197\t axisLabel: {\r\n 198\t formatter: (value: number) =\u003e formatValue(value),\r\n 199\t showMaxLabel: false,\r\n 200\t },\r\n 201\t },\r\n 202\t {\r\n 203\t show: false,\r\n 204\t min: \"dataMin\",\r\n 205\t },\r\n 206\t ],\r\n 207\t dataZoom: [\r\n 208\t {\r\n 209\t type: \"inside\",\r\n 210\t xAxisIndex: [0, 0],\r\n 211\t zoomLock,\r\n 212\t maxValueSpan,\r\n 213\t start: kLine.length \u003e= defaultShowBarCount ? ((kLine.length - defaultShowBarCount) / kLine.length) * 100 : 0,\r\n 214\t end: 99.99,\r\n 215\t // startValue: kLine.length \u003e= defaultShowBarCount ? kLine.length - defaultShowBarCount : 0,\r\n 216\t // endValue: kLine.length - 1,\r\n 217\t },\r\n 218\t ],\r\n 219\t series: [\r\n 220\t {\r\n 221\t name: \"k线\",\r\n 222\t type: \"candlestick\",\r\n 223\t data: kLine,\r\n 224\t markPoint: {\r\n 225\t data: markPointData,\r\n 226\t },\r\n 227\t markLine: {\r\n 228\t position: \"middle\",\r\n 229\t textStyle: { color: \"blue\", fontSize: 15 },\r\n 230\t animation: false,\r\n 231\t data: markLineData,\r\n 232\t lineStyle: {\r\n 233\t width: 3,\r\n 234\t type: \"solid\",\r\n 235\t },\r\n 236\t symbol: [\"none\", \"none\"],\r\n 237\t },\r\n 238\t itemStyle: {\r\n 239\t color: \"transparent\",\r\n 240\t color0: \"#00FFFF\",\r\n 241\t borderColor: \"#FF0000\",\r\n 242\t borderColor0: \"#00FFFF\",\r\n 243\t borderWidth: 1,\r\n 244\t },\r\n 245\t z: 1,\r\n 246\t },\r\n 247\t ...lineSeries,\r\n 248\t {\r\n 249\t name: \"资产持仓净值\",\r\n 250\t type: \"line\",\r\n 251\t data: netPositionData ?? [],\r\n 252\t symbol: \"none\",\r\n 253\t yAxisIndex: 1,\r\n 254\t connectNulls: true,\r\n 255\t itemStyle: {\r\n 256\t color: \"#666\",\r\n 257\t },\r\n 258\t },\r\n 259\t {\r\n 260\t name: \"二腿相关度\",\r\n 261\t type: \"line\",\r\n 262\t data: relevanceData ?? [],\r\n 263\t symbol: \"none\",\r\n 264\t yAxisIndex: 1,\r\n 265\t connectNulls: true,\r\n 266\t itemStyle: {\r\n 267\t color: \"#FFF\",\r\n 268\t },\r\n 269\t lineStyle: {\r\n 270\t // type: \"dashed\", // 虚线\r\n 271\t width: 2,\r\n 272\t },\r\n 273\t label: {\r\n 274\t show: true, // 显示文字\r\n 275\t position: \"top\", // 文字位置\r\n 276\t formatter: (params: any) =\u003e {\r\n 277\t return `相关度: ${params.data[1]}`;\r\n 278\t },\r\n 279\t textStyle: {\r\n 280\t color: \"#FFF\", // 文字颜色\r\n","preview":"1\timport { stMath } from \"st-func\";\n2\timport formatKlineData from \"./formatKlineData\";\n3\timport type { EChartsType } from \"echarts\";","bytes":9880,"is_error":false,"stored_at":"2026-07-27T07:07:11.1593782Z"}
|
|
16
|
+
{"id":"t1.tool44","tool_name":"read_file","full_text":"[read_file path=packages/Kline/option.ts lines=1-270/540 bytes=17011]\n[hint] more remains; next page: read_file(path=\"packages/Kline/option.ts\", offset=271, limit=270)\n[hint] this file is on disk, not in cold storage — call read_file again with the offset/limit above; DO NOT call view_detail\n 1\timport { stMath } from \"st-func\";\r\n 2\timport formatKlineData from \"./formatKlineData\";\r\n 3\timport type { EChartsType } from \"echarts\";\r\n 4\timport type {\r\n 5\t KlineDataType,\r\n 6\t IndicatorConfigType,\r\n 7\t InConfig,\r\n 8\t LineDataType,\r\n 9\t WarningDataItem,\r\n 10\t PositionDataItem,\r\n 11\t ConditionDataItem,\r\n 12\t GraphicEvent,\r\n 13\t} from \"./type.d.ts\";\r\n 14\timport { formatValue, getWarningItem, getPositionItem, getConditionItem, handlePoint } from \"./utils\";\r\n 15\t\r\n 16\t/**\r\n 17\t * @description: 格式化K线图配置项\r\n 18\t * @param {KlineDataType} klineData\r\n 19\t * @param {IndicatorConfigType} indicatorConfig\r\n 20\t * @param {any} markData\r\n 21\t * @param {InConfig} config\r\n 22\t *\r\n 23\t * @return {option} echarts基础配置\r\n 24\t */\r\n 25\texport const getOption = async (\r\n 26\t klineData: KlineDataType,\r\n 27\t markData: any,\r\n 28\t indicatorConfig: IndicatorConfigType,\r\n 29\t config: InConfig,\r\n 30\t netPositionData: any[],\r\n 31\t relevanceData: any[],\r\n 32\t) =\u003e {\r\n 33\t const { totalBarCount, defaultShowBarCount, maxValueSpan, gridLeft, gridTop, gridRight, gridBottom, zoomLock } =\r\n 34\t config;\r\n 35\t /**\r\n 36\t * @todo: 1.数据处理\r\n 37\t * @returns {Array\u003c'YYYY-MM-DD HH:mm:ss'\u003e} time 用于X轴的时间数组\r\n 38\t * @returns {Array\u003c{开,高,低,收}\u003e} KLine 用于渲染candlestick[K线]\r\n 39\t * @returns {Array\u003c{时间,开,高,低,收,成交量,成交额}\u003e} originData 源数据K线\r\n 40\t * @returns {Array\u003c{key:'DKX',color:'#FFFFFF',data:[开]}\u003e} indicator 用于渲染line[指标线]\r\n 41\t */\r\n 42\t const { time, kLine, originData, indicator } = await formatKlineData(klineData, indicatorConfig, totalBarCount);\r\n 43\t /**\r\n 44\t * @todo: 2.指标线渲染项处理\r\n 45\t */\r\n 46\t const lineSeries = indicator.map((item) =\u003e {\r\n 47\t const { key, data, color } = item;\r\n 48\t return {\r\n 49\t name: key,\r\n 50\t type: \"line\",\r\n 51\t silent: true,\r\n 52\t symbol: \"none\",\r\n 53\t data,\r\n 54\t lineStyle: {\r\n 55\t width: 1,\r\n 56\t },\r\n 57\t itemStyle: {\r\n 58\t color,\r\n 59\t },\r\n 60\t };\r\n 61\t });\r\n 62\t /**\r\n 63\t * @todo: 3.标注点位渲染项处理\r\n 64\t */\r\n 65\t const markPointData = handlePoint(markData, originData);\r\n 66\t /**\r\n 67\t * @todo: 4.标注点位连线渲染处理\r\n 68\t */\r\n 69\t let markLineData = [];\r\n 70\t markPointData.forEach((item) =\u003e {\r\n 71\t if (item.markLineTarget) {\r\n 72\t markLineData = [...markLineData, ...item.markLineTarget];\r\n 73\t }\r\n 74\t });\r\n 75\t // -----return------\r\n 76\t return {\r\n 77\t animation: false,\r\n 78\t dataset: {\r\n 79\t id: \"data\",\r\n 80\t source: {\r\n 81\t klineData: kLine,\r\n 82\t indicatorData: indicator,\r\n 83\t },\r\n 84\t },\r\n 85\t grid: {\r\n 86\t left: `${gridLeft}px`,\r\n 87\t top: `${gridTop}px`,\r\n 88\t right: `${gridRight}px`,\r\n 89\t bottom: `${gridBottom}px`,\r\n 90\t },\r\n 91\t tooltip: {\r\n 92\t trigger: \"axis\",\r\n 93\t axisPointer: {\r\n 94\t type: \"cross\",\r\n 95\t label: {\r\n 96\t formatter: (data: any) =\u003e {\r\n 97\t const { axisDimension, value } = data;\r\n 98\t if (axisDimension === \"x\") {\r\n 99\t return value;\r\n 100\t } else {\r\n 101\t return String(formatValue(value));\r\n 102\t }\r\n 103\t },\r\n 104\t },\r\n 105\t },\r\n 106\t formatter: (params) =\u003e {\r\n 107\t let html: null | string = null;\r\n 108\t let body: string = \"\";\r\n 109\t params.forEach((item) =\u003e {\r\n 110\t // 资产持仓净值tooltip渲染逻辑\r\n 111\t if (item.seriesName === \"资产持仓净值\" \u0026\u0026 item.data !== null) {\r\n 112\t body += `\u003cdiv\u003e资产持仓净值: ${item.data}\u003c/div\u003e`;\r\n 113\t }\r\n 114\t if (item.componentSubType === \"candlestick\") {\r\n 115\t // 买卖/开平tooltip渲染逻辑\r\n 116\t markData.forEach((mark) =\u003e {\r\n 117\t const { key, data } = mark;\r\n 118\t if (key === \"sellBuy\") {\r\n 119\t // 买卖点渲染逻辑\r\n 120\t const sellBuy = data.filter((i) =\u003e i.time === item.axisValue);\r\n 121\t let buy = 0;\r\n 122\t let sell = 0;\r\n 123\t sellBuy.forEach((i) =\u003e {\r\n 124\t if (i.tradeType === \"开多\" || i.tradeType === \"平空\") {\r\n 125\t // 买\r\n 126\t buy += i.amount;\r\n 127\t } else {\r\n 128\t // 卖\r\n 129\t sell += i.amount;\r\n 130\t }\r\n 131\t });\r\n 132\t if (buy) {\r\n 133\t body += `\u003cdiv\u003e买: ${buy}\u003c/div\u003e`;\r\n 134\t }\r\n 135\t if (sell) {\r\n 136\t body += `\u003cdiv\u003e卖: ${sell}\u003c/div\u003e`;\r\n 137\t }\r\n 138\t }\r\n 139\t if (key === \"openClose\") {\r\n 140\t // 开平点渲染逻辑\r\n 141\t data.forEach((i) =\u003e {\r\n 142\t if (i.time === item.axisValue) {\r\n 143\t body += i.tooltip;\r\n 144\t }\r\n 145\t });\r\n 146\t }\r\n 147\t });\r\n 148\t }\r\n 149\t if (body) {\r\n 150\t html = `\u003cdiv\u003e\u003cspan style=\"font-weight: bold;\"\u003e${item.axisValue}\u003c/span\u003e${body}\u003c/div\u003e`;\r\n 151\t }\r\n 152\t });\r\n 153\t return html;\r\n 154\t },\r\n 155\t },\r\n 156\t xAxis: {\r\n 157\t type: \"category\",\r\n 158\t data: time,\r\n 159\t axisLine: {\r\n 160\t show: true,\r\n 161\t },\r\n 162\t splitLine: {\r\n 163\t show: true,\r\n 164\t lineStyle: {\r\n 165\t type: \"dotted\",\r\n 166\t color: \"#333\",\r\n 167\t },\r\n 168\t },\r\n 169\t axisLabel: {\r\n 170\t show: true,\r\n 171\t formatter: (data: string) =\u003e data,\r\n 172\t },\r\n 173\t },\r\n 174\t yAxis: [\r\n 175\t {\r\n 176\t index: 0,\r\n 177\t splitLine: {\r\n 178\t show: true,\r\n 179\t lineStyle: {\r\n 180\t type: \"dotted\",\r\n 181\t color: \"#333\",\r\n 182\t },\r\n 183\t },\r\n 184\t min: (value: any) =\u003e {\r\n 185\t const { min, max } = value;\r\n 186\t const interval = Math.abs((max - min) / 10);\r\n 187\t return min - interval;\r\n 188\t },\r\n 189\t max: (value: any) =\u003e {\r\n 190\t const { min, max } = value;\r\n 191\t const interval = Math.abs((max - min) / 10);\r\n 192\t return max + interval;\r\n 193\t },\r\n 194\t axisLine: {\r\n 195\t show: true,\r\n 196\t },\r\n 197\t axisLabel: {\r\n 198\t formatter: (value: number) =\u003e formatValue(value),\r\n 199\t showMaxLabel: false,\r\n 200\t },\r\n 201\t },\r\n 202\t {\r\n 203\t show: false,\r\n 204\t min: \"dataMin\",\r\n 205\t },\r\n 206\t ],\r\n 207\t dataZoom: [\r\n 208\t {\r\n 209\t type: \"inside\",\r\n 210\t xAxisIndex: [0, 0],\r\n 211\t zoomLock,\r\n 212\t maxValueSpan,\r\n 213\t start: kLine.length \u003e= defaultShowBarCount ? ((kLine.length - defaultShowBarCount) / kLine.length) * 100 : 0,\r\n 214\t end: 99.99,\r\n 215\t // startValue: kLine.length \u003e= defaultShowBarCount ? kLine.length - defaultShowBarCount : 0,\r\n 216\t // endValue: kLine.length - 1,\r\n 217\t },\r\n 218\t ],\r\n 219\t series: [\r\n 220\t {\r\n 221\t name: \"k线\",\r\n 222\t type: \"candlestick\",\r\n 223\t data: kLine,\r\n 224\t markPoint: {\r\n 225\t data: markPointData,\r\n 226\t },\r\n 227\t markLine: {\r\n 228\t position: \"middle\",\r\n 229\t textStyle: { color: \"blue\", fontSize: 15 },\r\n 230\t animation: false,\r\n 231\t data: markLineData,\r\n 232\t lineStyle: {\r\n 233\t width: 3,\r\n 234\t type: \"solid\",\r\n 235\t },\r\n 236\t symbol: [\"none\", \"none\"],\r\n 237\t },\r\n 238\t itemStyle: {\r\n 239\t color: \"transparent\",\r\n 240\t color0: \"#00FFFF\",\r\n 241\t borderColor: \"#FF0000\",\r\n 242\t borderColor0: \"#00FFFF\",\r\n 243\t borderWidth: 1,\r\n 244\t },\r\n 245\t z: 1,\r\n 246\t },\r\n 247\t ...lineSeries,\r\n 248\t {\r\n 249\t name: \"资产持仓净值\",\r\n 250\t type: \"line\",\r\n 251\t data: netPositionData ?? [],\r\n 252\t symbol: \"none\",\r\n 253\t yAxisIndex: 1,\r\n 254\t connectNulls: true,\r\n 255\t itemStyle: {\r\n 256\t color: \"#666\",\r\n 257\t },\r\n 258\t },\r\n 259\t {\r\n 260\t name: \"二腿相关度\",\r\n 261\t type: \"line\",\r\n 262\t data: relevanceData ?? [],\r\n 263\t symbol: \"none\",\r\n 264\t yAxisIndex: 1,\r\n 265\t connectNulls: true,\r\n 266\t itemStyle: {\r\n 267\t color: \"#FFF\",\r\n 268\t },\r\n 269\t lineStyle: {\r\n 270\t // type: \"dashed\", // 虚线\r\n","preview":"1\timport { stMath } from \"st-func\";\n2\timport formatKlineData from \"./formatKlineData\";\n3\timport type { EChartsType } from \"echarts\";","bytes":9511,"is_error":false,"stored_at":"2026-07-27T07:07:17.6724258Z"}
|