exness-data-preprocess 3.0.0__tar.gz → 3.0.1__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.
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.prototools +1 -1
- exness_data_preprocess-3.0.1/.python-version +1 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/CHANGELOG.md +7 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/CLAUDE.md +1 -1
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/PKG-INFO +2 -1
- exness_data_preprocess-3.0.1/docs/release-notes/3.0.1.md +18 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/pyproject.toml +2 -9
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/validate_clickhouse.py +46 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_manager.py +1 -1
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_ohlc_generator.py +54 -47
- exness_data_preprocess-3.0.0/.python-version +0 -1
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.github/dependabot.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.github/workflows/publish.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.gitignore +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.moon/toolchain.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.moon/workspace.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.pre-commit-config.yaml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/.releaserc.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/CONTRIBUTING.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/LICENSE +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/CLICKHOUSE_USER_GUIDE.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/EXNESS_DATA_SOURCES.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/2025-12-09-codebase-pruning.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/2025-12-09-exness-clickhouse-migration.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/2025-12-10-clickhouse-e2e-validation-pipeline.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/2025-12-10-clickhouse-pydantic-config.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/2025-12-11-duckdb-removal-clickhouse.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/diagrams/2025-12-10-clickhouse-pydantic-config-after.dot +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/diagrams/2025-12-10-clickhouse-pydantic-config-arch.dot +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/adr/diagrams/2025-12-10-clickhouse-pydantic-config-before.dot +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/design/2025-12-09-codebase-pruning/spec.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/design/2025-12-09-exness-clickhouse-migration/spec.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/design/2025-12-10-clickhouse-e2e-validation-pipeline/spec.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/design/2025-12-10-clickhouse-pydantic-config/spec.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/design/2025-12-11-duckdb-removal-clickhouse/spec.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/release-notes/2.1.1.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/release-notes/3.0.0.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/HYBRID_SESSION_DETECTION_ANALYSIS.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/compression-benchmarks/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/compression-benchmarks/test_all_compression_methods.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/compression-benchmarks/test_delta_encoding_properly.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/compression-benchmarks/test_parquet_compression_methods.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/01-methodology.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/02-mode-truncated-analysis.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/03-hurdle-model-analysis.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/04-temporal-patterns.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/05-final-recommendations.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/scripts/reproduction_guide.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/hurdle-model/hurdle_decomposition.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/mode-truncated/cv_mode_comparison.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/mode-truncated/distribution_overlay.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/mode-truncated/signal_quality_comparison.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/mode-truncated/variance_decomposition.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/temporal/concentration_curve.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-spread-analysis/visualizations/temporal/percentile_analysis.png +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/01-methodology.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/02-baseline-sep2024.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/03-multiperiod-validation.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/04-discoveries-and-plan-evolution.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/findings/baseline-sep2024-comprehensive-summary.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/findings/phase2-mean-reversion-report.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/findings/phase3-volatility-model-report.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/findings/phase4-5-statistical-tests-survivorship-report.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/baseline-sep2024/01_mean_reversion_analysis.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/baseline-sep2024/02_volatility_model_simple.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/baseline-sep2024/03_liquidity_crisis_detection.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/baseline-sep2024/04_regime_detection_analysis.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase1_data_validation.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase2_mean_reversion.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase3_volatility_model.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase4_statistical_tests.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase5_survivorship_bias.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase5_survivorship_bias_v1.0.1.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase6a_interval_analysis.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase6b_price_clustering.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/multiperiod-validation/phase6c_combined_pattern.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/reproduction_guide.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/visualization/diagnostic_burst_decay.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/visualization/generate_interactive_dashboard.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/visualization/generate_lightweight_dashboard.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/docs/research/eurusd-zero-spread-deviations/scripts/visualization/prepare_visualization_data.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/examples/basic_usage.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/moon.yml +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/package-lock.json +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/package.json +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/clickhouse-start.sh +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/clickhouse-stop.sh +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/e2e-ephemeral.sh +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/generate_dbeaver_config.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/publish-release-notes.sh +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/smoke_install.sh +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/__init__.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_base.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_client.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_gap_detector.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/clickhouse_query_engine.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/downloader.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/exchanges.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/models.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/processor.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/session_detector.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/tick_loader.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/README.md +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/__init__.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/conftest.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_basic.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_database_bootstrap_order.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_date_bounds.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_functional_regression.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_models.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_package_structure.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_processor_pydantic.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_tick_seq.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_types.py +0 -0
- {exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/uv.toml +0 -0
|
@@ -0,0 +1 @@
|
|
|
1
|
+
3.15
|
|
@@ -1,3 +1,10 @@
|
|
|
1
|
+
## [3.0.1](https://github.com/terrylica/exness-data-preprocess/compare/v3.0.0...v3.0.1) (2026-10-19)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Bug Fixes
|
|
5
|
+
|
|
6
|
+
* **ohlc:** compute tick_count_standard and standard_spread_avg per minute from standard_ticks ([a26ec2c](https://github.com/terrylica/exness-data-preprocess/commit/a26ec2cca0e8e2b326c8b5c6f93f2c9f2a658986)), closes [#25](https://github.com/terrylica/exness-data-preprocess/issues/25)
|
|
7
|
+
|
|
1
8
|
# [3.0.0](https://github.com/terrylica/exness-data-preprocess/compare/v2.1.1...v3.0.0) (2026-10-19)
|
|
2
9
|
|
|
3
10
|
|
|
@@ -16,7 +16,7 @@ moon run repo:build # sdist + wheel into dist/
|
|
|
16
16
|
|
|
17
17
|
## Toolchain
|
|
18
18
|
|
|
19
|
-
- proto pins uv (`.prototools`); uv owns Python. `.python-version
|
|
19
|
+
- proto pins uv (`latest`) and moon (`.prototools`); uv owns Python. Development runs on the latest CPython (`.python-version`, currently 3.15); the published floor stays `requires-python >=3.11`, and ruff/mypy target 3.11 so code keeps working there. Never export `UV_PYTHON`: an env var outranks `.python-version`.
|
|
20
20
|
- Releases: write `docs/release-notes/<next>.md` (narrative paragraph + Keep a Changelog bullets, one line each) in the PR before releasing; semantic-release (`npm run release`) refuses to release without it, runs `uv version <next> --frozen`, and `scripts/publish-release-notes.sh` rebuilds the GitHub Release body as narrative + generated section. pyproject.toml is the only place the version lives; `__version__` reads installed metadata. Publishing is `.github/workflows/publish.yml` (PyPI Trusted Publishing on a GitHub Release), the only publish path.
|
|
21
21
|
- Dependency update PRs come from `.github/dependabot.yml`. No other CI runs in Actions; gates are local.
|
|
22
22
|
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: exness-data-preprocess
|
|
3
|
-
Version: 3.0.
|
|
3
|
+
Version: 3.0.1
|
|
4
4
|
Summary: Professional Exness forex tick data preprocessing with ClickHouse backend. Provides efficient storage with lossless precision and direct queryability.
|
|
5
5
|
Project-URL: Homepage, https://github.com/terrylica/exness-data-preprocess
|
|
6
6
|
Project-URL: Documentation, https://github.com/terrylica/exness-data-preprocess#readme
|
|
@@ -22,6 +22,7 @@ Classifier: Programming Language :: Python :: 3
|
|
|
22
22
|
Classifier: Programming Language :: Python :: 3.11
|
|
23
23
|
Classifier: Programming Language :: Python :: 3.12
|
|
24
24
|
Classifier: Programming Language :: Python :: 3.13
|
|
25
|
+
Classifier: Programming Language :: Python :: 3.15
|
|
25
26
|
Classifier: Topic :: Office/Business :: Financial :: Investment
|
|
26
27
|
Classifier: Topic :: Scientific/Engineering :: Information Analysis
|
|
27
28
|
Classifier: Topic :: Software Development :: Libraries :: Python Modules
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
## 3.0.1 (2026-10-19)
|
|
2
|
+
|
|
3
|
+
Two `ohlc_1m` columns did not measure what their documentation says. `tick_count_standard` is documented as the number of Standard ticks in the bar, but it always equalled the Raw_Spread count: it was computed as `countIf(... IS NOT NULL)` over an ASOF join that never produces NULLs. `standard_spread_avg` is documented as the average Standard spread in the bar, but it averaged whichever Standard quote preceded each Raw_Spread tick, and the choice among tied quotes could change between rebuilds. Each variant is now aggregated per minute on its own and the two are joined on the minute, so both columns match their definitions, rebuilds are deterministic, and the build no longer runs a per-tick join. On the E2E months the counts now differ on 64,889 of 78,658 bars, where before they were identical on every bar.
|
|
4
|
+
|
|
5
|
+
### Upgrading
|
|
6
|
+
|
|
7
|
+
- Bars already stored keep the old values until they are rebuilt. Rebuild an instrument's bars with `update_data(pair, start_date=..., force_redownload=True)`, which re-downloads and regenerates from `start_date`, or call `ExnessDataProcessor().ch_ohlc_generator.regenerate_ohlc(pair)` to rebuild every bar from the stored ticks.
|
|
8
|
+
|
|
9
|
+
### Fixed
|
|
10
|
+
|
|
11
|
+
- `tick_count_standard` counts the Standard ticks in the bar's minute (0 when there are none) (#25).
|
|
12
|
+
- `standard_spread_avg` is the mean Standard `ask - bid` in the bar's minute (NULL when there are none), deterministic across rebuilds (#25).
|
|
13
|
+
- `repo:e2e` checks that `sum(tick_count_raw_spread)` equals the stored Raw_Spread ticks and `sum(tick_count_standard)` equals the Standard ticks in bar minutes, that the two counts differ, and that a sampled `standard_spread_avg` equals a direct per-minute average (#25).
|
|
14
|
+
- Stale docstrings describing the v1 30-column schema, normalized metrics and the ASOF join are corrected (#25).
|
|
15
|
+
|
|
16
|
+
### Changed
|
|
17
|
+
|
|
18
|
+
- Development runs on Python 3.15.0 and moon 2.6.2; the supported range is unchanged (`requires-python >=3.11`) (#27, #28).
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
[project]
|
|
2
2
|
name = "exness-data-preprocess"
|
|
3
|
-
version = "3.0.
|
|
3
|
+
version = "3.0.1"
|
|
4
4
|
description = "Professional Exness forex tick data preprocessing with ClickHouse backend. Provides efficient storage with lossless precision and direct queryability."
|
|
5
5
|
readme = { file = "README.md", content-type = "text/markdown" }
|
|
6
6
|
authors = [
|
|
@@ -26,6 +26,7 @@ classifiers = [
|
|
|
26
26
|
"Programming Language :: Python :: 3.11",
|
|
27
27
|
"Programming Language :: Python :: 3.12",
|
|
28
28
|
"Programming Language :: Python :: 3.13",
|
|
29
|
+
"Programming Language :: Python :: 3.15",
|
|
29
30
|
"Topic :: Office/Business :: Financial :: Investment",
|
|
30
31
|
"Topic :: Scientific/Engineering :: Information Analysis",
|
|
31
32
|
"Topic :: Software Development :: Libraries :: Python Modules",
|
|
@@ -81,10 +82,6 @@ ignore = [
|
|
|
81
82
|
[tool.ruff.lint.isort]
|
|
82
83
|
known-first-party = ["exness_data_preprocess"]
|
|
83
84
|
|
|
84
|
-
[tool.black]
|
|
85
|
-
target-version = ['py311']
|
|
86
|
-
line-length = 100
|
|
87
|
-
|
|
88
85
|
[tool.mypy]
|
|
89
86
|
python_version = "3.11"
|
|
90
87
|
warn_return_any = true
|
|
@@ -97,10 +94,6 @@ no_implicit_optional = true
|
|
|
97
94
|
strict_optional = true
|
|
98
95
|
ignore_missing_imports = true
|
|
99
96
|
|
|
100
|
-
[[tool.mypy.overrides]]
|
|
101
|
-
module = "tests.*"
|
|
102
|
-
ignore_errors = true
|
|
103
|
-
|
|
104
97
|
[tool.pytest.ini_options]
|
|
105
98
|
minversion = "7.0"
|
|
106
99
|
addopts = "-ra -q --strict-markers"
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/validate_clickhouse.py
RENAMED
|
@@ -233,6 +233,51 @@ def check_ties() -> None:
|
|
|
233
233
|
print("✅ Tie pagination passed")
|
|
234
234
|
|
|
235
235
|
|
|
236
|
+
def check_ohlc_counts() -> None:
|
|
237
|
+
"""ohlc_1m tick counts and Standard spread match their definitions (per minute, per variant)."""
|
|
238
|
+
print("🔍 Validating OHLC tick counts and Standard spread...")
|
|
239
|
+
from exness_data_preprocess import get_clickhouse_client
|
|
240
|
+
|
|
241
|
+
client = get_clickhouse_client()
|
|
242
|
+
try:
|
|
243
|
+
sum_std, sum_raw, differing = client.query(
|
|
244
|
+
"SELECT sum(tick_count_standard), sum(tick_count_raw_spread),"
|
|
245
|
+
" countIf(tick_count_standard != tick_count_raw_spread)"
|
|
246
|
+
" FROM exness.ohlc_1m FINAL WHERE instrument = 'EURUSD'"
|
|
247
|
+
).first_row
|
|
248
|
+
raw_total = client.query(
|
|
249
|
+
"SELECT count() FROM exness.raw_spread_ticks FINAL WHERE instrument = 'EURUSD'"
|
|
250
|
+
).first_row[0]
|
|
251
|
+
std_in_bars = client.query(
|
|
252
|
+
"SELECT count() FROM exness.standard_ticks FINAL WHERE instrument = 'EURUSD'"
|
|
253
|
+
" AND toStartOfMinute(timestamp) IN"
|
|
254
|
+
" (SELECT timestamp FROM exness.ohlc_1m FINAL WHERE instrument = 'EURUSD')"
|
|
255
|
+
).first_row[0]
|
|
256
|
+
minute, bar_avg, direct_avg = client.query(
|
|
257
|
+
"SELECT o.timestamp, o.standard_spread_avg, d.avg_spread FROM exness.ohlc_1m AS o FINAL"
|
|
258
|
+
" INNER JOIN (SELECT toStartOfMinute(timestamp) AS m, avg(ask - bid) AS avg_spread, count() AS n"
|
|
259
|
+
" FROM exness.standard_ticks FINAL WHERE instrument = 'EURUSD' GROUP BY m"
|
|
260
|
+
" ORDER BY n DESC, m LIMIT 1) AS d ON o.timestamp = d.m WHERE o.instrument = 'EURUSD'"
|
|
261
|
+
).first_row
|
|
262
|
+
finally:
|
|
263
|
+
client.close()
|
|
264
|
+
assert sum_raw == raw_total, (
|
|
265
|
+
f"sum(tick_count_raw_spread)={sum_raw:,} != stored Raw_Spread ticks {raw_total:,}"
|
|
266
|
+
)
|
|
267
|
+
assert sum_std == std_in_bars, (
|
|
268
|
+
f"sum(tick_count_standard)={sum_std:,} != Standard ticks in bar minutes {std_in_bars:,}"
|
|
269
|
+
)
|
|
270
|
+
assert differing > 0, "tick_count_standard equals tick_count_raw_spread on every bar"
|
|
271
|
+
assert abs(bar_avg - direct_avg) < 1e-12, (
|
|
272
|
+
f"standard_spread_avg {bar_avg} != direct {direct_avg} at {minute}"
|
|
273
|
+
)
|
|
274
|
+
print(
|
|
275
|
+
f" ✓ sum(tick_count_raw_spread)={sum_raw:,} = stored; sum(tick_count_standard)={sum_std:,} ="
|
|
276
|
+
f" Standard ticks in bar minutes; counts differ on {differing:,} bars; spread avg exact at {minute}"
|
|
277
|
+
)
|
|
278
|
+
print("✅ OHLC count validation passed")
|
|
279
|
+
|
|
280
|
+
|
|
236
281
|
def check_data() -> None:
|
|
237
282
|
print("🔍 Validating ClickHouse data operations...")
|
|
238
283
|
from exness_data_preprocess import ClickHouseQueryEngine
|
|
@@ -318,6 +363,7 @@ CHECKS: dict[str, Callable[[], None]] = {
|
|
|
318
363
|
"pagination": check_pagination,
|
|
319
364
|
"ties": check_ties,
|
|
320
365
|
"ohlc": check_ohlc,
|
|
366
|
+
"ohlc_counts": check_ohlc_counts,
|
|
321
367
|
}
|
|
322
368
|
|
|
323
369
|
|
|
@@ -242,7 +242,7 @@ class ClickHouseManager(ClickHouseClientMixin):
|
|
|
242
242
|
"standard_ticks",
|
|
243
243
|
"Standard tick data from Exness. Columns: instrument (forex pair), timestamp "
|
|
244
244
|
"(microsecond), seq (position among same-timestamp ticks in source order), bid/ask "
|
|
245
|
-
"(traditional quotes, Bid < Ask).
|
|
245
|
+
"(traditional quotes, Bid < Ask). Aggregated per minute into ohlc_1m.",
|
|
246
246
|
)
|
|
247
247
|
|
|
248
248
|
def _create_ticks_table(self, table: str, comment: str) -> None:
|
|
@@ -5,15 +5,14 @@ ADR: /docs/adr/2025-12-09-exness-clickhouse-migration.md
|
|
|
5
5
|
|
|
6
6
|
SLOs:
|
|
7
7
|
- Availability: OHLC generation must succeed or raise exception (no silent failures)
|
|
8
|
-
- Correctness:
|
|
8
|
+
- Correctness: 27-column ohlc_1m with dual-variant spreads and tick counts, holiday/session detection
|
|
9
9
|
- Observability: All OHLC operations logged via structured logging
|
|
10
10
|
- Maintainability: Single module for all OHLC logic, off-the-shelf ClickHouse and exchange_calendars
|
|
11
11
|
|
|
12
12
|
Handles:
|
|
13
|
-
-
|
|
13
|
+
- 27-column OHLC schema (ohlc_1m)
|
|
14
14
|
- BID-only OHLC construction from raw_spread_ticks
|
|
15
|
-
-
|
|
16
|
-
- Normalized metrics (range_per_spread, range_per_tick, body_per_spread, body_per_tick)
|
|
15
|
+
- Per-minute Standard spread and tick count from standard_ticks, joined on the minute
|
|
17
16
|
- Timezone/session tracking (NY and London sessions)
|
|
18
17
|
- Holiday detection (US, UK, major holidays via exchange_calendars)
|
|
19
18
|
- 10 global exchange session flags with trading hour detection
|
|
@@ -33,12 +32,11 @@ from exness_data_preprocess.session_detector import SessionDetector
|
|
|
33
32
|
|
|
34
33
|
class ClickHouseOHLCGenerator(ClickHouseClientMixin):
|
|
35
34
|
"""
|
|
36
|
-
Generate
|
|
35
|
+
Generate 27-column 1-minute OHLC from dual-variant tick data in ClickHouse.
|
|
37
36
|
|
|
38
37
|
Responsibilities:
|
|
39
38
|
- Delete existing OHLC data before regeneration (by instrument)
|
|
40
|
-
- Generate BID-only OHLC from raw_spread_ticks
|
|
41
|
-
- Calculate normalized spread metrics (NULL-safe)
|
|
39
|
+
- Generate BID-only OHLC from raw_spread_ticks plus per-minute Standard aggregates
|
|
42
40
|
- Add timezone/session tracking for NY and London
|
|
43
41
|
- Delegate holiday and session detection to SessionDetector
|
|
44
42
|
- Insert OHLC data with holiday and session flags
|
|
@@ -69,11 +67,12 @@ class ClickHouseOHLCGenerator(ClickHouseClientMixin):
|
|
|
69
67
|
end_date: str | None = None,
|
|
70
68
|
) -> int:
|
|
71
69
|
"""
|
|
72
|
-
Generate/update
|
|
70
|
+
Generate/update ohlc_1m (27 columns).
|
|
73
71
|
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
72
|
+
Raw_Spread and Standard ticks are each aggregated per minute and joined on the minute:
|
|
73
|
+
standard_spread_avg and tick_count_standard describe the Standard ticks in that minute
|
|
74
|
+
(NULL / 0 when there are none). Includes timezone/session tracking, holiday detection,
|
|
75
|
+
and 10 global exchange session flags.
|
|
77
76
|
|
|
78
77
|
Supports three operation modes:
|
|
79
78
|
1. Full regeneration (start_date=None, end_date=None): DELETE all + INSERT all
|
|
@@ -143,44 +142,42 @@ class ClickHouseOHLCGenerator(ClickHouseClientMixin):
|
|
|
143
142
|
)
|
|
144
143
|
|
|
145
144
|
where_sql = " AND ".join(where_parts)
|
|
146
|
-
standard_where = ["instrument = {instrument:String}"]
|
|
147
|
-
if start_date:
|
|
148
|
-
standard_where.append("timestamp >= {start_date:DateTime64(6, 'UTC')} - INTERVAL 7 DAY")
|
|
149
|
-
standard_where_sql = " AND ".join(standard_where)
|
|
150
145
|
|
|
151
146
|
# Generate session column initializations from exchange registry
|
|
152
147
|
session_columns = ", ".join([f"0 AS is_{name}_session" for name in EXCHANGES.keys()])
|
|
153
148
|
|
|
154
|
-
#
|
|
155
|
-
#
|
|
149
|
+
# Each variant is aggregated per minute on its own and the two are joined on the minute.
|
|
150
|
+
# Bars exist for minutes with Raw_Spread ticks; standard_spread_avg / tick_count_standard
|
|
151
|
+
# describe the Standard ticks inside the same minute (NULL / 0 when there are none).
|
|
152
|
+
# Both subqueries apply the same instrument and date filter (alias r in each).
|
|
156
153
|
insert_sql = f"""
|
|
157
154
|
INSERT INTO {self.DATABASE}.ohlc_1m
|
|
158
155
|
SELECT
|
|
159
156
|
{{instrument:String}} AS instrument,
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
157
|
+
r.minute AS timestamp,
|
|
158
|
+
r.open AS open,
|
|
159
|
+
r.high AS high,
|
|
160
|
+
r.low AS low,
|
|
161
|
+
r.close AS close,
|
|
162
|
+
r.raw_spread_avg AS raw_spread_avg,
|
|
163
|
+
s.standard_spread_avg AS standard_spread_avg,
|
|
164
|
+
r.tick_count_raw_spread AS tick_count_raw_spread,
|
|
165
|
+
ifNull(s.tick_count_standard, toUInt32(0)) AS tick_count_standard,
|
|
169
166
|
-- Timezone-aware session columns
|
|
170
|
-
toUInt8(toHour(toTimezone(
|
|
171
|
-
toUInt8(toHour(toTimezone(
|
|
167
|
+
toUInt8(toHour(toTimezone(r.minute, 'America/New_York'))) AS ny_hour,
|
|
168
|
+
toUInt8(toHour(toTimezone(r.minute, 'Europe/London'))) AS london_hour,
|
|
172
169
|
multiIf(
|
|
173
|
-
toHour(toTimezone(
|
|
174
|
-
AND toHour(toTimezone(
|
|
170
|
+
toHour(toTimezone(r.minute, 'America/New_York')) >= 9
|
|
171
|
+
AND toHour(toTimezone(r.minute, 'America/New_York')) <= 16,
|
|
175
172
|
'NY_Session',
|
|
176
|
-
toHour(toTimezone(
|
|
177
|
-
AND toHour(toTimezone(
|
|
173
|
+
toHour(toTimezone(r.minute, 'America/New_York')) >= 17
|
|
174
|
+
AND toHour(toTimezone(r.minute, 'America/New_York')) <= 20,
|
|
178
175
|
'NY_After_Hours',
|
|
179
176
|
'NY_Closed'
|
|
180
177
|
) AS ny_session,
|
|
181
178
|
if(
|
|
182
|
-
toHour(toTimezone(
|
|
183
|
-
AND toHour(toTimezone(
|
|
179
|
+
toHour(toTimezone(r.minute, 'Europe/London')) >= 8
|
|
180
|
+
AND toHour(toTimezone(r.minute, 'Europe/London')) <= 16,
|
|
184
181
|
'London_Session',
|
|
185
182
|
'London_Closed'
|
|
186
183
|
) AS london_session,
|
|
@@ -190,20 +187,30 @@ class ClickHouseOHLCGenerator(ClickHouseClientMixin):
|
|
|
190
187
|
toUInt8(0) AS is_major_holiday,
|
|
191
188
|
-- Trading session flags (initialized to 0, updated via session_detector)
|
|
192
189
|
{session_columns}
|
|
193
|
-
FROM
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
|
|
200
|
-
|
|
201
|
-
|
|
202
|
-
|
|
203
|
-
|
|
204
|
-
|
|
205
|
-
|
|
190
|
+
FROM (
|
|
191
|
+
SELECT
|
|
192
|
+
toStartOfMinute(r.timestamp) AS minute,
|
|
193
|
+
argMin(r.bid, (r.timestamp, r.seq)) AS open,
|
|
194
|
+
max(r.bid) AS high,
|
|
195
|
+
min(r.bid) AS low,
|
|
196
|
+
argMax(r.bid, (r.timestamp, r.seq)) AS close,
|
|
197
|
+
avg(r.ask - r.bid) AS raw_spread_avg,
|
|
198
|
+
toUInt32(count()) AS tick_count_raw_spread
|
|
199
|
+
FROM {self.DATABASE}.raw_spread_ticks AS r FINAL
|
|
200
|
+
WHERE {where_sql}
|
|
201
|
+
GROUP BY minute
|
|
202
|
+
) AS r
|
|
203
|
+
LEFT JOIN (
|
|
204
|
+
SELECT
|
|
205
|
+
toStartOfMinute(r.timestamp) AS minute,
|
|
206
|
+
avg(r.ask - r.bid) AS standard_spread_avg,
|
|
207
|
+
toUInt32(count()) AS tick_count_standard
|
|
208
|
+
FROM {self.DATABASE}.standard_ticks AS r FINAL
|
|
209
|
+
WHERE {where_sql}
|
|
210
|
+
GROUP BY minute
|
|
211
|
+
) AS s ON r.minute = s.minute
|
|
206
212
|
ORDER BY timestamp
|
|
213
|
+
SETTINGS join_use_nulls = 1
|
|
207
214
|
"""
|
|
208
215
|
|
|
209
216
|
execute_command(self.client, insert_sql, parameters=params)
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
3.11
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/generate_dbeaver_config.py
RENAMED
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/scripts/publish-release-notes.sh
RENAMED
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/__init__.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/src/exness_data_preprocess/models.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_database_bootstrap_order.py
RENAMED
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_functional_regression.py
RENAMED
|
File without changes
|
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_package_structure.py
RENAMED
|
File without changes
|
{exness_data_preprocess-3.0.0 → exness_data_preprocess-3.0.1}/tests/test_processor_pydantic.py
RENAMED
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|