mopr 0.0.57__tar.gz → 0.0.57.dev1__tar.gz
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.
- {mopr-0.0.57 → mopr-0.0.57.dev1}/CHANGELOG.md +1 -26
- {mopr-0.0.57/mopr.egg-info → mopr-0.0.57.dev1}/PKG-INFO +1 -1
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/__init__.py +1 -9
- {mopr-0.0.57 → mopr-0.0.57.dev1/mopr.egg-info}/PKG-INFO +1 -1
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/SOURCES.txt +0 -19
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/scm_file_list.json +0 -7
- mopr-0.0.57.dev1/mopr.egg-info/scm_version.json +8 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/pyproject.toml +0 -6
- {mopr-0.0.57 → mopr-0.0.57.dev1}/python/_version.py +3 -3
- {mopr-0.0.57 → mopr-0.0.57.dev1}/runtime/ringbuffer.py +2 -2
- mopr-0.0.57/base/dataclass_utils.py +0 -36
- mopr-0.0.57/base/types.py +0 -8
- mopr-0.0.57/finance/__init__.py +0 -0
- mopr-0.0.57/finance/forex/__init__.py +0 -0
- mopr-0.0.57/finance/forex/demo.py +0 -109
- mopr-0.0.57/finance/forex/engine.py +0 -144
- mopr-0.0.57/finance/forex/exchange_rate_api.py +0 -319
- mopr-0.0.57/finance/forex/forex.py +0 -374
- mopr-0.0.57/finance/forex/ingest.py +0 -129
- mopr-0.0.57/finance/forex/models.py +0 -96
- mopr-0.0.57/finance/forex/scheduler.py +0 -47
- mopr-0.0.57/finance/forex/seed.py +0 -70
- mopr-0.0.57/finance/forex/test_engine.py +0 -81
- mopr-0.0.57/finance/forex/yahoo.py +0 -202
- mopr-0.0.57/finance/forex//344/272/272/346/260/221/345/270/201/346/261/207/347/216/207/344/270/255/351/227/264/344/273/267/357/274/2101994-2025/357/274/211.xlsx +0 -0
- mopr-0.0.57/mopr.egg-info/scm_version.json +0 -8
- mopr-0.0.57/storage/__init__.py +0 -11
- mopr-0.0.57/storage/csv.py +0 -141
- mopr-0.0.57/storage/storage.md +0 -198
- mopr-0.0.57/storage/storage.py +0 -69
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.clang-format +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.clang-tidy +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.clangd +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.editorconfig +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.gitattributes +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.githooks/commit-msg +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.gitignore +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.vscode/launch.json +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.vscode/settings.json +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/.vscode/tasks.json +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/AGENTS.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/BUILDING.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/CONTRIBUTING.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/LICENSE +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/MANIFEST.in +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/README.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/THIRD_PARTY_NOTICES.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/environment.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/filesystem.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/region.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/region.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/strings.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/strings.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/time.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/timestamp.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/base/timestamp.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/FileStateStore.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/Generator.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/HLC.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/Id.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/Option.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/PersistentState.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/README.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/StateStore.java +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/generator.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/hlc.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/id.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/option.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/queue.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/state_store.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/distributed/id/tests.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/forex/DESIGN.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/forex/README.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/forex/seed_rates.csv +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/forex/seed_rates_hkd.csv +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/rates/DESIGN.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/finance/rates/README.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/log/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/log/core.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/machine/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/machine/id.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/dependency_links.txt +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/entry_points.txt +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/requires.txt +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/mopr.egg-info/top_level.txt +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/python/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/runtime/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/runtime/ringbuffer.md +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/runtime/ringbuffer_test.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/setup.cfg +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/__main__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/__main__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/cli.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/cpp.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/go.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/java.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/python.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/handlers/rust.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/notices.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/report.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_audit/todo.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/__main__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/cli.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/core.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/__init__.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/base.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/cpp.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/go.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/java.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/python.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/handlers/rust.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/license_header/project_meta.py +0 -0
- {mopr-0.0.57 → mopr-0.0.57.dev1}/tools/run_module.py +0 -0
|
@@ -3,30 +3,6 @@ All notable changes to this project will be documented in this file.
|
|
|
3
3
|
|
|
4
4
|
## [Unreleased]
|
|
5
5
|
|
|
6
|
-
## [0.0.57] - 2026-09-08
|
|
7
|
-
### Changed
|
|
8
|
-
- chore[java]:包名统一回 mopr.distributed.id;vscode 移除 chat autoApprove 并加入 Java 诊断过滤
|
|
9
|
-
- fix[java]: deploy改为自动发布
|
|
10
|
-
- refactor[python]: 泛型 T 收敛到 base.types 单一来源
|
|
11
|
-
- base/types.py 定义仓库唯一 TypeVar T; base/__init__.py 导出 T
|
|
12
|
-
- runtime/ringbuffer.py 改为 from ..base.types import T, 消除本地自建 T
|
|
13
|
-
- 新增 base/dataclass_utils.py (get_field_names 等泛型字段工具)
|
|
14
|
-
- 全仓库泛型 T 引用方统一到 base.types.T, 杜绝类型变量漂移
|
|
15
|
-
- feat[python]: finance/forex 汇率缓存模块与 storage 存储抽象
|
|
16
|
-
- storage: FileStorage/MetaFileStorage + CSV 存储 (供缓存生命周期复用)
|
|
17
|
-
- finance/forex: ExchangeRateCache 多源汇率缓存
|
|
18
|
-
* frankfurter(ECB): 默认, 1999 起日频历史区间
|
|
19
|
-
* exchangerate(er-api): 免费层当日快照逐日累积
|
|
20
|
-
* yahoo: GHS 等 ECB 篮子外货币, Yahoo chart 一次回填历史段 +
|
|
21
|
-
er-api 当日定盘覆盖 (含离线自检)
|
|
22
|
-
- pyproject 注册 mopr.storage / mopr.finance 子包
|
|
23
|
-
- feat[base]: 新增 affinity/filesystem/closer 多语言基础模块
|
|
24
|
-
- c++: base/affinity (CPU 亲和) 纳入 mopr_core; base/strings.h 入编译
|
|
25
|
-
- go: base/filesystem + closer (gocsv 依赖)
|
|
26
|
-
- rust: base/filesystem 模块接线 mod.rs; storage 模块注册 lib.rs
|
|
27
|
-
- Cargo/CMake/go 依赖与锁文件同步; .githooks 补版权头
|
|
28
|
-
- release version 0.0.57
|
|
29
|
-
|
|
30
6
|
## [0.0.56] - 2026-09-07
|
|
31
7
|
### Changed
|
|
32
8
|
- docs[java]:新增 Maven Central 发布指南文档
|
|
@@ -816,8 +792,7 @@ All notable changes to this project will be documented in this file.
|
|
|
816
792
|
- release version 0.0.1
|
|
817
793
|
|
|
818
794
|
|
|
819
|
-
[Unreleased]: https://gitee.com/quant1x/mopr/compare/v0.0.
|
|
820
|
-
[0.0.57]: https://gitee.com/quant1x/mopr/compare/v0.0.56...v0.0.57
|
|
795
|
+
[Unreleased]: https://gitee.com/quant1x/mopr/compare/v0.0.56...HEAD
|
|
821
796
|
[0.0.56]: https://gitee.com/quant1x/mopr/compare/v0.0.55...v0.0.56
|
|
822
797
|
[0.0.55]: https://gitee.com/quant1x/mopr/compare/v0.0.54...v0.0.55
|
|
823
798
|
[0.0.54]: https://gitee.com/quant1x/mopr/compare/v0.0.53...v0.0.54
|
|
@@ -11,10 +11,8 @@ base 各单文件模块的公共函数与类型经 __init__ 直接导出, 业务
|
|
|
11
11
|
"""
|
|
12
12
|
|
|
13
13
|
# 模块对象保留为包属性; environment 依赖 filesystem, 故 filesystem 在前
|
|
14
|
-
from . import
|
|
14
|
+
from . import environment, filesystem, region, strings, time, timestamp
|
|
15
15
|
|
|
16
|
-
# dataclass_utils
|
|
17
|
-
from .dataclass_utils import get_field_names
|
|
18
16
|
# filesystem
|
|
19
17
|
from .filesystem import expand_user, homedir, mkdirs, touch, update_file_mtime
|
|
20
18
|
# environment
|
|
@@ -27,12 +25,8 @@ from .timestamp import Timestamp
|
|
|
27
25
|
from .strings import str_to_bool, to_snake_case
|
|
28
26
|
# region
|
|
29
27
|
from .region import Region, is_offshore, is_onshore
|
|
30
|
-
# types
|
|
31
|
-
from .types import T
|
|
32
28
|
|
|
33
29
|
__all__ = [
|
|
34
|
-
# dataclass_utils
|
|
35
|
-
"get_field_names",
|
|
36
30
|
# filesystem
|
|
37
31
|
"expand_user", "homedir", "mkdirs", "touch", "update_file_mtime",
|
|
38
32
|
# environment
|
|
@@ -45,6 +39,4 @@ __all__ = [
|
|
|
45
39
|
"str_to_bool", "to_snake_case",
|
|
46
40
|
# region
|
|
47
41
|
"Region", "is_offshore", "is_onshore",
|
|
48
|
-
# types
|
|
49
|
-
"T",
|
|
50
42
|
]
|
|
@@ -18,7 +18,6 @@ pyproject.toml
|
|
|
18
18
|
.vscode/settings.json
|
|
19
19
|
.vscode/tasks.json
|
|
20
20
|
base/__init__.py
|
|
21
|
-
base/dataclass_utils.py
|
|
22
21
|
base/environment.py
|
|
23
22
|
base/filesystem.py
|
|
24
23
|
base/region.md
|
|
@@ -28,7 +27,6 @@ base/strings.py
|
|
|
28
27
|
base/time.py
|
|
29
28
|
base/timestamp.md
|
|
30
29
|
base/timestamp.py
|
|
31
|
-
base/types.py
|
|
32
30
|
distributed/__init__.py
|
|
33
31
|
distributed/id/FileStateStore.java
|
|
34
32
|
distributed/id/Generator.java
|
|
@@ -46,23 +44,10 @@ distributed/id/option.py
|
|
|
46
44
|
distributed/id/queue.py
|
|
47
45
|
distributed/id/state_store.py
|
|
48
46
|
distributed/id/tests.py
|
|
49
|
-
finance/__init__.py
|
|
50
47
|
finance/forex/DESIGN.md
|
|
51
48
|
finance/forex/README.md
|
|
52
|
-
finance/forex/__init__.py
|
|
53
|
-
finance/forex/demo.py
|
|
54
|
-
finance/forex/engine.py
|
|
55
|
-
finance/forex/exchange_rate_api.py
|
|
56
|
-
finance/forex/forex.py
|
|
57
|
-
finance/forex/ingest.py
|
|
58
|
-
finance/forex/models.py
|
|
59
|
-
finance/forex/scheduler.py
|
|
60
|
-
finance/forex/seed.py
|
|
61
49
|
finance/forex/seed_rates.csv
|
|
62
50
|
finance/forex/seed_rates_hkd.csv
|
|
63
|
-
finance/forex/test_engine.py
|
|
64
|
-
finance/forex/yahoo.py
|
|
65
|
-
finance/forex/人民币汇率中间价(1994-2025).xlsx
|
|
66
51
|
finance/rates/DESIGN.md
|
|
67
52
|
finance/rates/README.md
|
|
68
53
|
log/__init__.py
|
|
@@ -83,10 +68,6 @@ runtime/__init__.py
|
|
|
83
68
|
runtime/ringbuffer.md
|
|
84
69
|
runtime/ringbuffer.py
|
|
85
70
|
runtime/ringbuffer_test.py
|
|
86
|
-
storage/__init__.py
|
|
87
|
-
storage/csv.py
|
|
88
|
-
storage/storage.md
|
|
89
|
-
storage/storage.py
|
|
90
71
|
tools/__init__.py
|
|
91
72
|
tools/__main__.py
|
|
92
73
|
tools/run_module.py
|
|
@@ -28,9 +28,6 @@
|
|
|
28
28
|
"base\\cache\\mmap_windows.go",
|
|
29
29
|
"base\\cache\\pool.go",
|
|
30
30
|
"base\\cache\\pool_test.go",
|
|
31
|
-
"base\\closer.go",
|
|
32
|
-
"base\\filesystem.go",
|
|
33
|
-
"base\\filesystem_test.go",
|
|
34
31
|
"base\\mem\\page.go",
|
|
35
32
|
"base\\mem\\page_test.go",
|
|
36
33
|
"base\\mem\\size.go",
|
|
@@ -79,7 +76,6 @@
|
|
|
79
76
|
"finance\\forex\\README.md",
|
|
80
77
|
"finance\\forex\\seed_rates.csv",
|
|
81
78
|
"finance\\forex\\seed_rates_hkd.csv",
|
|
82
|
-
"finance\\forex\\\u4eba\u6c11\u5e01\u6c47\u7387\u4e2d\u95f4\u4ef7\uff081994-2025\uff09.xlsx",
|
|
83
79
|
"finance\\rates\\DESIGN.md",
|
|
84
80
|
"finance\\rates\\README.md",
|
|
85
81
|
"go.mod",
|
|
@@ -87,9 +83,6 @@
|
|
|
87
83
|
"runtime\\ringbuffer.go",
|
|
88
84
|
"runtime\\ringbuffer.md",
|
|
89
85
|
"runtime\\ringbuffer_test.go",
|
|
90
|
-
"storage\\csv.go",
|
|
91
|
-
"storage\\storage.go",
|
|
92
|
-
"storage\\storage.md",
|
|
93
86
|
"tests\\runtime\\LMAXDisruptorBenchmark.java"
|
|
94
87
|
]
|
|
95
88
|
}
|
|
@@ -55,9 +55,6 @@ packages = [
|
|
|
55
55
|
"mopr.distributed.id",
|
|
56
56
|
"mopr.machine",
|
|
57
57
|
"mopr.log",
|
|
58
|
-
"mopr.storage",
|
|
59
|
-
"mopr.finance",
|
|
60
|
-
"mopr.finance.forex",
|
|
61
58
|
# 内置工具集合 (仓库根 tools/, 每个工具一个子包): mopr 的功能模块仅
|
|
62
59
|
# base/distributed/runtime/machine, 其余辅助工具一律归集于此, 保持一级命名空间干净
|
|
63
60
|
"mopr.tools",
|
|
@@ -75,9 +72,6 @@ mopr = "python"
|
|
|
75
72
|
"mopr.distributed.id" = "distributed/id"
|
|
76
73
|
"mopr.machine" = "machine"
|
|
77
74
|
"mopr.log" = "log"
|
|
78
|
-
"mopr.storage" = "storage"
|
|
79
|
-
"mopr.finance" = "finance"
|
|
80
|
-
"mopr.finance.forex" = "finance/forex"
|
|
81
75
|
"mopr.tools" = "tools"
|
|
82
76
|
"mopr.tools.license_header" = "tools/license_header"
|
|
83
77
|
"mopr.tools.license_header.handlers" = "tools/license_header/handlers"
|
|
@@ -18,7 +18,7 @@ version_tuple: tuple[int | str, ...]
|
|
|
18
18
|
commit_id: str | None
|
|
19
19
|
__commit_id__: str | None
|
|
20
20
|
|
|
21
|
-
__version__ = version = '0.0.57'
|
|
22
|
-
__version_tuple__ = version_tuple = (0, 0, 57)
|
|
21
|
+
__version__ = version = '0.0.57.dev1'
|
|
22
|
+
__version_tuple__ = version_tuple = (0, 0, 57, 'dev1')
|
|
23
23
|
|
|
24
|
-
__commit_id__ = commit_id = '
|
|
24
|
+
__commit_id__ = commit_id = 'g29062dc61'
|
|
@@ -1,36 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
# Copyright (C) 2026 Quant1X <wangfengxy@sina.cn>
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
-
|
|
5
|
-
"""Dataclass 反射/序列化工具 (Python spec, 对齐 quant1x.base.dataclass_utils).
|
|
6
|
-
|
|
7
|
-
提供 dataclass 字段元信息提取 (get_field_names), 支撑 mopr.storage/csv 的
|
|
8
|
-
dataclass <-> CSV 双向转换.
|
|
9
|
-
"""
|
|
10
|
-
from dataclasses import fields, is_dataclass
|
|
11
|
-
from typing import List, Type
|
|
12
|
-
|
|
13
|
-
from .types import T
|
|
14
|
-
from . import strings
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
def get_field_names(cls: Type[T]) -> List[str]:
|
|
18
|
-
"""获取 dataclass 类的所有字段名称列表 (snake_case 化).
|
|
19
|
-
|
|
20
|
-
Args:
|
|
21
|
-
cls (Type[T]): 需要获取字段名的 dataclass 类
|
|
22
|
-
|
|
23
|
-
Returns:
|
|
24
|
-
List[str]: 包含所有字段名称的列表
|
|
25
|
-
|
|
26
|
-
Raises:
|
|
27
|
-
TypeError: 如果传入的类不是 dataclass 类型
|
|
28
|
-
"""
|
|
29
|
-
if not is_dataclass(cls):
|
|
30
|
-
raise TypeError(
|
|
31
|
-
f"Expected a dataclass type, but got {cls!r}. "
|
|
32
|
-
"Please decorate your class with @dataclass."
|
|
33
|
-
)
|
|
34
|
-
cls_fields = fields(cls)
|
|
35
|
-
field_names = [strings.to_snake_case(f.name) for f in cls_fields]
|
|
36
|
-
return field_names
|
mopr-0.0.57/base/types.py
DELETED
mopr-0.0.57/finance/__init__.py
DELETED
|
File without changes
|
|
File without changes
|
|
@@ -1,109 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
# Copyright (C) 2026 Quant1X <wangfengxy@sina.cn>
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
端到端演示
|
|
7
|
-
==========
|
|
8
|
-
流程:初始化锚点 -> 灌入演示汇率 -> 派生全量币种对 -> 折算 -> 自洽性校验
|
|
9
|
-
含双轨基准与记账时刻语义演示:
|
|
10
|
-
- CN 锚(CFETS):人行 9:15 发布,09:13 记账 -> 以前一交易日(9/3)定盘
|
|
11
|
-
- HK 锚(HKMA):金管局 19:00 发布,18:00 记账 -> 以前一交易日(9/3)定盘
|
|
12
|
-
- 约定汇率(contractual)优先于官方定盘
|
|
13
|
-
"""
|
|
14
|
-
import os, sys, datetime as dt
|
|
15
|
-
from zoneinfo import ZoneInfo
|
|
16
|
-
sys.path.insert(0, os.path.dirname(__file__))
|
|
17
|
-
try:
|
|
18
|
-
sys.stdout.reconfigure(encoding="utf-8") # type: ignore[attr-defined] # Windows GBK 控制台
|
|
19
|
-
except Exception:
|
|
20
|
-
pass
|
|
21
|
-
|
|
22
|
-
# 必须在 import models 之前设置(models 在 import 时读取 DB_PATH)
|
|
23
|
-
os.environ["FX_DB_PATH"] = "/tmp/fx_module/demo.db"
|
|
24
|
-
if os.path.exists("/tmp/fx_module/demo.db"):
|
|
25
|
-
os.remove("/tmp/fx_module/demo.db")
|
|
26
|
-
|
|
27
|
-
from models import make_engine, session_factory, Base, FxRate, FxPair
|
|
28
|
-
import seed, ingest, engine
|
|
29
|
-
from ingest import demo_csv_path, demo_csv_hkd_path
|
|
30
|
-
|
|
31
|
-
engine_db = make_engine()
|
|
32
|
-
Base.metadata.create_all(engine_db)
|
|
33
|
-
SessionLocal = session_factory()
|
|
34
|
-
|
|
35
|
-
seed.init_anchors()
|
|
36
|
-
seed.write_seed_csv()
|
|
37
|
-
seed.write_seed_hkd_csv()
|
|
38
|
-
|
|
39
|
-
SH = ZoneInfo("Asia/Shanghai")
|
|
40
|
-
HK = ZoneInfo("Asia/Hong_Kong")
|
|
41
|
-
|
|
42
|
-
with SessionLocal() as s:
|
|
43
|
-
# 1) 灌入演示数据(生产环境替换为 ingest_official / HKMA 接入)
|
|
44
|
-
d = ingest.ingest_from_csv(s, demo_csv_path(), anchor_ccy="CNY")
|
|
45
|
-
print(f"[1] CN锚 灌入 CNY 在 {d} 的汇率 (CFETS口径)")
|
|
46
|
-
d2 = ingest.ingest_from_csv(s, demo_csv_hkd_path(), anchor_ccy="HKD", source="HKMA-csv")
|
|
47
|
-
print(f"[1] HK锚 灌入 HKD 在 {d2} 的汇率 (HKMA口径)")
|
|
48
|
-
|
|
49
|
-
# 2) 派生全量币种对(三角套算)
|
|
50
|
-
n = engine.build_and_persist_pairs(s, "CNY", d)
|
|
51
|
-
n2 = engine.build_and_persist_pairs(s, "HKD", d2)
|
|
52
|
-
print(f"[2] 派生全量币种对 CNY×{n} / HKD×{n2} 对")
|
|
53
|
-
|
|
54
|
-
# 3) 折算:100 美元 -> 欧元 (CN锚, 9/4 定盘)
|
|
55
|
-
r1 = engine.convert(s, 100, "USD", "EUR", d, "CNY")
|
|
56
|
-
print(f"[3] CN锚 100 USD -> EUR = {r1:.4f} (校验: 7.0980/8.16 = {7.0980/8.16:.6f})")
|
|
57
|
-
|
|
58
|
-
# 4) 记账时刻语义:人行 9:15 发布当日,09:13 记账 -> 用前一交易日(9/3)定盘
|
|
59
|
-
as_of_0913 = dt.datetime(2026, 9, 4, 9, 13, tzinfo=SH)
|
|
60
|
-
rd_0913 = engine.resolve_rate_date(s, "CNY", as_of_0913)
|
|
61
|
-
r2 = engine.convert(s, 100, "USD", "CNY", d, "CNY", as_of=as_of_0913)
|
|
62
|
-
print(f"[4] CN锚 09/04 09:13 记账: 定盘日={rd_0913} "
|
|
63
|
-
f"100 USD -> CNY = {r2:.4f} (9/3中间价7.1100, 非当日7.0980)")
|
|
64
|
-
|
|
65
|
-
# 5) 同一天 09:30(已过 9:15 发布)-> 取当日 9/4 定盘
|
|
66
|
-
as_of_0930 = dt.datetime(2026, 9, 4, 9, 30, tzinfo=SH)
|
|
67
|
-
rd_0930 = engine.resolve_rate_date(s, "CNY", as_of_0930)
|
|
68
|
-
r3 = engine.convert(s, 100, "USD", "CNY", d, "CNY", as_of=as_of_0930)
|
|
69
|
-
print(f"[5] CN锚 09/04 09:30 记账: 定盘日={rd_0930} "
|
|
70
|
-
f"100 USD -> CNY = {r3:.4f} (当日7.0980)")
|
|
71
|
-
|
|
72
|
-
# 6) HK锚 金管局口径:19:00 发布,18:00 记账 -> 用前一交易日(9/3)
|
|
73
|
-
as_of_hk = dt.datetime(2026, 9, 4, 18, 0, tzinfo=HK)
|
|
74
|
-
rd_hk = engine.resolve_rate_date(s, "HKD", as_of_hk)
|
|
75
|
-
r4 = engine.convert(s, 100, "USD", "CNY", d2, "HKD", as_of=as_of_hk)
|
|
76
|
-
print(f"[6] HK锚 09/04 18:00 记账: 定盘日={rd_hk} "
|
|
77
|
-
f"100 USD -> CNY = {r4:.4f} (按HKMA 9/3: 7.7950/1.0960)")
|
|
78
|
-
|
|
79
|
-
# 7) HK锚 19:30(已过 19:00 发布)-> 取当日 9/4
|
|
80
|
-
as_of_hk2 = dt.datetime(2026, 9, 4, 19, 30, tzinfo=HK)
|
|
81
|
-
rd_hk2 = engine.resolve_rate_date(s, "HKD", as_of_hk2)
|
|
82
|
-
r5 = engine.convert(s, 100, "USD", "CNY", d2, "HKD", as_of=as_of_hk2)
|
|
83
|
-
print(f"[7] HK锚 09/04 19:30 记账: 定盘日={rd_hk2} "
|
|
84
|
-
f"100 USD -> CNY = {r5:.4f} (按HKMA当日: 7.7900/1.0970)")
|
|
85
|
-
|
|
86
|
-
# 8) 交易双方约定汇率优先:单据约定 1 USD = 7.20 CNY,不再走官方定盘
|
|
87
|
-
r6 = engine.convert(s, 100, "USD", "CNY", d, "CNY", contractual_rate=7.20)
|
|
88
|
-
print(f"[8] 约定汇率 1 USD = 7.20 CNY: 100 USD -> CNY = {r6:.4f} (绕过官方定盘)")
|
|
89
|
-
|
|
90
|
-
# 9) 反向折算自洽: 100 EUR -> USD 再 -> EUR 应还原
|
|
91
|
-
usd = engine.convert(s, 100, "EUR", "USD", d, "CNY")
|
|
92
|
-
eur_back = engine.convert(s, usd, "USD", "EUR", d, "CNY")
|
|
93
|
-
print(f"[9] 100 EUR -> USD = {usd:.4f}, 再折回 EUR = {eur_back:.4f} (应≈100)")
|
|
94
|
-
|
|
95
|
-
# 10) 自洽性校验:遍历所有币种对,a->b->a 误差应 < 1e-6
|
|
96
|
-
pairs = s.query(FxPair).filter_by(anchor_ccy="CNY", rate_date=d).all()
|
|
97
|
-
ccies = sorted({str(p.base) for p in pairs})
|
|
98
|
-
max_err = 0
|
|
99
|
-
for a in ccies:
|
|
100
|
-
for b in ccies:
|
|
101
|
-
if a == b:
|
|
102
|
-
continue
|
|
103
|
-
ab = engine.get_rate_on(s, a, b, d, "CNY")
|
|
104
|
-
ba = engine.get_rate_on(s, b, a, d, "CNY")
|
|
105
|
-
err = abs(ab * ba - 1.0)
|
|
106
|
-
max_err = max(max_err, err)
|
|
107
|
-
print(f"[10] 三角套算自洽性校验: 最大 |rate(a,b)*rate(b,a)-1| = {max_err:.2e} (应 < 1e-6)")
|
|
108
|
-
|
|
109
|
-
print("\n演示完成")
|
|
@@ -1,144 +0,0 @@
|
|
|
1
|
-
# -*- coding: UTF-8 -*-
|
|
2
|
-
# Copyright (C) 2026 Quant1X <wangfengxy@sina.cn>
|
|
3
|
-
# SPDX-License-Identifier: AGPL-3.0-only
|
|
4
|
-
|
|
5
|
-
"""
|
|
6
|
-
折算引擎
|
|
7
|
-
========
|
|
8
|
-
核心:全量币种对 = 锚点中间价 + 三角套算。
|
|
9
|
-
规则(以 CNY 为中心枢纽):
|
|
10
|
-
- 官方直接提供: foreign/CNY (1 foreign = ? CNY)
|
|
11
|
-
- 任意两外币: A/B = (A/CNY) / (B/CNY) # 三角套算
|
|
12
|
-
- 锚币本身: CNY/CNY = 1
|
|
13
|
-
|
|
14
|
-
"各自锚点"体现:每个主体以自身本币为锚,且绑定官方基准源(provider)。
|
|
15
|
-
例如 CN 主体锚 CNY 按人行(CFETS 9:15),HK 主体锚 HKD 按金管局(19:00)。
|
|
16
|
-
锚币 = 中心枢纽,套算逻辑完全一致 —— 这就是"各自锚点"的工程实现。
|
|
17
|
-
|
|
18
|
-
记账时点语义(重要):
|
|
19
|
-
官方定盘在每日固定时刻发布(人行 9:15 / 金管局 19:00),若记账时刻早于发布,
|
|
20
|
-
当日定盘尚不可用,须取"最近一个已发布的定盘日"(通常为前一交易日);
|
|
21
|
-
若交易双方在单据中约定了汇率(contractual_rate),约定价优先,不再查官方表。
|
|
22
|
-
"""
|
|
23
|
-
import datetime as dt
|
|
24
|
-
from zoneinfo import ZoneInfo
|
|
25
|
-
from typing import Dict, Optional
|
|
26
|
-
from sqlalchemy.orm import Session
|
|
27
|
-
from models import Anchor, FxRate, FxPair
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
def resolve_rate_date(session: Session, anchor_ccy: str, as_of: dt.datetime) -> dt.date:
|
|
31
|
-
"""记账时刻 as_of 下,该锚点"最近一个已发布定盘"的日期。
|
|
32
|
-
|
|
33
|
-
规则:
|
|
34
|
-
- 查锚点注册的 provider 发布时点 publish_time / publish_tz;
|
|
35
|
-
- as_of 换算到发布时区后,若已过发布时点则当日定盘可用,否则以前一日起算;
|
|
36
|
-
- 从候选日起向前找最近一个真实存在数据(fx_rate)的日期
|
|
37
|
-
(跳过周末/节假日等无发布日,天然兼容交易日历)。
|
|
38
|
-
"""
|
|
39
|
-
anchor = session.query(Anchor).filter_by(anchor_currency=anchor_ccy).first()
|
|
40
|
-
if anchor is None:
|
|
41
|
-
raise ValueError(f"锚点 {anchor_ccy} 未注册(anchor 表),请先 seed.init_anchors()")
|
|
42
|
-
tz = ZoneInfo(anchor.publish_tz or "UTC")
|
|
43
|
-
publish = dt.time.fromisoformat(anchor.publish_time or "09:15")
|
|
44
|
-
local = as_of.astimezone(tz)
|
|
45
|
-
pub_dt = dt.datetime.combine(local.date(), publish, tzinfo=tz)
|
|
46
|
-
if local >= pub_dt:
|
|
47
|
-
candidate = local.date()
|
|
48
|
-
else:
|
|
49
|
-
candidate = local.date() - dt.timedelta(days=1)
|
|
50
|
-
# 向前找最近有数据的定盘日(幂等:数据已入库即视为已发布)
|
|
51
|
-
rows = (session.query(FxRate.rate_date)
|
|
52
|
-
.filter(FxRate.anchor_ccy == anchor_ccy, FxRate.rate_date <= candidate)
|
|
53
|
-
.order_by(FxRate.rate_date.desc())
|
|
54
|
-
.all())
|
|
55
|
-
if not rows:
|
|
56
|
-
raise ValueError(f"锚点 {anchor_ccy} 在 {as_of} 前无任何已发布定盘数据")
|
|
57
|
-
return rows[0][0]
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
def build_cross_rates(rates_against_anchor: Dict[str, float], anchor_ccy: str) -> Dict[str, Dict[str, float]]:
|
|
61
|
-
"""
|
|
62
|
-
由 {外币: 1外币=?锚币} 派生全量币种对矩阵 {base: {quote: rate}}。
|
|
63
|
-
rate = base_amount 折算成 quote 时的系数 (1 base = rate quote)。
|
|
64
|
-
注:rates_against_anchor 均应为正向口径 "1 foreign = ? anchor"。
|
|
65
|
-
"""
|
|
66
|
-
all_ccy = list(rates_against_anchor.keys()) + [anchor_ccy]
|
|
67
|
-
matrix: Dict[str, Dict[str, float]] = {c: {} for c in all_ccy}
|
|
68
|
-
# 锚币自身
|
|
69
|
-
matrix[anchor_ccy][anchor_ccy] = 1.0
|
|
70
|
-
for c in rates_against_anchor:
|
|
71
|
-
matrix[c][anchor_ccy] = rates_against_anchor[c] # c -> anchor
|
|
72
|
-
matrix[anchor_ccy][c] = 1.0 / rates_against_anchor[c] # anchor -> c
|
|
73
|
-
# 三角套算:c1/c2 = (c1/anchor) / (c2/anchor)
|
|
74
|
-
for c1 in all_ccy:
|
|
75
|
-
for c2 in all_ccy:
|
|
76
|
-
if c1 in matrix and c2 in matrix and matrix[c1].get(anchor_ccy) and matrix[c2].get(anchor_ccy):
|
|
77
|
-
matrix[c1][c2] = matrix[c1][anchor_ccy] / matrix[c2][anchor_ccy]
|
|
78
|
-
return matrix
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
def build_and_persist_pairs(session: Session, anchor_ccy: str, date) -> int:
|
|
82
|
-
"""把某日全量币种对套算结果写入 fx_pair 表,返回对数。"""
|
|
83
|
-
rows = session.query(FxRate).filter_by(anchor_ccy=anchor_ccy, rate_date=date).all()
|
|
84
|
-
if not rows:
|
|
85
|
-
raise ValueError(f"无 {anchor_ccy} 锚点在 {date} 的汇率数据,请先 ingest")
|
|
86
|
-
rates = {r.foreign_ccy: r.rate for r in rows}
|
|
87
|
-
matrix = build_cross_rates(rates, anchor_ccy)
|
|
88
|
-
count = 0
|
|
89
|
-
for base, quotes in matrix.items():
|
|
90
|
-
for quote, rate in quotes.items():
|
|
91
|
-
existing = session.query(FxPair).filter_by(
|
|
92
|
-
anchor_ccy=anchor_ccy, base=base, quote=quote, rate_date=date).first()
|
|
93
|
-
if existing:
|
|
94
|
-
existing.rate = rate
|
|
95
|
-
else:
|
|
96
|
-
session.add(FxPair(anchor_ccy=anchor_ccy, base=base, quote=quote,
|
|
97
|
-
rate_date=date, rate=rate, source="cross"))
|
|
98
|
-
count += 1
|
|
99
|
-
session.commit()
|
|
100
|
-
return count
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
def get_rate_on(session: Session, base: str, quote: str, date, anchor_ccy: str,
|
|
104
|
-
as_of: Optional[dt.datetime] = None) -> float:
|
|
105
|
-
"""查询某定盘日币种对汇率(优先 fx_pair,缺失则实时套算)。
|
|
106
|
-
|
|
107
|
-
传 as_of 时按记账时刻解析"最近已发布定盘日",忽略 date。
|
|
108
|
-
"""
|
|
109
|
-
if as_of is not None:
|
|
110
|
-
date = resolve_rate_date(session, anchor_ccy, as_of)
|
|
111
|
-
p = session.query(FxPair).filter_by(anchor_ccy=anchor_ccy, base=base, quote=quote,
|
|
112
|
-
rate_date=date).first()
|
|
113
|
-
if p:
|
|
114
|
-
return p.rate
|
|
115
|
-
# 实时套算兜底(锚币自身对锚币恒为 1,fx_rate 不落锚币自身行)
|
|
116
|
-
def _rate_vs_anchor(ccy: str) -> float:
|
|
117
|
-
if ccy == anchor_ccy:
|
|
118
|
-
return 1.0
|
|
119
|
-
r = session.query(FxRate).filter_by(anchor_ccy=anchor_ccy, foreign_ccy=ccy, rate_date=date).first()
|
|
120
|
-
if not r:
|
|
121
|
-
raise ValueError(f"缺少 {ccy} 在 {date} 对 {anchor_ccy} 的汇率")
|
|
122
|
-
return r.rate
|
|
123
|
-
return _rate_vs_anchor(base) / _rate_vs_anchor(quote)
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
def convert(session: Session, amount: float, from_ccy: str, to_ccy: str, date,
|
|
127
|
-
anchor_ccy: str, contractual_rate: Optional[float] = None,
|
|
128
|
-
as_of: Optional[dt.datetime] = None) -> float:
|
|
129
|
-
"""折算:amount (from_ccy) -> to_ccy。
|
|
130
|
-
|
|
131
|
-
汇率来源优先级(从高到低):
|
|
132
|
-
1. 交易双方约定汇率 contractual_rate(单据/合同载明,直接使用);
|
|
133
|
-
2. 官方已发布定盘:as_of 记账时刻最近已发布日(as_of 缺省用 date 当日)。
|
|
134
|
-
注意:约定汇率口径应与 from_ccy/to_ccy 对应(1 from_ccy = ? to_ccy)。
|
|
135
|
-
"""
|
|
136
|
-
if date is None and as_of is None and contractual_rate is None:
|
|
137
|
-
raise ValueError("date / as_of / contractual_rate 三者至少提供一个")
|
|
138
|
-
if from_ccy == to_ccy:
|
|
139
|
-
return amount
|
|
140
|
-
if contractual_rate is not None:
|
|
141
|
-
return amount * contractual_rate
|
|
142
|
-
if as_of is not None:
|
|
143
|
-
date = resolve_rate_date(session, anchor_ccy, as_of)
|
|
144
|
-
return amount * get_rate_on(session, from_ccy, to_ccy, date, anchor_ccy)
|