echolon 0.3.0__tar.gz → 0.3.2__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.
- echolon-0.3.2/CHANGELOG.md +30 -0
- {echolon-0.3.0 → echolon-0.3.2}/PKG-INFO +1 -1
- echolon-0.3.2/docs/API_REFERENCE.md +188 -0
- echolon-0.3.2/docs/COMPONENT_GUIDE.md +115 -0
- echolon-0.3.2/docs/CONFIG_REFERENCE.md +68 -0
- echolon-0.3.2/docs/ERROR_CATALOG.md +19 -0
- echolon-0.3.2/docs/PATTERNS.md +185 -0
- echolon-0.3.2/docs/QUICK_START.md +50 -0
- echolon-0.3.2/docs/errors/CFG-001.md +35 -0
- echolon-0.3.2/docs/errors/CFG-002.md +40 -0
- echolon-0.3.2/docs/errors/DAT-001.md +41 -0
- echolon-0.3.2/docs/errors/IND-001.md +31 -0
- echolon-0.3.2/docs/errors/IND-002.md +36 -0
- echolon-0.3.2/docs/errors/PRM-001.md +44 -0
- echolon-0.3.2/docs/errors/PRM-002.md +50 -0
- echolon-0.3.2/docs/errors/STR-001.md +35 -0
- echolon-0.3.2/docs/errors/STR-002.md +48 -0
- echolon-0.3.2/docs/errors/STR-003.md +42 -0
- echolon-0.3.2/docs/errors/VAL-001.md +50 -0
- echolon-0.3.2/docs/errors/VAL-002.md +34 -0
- echolon-0.3.2/docs/errors/VAL-003.md +38 -0
- echolon-0.3.2/echolon/__init__.py +20 -0
- echolon-0.3.2/echolon/config/__init__.py +0 -0
- echolon-0.3.2/echolon/config/backtest_config.py +57 -0
- echolon-0.3.2/echolon/config/feature_flags.py +54 -0
- echolon-0.3.2/echolon/config/indicator_config.py +32 -0
- echolon-0.3.2/echolon/config/markets/__init__.py +0 -0
- echolon-0.3.2/echolon/config/markets/core/__init__.py +0 -0
- echolon-0.3.2/echolon/config/markets/core/context.py +631 -0
- echolon-0.3.2/echolon/config/markets/core/encoding.py +27 -0
- echolon-0.3.2/echolon/config/markets/core/registry.py +248 -0
- echolon-0.3.2/echolon/config/markets/core/trading_target.py +490 -0
- echolon-0.3.2/echolon/config/markets/core/types.py +217 -0
- echolon-0.3.2/echolon/config/markets/crypto/__init__.py +0 -0
- echolon-0.3.2/echolon/config/markets/crypto/config.py +70 -0
- echolon-0.3.2/echolon/config/markets/crypto/perpetuals.py +144 -0
- echolon-0.3.2/echolon/config/markets/factory.py +343 -0
- echolon-0.3.2/echolon/config/markets/shfe/__init__.py +0 -0
- echolon-0.3.2/echolon/config/markets/shfe/config.py +90 -0
- echolon-0.3.2/echolon/config/markets/shfe/constants.py +166 -0
- echolon-0.3.2/echolon/config/markets/shfe/instruments.py +321 -0
- echolon-0.3.2/echolon/config/markets/shfe/phases.py +622 -0
- echolon-0.3.2/echolon/config/markets/shfe/sessions.py +79 -0
- echolon-0.3.2/echolon/config/optuna_config.py +20 -0
- echolon-0.3.2/echolon/config/quant_engine.py +87 -0
- echolon-0.3.2/echolon/config/quick_start.py +41 -0
- echolon-0.3.2/echolon/config/settings.py +61 -0
- echolon-0.3.2/echolon/data_pipeline/__init__.py +6 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/base.py +113 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/binance/__init__.py +10 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/binance/perpetual_extractor.py +587 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/shfe/day_extractor.py +164 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/shfe/live_day_extractor.py +580 -0
- echolon-0.3.2/echolon/data_pipeline/extractors/shfe/minute_extractor.py +277 -0
- echolon-0.3.2/echolon/data_pipeline/loaders/calendar_loader.py +180 -0
- echolon-0.3.2/echolon/data_pipeline/loaders/ohlcv_loader.py +161 -0
- echolon-0.3.2/echolon/data_pipeline/loaders/session_availability_loader.py +477 -0
- echolon-0.3.2/echolon/data_pipeline/run_pipeline.py +486 -0
- echolon-0.3.2/echolon/data_pipeline/schemas/ohlcv.py +43 -0
- echolon-0.3.2/echolon/data_pipeline/schemas/standard_schema.py +536 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/calendar_generator.py +211 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/contract_splitter.py +113 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/ohlcv_resampler.py +218 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/ohlcv_standardizer.py +354 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/session_filter.py +136 -0
- echolon-0.3.2/echolon/data_pipeline/transformers/shfe_session_analyzer.py +320 -0
- echolon-0.3.2/echolon/indicators/__init__.py +6 -0
- echolon-0.3.2/echolon/indicators/calculators/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/indicator_dictionary.json +187 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/indicator_mapping.py +300 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/market_regime.py +425 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/price_channel.py +49 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/sr_zone.py +150 -0
- echolon-0.3.2/echolon/indicators/calculators/interday/ta_lib.py +1157 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/indicator_dictionary.json +88 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/indicator_mapping.py +337 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/indicators.py +1108 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/market_context.py +398 -0
- echolon-0.3.2/echolon/indicators/calculators/intraday/ta_lib.py +1082 -0
- echolon-0.3.2/echolon/indicators/config/interday_analysis_indicators.json +147 -0
- echolon-0.3.2/echolon/indicators/config/interday_indicators_classification.md +260 -0
- echolon-0.3.2/echolon/indicators/config/interday_indicators_documentation.md +255 -0
- echolon-0.3.2/echolon/indicators/config/intraday_analysis_indicators.json +356 -0
- echolon-0.3.2/echolon/indicators/config/intraday_indicators_classification.md +386 -0
- echolon-0.3.2/echolon/indicators/config/intraday_indicators_documentation.md +134 -0
- echolon-0.3.2/echolon/indicators/engine/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/engine/processor.py +1285 -0
- echolon-0.3.2/echolon/indicators/optimization/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/optimization/interday_regime_optimizer.py +1039 -0
- echolon-0.3.2/echolon/indicators/optimization/regime_utils.py +230 -0
- echolon-0.3.2/echolon/indicators/registry/__init__.py +0 -0
- echolon-0.3.2/echolon/indicators/registry/utils.py +146 -0
- echolon-0.3.2/echolon/indicators/run_indicators.py +160 -0
- echolon-0.3.2/echolon/indicators/utils/__init__.py +1 -0
- echolon-0.3.2/echolon/indicators/utils/indicator_loader.py +67 -0
- echolon-0.3.2/echolon/indicators/utils/merge_indicators.py +67 -0
- echolon-0.3.2/echolon/lib/__init__.py +0 -0
- echolon-0.3.2/echolon/lib/json_utils.py +175 -0
- echolon-0.3.2/echolon/lib/regime_utils.py +160 -0
- echolon-0.3.2/echolon/lib/stats_utils.py +277 -0
- echolon-0.3.2/echolon/lib/strategy_log.py +577 -0
- echolon-0.3.2/echolon/native/__init__.py +9 -0
- echolon-0.3.2/echolon/native/cli/__init__.py +0 -0
- echolon-0.3.2/echolon/native/cli/examples.py +46 -0
- echolon-0.3.2/echolon/native/cli/init.py +34 -0
- echolon-0.3.2/echolon/native/cli/main.py +31 -0
- echolon-0.3.2/echolon/native/cli/run.py +55 -0
- echolon-0.3.2/echolon/native/cli/schema.py +37 -0
- echolon-0.3.2/echolon/native/cli/validate.py +57 -0
- echolon-0.3.2/echolon/native/examples_registry.py +18 -0
- echolon-0.3.2/echolon/native/validation/__init__.py +30 -0
- echolon-0.3.2/echolon/native/validation/errors.py +227 -0
- echolon-0.3.2/echolon/native/validation/indicator_validator.py +75 -0
- echolon-0.3.2/echolon/native/validation/strategy_validator.py +126 -0
- echolon-0.3.2/echolon/quant_engine/LOGGING_DESIGN.md +725 -0
- echolon-0.3.2/echolon/quant_engine/__init__.py +27 -0
- echolon-0.3.2/echolon/quant_engine/backtest/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/analyzers.py +1106 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/backtest_runner.py +658 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/backtrader_engine.py +1444 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/backtrader_strategy.py +479 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/enriched_pandas_data.py +245 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/futures/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/futures/enhanced_position.py +187 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/base.py +175 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/contract_aware/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/contract_aware/broker.py +583 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/contract_aware/hook.py +197 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/contract_aware/observer.py +376 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/hooks/session_aware.py +184 -0
- echolon-0.3.2/echolon/quant_engine/backtest/engine/optimization_runner.py +403 -0
- echolon-0.3.2/echolon/quant_engine/backtest/optimization/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/backtest/optimization/optuna_study.py +585 -0
- echolon-0.3.2/echolon/quant_engine/backtest/optimization/select_best_trial.py +476 -0
- echolon-0.3.2/echolon/quant_engine/backtest/portfolio_backtest_runner.py +442 -0
- echolon-0.3.2/echolon/quant_engine/backtest/portfolio_metrics.py +261 -0
- echolon-0.3.2/echolon/quant_engine/backtest/wfa/__init__.py +11 -0
- echolon-0.3.2/echolon/quant_engine/backtest/wfa/analyzer.py +173 -0
- echolon-0.3.2/echolon/quant_engine/backtest/wfa/drs_calculator.py +755 -0
- echolon-0.3.2/echolon/quant_engine/backtest/wfa/runner.py +361 -0
- echolon-0.3.2/echolon/quant_engine/backtest/wfa/window.py +48 -0
- echolon-0.3.2/echolon/quant_engine/calculate_mfe_mae.py +551 -0
- echolon-0.3.2/echolon/quant_engine/core/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/base/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/base/base_component.py +861 -0
- echolon-0.3.2/echolon/quant_engine/core/base/base_strategy.py +1332 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/component_hook_base.py +90 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/forced_exit_strategy_hook.py +324 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/session_aware_component_hook.py +277 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/session_aware_strategy_hook.py +410 -0
- echolon-0.3.2/echolon/quant_engine/core/base/hooks/strategy_hook_base.py +152 -0
- echolon-0.3.2/echolon/quant_engine/core/base/parameter_architecture.py +280 -0
- echolon-0.3.2/echolon/quant_engine/core/base/state_manager.py +280 -0
- echolon-0.3.2/echolon/quant_engine/core/frequency/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/frequency/interday_context.py +88 -0
- echolon-0.3.2/echolon/quant_engine/core/frequency/intraday_context.py +159 -0
- echolon-0.3.2/echolon/quant_engine/core/frequency/session_context_provider.py +467 -0
- echolon-0.3.2/echolon/quant_engine/core/interfaces/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/interfaces/frequency_context.py +156 -0
- echolon-0.3.2/echolon/quant_engine/core/interfaces/market_adapter.py +413 -0
- echolon-0.3.2/echolon/quant_engine/core/interfaces/session_context.py +540 -0
- echolon-0.3.2/echolon/quant_engine/core/interfaces/trading_interfaces.py +839 -0
- echolon-0.3.2/echolon/quant_engine/core/logging/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/core/logging/strategy_logger.py +492 -0
- echolon-0.3.2/echolon/quant_engine/data_loader/SHFE_loader.py +175 -0
- echolon-0.3.2/echolon/quant_engine/data_loader/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/data_loader/contract_data.py +397 -0
- echolon-0.3.2/echolon/quant_engine/deploy/__init__.py +1 -0
- echolon-0.3.2/echolon/quant_engine/deploy/config/__init__.py +1 -0
- echolon-0.3.2/echolon/quant_engine/deploy/config/deploy_config.py +157 -0
- echolon-0.3.2/echolon/quant_engine/deploy/config/logging_config.py +159 -0
- echolon-0.3.2/echolon/quant_engine/deploy/config/portfolio_deploy_config.py +170 -0
- echolon-0.3.2/echolon/quant_engine/deploy/config/trading_calendar.csv +366 -0
- echolon-0.3.2/echolon/quant_engine/deploy/data_pipeline/__init__.py +12 -0
- echolon-0.3.2/echolon/quant_engine/deploy/data_pipeline/trading_util.py +49 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/__init__.py +9 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/capital_slot.py +108 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/dashboard_aggregator.py +397 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/dashboard_data_generator.py +393 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/dashboard_data_sender.py +76 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/portfolio_risk_overlay.py +343 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/portfolio_trading_runner.py +1384 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/slot_aware_portfolio.py +348 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/trading_data_logger.py +384 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/trading_runner.py +1194 -0
- echolon-0.3.2/echolon/quant_engine/deploy/engine/trading_slot.py +474 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/__init__.py +1 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/ccxt/__init__.py +45 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/ccxt/ccxt_client.py +47 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/ccxt/ccxt_engine.py +47 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/miniqmt/__init__.py +1 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/miniqmt/qmt_client.py +1784 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/miniqmt/qmt_engine.py +1132 -0
- echolon-0.3.2/echolon/quant_engine/deploy/platforms/miniqmt/xtdc_client.py +357 -0
- echolon-0.3.2/echolon/quant_engine/engine_factory.py +383 -0
- echolon-0.3.2/echolon/quant_engine/logging_utils.py +392 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/base_adapter.py +398 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/crypto/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/crypto/crypto_adapter.py +500 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/crypto/crypto_session_provider.py +169 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/crypto/perpetual_rules.py +264 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/crypto/session_config.py +73 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/shfe/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/shfe/contract_rules.py +484 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/shfe/shfe_adapter.py +581 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/shfe/shfe_session_provider.py +328 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/shfe/trading_calendar.py +315 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/us_futures/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/us_futures/cme_adapter.py +33 -0
- echolon-0.3.2/echolon/quant_engine/market_adapters/us_futures/session_config.py +33 -0
- echolon-0.3.2/echolon/quant_engine/reporting.py +319 -0
- echolon-0.3.2/echolon/quant_engine/run_backtest.py +279 -0
- echolon-0.3.2/echolon/quant_engine/schemas/README.md +376 -0
- echolon-0.3.2/echolon/quant_engine/schemas/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/schemas/backtest_results.py +364 -0
- echolon-0.3.2/echolon/quant_engine/schemas/selected_trial.py +132 -0
- echolon-0.3.2/echolon/quant_engine/schemas/strategy_log.py +283 -0
- echolon-0.3.2/echolon/quant_engine/schemas/trade_log.py +370 -0
- echolon-0.3.2/echolon/quant_engine/strategy/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/entry.py +277 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/exit.py +390 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/regime_params.json +40 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/risk.py +217 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/selected_robust_trial.json +350 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/sizer.py +300 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/strategy.py +176 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/strategy_code_combined.py +2746 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/strategy_indicator_list.json +17 -0
- echolon-0.3.2/echolon/quant_engine/strategy/al_s1/strategy_params.py +622 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/entry.py +362 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/exit.py +487 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/regime_params.json +40 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/risk.py +213 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/selected_robust_trial.json +388 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/sizer.py +199 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/strategy.py +326 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/strategy_code_combined.py +3018 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/strategy_indicator_list.json +18 -0
- echolon-0.3.2/echolon/quant_engine/strategy/cu_s1/strategy_params.py +590 -0
- echolon-0.3.2/echolon/quant_engine/strategy/generators/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/strategy/generators/strategy_params_generator.py +1171 -0
- echolon-0.3.2/echolon/quant_engine/strategy/loader.py +130 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/__init__.py +0 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/entry.py +324 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/exit.py +398 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/regime_params.json +40 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/risk.py +404 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/selected_robust_trial.json +295 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/sizer.py +195 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/strategy.py +210 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/strategy_code_combined.py +2673 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/strategy_indicator_list.json +17 -0
- echolon-0.3.2/echolon/quant_engine/strategy/zn_s1/strategy_params.py +488 -0
- echolon-0.3.2/echolon/quant_engine/types.py +599 -0
- echolon-0.3.2/examples/01_minimal/README.md +30 -0
- echolon-0.3.2/examples/01_minimal/entry.py +19 -0
- echolon-0.3.2/examples/01_minimal/exit.py +24 -0
- echolon-0.3.2/examples/01_minimal/risk.py +12 -0
- echolon-0.3.2/examples/01_minimal/sizer.py +16 -0
- echolon-0.3.2/examples/01_minimal/strategy.py +24 -0
- echolon-0.3.2/examples/01_minimal/strategy_indicator_list.json +1 -0
- echolon-0.3.2/examples/01_minimal/strategy_params.py +26 -0
- echolon-0.3.2/examples/02_momentum_breakout/README.md +37 -0
- echolon-0.3.2/examples/02_momentum_breakout/entry.py +29 -0
- echolon-0.3.2/examples/02_momentum_breakout/exit.py +39 -0
- echolon-0.3.2/examples/02_momentum_breakout/risk.py +12 -0
- echolon-0.3.2/examples/02_momentum_breakout/sizer.py +16 -0
- echolon-0.3.2/examples/02_momentum_breakout/strategy.py +24 -0
- echolon-0.3.2/examples/02_momentum_breakout/strategy_indicator_list.json +1 -0
- echolon-0.3.2/examples/02_momentum_breakout/strategy_params.py +27 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/README.md +37 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/entry.py +29 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/exit.py +39 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/risk.py +12 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/sizer.py +16 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/strategy.py +24 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/strategy_indicator_list.json +1 -0
- echolon-0.3.2/examples/03_rsi_mean_reversion/strategy_params.py +30 -0
- echolon-0.3.2/llms.txt +37 -0
- {echolon-0.3.0 → echolon-0.3.2}/pyproject.toml +2 -5
- echolon-0.3.2/tests/__init__.py +0 -0
- echolon-0.3.2/tests/native/__init__.py +0 -0
- echolon-0.3.2/tests/native/test_ai_native_smoke.py +105 -0
- echolon-0.3.2/tests/native/test_cli_examples.py +30 -0
- echolon-0.3.2/tests/native/test_cli_init.py +57 -0
- echolon-0.3.2/tests/native/test_cli_run.py +41 -0
- echolon-0.3.2/tests/native/test_cli_schema.py +37 -0
- echolon-0.3.2/tests/native/test_cli_validate.py +113 -0
- echolon-0.3.2/tests/native/test_errors.py +140 -0
- echolon-0.3.2/tests/native/test_indicator_validator.py +57 -0
- echolon-0.3.2/tests/native/test_strategy_validator.py +149 -0
- echolon-0.3.2/tests/test_backtest_config.py +92 -0
- echolon-0.3.2/tests/test_imports.py +63 -0
- echolon-0.3.2/tests/test_indicator_config.py +29 -0
- echolon-0.3.2/tests/test_market_adapters.py +251 -0
- echolon-0.3.2/tests/test_optuna_config.py +42 -0
- echolon-0.3.2/tests/test_optuna_optimizer_config.py +8 -0
- echolon-0.3.2/tests/test_quick_start.py +45 -0
- echolon-0.3.2/tests/test_strategy_loader.py +88 -0
- echolon-0.3.2/tests/test_trading_context.py +16 -0
- {echolon-0.3.0 → echolon-0.3.2}/.gitignore +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/LICENSE +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/README.md +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/__init__.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/README.md +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/entry.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/exit.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/risk.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/sizer.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/strategy.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/strategy_indicator_list.json +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/minimal/strategy_params.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/README.md +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/entry.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/exit.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/risk.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/sizer.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/strategy.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/strategy_indicator_list.json +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/momentum_breakout/strategy_params.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/README.md +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/entry.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/exit.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/risk.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/sizer.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/strategy.py +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/strategy_indicator_list.json +0 -0
- {echolon-0.3.0 → echolon-0.3.2}/echolon/native/templates/rsi_mean_reversion/strategy_params.py +0 -0
|
@@ -0,0 +1,30 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
## 0.3.0 — AI-Native Layer (unreleased)
|
|
4
|
+
|
|
5
|
+
### Added
|
|
6
|
+
- `echolon.native` subpackage with error system, validators, CLI, and templates
|
|
7
|
+
- `EchelonError` class hierarchy with 13 initial error codes
|
|
8
|
+
- `echolon` CLI with 5 commands: `validate`, `init-strategy`, `run`, `schema`, `examples`
|
|
9
|
+
- 3 strategy templates: `minimal`, `momentum_breakout`, `rsi_mean_reversion`
|
|
10
|
+
- 3 working examples at `examples/`
|
|
11
|
+
- `llms.txt` at repo root (AI agent entry point)
|
|
12
|
+
- `docs/` directory with QUICK_START, API_REFERENCE, COMPONENT_GUIDE, CONFIG_REFERENCE, PATTERNS, ERROR_CATALOG, and per-error docs
|
|
13
|
+
|
|
14
|
+
### Changed
|
|
15
|
+
- Added `typer>=0.9.0` as a dependency for the CLI
|
|
16
|
+
|
|
17
|
+
## 0.2.0 — Config Interface
|
|
18
|
+
|
|
19
|
+
### Breaking Changes
|
|
20
|
+
- BacktestRunner, OptunaOptimizer, WFARunner, and PortfolioBacktestRunner now require explicit configs
|
|
21
|
+
- Removed module-level globals from `echolon.config.quant_engine`
|
|
22
|
+
|
|
23
|
+
### Added
|
|
24
|
+
- BacktestConfig, OptunaConfig, IndicatorConfig Pydantic models
|
|
25
|
+
- `TradingContext.from_market()` classmethod
|
|
26
|
+
- `echolon.quick_start()` convenience helper
|
|
27
|
+
|
|
28
|
+
## 0.1.0 — Initial Release
|
|
29
|
+
|
|
30
|
+
Initial extraction from DolphinQuantStrategy monorepo.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: echolon
|
|
3
|
-
Version: 0.3.
|
|
3
|
+
Version: 0.3.2
|
|
4
4
|
Summary: See what others can't. Market-agnostic quantitative trading engine for futures backtesting, optimization, and deployment.
|
|
5
5
|
Project-URL: Homepage, https://github.com/dolphinquant/echolon
|
|
6
6
|
Project-URL: Repository, https://github.com/dolphinquant/echolon
|
|
@@ -0,0 +1,188 @@
|
|
|
1
|
+
# API Reference
|
|
2
|
+
|
|
3
|
+
All public Echolon classes and their signatures.
|
|
4
|
+
|
|
5
|
+
## BacktestConfig
|
|
6
|
+
|
|
7
|
+
**Module:** `echolon.config.backtest_config`
|
|
8
|
+
|
|
9
|
+
**Purpose:** Configuration for a single backtest run — dates, paths, thresholds.
|
|
10
|
+
|
|
11
|
+
**Signature:**
|
|
12
|
+
|
|
13
|
+
```python
|
|
14
|
+
BacktestConfig(
|
|
15
|
+
start_date: str, # "YYYY-MM-DD"
|
|
16
|
+
end_date: str, # "YYYY-MM-DD"
|
|
17
|
+
strategy_dir: Path,
|
|
18
|
+
market_data_dir: Path,
|
|
19
|
+
indicator_dir: Path,
|
|
20
|
+
results_dir: Path,
|
|
21
|
+
max_drawdown_pct: float = 15.0,
|
|
22
|
+
is_end_date: Optional[str] = None,
|
|
23
|
+
oos_start_date: Optional[str] = None,
|
|
24
|
+
market_research_end_date: Optional[str] = None,
|
|
25
|
+
)
|
|
26
|
+
```
|
|
27
|
+
|
|
28
|
+
**Example:**
|
|
29
|
+
|
|
30
|
+
```python
|
|
31
|
+
from echolon import BacktestConfig
|
|
32
|
+
from pathlib import Path
|
|
33
|
+
|
|
34
|
+
cfg = BacktestConfig(
|
|
35
|
+
start_date="2020-01-01",
|
|
36
|
+
end_date="2023-12-31",
|
|
37
|
+
strategy_dir=Path("./my_strategy"),
|
|
38
|
+
market_data_dir=Path("./data/market"),
|
|
39
|
+
indicator_dir=Path("./data/indicators"),
|
|
40
|
+
results_dir=Path("./results"),
|
|
41
|
+
)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
**Common errors:** CFG-001, CFG-002.
|
|
45
|
+
|
|
46
|
+
## OptunaConfig
|
|
47
|
+
|
|
48
|
+
**Module:** `echolon.config.optuna_config`
|
|
49
|
+
|
|
50
|
+
**Purpose:** Optuna hyperparameter optimization settings.
|
|
51
|
+
|
|
52
|
+
**Signature:**
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
OptunaConfig(
|
|
56
|
+
n_trials: int = 100,
|
|
57
|
+
n_jobs: int = -1,
|
|
58
|
+
timeout: Optional[int] = None,
|
|
59
|
+
target: Literal["sharpe_ratio", "total_return", "annual_return", "drawdown", "multi_objective"] = "sharpe_ratio",
|
|
60
|
+
n_trials_debug: int = 20,
|
|
61
|
+
aggressive_memory_management: bool = False,
|
|
62
|
+
enhanced_monitoring: bool = True,
|
|
63
|
+
)
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
## IndicatorConfig
|
|
67
|
+
|
|
68
|
+
**Module:** `echolon.config.indicator_config`
|
|
69
|
+
|
|
70
|
+
**Purpose:** Technical indicator period caps (most users never override).
|
|
71
|
+
|
|
72
|
+
**Signature:**
|
|
73
|
+
|
|
74
|
+
```python
|
|
75
|
+
IndicatorConfig(
|
|
76
|
+
interday_caps: dict[str, int],
|
|
77
|
+
intraday_caps: dict[str, int],
|
|
78
|
+
)
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
## TradingContext
|
|
82
|
+
|
|
83
|
+
**Module:** `echolon.config.markets.core.context`
|
|
84
|
+
|
|
85
|
+
**Purpose:** Market + instrument + frequency runtime context.
|
|
86
|
+
|
|
87
|
+
**Classmethod:**
|
|
88
|
+
|
|
89
|
+
```python
|
|
90
|
+
TradingContext.from_market(
|
|
91
|
+
market: str, # e.g., "shfe"
|
|
92
|
+
instrument: str, # e.g., "cu"
|
|
93
|
+
frequency: str = "interday",
|
|
94
|
+
bar_size: str = "1d",
|
|
95
|
+
) -> TradingContext
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## quick_start()
|
|
99
|
+
|
|
100
|
+
**Module:** `echolon`
|
|
101
|
+
|
|
102
|
+
**Purpose:** Build sensible default configs for common cases.
|
|
103
|
+
|
|
104
|
+
```python
|
|
105
|
+
from echolon import quick_start
|
|
106
|
+
|
|
107
|
+
ctx, bt, opt = quick_start(
|
|
108
|
+
market="shfe",
|
|
109
|
+
instrument="cu",
|
|
110
|
+
start_date="2020-01-01",
|
|
111
|
+
end_date="2023-12-31",
|
|
112
|
+
)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
## EntrySignalOutput
|
|
116
|
+
|
|
117
|
+
**Module:** `echolon.quant_engine.types`
|
|
118
|
+
|
|
119
|
+
**Returned by:** `entry_rule.generate_signal()`
|
|
120
|
+
|
|
121
|
+
**Required fields:**
|
|
122
|
+
- `signal: Literal['LONG', 'SHORT', 'HOLD']`
|
|
123
|
+
- `strength: float` (0.0 to 1.0)
|
|
124
|
+
- `type: str`
|
|
125
|
+
- `entry_reason: str`
|
|
126
|
+
- `regime: str`
|
|
127
|
+
|
|
128
|
+
**Optional:**
|
|
129
|
+
- `intent: Optional[OrderIntent]` — required for non-HOLD signals
|
|
130
|
+
|
|
131
|
+
**Common errors:** VAL-001, VAL-002.
|
|
132
|
+
|
|
133
|
+
## ExitSignalOutput
|
|
134
|
+
|
|
135
|
+
**Returned by:** `exit_rule.should_exit()`
|
|
136
|
+
|
|
137
|
+
**Required fields:**
|
|
138
|
+
- `should_exit: bool`
|
|
139
|
+
- `exit_reason: str`
|
|
140
|
+
- `position_size: float` (≥ 0)
|
|
141
|
+
- `bars_since_entry: int` (≥ 0)
|
|
142
|
+
|
|
143
|
+
**Optional:**
|
|
144
|
+
- `intent: Optional[OrderIntent]` — required when `should_exit=True`
|
|
145
|
+
|
|
146
|
+
## RiskOutput
|
|
147
|
+
|
|
148
|
+
**Returned by:** `risk_manager.can_trade()`
|
|
149
|
+
|
|
150
|
+
**Required fields:**
|
|
151
|
+
- `trading_allowed: bool`
|
|
152
|
+
- `risk_reason: str`
|
|
153
|
+
|
|
154
|
+
## SizerOutput
|
|
155
|
+
|
|
156
|
+
**Returned by:** `position_sizer.calculate_size(signal_data)`
|
|
157
|
+
|
|
158
|
+
**Required fields:**
|
|
159
|
+
- `calculated_size: int` (≥ 0, whole contracts)
|
|
160
|
+
- `signal_direction: Literal['LONG', 'SHORT', 'HOLD']`
|
|
161
|
+
- `sizing_reason: str`
|
|
162
|
+
- `raw_size: float` (≥ 0)
|
|
163
|
+
|
|
164
|
+
## OrderIntent (enum)
|
|
165
|
+
|
|
166
|
+
**Module:** `echolon.quant_engine.core.interfaces.trading_interfaces`
|
|
167
|
+
|
|
168
|
+
**Values:**
|
|
169
|
+
- `ENTRY_LONG`, `ENTRY_SHORT`
|
|
170
|
+
- `EXIT_LONG`, `EXIT_SHORT`
|
|
171
|
+
- `FORCED_EXIT`
|
|
172
|
+
- `ROLLOVER_CLOSE`, `ROLLOVER_OPEN`
|
|
173
|
+
|
|
174
|
+
## EchelonError
|
|
175
|
+
|
|
176
|
+
**Module:** `echolon` (top-level) or `echolon.native.validation.errors`
|
|
177
|
+
|
|
178
|
+
**Base class** for all Echolon validation errors. Every error has `code`, `what`, `why`, `fix`, `context`, `docs_url`.
|
|
179
|
+
|
|
180
|
+
**Subclasses:**
|
|
181
|
+
- `ValidationError` (VAL-xxx)
|
|
182
|
+
- `ConfigError` (CFG-xxx)
|
|
183
|
+
- `StrategyStructureError` (STR-xxx)
|
|
184
|
+
- `IndicatorError` (IND-xxx)
|
|
185
|
+
- `ParameterError` (PRM-xxx)
|
|
186
|
+
- `DataError` (DAT-xxx)
|
|
187
|
+
|
|
188
|
+
See [ERROR_CATALOG.md](ERROR_CATALOG.md).
|
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# Component Guide
|
|
2
|
+
|
|
3
|
+
Every Echolon strategy has 4 components. This guide explains each one.
|
|
4
|
+
|
|
5
|
+
## entry_rule (entry.py)
|
|
6
|
+
|
|
7
|
+
**Class name:** `entry_rule` (exact — required by StrategyLoader)
|
|
8
|
+
**Base:** `BaseComponent`
|
|
9
|
+
**Method:** `generate_signal() -> EntrySignalOutput`
|
|
10
|
+
|
|
11
|
+
Called every bar when there is no open position and no pending orders.
|
|
12
|
+
|
|
13
|
+
**Example:**
|
|
14
|
+
|
|
15
|
+
```python
|
|
16
|
+
from echolon.quant_engine.core.base.base_component import BaseComponent
|
|
17
|
+
from echolon.quant_engine.core.interfaces.trading_interfaces import OrderIntent
|
|
18
|
+
from echolon.quant_engine.types import EntrySignalOutput
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
class entry_rule(BaseComponent):
|
|
22
|
+
def __init__(self, trading_engine, **params):
|
|
23
|
+
super().__init__(trading_engine, **params)
|
|
24
|
+
self.rsi_period = self.params["rsi_period"]
|
|
25
|
+
|
|
26
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
27
|
+
rsi = self.get_indicator(f"rsi_{self.rsi_period}")
|
|
28
|
+
regime = self.get_market_regime()
|
|
29
|
+
if rsi < 30:
|
|
30
|
+
return EntrySignalOutput(
|
|
31
|
+
signal="LONG", strength=1.0,
|
|
32
|
+
type="oversold_entry",
|
|
33
|
+
entry_reason=f"RSI({self.rsi_period})={rsi} < 30",
|
|
34
|
+
intent=OrderIntent.ENTRY_LONG,
|
|
35
|
+
regime=regime,
|
|
36
|
+
)
|
|
37
|
+
return EntrySignalOutput(
|
|
38
|
+
signal="HOLD", strength=0.0, type="hold",
|
|
39
|
+
entry_reason="Not oversold", regime=regime,
|
|
40
|
+
)
|
|
41
|
+
```
|
|
42
|
+
|
|
43
|
+
**Common errors:**
|
|
44
|
+
- VAL-001: missing required field
|
|
45
|
+
- VAL-003: wrong __init__ signature
|
|
46
|
+
- IND-001: uppercase indicator name
|
|
47
|
+
|
|
48
|
+
## exit_rule (exit.py)
|
|
49
|
+
|
|
50
|
+
**Class name:** `exit_rule`
|
|
51
|
+
**Base:** `BaseComponent`
|
|
52
|
+
**Method:** `should_exit() -> ExitSignalOutput`
|
|
53
|
+
|
|
54
|
+
Called every bar when there is an open position.
|
|
55
|
+
|
|
56
|
+
Stateful exit rules (trailing stops, bars-held counters) must reset their state when there is no position. See the minimal template's `exit.py`.
|
|
57
|
+
|
|
58
|
+
## risk_manager (risk.py)
|
|
59
|
+
|
|
60
|
+
**Class name:** `risk_manager`
|
|
61
|
+
**Base:** `BaseComponent`
|
|
62
|
+
**Method:** `can_trade() -> RiskOutput`
|
|
63
|
+
|
|
64
|
+
Called at the start of every bar. Returns `trading_allowed=True` to permit trading, `False` to block all new entries.
|
|
65
|
+
|
|
66
|
+
Use cases: max daily loss, max consecutive losses, trading-hours filter.
|
|
67
|
+
|
|
68
|
+
## position_sizer (sizer.py)
|
|
69
|
+
|
|
70
|
+
**Class name:** `position_sizer`
|
|
71
|
+
**Base:** `BaseComponent`
|
|
72
|
+
**Method:** `calculate_size(signal_data: EntrySignalOutput) -> SizerOutput`
|
|
73
|
+
|
|
74
|
+
Called after `entry_rule.generate_signal()` produces a non-HOLD signal. Receives the signal (so sizer can branch on regime, strength, etc.).
|
|
75
|
+
|
|
76
|
+
The sizer MUST call `self.validate_and_convert_position_size(raw_float)` before setting `calculated_size` — this rounds to whole contracts and handles edge cases.
|
|
77
|
+
|
|
78
|
+
## Data access helpers
|
|
79
|
+
|
|
80
|
+
All components inherit these helpers from `BaseComponent`:
|
|
81
|
+
|
|
82
|
+
- `self.get_current_price() -> float` — close of current bar
|
|
83
|
+
- `self.get_indicator(name: str) -> float` — value of pre-computed indicator (use lowercase names!)
|
|
84
|
+
- `self.get_market_regime() -> str` — one of: `ranging`, `trending_up`, `trending_down`, `volatile`
|
|
85
|
+
- `self.params: dict` — parameters from strategy_params.py
|
|
86
|
+
- `self.portfolio: IPortfolio` — access position info
|
|
87
|
+
- `self.validate_and_convert_position_size(float) -> int` — sizer helper
|
|
88
|
+
|
|
89
|
+
## Strategy coordinator (strategy.py)
|
|
90
|
+
|
|
91
|
+
**Class name:** `strategy_main`
|
|
92
|
+
**Base:** `BaseStrategy`
|
|
93
|
+
**Method:** `_execute_bar() -> None` (not `on_bar`)
|
|
94
|
+
|
|
95
|
+
The canonical pattern:
|
|
96
|
+
|
|
97
|
+
```python
|
|
98
|
+
def _execute_bar(self) -> None:
|
|
99
|
+
risk_out = self.risk_manager.can_trade()
|
|
100
|
+
if not risk_out.trading_allowed:
|
|
101
|
+
return
|
|
102
|
+
if self.has_position() and not self.has_pending_orders():
|
|
103
|
+
exit_out = self.exit_rule.should_exit()
|
|
104
|
+
if exit_out.should_exit and exit_out.intent is not None:
|
|
105
|
+
self.exit(exit_out.intent)
|
|
106
|
+
return
|
|
107
|
+
if not self.has_position() and not self.has_pending_orders():
|
|
108
|
+
entry_out = self.entry_rule.generate_signal()
|
|
109
|
+
if entry_out.signal != "HOLD" and entry_out.intent is not None:
|
|
110
|
+
sizer_out = self.position_sizer.calculate_size(entry_out)
|
|
111
|
+
if sizer_out.calculated_size > 0:
|
|
112
|
+
self.entry(entry_out.intent, sizer_out.calculated_size)
|
|
113
|
+
```
|
|
114
|
+
|
|
115
|
+
Every bundled template uses this exact pattern. Only customize if your strategy truly needs a different structure.
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
# Configuration Reference
|
|
2
|
+
|
|
3
|
+
Echolon uses Pydantic v2 models for all configuration. Each config class is a typed object — IDEs and LLMs can introspect it.
|
|
4
|
+
|
|
5
|
+
## BacktestConfig
|
|
6
|
+
|
|
7
|
+
See [API_REFERENCE.md#backtestconfig](API_REFERENCE.md#backtestconfig).
|
|
8
|
+
|
|
9
|
+
Common mistakes:
|
|
10
|
+
- `end_date` before `start_date` → CFG-001
|
|
11
|
+
- Missing directory path → CFG-002
|
|
12
|
+
|
|
13
|
+
## OptunaConfig
|
|
14
|
+
|
|
15
|
+
See [API_REFERENCE.md#optunaconfig](API_REFERENCE.md#optunaconfig).
|
|
16
|
+
|
|
17
|
+
The `target` field accepts:
|
|
18
|
+
- `"sharpe_ratio"` — maximize risk-adjusted return (default)
|
|
19
|
+
- `"total_return"` — maximize absolute return
|
|
20
|
+
- `"annual_return"` — maximize annualized return
|
|
21
|
+
- `"drawdown"` — minimize max drawdown
|
|
22
|
+
- `"multi_objective"` — Pareto frontier over Sharpe + drawdown
|
|
23
|
+
|
|
24
|
+
## TradingContext
|
|
25
|
+
|
|
26
|
+
Runtime context: market, instrument, frequency, bar size.
|
|
27
|
+
|
|
28
|
+
```python
|
|
29
|
+
from echolon import TradingContext
|
|
30
|
+
|
|
31
|
+
ctx = TradingContext.from_market(
|
|
32
|
+
market="shfe",
|
|
33
|
+
instrument="cu",
|
|
34
|
+
frequency="interday",
|
|
35
|
+
bar_size="1d",
|
|
36
|
+
)
|
|
37
|
+
```
|
|
38
|
+
|
|
39
|
+
## Environment Variables
|
|
40
|
+
|
|
41
|
+
| Variable | Default | Purpose |
|
|
42
|
+
|----------|---------|---------|
|
|
43
|
+
| `ECHOLON_WORKSPACE_DIR` | `./workspace` | Workspace root |
|
|
44
|
+
| `ECHOLON_DATA_DIR` | `./data` | Market data + indicators |
|
|
45
|
+
| `ECHOLON_LOG_LEVEL` | `INFO` | Logging verbosity |
|
|
46
|
+
| `ECHOLON_N_JOBS_DEFAULT` | `-1` | Default Optuna parallelism |
|
|
47
|
+
|
|
48
|
+
Set via shell export, `.env` file, or Docker env config.
|
|
49
|
+
|
|
50
|
+
## quick_start()
|
|
51
|
+
|
|
52
|
+
For common cases, use the convenience helper:
|
|
53
|
+
|
|
54
|
+
```python
|
|
55
|
+
from echolon import quick_start
|
|
56
|
+
|
|
57
|
+
ctx, bt, opt = quick_start(
|
|
58
|
+
market="shfe",
|
|
59
|
+
instrument="cu",
|
|
60
|
+
start_date="2020-01-01",
|
|
61
|
+
end_date="2023-12-31",
|
|
62
|
+
)
|
|
63
|
+
# Override anything
|
|
64
|
+
opt.n_trials = 500
|
|
65
|
+
bt.max_drawdown_pct = 20.0
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
`quick_start` uses env vars for paths with fallbacks to `./workspace` and `./data`.
|
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
# Error Catalog
|
|
2
|
+
|
|
3
|
+
All Echolon errors use stable codes. Click any code for the full fix guide.
|
|
4
|
+
|
|
5
|
+
| Code | Category | What went wrong |
|
|
6
|
+
|------|----------|-----------------|
|
|
7
|
+
| [VAL-001](errors/VAL-001.md) | Validation | Missing required field in component output |
|
|
8
|
+
| [VAL-002](errors/VAL-002.md) | Validation | Invalid enum value in signal field |
|
|
9
|
+
| [VAL-003](errors/VAL-003.md) | Validation | Component class signature mismatch |
|
|
10
|
+
| [CFG-001](errors/CFG-001.md) | Config | end_date before start_date |
|
|
11
|
+
| [CFG-002](errors/CFG-002.md) | Config | Required directory does not exist |
|
|
12
|
+
| [STR-001](errors/STR-001.md) | Structure | Strategy directory missing required file |
|
|
13
|
+
| [STR-002](errors/STR-002.md) | Structure | Required class not found in file |
|
|
14
|
+
| [STR-003](errors/STR-003.md) | Structure | Required method not implemented |
|
|
15
|
+
| [IND-001](errors/IND-001.md) | Indicator | Indicator name casing mismatch |
|
|
16
|
+
| [IND-002](errors/IND-002.md) | Indicator | Indicator not declared in JSON |
|
|
17
|
+
| [PRM-001](errors/PRM-001.md) | Parameter | Missing 'printlog' key in component params |
|
|
18
|
+
| [PRM-002](errors/PRM-002.md) | Parameter | DEFAULT_PARAMS structure mismatch |
|
|
19
|
+
| [DAT-001](errors/DAT-001.md) | Data | Missing OHLCV file |
|
|
@@ -0,0 +1,185 @@
|
|
|
1
|
+
# Patterns
|
|
2
|
+
|
|
3
|
+
Canonical strategy patterns in Echolon. Each pattern includes when to use it, the core idea, files to customize, and common errors.
|
|
4
|
+
|
|
5
|
+
## Indicator Naming Rules
|
|
6
|
+
|
|
7
|
+
1. **Column names are always lowercase.** `ATR` in JSON → `atr_14`, `atr_15`, ... in code.
|
|
8
|
+
2. **Use `self.get_indicator('lowercase_name')`.** Uppercase causes silent `KeyError` or `NaN`.
|
|
9
|
+
3. **Declare every indicator in `strategy_indicator_list.json`.** Undeclared indicators aren't pre-computed.
|
|
10
|
+
4. **System indicators** (`market_regime`, `session_phase`) go in `indicators_with_special_params`.
|
|
11
|
+
|
|
12
|
+
See [IND-001](errors/IND-001.md) for the casing-mismatch error.
|
|
13
|
+
|
|
14
|
+
## 1. Trend Breakout
|
|
15
|
+
|
|
16
|
+
**When to use:** Strong trending markets; instruments with persistent momentum (index futures, commodities in backwardation).
|
|
17
|
+
|
|
18
|
+
**Key idea:** Enter when price breaks above an N-day rolling high (or below an N-day rolling low for shorts). Exit on a trailing-low stop or opposite-direction break.
|
|
19
|
+
|
|
20
|
+
**Files to customize:**
|
|
21
|
+
- `entry.py` — compare `self.get_current_price()` to `self.get_indicator('high_20')` (or similar)
|
|
22
|
+
- `exit.py` — track highest-close-since-entry, exit on pullback
|
|
23
|
+
- `strategy_indicator_list.json` — declare `rolling_high`, `rolling_low`, `atr`
|
|
24
|
+
|
|
25
|
+
**Sketch:**
|
|
26
|
+
|
|
27
|
+
```python
|
|
28
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
29
|
+
price = self.get_current_price()
|
|
30
|
+
upper = self.get_indicator(f"rolling_high_{self.lookback}")
|
|
31
|
+
regime = self.get_market_regime()
|
|
32
|
+
if price > upper:
|
|
33
|
+
return EntrySignalOutput(
|
|
34
|
+
signal="LONG", strength=1.0, type="breakout",
|
|
35
|
+
entry_reason=f"Close {price} > {self.lookback}d high {upper}",
|
|
36
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
37
|
+
)
|
|
38
|
+
return EntrySignalOutput(signal="HOLD", strength=0.0, type="hold",
|
|
39
|
+
entry_reason="No breakout", regime=regime)
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
**Common errors:** IND-001 (uppercase `ROLLING_HIGH`), VAL-001 (missing `regime`).
|
|
43
|
+
|
|
44
|
+
## 2. Mean Reversion
|
|
45
|
+
|
|
46
|
+
**When to use:** Range-bound markets; instruments that oscillate around a mean (pairs, yield curve spreads, some FX crosses).
|
|
47
|
+
|
|
48
|
+
**Key idea:** Enter when an oscillator crosses an oversold/overbought threshold. Exit when it reverts to neutral.
|
|
49
|
+
|
|
50
|
+
**Files to customize:**
|
|
51
|
+
- `entry.py` — check `self.get_indicator(f"rsi_{period}")` < 30 (oversold)
|
|
52
|
+
- `exit.py` — exit when RSI > 50 or a bars-held cap is reached
|
|
53
|
+
- `strategy_indicator_list.json` — declare `rsi` at your chosen period
|
|
54
|
+
|
|
55
|
+
**Sketch:**
|
|
56
|
+
|
|
57
|
+
```python
|
|
58
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
59
|
+
rsi = self.get_indicator(f"rsi_{self.rsi_period}")
|
|
60
|
+
regime = self.get_market_regime()
|
|
61
|
+
if rsi < self.oversold_threshold:
|
|
62
|
+
return EntrySignalOutput(
|
|
63
|
+
signal="LONG", strength=1.0, type="oversold",
|
|
64
|
+
entry_reason=f"RSI={rsi} below {self.oversold_threshold}",
|
|
65
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
66
|
+
)
|
|
67
|
+
return EntrySignalOutput(signal="HOLD", strength=0.0, type="hold",
|
|
68
|
+
entry_reason="Not oversold", regime=regime)
|
|
69
|
+
```
|
|
70
|
+
|
|
71
|
+
**Common errors:** PRM-001 (missing `printlog` in `entry_params`), VAL-002 (using `BUY` instead of `LONG`).
|
|
72
|
+
|
|
73
|
+
## 3. Regime-Switching
|
|
74
|
+
|
|
75
|
+
**When to use:** Strategies that must behave differently based on market condition — e.g., momentum in trending regimes, mean reversion in ranging.
|
|
76
|
+
|
|
77
|
+
**Key idea:** Branch the entry/exit logic on `self.get_market_regime()`. The system regime indicator classifies bars as `trending_up`, `trending_down`, `ranging`, or `volatile`.
|
|
78
|
+
|
|
79
|
+
**Files to customize:**
|
|
80
|
+
- `entry.py` — outer `if regime == "trending_up"` branches
|
|
81
|
+
- `strategy_indicator_list.json` — add `market_regime` to `indicators_with_special_params`
|
|
82
|
+
- `strategy_params.py` — define per-regime thresholds (e.g., `trend_threshold`, `range_threshold`)
|
|
83
|
+
|
|
84
|
+
**Sketch:**
|
|
85
|
+
|
|
86
|
+
```python
|
|
87
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
88
|
+
regime = self.get_market_regime()
|
|
89
|
+
price = self.get_current_price()
|
|
90
|
+
if regime == "trending_up":
|
|
91
|
+
upper = self.get_indicator(f"rolling_high_{self.trend_lookback}")
|
|
92
|
+
if price > upper:
|
|
93
|
+
return EntrySignalOutput(
|
|
94
|
+
signal="LONG", strength=1.0, type="trend_break",
|
|
95
|
+
entry_reason="Trending regime breakout",
|
|
96
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
97
|
+
)
|
|
98
|
+
elif regime == "ranging":
|
|
99
|
+
rsi = self.get_indicator(f"rsi_{self.rsi_period}")
|
|
100
|
+
if rsi < 30:
|
|
101
|
+
return EntrySignalOutput(
|
|
102
|
+
signal="LONG", strength=1.0, type="range_revert",
|
|
103
|
+
entry_reason="Range oversold",
|
|
104
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
105
|
+
)
|
|
106
|
+
return EntrySignalOutput(signal="HOLD", strength=0.0, type="hold",
|
|
107
|
+
entry_reason=f"No signal in {regime}", regime=regime)
|
|
108
|
+
```
|
|
109
|
+
|
|
110
|
+
**Common errors:** IND-002 (forgetting to declare `market_regime` in JSON), VAL-001 (missing `regime` in HOLD branch).
|
|
111
|
+
|
|
112
|
+
## 4. Multi-Timeframe
|
|
113
|
+
|
|
114
|
+
**When to use:** Intraday execution informed by a higher-timeframe bias (e.g., take long signals only when daily EMA is rising).
|
|
115
|
+
|
|
116
|
+
**Key idea:** Pre-compute the higher-timeframe indicator at daily frequency, then expose its last value to each intraday bar via the indicator pipeline. The strategy code sees a single indicator column; resampling is done upstream.
|
|
117
|
+
|
|
118
|
+
**Files to customize:**
|
|
119
|
+
- `strategy_indicator_list.json` — add a daily-resampled indicator like `ema_daily_50`
|
|
120
|
+
- `entry.py` — gate signals on the daily indicator before checking intraday triggers
|
|
121
|
+
|
|
122
|
+
**Sketch:**
|
|
123
|
+
|
|
124
|
+
```python
|
|
125
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
126
|
+
daily_ema = self.get_indicator("ema_daily_50")
|
|
127
|
+
intraday_ema = self.get_indicator(f"ema_{self.fast_period}")
|
|
128
|
+
price = self.get_current_price()
|
|
129
|
+
regime = self.get_market_regime()
|
|
130
|
+
long_bias = price > daily_ema
|
|
131
|
+
if long_bias and price > intraday_ema:
|
|
132
|
+
return EntrySignalOutput(
|
|
133
|
+
signal="LONG", strength=1.0, type="mtf_pullback",
|
|
134
|
+
entry_reason="Above daily EMA and intraday EMA",
|
|
135
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
136
|
+
)
|
|
137
|
+
return EntrySignalOutput(signal="HOLD", strength=0.0, type="hold",
|
|
138
|
+
entry_reason="No MTF alignment", regime=regime)
|
|
139
|
+
```
|
|
140
|
+
|
|
141
|
+
**Common errors:** IND-002 (daily indicator not declared), DAT-001 (daily data file missing).
|
|
142
|
+
|
|
143
|
+
## 5. ML Signal
|
|
144
|
+
|
|
145
|
+
**When to use:** You have a trained model (scikit-learn, XGBoost, small PyTorch) that outputs a directional score from a fixed feature vector.
|
|
146
|
+
|
|
147
|
+
**Key idea:** Load the pickled model once in `__init__`. In `generate_signal()`, build the feature vector from `self.get_indicator(...)` calls, call `model.predict_proba`, and threshold the output.
|
|
148
|
+
|
|
149
|
+
**Files to customize:**
|
|
150
|
+
- `entry.py` — `__init__` loads the model; `generate_signal` builds features and predicts
|
|
151
|
+
- `strategy_indicator_list.json` — declare every feature the model expects
|
|
152
|
+
- `strategy_params.py` — expose the probability threshold as a tunable parameter
|
|
153
|
+
|
|
154
|
+
**Sketch:**
|
|
155
|
+
|
|
156
|
+
```python
|
|
157
|
+
import joblib
|
|
158
|
+
import numpy as np
|
|
159
|
+
from pathlib import Path
|
|
160
|
+
|
|
161
|
+
class entry_rule(BaseComponent):
|
|
162
|
+
def __init__(self, trading_engine, **params):
|
|
163
|
+
super().__init__(trading_engine, **params)
|
|
164
|
+
self.model = joblib.load(Path(self.params["model_path"]))
|
|
165
|
+
self.prob_threshold = self.params["prob_threshold"]
|
|
166
|
+
|
|
167
|
+
def generate_signal(self) -> EntrySignalOutput:
|
|
168
|
+
regime = self.get_market_regime()
|
|
169
|
+
features = np.array([[
|
|
170
|
+
self.get_indicator("rsi_14"),
|
|
171
|
+
self.get_indicator("atr_14"),
|
|
172
|
+
self.get_indicator("ema_20"),
|
|
173
|
+
]])
|
|
174
|
+
prob_long = float(self.model.predict_proba(features)[0, 1])
|
|
175
|
+
if prob_long > self.prob_threshold:
|
|
176
|
+
return EntrySignalOutput(
|
|
177
|
+
signal="LONG", strength=prob_long, type="ml_long",
|
|
178
|
+
entry_reason=f"p(long)={prob_long:.3f}",
|
|
179
|
+
intent=OrderIntent.ENTRY_LONG, regime=regime,
|
|
180
|
+
)
|
|
181
|
+
return EntrySignalOutput(signal="HOLD", strength=0.0, type="hold",
|
|
182
|
+
entry_reason="Below threshold", regime=regime)
|
|
183
|
+
```
|
|
184
|
+
|
|
185
|
+
**Common errors:** DAT-001 (model file missing), IND-002 (feature indicator not declared), PRM-002 (`model_path` missing from `entry_params`).
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
# Quick Start
|
|
2
|
+
|
|
3
|
+
Install Echolon, create a minimal strategy, run your first backtest.
|
|
4
|
+
|
|
5
|
+
## 1. Install
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
pip install echolon
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## 2. Create a strategy
|
|
12
|
+
|
|
13
|
+
```bash
|
|
14
|
+
echolon init-strategy my_first --template minimal
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
This creates `./my_first/` with 7 files:
|
|
18
|
+
- `strategy.py` — strategy coordinator
|
|
19
|
+
- `entry.py`, `exit.py`, `risk.py`, `sizer.py` — component files
|
|
20
|
+
- `strategy_params.py` — parameter definitions
|
|
21
|
+
- `strategy_indicator_list.json` — which indicators to compute
|
|
22
|
+
- `README.md` — template notes
|
|
23
|
+
|
|
24
|
+
## 3. Validate it
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
echolon validate my_first/
|
|
28
|
+
```
|
|
29
|
+
|
|
30
|
+
Should print: `✓ Strategy directory is valid.`
|
|
31
|
+
|
|
32
|
+
## 4. Customize `entry.py`
|
|
33
|
+
|
|
34
|
+
Open `my_first/entry.py` and replace the HOLD-forever logic with your signal.
|
|
35
|
+
|
|
36
|
+
## 5. Run backtest
|
|
37
|
+
|
|
38
|
+
```bash
|
|
39
|
+
echolon run my_first/ --instrument cu --start 2020-01-01 --end 2023-12-31
|
|
40
|
+
```
|
|
41
|
+
|
|
42
|
+
## If you hit an error
|
|
43
|
+
|
|
44
|
+
Every error includes a code like `[VAL-001]`. Look it up in [ERROR_CATALOG.md](ERROR_CATALOG.md) or at `https://echolon.dev/docs/errors/{code}`.
|
|
45
|
+
|
|
46
|
+
## Next Steps
|
|
47
|
+
|
|
48
|
+
- Read [COMPONENT_GUIDE.md](COMPONENT_GUIDE.md) to understand each component
|
|
49
|
+
- Browse [PATTERNS.md](PATTERNS.md) for canonical strategy shapes
|
|
50
|
+
- See [CONFIG_REFERENCE.md](CONFIG_REFERENCE.md) for full configuration options
|