rangebar 11.6.1__tar.gz → 12.0.0__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.
- {rangebar-11.6.1 → rangebar-12.0.0}/.mise.toml +36 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/CHANGELOG.md +32 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/Cargo.lock +9 -9
- {rangebar-11.6.1 → rangebar-12.0.0}/Cargo.toml +2 -2
- {rangebar-11.6.1 → rangebar-12.0.0}/PKG-INFO +1 -1
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/checkpoint.rs +8 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/drawdown.rs +174 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/features.rs +729 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/ith.rs +460 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/mod.rs +38 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/normalize.rs +233 -0
- rangebar-12.0.0/crates/rangebar-core/src/intrabar/types.rs +47 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/lib.rs +3 -1
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/processor.rs +189 -10
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/test_utils/mod.rs +23 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/types.rs +155 -0
- rangebar-12.0.0/docs/research/2026-02-03-cfm-optimal-threshold-gemini-3-pro.md +510 -0
- rangebar-12.0.0/docs/verification/issue-62-verification-report.md +285 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/__init__.py +20 -2
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/constants.py +52 -1
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/exness.py +13 -4
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/count_bounded.py +3 -23
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/precompute.py +4 -7
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/range_bars.py +3 -20
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/processors/api.py +18 -3
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/processors/core.py +22 -1
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/streaming.py +20 -1
- rangebar-12.0.0/python/rangebar/threshold.py +453 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/gap_classification.py +2 -1
- rangebar-12.0.0/scripts/purge_crypto_low_threshold.py +116 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/src/lib.rs +105 -1
- rangebar-12.0.0/tests/conftest.py +115 -0
- rangebar-12.0.0/tests/test_crypto_minimum_threshold.py +306 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_edge_cases.py +4 -3
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_get_n_range_bars.py +22 -3
- rangebar-11.6.1/tests/conftest.py +0 -50
- {rangebar-11.6.1 → rangebar-12.0.0}/.gitignore +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/.mcp.json +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/.pre-commit-config.yaml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/.releaserc.yml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/CLAUDE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/LICENSE +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/RESUME.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/STATISTICAL_VALIDITY_AUDIT.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/benches/rangebar_bench.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/build.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/CLAUDE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/CHANGELOG.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/Cargo.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/arrow_export.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/fixed_point.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/interbar.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/test_data_loader.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/test_utils/generators.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/src/timestamp.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/tests/cross_boundary_validation.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/tests/cross_date_real_data_validation.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/tests/cross_year_boundary_test.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-core/tests/incomplete_bar_continuation_proof.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/CHANGELOG.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/Cargo.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/binance/checksum.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/binance/historical.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/binance/mod.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/binance/symbols.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/binance/websocket.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/exness/builder.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/exness/client.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/exness/conversion.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/exness/mod.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/exness/types.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-providers/src/lib.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/CHANGELOG.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/Cargo.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/indicators.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/lib.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/processor.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/replay_buffer.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/stats.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/crates/rangebar-streaming/src/universal.rs +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/deny.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/ARCHITECTURE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/CONTEXT.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/MEMORY_REMEDIATION_PLAN.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/adr/2026-01-31-realtime-streaming-api.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/analysis/2025-10-10-flash-crash.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/api.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/development/PERFORMANCE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/development/RELEASE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/migration-v8.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/plans/issue-59-inter-bar-features.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/rangebar_core_api.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/2026-02-02-intrabar-microstructure-claude-opus.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/2026-02-02-intrabar-microstructure-gemini-3-pro.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/INDEX.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/adversarial-audit-methodology.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/external/time-to-convergence-stationarity-gap.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/labeling-for-ml.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/market-regime-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/multi-threshold-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/multifactor-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/pattern-research-summary.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/price-action-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/tda-parameter-sensitivity-audit.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/tda-regime-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/docs/research/volatility-regime-patterns.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/backtesting_integration.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/basic_usage.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/binance_csv_example.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/get_range_bars_example.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/validate_output.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/examples/with_clickhouse_cache.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/package-lock.json +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/package.json +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/pyproject.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/CLAUDE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/__init__.pyi +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/checkpoint.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/cli.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/CLAUDE.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/bulk_operations.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/cache.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/client.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/config.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/mixin.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/preflight.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/query_operations.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/schema.sql +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/clickhouse/tunnel.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/conversion.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/exceptions.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/hooks.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/logging.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/notify/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/notify/pushover.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/notify/telegram.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/helpers.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/models.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/orchestration/tick_fetcher.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/ouroboros.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/processors/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/resource_guard.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/storage/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/storage/checksum_registry.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/storage/parquet.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/__init__.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/cache_staleness.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/continuity.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/post_storage.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/tier1.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/python/rangebar/validation/tier2.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/rust-toolchain.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/rustfmt.toml +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/adwin_regime_detection_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/analyze_flash_crash.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/bootstrap_permutation_validation_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/build-release.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/cache_clear.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/cache_status.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/check-release-config.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/coarse_to_fine_cascade.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/combined_pattern_audit_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/combined_regime_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/combined_rv_alignment_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/cross_asset_correlation_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/cross_regime_correlation_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/cross_threshold_alignment.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/dependency_monitor.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/direction_patterns_reaudit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/download_exness_eurusd.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/download_exness_forex.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/duration_autocorrelation.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/duration_autocorrelation_audit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/duration_volatility_prediction.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/fdr_corrected_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/fill_all_symbols.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/fill_gaps_2025_2026.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/fill_gaps_littleblack.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/fill_gaps_retry.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/historical_formation_patterns_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/historical_formation_regime_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/hurst_adjusted_kelly_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/hurst_adjusted_psr_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/hurst_exponent_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/hurst_multi_estimator_audit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/microstructure_clickhouse.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/microstructure_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/microstructure_simple.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/monitor_population.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multi_threshold_pattern_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multibar_continuation.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multibar_forward_returns_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multifactor_multigranularity_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multifactor_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multifactor_patterns_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multithreshold_combinations_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/multithreshold_regime_combinations_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/oos_validation_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/parameter_sensitivity_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/pattern_correlation_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/pattern_return_profiles_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/pattern_return_stats.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/populate_1000dbps.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/populate_250dbps.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/populate_250dbps_extended.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/populate_safe.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/position_sizing_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/process_exness_eurusd_to_cache.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/profiling_tools.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/psr_mintrl_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/publish-to-pypi.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/publish-wheels.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/regenerate_cache.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/regime_analysis.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/regime_analysis_50dbps_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/regime_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/regime_transition_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/run_large_scale_benchmark.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/run_length_momentum_analysis.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/run_length_momentum_multi_symbol.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/run_length_momentum_wfo.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/rv_return_profile_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/semantic-release.sh +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_break_event_alignment_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_conditioned_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_cupy_accelerated.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_gpu_analysis.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_hurst_by_regime_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_parameter_sweep_audit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_regime_hurst_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_regime_pattern_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_ripser_plusplus.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_rolling_threshold.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_solusdt_250_audit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_solusdt_250_littleblack.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_structural_break_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_volatility_forecast.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_wfo_3way.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_wfo_abstain.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/tda_wfo_audit.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/temporal_safe_patterns_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/three_bar_alignment_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/three_bar_pattern_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/three_bar_pattern_audit_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/three_factor_hurst_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/three_factor_pattern_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/transaction_cost_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/trend_filter_analysis.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/upload_eurusd_to_clickhouse.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/validate_clickhouse.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/validate_memory_efficiency.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/validate_microstructure_features.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/validate_n_range_bars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/volatility_regime_analysis_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/volatility_regime_audit_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/volume_conditioned_patterns.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/scripts/volume_conditioned_patterns_polars.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/test_data/BTCUSDT/BTCUSDT_aggTrades_20250901.csv +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/test_data/ETHUSDT/ETHUSDT_aggTrades_20250901.csv +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/test_data/README.md +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/fixtures/.gitignore +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/fixtures/BTCUSDT-aggTrades-2024-01-01.zip +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_cache_schema_evolution.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_clickhouse.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_clickhouse_integration.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_e2e_optimized.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_examples.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_get_range_bars_e2e.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_issue_5_reproduction.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_issues_7_8.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_microstructure_features.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_ouroboros.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_polars_only_downstream.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_preflight.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_python_api.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_real_data.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_rust_bindings.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_storage.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_streaming.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/tests/test_validation_presets.py +0 -0
- {rangebar-11.6.1 → rangebar-12.0.0}/uv.lock +0 -0
|
@@ -1,4 +1,40 @@
|
|
|
1
1
|
[env]
|
|
2
|
+
# =============================================================================
|
|
3
|
+
# SSoT: Minimum Thresholds by Asset Class (Issue #62)
|
|
4
|
+
# =============================================================================
|
|
5
|
+
# These are the canonical values for threshold validation. Change here, affects
|
|
6
|
+
# all entry points (Python API + Rust core).
|
|
7
|
+
#
|
|
8
|
+
# Values are in decimal basis points (dbps): 1000 dbps = 1%
|
|
9
|
+
# Research: docs/research/2026-02-03-cfm-optimal-threshold-gemini-3-pro.md
|
|
10
|
+
#
|
|
11
|
+
# Resolution order (highest priority first):
|
|
12
|
+
# 1. Per-symbol: RANGEBAR_MIN_THRESHOLD_<SYMBOL>
|
|
13
|
+
# 2. Asset-class: RANGEBAR_<ASSET_CLASS>_MIN_THRESHOLD
|
|
14
|
+
# 3. Fallback defaults in code (with warning)
|
|
15
|
+
|
|
16
|
+
# Crypto: Default minimum (1% - cannot overcome trading costs below this)
|
|
17
|
+
# CFM formula validates this as "conservative guardrails"
|
|
18
|
+
RANGEBAR_CRYPTO_MIN_THRESHOLD = "1000"
|
|
19
|
+
|
|
20
|
+
# Forex: Default minimum (tighter spreads allow lower)
|
|
21
|
+
RANGEBAR_FOREX_MIN_THRESHOLD = "50"
|
|
22
|
+
|
|
23
|
+
# Equities: Default minimum
|
|
24
|
+
RANGEBAR_EQUITIES_MIN_THRESHOLD = "100"
|
|
25
|
+
|
|
26
|
+
# Unknown: Fallback (no enforcement)
|
|
27
|
+
RANGEBAR_UNKNOWN_MIN_THRESHOLD = "1"
|
|
28
|
+
|
|
29
|
+
# =============================================================================
|
|
30
|
+
# Per-Symbol Overrides (Optional - uncomment to enable)
|
|
31
|
+
# =============================================================================
|
|
32
|
+
# Override specific symbols when research justifies different thresholds:
|
|
33
|
+
#
|
|
34
|
+
# RANGEBAR_MIN_THRESHOLD_BTCUSDT = "1500" # BTC needs higher threshold
|
|
35
|
+
# RANGEBAR_MIN_THRESHOLD_XAUUSD = "200" # Gold needs higher than forex
|
|
36
|
+
# RANGEBAR_MIN_THRESHOLD_EURUSD = "25" # EUR/USD can go lower
|
|
37
|
+
|
|
2
38
|
# =============================================================================
|
|
3
39
|
# GitHub Account Isolation (CRITICAL for multi-account setups)
|
|
4
40
|
# =============================================================================
|
|
@@ -1,3 +1,35 @@
|
|
|
1
|
+
# [12.0.0](https://github.com/terrylica/rangebar-py/compare/v11.7.0...v12.0.0) (2026-02-03)
|
|
2
|
+
|
|
3
|
+
|
|
4
|
+
### Features
|
|
5
|
+
|
|
6
|
+
* **threshold:** enforce configurable minimum threshold for crypto (Issue [#64](https://github.com/terrylica/rangebar-py/issues/64)) ([4030c7d](https://github.com/terrylica/rangebar-py/commit/4030c7dc9e6789a4c9ae11f21c03261dc56e6c95))
|
|
7
|
+
|
|
8
|
+
|
|
9
|
+
### BREAKING CHANGES
|
|
10
|
+
|
|
11
|
+
* **threshold:** ThresholdError raised for crypto symbols with threshold < 1000 dbps
|
|
12
|
+
|
|
13
|
+
- Add threshold.py module with hierarchical SSoT configuration
|
|
14
|
+
- Per-symbol override: RANGEBAR_MIN_THRESHOLD_<SYMBOL>
|
|
15
|
+
- Asset-class default: RANGEBAR_<ASSET_CLASS>_MIN_THRESHOLD
|
|
16
|
+
- Default crypto minimum: 1000 dbps (1%) per CFM research
|
|
17
|
+
- Add validation to all Python entry points (21 locations)
|
|
18
|
+
- Add validation to Rust checkpoint restoration (lib.rs, processor.rs)
|
|
19
|
+
- Add NDJSON logging and Pushover alerts for violations
|
|
20
|
+
- Add cache purge script for invalidating low-threshold data
|
|
21
|
+
- Add 32 comprehensive tests for threshold enforcement
|
|
22
|
+
|
|
23
|
+
SRED-Type: applied-research
|
|
24
|
+
SRED-Claim: THRESHOLD
|
|
25
|
+
|
|
26
|
+
# [11.7.0](https://github.com/terrylica/rangebar-py/compare/v11.6.1...v11.7.0) (2026-02-02)
|
|
27
|
+
|
|
28
|
+
|
|
29
|
+
### Features
|
|
30
|
+
|
|
31
|
+
* **intrabar:** add 22 intra-bar microstructure features (Issue [#59](https://github.com/terrylica/rangebar-py/issues/59)) ([4200631](https://github.com/terrylica/rangebar-py/commit/4200631576269ad90b957f6c250e07866d30c3e4))
|
|
32
|
+
|
|
1
33
|
## [11.6.1](https://github.com/terrylica/rangebar-py/compare/v11.6.0...v11.6.1) (2026-02-02)
|
|
2
34
|
|
|
3
35
|
|
|
@@ -3430,7 +3430,7 @@ dependencies = [
|
|
|
3430
3430
|
|
|
3431
3431
|
[[package]]
|
|
3432
3432
|
name = "rangebar"
|
|
3433
|
-
version = "
|
|
3433
|
+
version = "12.0.0"
|
|
3434
3434
|
dependencies = [
|
|
3435
3435
|
"chrono",
|
|
3436
3436
|
"insta",
|
|
@@ -3452,7 +3452,7 @@ dependencies = [
|
|
|
3452
3452
|
|
|
3453
3453
|
[[package]]
|
|
3454
3454
|
name = "rangebar-batch"
|
|
3455
|
-
version = "
|
|
3455
|
+
version = "12.0.0"
|
|
3456
3456
|
dependencies = [
|
|
3457
3457
|
"polars",
|
|
3458
3458
|
"rangebar-core",
|
|
@@ -3464,7 +3464,7 @@ dependencies = [
|
|
|
3464
3464
|
|
|
3465
3465
|
[[package]]
|
|
3466
3466
|
name = "rangebar-cli"
|
|
3467
|
-
version = "
|
|
3467
|
+
version = "12.0.0"
|
|
3468
3468
|
dependencies = [
|
|
3469
3469
|
"chrono",
|
|
3470
3470
|
"clap",
|
|
@@ -3489,7 +3489,7 @@ dependencies = [
|
|
|
3489
3489
|
|
|
3490
3490
|
[[package]]
|
|
3491
3491
|
name = "rangebar-config"
|
|
3492
|
-
version = "
|
|
3492
|
+
version = "12.0.0"
|
|
3493
3493
|
dependencies = [
|
|
3494
3494
|
"chrono",
|
|
3495
3495
|
"config",
|
|
@@ -3501,7 +3501,7 @@ dependencies = [
|
|
|
3501
3501
|
|
|
3502
3502
|
[[package]]
|
|
3503
3503
|
name = "rangebar-core"
|
|
3504
|
-
version = "
|
|
3504
|
+
version = "12.0.0"
|
|
3505
3505
|
dependencies = [
|
|
3506
3506
|
"ahash",
|
|
3507
3507
|
"arrow",
|
|
@@ -3518,7 +3518,7 @@ dependencies = [
|
|
|
3518
3518
|
|
|
3519
3519
|
[[package]]
|
|
3520
3520
|
name = "rangebar-io"
|
|
3521
|
-
version = "
|
|
3521
|
+
version = "12.0.0"
|
|
3522
3522
|
dependencies = [
|
|
3523
3523
|
"polars",
|
|
3524
3524
|
"rangebar-core",
|
|
@@ -3528,7 +3528,7 @@ dependencies = [
|
|
|
3528
3528
|
|
|
3529
3529
|
[[package]]
|
|
3530
3530
|
name = "rangebar-providers"
|
|
3531
|
-
version = "
|
|
3531
|
+
version = "12.0.0"
|
|
3532
3532
|
dependencies = [
|
|
3533
3533
|
"chrono",
|
|
3534
3534
|
"csv",
|
|
@@ -3548,7 +3548,7 @@ dependencies = [
|
|
|
3548
3548
|
|
|
3549
3549
|
[[package]]
|
|
3550
3550
|
name = "rangebar-py"
|
|
3551
|
-
version = "
|
|
3551
|
+
version = "12.0.0"
|
|
3552
3552
|
dependencies = [
|
|
3553
3553
|
"arrow",
|
|
3554
3554
|
"arrow-array",
|
|
@@ -3567,7 +3567,7 @@ dependencies = [
|
|
|
3567
3567
|
|
|
3568
3568
|
[[package]]
|
|
3569
3569
|
name = "rangebar-streaming"
|
|
3570
|
-
version = "
|
|
3570
|
+
version = "12.0.0"
|
|
3571
3571
|
dependencies = [
|
|
3572
3572
|
"async-trait",
|
|
3573
3573
|
"chrono",
|
|
@@ -10,7 +10,7 @@ resolver = "2"
|
|
|
10
10
|
[workspace.package]
|
|
11
11
|
# CRITICAL: Prevent crates.io publishing - this is a PyPI-only project
|
|
12
12
|
publish = false
|
|
13
|
-
version = "
|
|
13
|
+
version = "12.0.0"
|
|
14
14
|
authors = ["Terry Li <terry@eonlabs.com>"]
|
|
15
15
|
categories = ["algorithms", "data-structures", "finance"]
|
|
16
16
|
documentation = "https://docs.rs/rangebar"
|
|
@@ -97,7 +97,7 @@ criterion = { version = "0.5", features = ["html_reports"] }
|
|
|
97
97
|
|
|
98
98
|
[package]
|
|
99
99
|
name = "rangebar-py"
|
|
100
|
-
version = "
|
|
100
|
+
version = "12.0.0"
|
|
101
101
|
edition = "2021"
|
|
102
102
|
authors = ["Terry Li"]
|
|
103
103
|
license = "MIT"
|
|
@@ -243,6 +243,14 @@ pub enum CheckpointError {
|
|
|
243
243
|
/// Checkpoint serialization/deserialization error
|
|
244
244
|
#[error("Checkpoint serialization error: {message}")]
|
|
245
245
|
SerializationError { message: String },
|
|
246
|
+
|
|
247
|
+
/// Invalid threshold in checkpoint (Issue #62: crypto minimum threshold enforcement)
|
|
248
|
+
#[error("Invalid threshold in checkpoint: {threshold} dbps. Valid range: {min_threshold}-{max_threshold} dbps")]
|
|
249
|
+
InvalidThreshold {
|
|
250
|
+
threshold: u32,
|
|
251
|
+
min_threshold: u32,
|
|
252
|
+
max_threshold: u32,
|
|
253
|
+
},
|
|
246
254
|
}
|
|
247
255
|
|
|
248
256
|
/// Price window for computing position verification hash
|
|
@@ -0,0 +1,174 @@
|
|
|
1
|
+
//! Max drawdown and max runup computation for TMAEG calculation.
|
|
2
|
+
//!
|
|
3
|
+
//! Issue #59: Intra-bar microstructure features for large range bars.
|
|
4
|
+
//!
|
|
5
|
+
//! ORIGIN: trading-fitness/packages/metrics-rust/src/ith_rolling.rs:79-142
|
|
6
|
+
//! COPIED: 2026-02-02
|
|
7
|
+
//! MODIFICATIONS: Extracted as standalone functions for intra-bar use
|
|
8
|
+
//!
|
|
9
|
+
//! These functions compute the Maximum Drawdown and Maximum Runup which are used
|
|
10
|
+
//! as the TMAEG (Target Maximum Acceptable Excess Gain) threshold for ITH analysis.
|
|
11
|
+
//!
|
|
12
|
+
//! # Key Design Decision: TMAEG = Max Drawdown / Max Runup
|
|
13
|
+
//!
|
|
14
|
+
//! This approach is elegant because:
|
|
15
|
+
//! 1. TMAEG is derived directly from the window's own extremes
|
|
16
|
+
//! 2. No arbitrary parameters or percentile tuning
|
|
17
|
+
//! 3. Epochs trigger when gains exceed the maximum adverse movement
|
|
18
|
+
//! 4. Mathematically symmetric: drawdown ↔ runup
|
|
19
|
+
|
|
20
|
+
/// Compute Maximum Drawdown for Bull ITH TMAEG.
|
|
21
|
+
///
|
|
22
|
+
/// Maximum Drawdown = 1 - (trough / peak)
|
|
23
|
+
///
|
|
24
|
+
/// This is the simplest, most mathematically pure definition:
|
|
25
|
+
/// - An epoch triggers when excess gain exceeds the maximum adverse movement
|
|
26
|
+
/// - No arbitrary parameters, no percentile tuning
|
|
27
|
+
/// - TMAEG is derived directly from the window's own extremes
|
|
28
|
+
///
|
|
29
|
+
/// # Arguments
|
|
30
|
+
/// * `window` - Normalized price window (first value = 1.0 recommended)
|
|
31
|
+
///
|
|
32
|
+
/// # Returns
|
|
33
|
+
/// Maximum drawdown as a fraction [0, 1). Returns `f64::EPSILON` for windows < 2.
|
|
34
|
+
pub fn compute_max_drawdown(window: &[f64]) -> f64 {
|
|
35
|
+
if window.len() < 2 {
|
|
36
|
+
return f64::EPSILON;
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
let mut running_max = window[0];
|
|
40
|
+
let mut max_drawdown = 0.0;
|
|
41
|
+
|
|
42
|
+
for &val in window.iter().skip(1) {
|
|
43
|
+
if val > running_max {
|
|
44
|
+
running_max = val;
|
|
45
|
+
}
|
|
46
|
+
if running_max > 0.0 && val.is_finite() {
|
|
47
|
+
let drawdown = 1.0 - val / running_max;
|
|
48
|
+
if drawdown > max_drawdown {
|
|
49
|
+
max_drawdown = drawdown;
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
// Ensure a minimum threshold to avoid division issues
|
|
55
|
+
max_drawdown.max(f64::EPSILON)
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/// Compute Maximum Runup for Bear ITH TMAEG.
|
|
59
|
+
///
|
|
60
|
+
/// Maximum Runup = 1 - (trough / peak) where we track the inverse:
|
|
61
|
+
/// - Running minimum (trough)
|
|
62
|
+
/// - Then measure how much price rises from that trough
|
|
63
|
+
///
|
|
64
|
+
/// This is the symmetric counterpart to Maximum Drawdown:
|
|
65
|
+
/// - Drawdown: how much price falls from peak (adverse for longs)
|
|
66
|
+
/// - Runup: how much price rises from trough (adverse for shorts)
|
|
67
|
+
///
|
|
68
|
+
/// # Arguments
|
|
69
|
+
/// * `window` - Normalized price window (first value = 1.0 recommended)
|
|
70
|
+
///
|
|
71
|
+
/// # Returns
|
|
72
|
+
/// Maximum runup as a fraction [0, 1). Returns `f64::EPSILON` for windows < 2.
|
|
73
|
+
pub fn compute_max_runup(window: &[f64]) -> f64 {
|
|
74
|
+
if window.len() < 2 {
|
|
75
|
+
return f64::EPSILON;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
let mut running_min = window[0];
|
|
79
|
+
let mut max_runup = 0.0;
|
|
80
|
+
|
|
81
|
+
for &val in window.iter().skip(1) {
|
|
82
|
+
if val < running_min {
|
|
83
|
+
running_min = val;
|
|
84
|
+
}
|
|
85
|
+
if val > 0.0 && running_min > 0.0 && val.is_finite() {
|
|
86
|
+
// Runup = how much price has risen from the trough
|
|
87
|
+
// Formula: 1 - (trough / current) = (current - trough) / current
|
|
88
|
+
let runup = 1.0 - running_min / val;
|
|
89
|
+
if runup > max_runup {
|
|
90
|
+
max_runup = runup;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
// Ensure a minimum threshold to avoid division issues
|
|
96
|
+
max_runup.max(f64::EPSILON)
|
|
97
|
+
}
|
|
98
|
+
|
|
99
|
+
#[cfg(test)]
|
|
100
|
+
mod tests {
|
|
101
|
+
use super::*;
|
|
102
|
+
|
|
103
|
+
#[test]
|
|
104
|
+
fn test_max_drawdown_uptrend() {
|
|
105
|
+
// Pure uptrend should have zero drawdown
|
|
106
|
+
let prices = vec![1.0, 1.01, 1.02, 1.03, 1.04, 1.05];
|
|
107
|
+
let dd = compute_max_drawdown(&prices);
|
|
108
|
+
assert!(dd < 0.001, "Pure uptrend should have near-zero drawdown");
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
#[test]
|
|
112
|
+
fn test_max_drawdown_downtrend() {
|
|
113
|
+
// Decline from 1.0 to 0.8 = 20% drawdown
|
|
114
|
+
let prices = vec![1.0, 0.95, 0.9, 0.85, 0.8];
|
|
115
|
+
let dd = compute_max_drawdown(&prices);
|
|
116
|
+
assert!(
|
|
117
|
+
(dd - 0.2).abs() < 0.01,
|
|
118
|
+
"Expected 20% drawdown, got {}",
|
|
119
|
+
dd
|
|
120
|
+
);
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
#[test]
|
|
124
|
+
fn test_max_drawdown_recovery() {
|
|
125
|
+
// Peak at 1.1, trough at 0.9, max DD = 1 - 0.9/1.1 ≈ 18.2%
|
|
126
|
+
let prices = vec![1.0, 1.1, 1.0, 0.9, 1.0, 1.1];
|
|
127
|
+
let dd = compute_max_drawdown(&prices);
|
|
128
|
+
assert!(
|
|
129
|
+
(dd - 0.182).abs() < 0.01,
|
|
130
|
+
"Expected ~18.2% drawdown, got {}",
|
|
131
|
+
dd
|
|
132
|
+
);
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
#[test]
|
|
136
|
+
fn test_max_runup_downtrend() {
|
|
137
|
+
// Pure downtrend should have zero runup
|
|
138
|
+
let prices = vec![1.0, 0.99, 0.98, 0.97, 0.96, 0.95];
|
|
139
|
+
let ru = compute_max_runup(&prices);
|
|
140
|
+
assert!(ru < 0.001, "Pure downtrend should have near-zero runup");
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
#[test]
|
|
144
|
+
fn test_max_runup_uptrend() {
|
|
145
|
+
// Rise from 1.0 to 1.25 = 1 - 1.0/1.25 = 20% runup
|
|
146
|
+
let prices = vec![1.0, 1.05, 1.1, 1.15, 1.2, 1.25];
|
|
147
|
+
let ru = compute_max_runup(&prices);
|
|
148
|
+
assert!((ru - 0.2).abs() < 0.01, "Expected 20% runup, got {}", ru);
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
#[test]
|
|
152
|
+
fn test_max_runup_recovery() {
|
|
153
|
+
// Trough at 0.9, peak at 1.1, max RU = 1 - 0.9/1.1 ≈ 18.2%
|
|
154
|
+
let prices = vec![1.0, 0.9, 0.95, 1.0, 1.05, 1.1];
|
|
155
|
+
let ru = compute_max_runup(&prices);
|
|
156
|
+
assert!(
|
|
157
|
+
(ru - 0.182).abs() < 0.01,
|
|
158
|
+
"Expected ~18.2% runup, got {}",
|
|
159
|
+
ru
|
|
160
|
+
);
|
|
161
|
+
}
|
|
162
|
+
|
|
163
|
+
#[test]
|
|
164
|
+
fn test_empty_window() {
|
|
165
|
+
assert_eq!(compute_max_drawdown(&[]), f64::EPSILON);
|
|
166
|
+
assert_eq!(compute_max_runup(&[]), f64::EPSILON);
|
|
167
|
+
}
|
|
168
|
+
|
|
169
|
+
#[test]
|
|
170
|
+
fn test_single_element() {
|
|
171
|
+
assert_eq!(compute_max_drawdown(&[1.0]), f64::EPSILON);
|
|
172
|
+
assert_eq!(compute_max_runup(&[1.0]), f64::EPSILON);
|
|
173
|
+
}
|
|
174
|
+
}
|