@senpi/trading-runtime 1.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +117 -0
- package/dist/actions/decision-engine.d.ts +28 -0
- package/dist/actions/decision-engine.d.ts.map +1 -0
- package/dist/actions/decision-engine.js +65 -0
- package/dist/actions/decision-engine.js.map +1 -0
- package/dist/actions/llm-decision/anthropic-llm-decision.d.ts +12 -0
- package/dist/actions/llm-decision/anthropic-llm-decision.d.ts.map +1 -0
- package/dist/actions/llm-decision/anthropic-llm-decision.js +74 -0
- package/dist/actions/llm-decision/anthropic-llm-decision.js.map +1 -0
- package/dist/actions/llm-decision/factory.d.ts +17 -0
- package/dist/actions/llm-decision/factory.d.ts.map +1 -0
- package/dist/actions/llm-decision/factory.js +29 -0
- package/dist/actions/llm-decision/factory.js.map +1 -0
- package/dist/actions/llm-decision/index.d.ts +8 -0
- package/dist/actions/llm-decision/index.d.ts.map +1 -0
- package/dist/actions/llm-decision/index.js +5 -0
- package/dist/actions/llm-decision/index.js.map +1 -0
- package/dist/actions/llm-decision/openclaw-llm-decision.d.ts +14 -0
- package/dist/actions/llm-decision/openclaw-llm-decision.d.ts.map +1 -0
- package/dist/actions/llm-decision/openclaw-llm-decision.js +58 -0
- package/dist/actions/llm-decision/openclaw-llm-decision.js.map +1 -0
- package/dist/actions/llm-decision/shared.d.ts +41 -0
- package/dist/actions/llm-decision/shared.d.ts.map +1 -0
- package/dist/actions/llm-decision/shared.js +117 -0
- package/dist/actions/llm-decision/shared.js.map +1 -0
- package/dist/actions/open-position/open-position.action.d.ts +15 -0
- package/dist/actions/open-position/open-position.action.d.ts.map +1 -0
- package/dist/actions/open-position/open-position.action.js +302 -0
- package/dist/actions/open-position/open-position.action.js.map +1 -0
- package/dist/actions/types.d.ts +123 -0
- package/dist/actions/types.d.ts.map +1 -0
- package/dist/actions/types.js +3 -0
- package/dist/actions/types.js.map +1 -0
- package/dist/cli/guide-commands.d.ts +26 -0
- package/dist/cli/guide-commands.d.ts.map +1 -0
- package/dist/cli/guide-commands.js +332 -0
- package/dist/cli/guide-commands.js.map +1 -0
- package/dist/cli/senpi-commands.d.ts +22 -0
- package/dist/cli/senpi-commands.d.ts.map +1 -0
- package/dist/cli/senpi-commands.js +138 -0
- package/dist/cli/senpi-commands.js.map +1 -0
- package/dist/constants/dsl/index.d.ts +39 -0
- package/dist/constants/dsl/index.d.ts.map +1 -0
- package/dist/constants/dsl/index.js +39 -0
- package/dist/constants/dsl/index.js.map +1 -0
- package/dist/context/context-builder.d.ts +14 -0
- package/dist/context/context-builder.d.ts.map +1 -0
- package/dist/context/context-builder.js +36 -0
- package/dist/context/context-builder.js.map +1 -0
- package/dist/context/index.d.ts +5 -0
- package/dist/context/index.d.ts.map +1 -0
- package/dist/context/index.js +5 -0
- package/dist/context/index.js.map +1 -0
- package/dist/context/interpolate.d.ts +6 -0
- package/dist/context/interpolate.d.ts.map +1 -0
- package/dist/context/interpolate.js +15 -0
- package/dist/context/interpolate.js.map +1 -0
- package/dist/context/providers/asset-trend.provider.d.ts +42 -0
- package/dist/context/providers/asset-trend.provider.d.ts.map +1 -0
- package/dist/context/providers/asset-trend.provider.js +94 -0
- package/dist/context/providers/asset-trend.provider.js.map +1 -0
- package/dist/context/providers/index.d.ts +5 -0
- package/dist/context/providers/index.d.ts.map +1 -0
- package/dist/context/providers/index.js +5 -0
- package/dist/context/providers/index.js.map +1 -0
- package/dist/context/providers/regime.provider.d.ts +14 -0
- package/dist/context/providers/regime.provider.d.ts.map +1 -0
- package/dist/context/providers/regime.provider.js +29 -0
- package/dist/context/providers/regime.provider.js.map +1 -0
- package/dist/context/providers/signal.provider.d.ts +11 -0
- package/dist/context/providers/signal.provider.d.ts.map +1 -0
- package/dist/context/providers/signal.provider.js +27 -0
- package/dist/context/providers/signal.provider.js.map +1 -0
- package/dist/context/providers/strategy.provider.d.ts +14 -0
- package/dist/context/providers/strategy.provider.d.ts.map +1 -0
- package/dist/context/providers/strategy.provider.js +68 -0
- package/dist/context/providers/strategy.provider.js.map +1 -0
- package/dist/context/types.d.ts +30 -0
- package/dist/context/types.d.ts.map +1 -0
- package/dist/context/types.js +2 -0
- package/dist/context/types.js.map +1 -0
- package/dist/dsl/config/index.d.ts +43 -0
- package/dist/dsl/config/index.d.ts.map +1 -0
- package/dist/dsl/config/index.js +108 -0
- package/dist/dsl/config/index.js.map +1 -0
- package/dist/dsl/constants/index.d.ts +3 -0
- package/dist/dsl/constants/index.d.ts.map +1 -0
- package/dist/dsl/constants/index.js +3 -0
- package/dist/dsl/constants/index.js.map +1 -0
- package/dist/dsl/engine/index.d.ts +7 -0
- package/dist/dsl/engine/index.d.ts.map +1 -0
- package/dist/dsl/engine/index.js +123 -0
- package/dist/dsl/engine/index.js.map +1 -0
- package/dist/dsl/events/bus-types.d.ts +7 -0
- package/dist/dsl/events/bus-types.d.ts.map +1 -0
- package/dist/dsl/events/bus-types.js +6 -0
- package/dist/dsl/events/bus-types.js.map +1 -0
- package/dist/dsl/events/guards.d.ts +17 -0
- package/dist/dsl/events/guards.d.ts.map +1 -0
- package/dist/dsl/events/guards.js +94 -0
- package/dist/dsl/events/guards.js.map +1 -0
- package/dist/dsl/events/handlers.d.ts +15 -0
- package/dist/dsl/events/handlers.d.ts.map +1 -0
- package/dist/dsl/events/handlers.js +229 -0
- package/dist/dsl/events/handlers.js.map +1 -0
- package/dist/dsl/events/index.d.ts +4 -0
- package/dist/dsl/events/index.d.ts.map +1 -0
- package/dist/dsl/events/index.js +4 -0
- package/dist/dsl/events/index.js.map +1 -0
- package/dist/dsl/index.d.ts +13 -0
- package/dist/dsl/index.d.ts.map +1 -0
- package/dist/dsl/index.js +35 -0
- package/dist/dsl/index.js.map +1 -0
- package/dist/dsl/monitor/index.d.ts +18 -0
- package/dist/dsl/monitor/index.d.ts.map +1 -0
- package/dist/dsl/monitor/index.js +296 -0
- package/dist/dsl/monitor/index.js.map +1 -0
- package/dist/dsl/plugin/index.d.ts +29 -0
- package/dist/dsl/plugin/index.d.ts.map +1 -0
- package/dist/dsl/plugin/index.js +85 -0
- package/dist/dsl/plugin/index.js.map +1 -0
- package/dist/dsl/types/index.d.ts +3 -0
- package/dist/dsl/types/index.d.ts.map +1 -0
- package/dist/dsl/types/index.js +3 -0
- package/dist/dsl/types/index.js.map +1 -0
- package/dist/health/index.d.ts +9 -0
- package/dist/health/index.d.ts.map +1 -0
- package/dist/health/index.js +9 -0
- package/dist/health/index.js.map +1 -0
- package/dist/index.d.ts +94 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +218 -0
- package/dist/index.js.map +1 -0
- package/dist/risk/index.d.ts +9 -0
- package/dist/risk/index.d.ts.map +1 -0
- package/dist/risk/index.js +9 -0
- package/dist/risk/index.js.map +1 -0
- package/dist/runtime/action-registry.d.ts +12 -0
- package/dist/runtime/action-registry.d.ts.map +1 -0
- package/dist/runtime/action-registry.js +18 -0
- package/dist/runtime/action-registry.js.map +1 -0
- package/dist/runtime/build-action-scan-payload.d.ts +20 -0
- package/dist/runtime/build-action-scan-payload.d.ts.map +1 -0
- package/dist/runtime/build-action-scan-payload.js +52 -0
- package/dist/runtime/build-action-scan-payload.js.map +1 -0
- package/dist/runtime/bus.d.ts +6 -0
- package/dist/runtime/bus.d.ts.map +1 -0
- package/dist/runtime/bus.js +19 -0
- package/dist/runtime/bus.js.map +1 -0
- package/dist/runtime/create-action-context.d.ts +34 -0
- package/dist/runtime/create-action-context.d.ts.map +1 -0
- package/dist/runtime/create-action-context.js +22 -0
- package/dist/runtime/create-action-context.js.map +1 -0
- package/dist/runtime/create-actions.d.ts +30 -0
- package/dist/runtime/create-actions.d.ts.map +1 -0
- package/dist/runtime/create-actions.js +78 -0
- package/dist/runtime/create-actions.js.map +1 -0
- package/dist/runtime/create-runtime-context.d.ts +33 -0
- package/dist/runtime/create-runtime-context.d.ts.map +1 -0
- package/dist/runtime/create-runtime-context.js +32 -0
- package/dist/runtime/create-runtime-context.js.map +1 -0
- package/dist/runtime/create-scanner-module.d.ts +14 -0
- package/dist/runtime/create-scanner-module.d.ts.map +1 -0
- package/dist/runtime/create-scanner-module.js +26 -0
- package/dist/runtime/create-scanner-module.js.map +1 -0
- package/dist/runtime/create-scanner-providers-for-run.d.ts +20 -0
- package/dist/runtime/create-scanner-providers-for-run.d.ts.map +1 -0
- package/dist/runtime/create-scanner-providers-for-run.js +34 -0
- package/dist/runtime/create-scanner-providers-for-run.js.map +1 -0
- package/dist/runtime/default-state-dir.d.ts +10 -0
- package/dist/runtime/default-state-dir.d.ts.map +1 -0
- package/dist/runtime/default-state-dir.js +22 -0
- package/dist/runtime/default-state-dir.js.map +1 -0
- package/dist/runtime/env-resolve.d.ts +17 -0
- package/dist/runtime/env-resolve.d.ts.map +1 -0
- package/dist/runtime/env-resolve.js +45 -0
- package/dist/runtime/env-resolve.js.map +1 -0
- package/dist/runtime/hook-system.d.ts +7 -0
- package/dist/runtime/hook-system.d.ts.map +1 -0
- package/dist/runtime/hook-system.js +16 -0
- package/dist/runtime/hook-system.js.map +1 -0
- package/dist/runtime/index.d.ts +37 -0
- package/dist/runtime/index.d.ts.map +1 -0
- package/dist/runtime/index.js +26 -0
- package/dist/runtime/index.js.map +1 -0
- package/dist/runtime/load-trading-strategy.d.ts +18 -0
- package/dist/runtime/load-trading-strategy.d.ts.map +1 -0
- package/dist/runtime/load-trading-strategy.js +51 -0
- package/dist/runtime/load-trading-strategy.js.map +1 -0
- package/dist/runtime/map-exit-config.d.ts +12 -0
- package/dist/runtime/map-exit-config.d.ts.map +1 -0
- package/dist/runtime/map-exit-config.js +89 -0
- package/dist/runtime/map-exit-config.js.map +1 -0
- package/dist/runtime/map-strategies-registration.d.ts +12 -0
- package/dist/runtime/map-strategies-registration.d.ts.map +1 -0
- package/dist/runtime/map-strategies-registration.js +52 -0
- package/dist/runtime/map-strategies-registration.js.map +1 -0
- package/dist/runtime/map-strategies.d.ts +12 -0
- package/dist/runtime/map-strategies.d.ts.map +1 -0
- package/dist/runtime/map-strategies.js +46 -0
- package/dist/runtime/map-strategies.js.map +1 -0
- package/dist/runtime/notifications.d.ts +10 -0
- package/dist/runtime/notifications.d.ts.map +1 -0
- package/dist/runtime/notifications.js +14 -0
- package/dist/runtime/notifications.js.map +1 -0
- package/dist/runtime/parse-interval.d.ts +11 -0
- package/dist/runtime/parse-interval.d.ts.map +1 -0
- package/dist/runtime/parse-interval.js +35 -0
- package/dist/runtime/parse-interval.js.map +1 -0
- package/dist/runtime/reconcile-state.d.ts +25 -0
- package/dist/runtime/reconcile-state.d.ts.map +1 -0
- package/dist/runtime/reconcile-state.js +114 -0
- package/dist/runtime/reconcile-state.js.map +1 -0
- package/dist/runtime/risk-guard.d.ts +6 -0
- package/dist/runtime/risk-guard.d.ts.map +1 -0
- package/dist/runtime/risk-guard.js +14 -0
- package/dist/runtime/risk-guard.js.map +1 -0
- package/dist/runtime/run.d.ts +21 -0
- package/dist/runtime/run.d.ts.map +1 -0
- package/dist/runtime/run.js +43 -0
- package/dist/runtime/run.js.map +1 -0
- package/dist/runtime/runtime.d.ts +40 -0
- package/dist/runtime/runtime.d.ts.map +1 -0
- package/dist/runtime/runtime.js +249 -0
- package/dist/runtime/runtime.js.map +1 -0
- package/dist/runtime/scanner-registry.d.ts +22 -0
- package/dist/runtime/scanner-registry.d.ts.map +1 -0
- package/dist/runtime/scanner-registry.js +72 -0
- package/dist/runtime/scanner-registry.js.map +1 -0
- package/dist/runtime/strategy-registry.d.ts +49 -0
- package/dist/runtime/strategy-registry.d.ts.map +1 -0
- package/dist/runtime/strategy-registry.js +150 -0
- package/dist/runtime/strategy-registry.js.map +1 -0
- package/dist/runtime/trading-strategy-config.d.ts +6 -0
- package/dist/runtime/trading-strategy-config.d.ts.map +1 -0
- package/dist/runtime/trading-strategy-config.js +5 -0
- package/dist/runtime/trading-strategy-config.js.map +1 -0
- package/dist/runtime/trading-strategy-schema.d.ts +19757 -0
- package/dist/runtime/trading-strategy-schema.d.ts.map +1 -0
- package/dist/runtime/trading-strategy-schema.js +107 -0
- package/dist/runtime/trading-strategy-schema.js.map +1 -0
- package/dist/scanners/__tests__/fixtures/index.d.ts +3 -0
- package/dist/scanners/__tests__/fixtures/index.d.ts.map +1 -0
- package/dist/scanners/__tests__/fixtures/index.js +3 -0
- package/dist/scanners/__tests__/fixtures/index.js.map +1 -0
- package/dist/scanners/__tests__/fixtures/scan-results.d.ts +23 -0
- package/dist/scanners/__tests__/fixtures/scan-results.d.ts.map +1 -0
- package/dist/scanners/__tests__/fixtures/scan-results.js +128 -0
- package/dist/scanners/__tests__/fixtures/scan-results.js.map +1 -0
- package/dist/scanners/__tests__/fixtures/signals.d.ts +18 -0
- package/dist/scanners/__tests__/fixtures/signals.d.ts.map +1 -0
- package/dist/scanners/__tests__/fixtures/signals.js +84 -0
- package/dist/scanners/__tests__/fixtures/signals.js.map +1 -0
- package/dist/scanners/__tests__/test-helpers.d.ts +4 -0
- package/dist/scanners/__tests__/test-helpers.d.ts.map +1 -0
- package/dist/scanners/__tests__/test-helpers.js +23 -0
- package/dist/scanners/__tests__/test-helpers.js.map +1 -0
- package/dist/scanners/artifacts.d.ts +119 -0
- package/dist/scanners/artifacts.d.ts.map +1 -0
- package/dist/scanners/artifacts.js +72 -0
- package/dist/scanners/artifacts.js.map +1 -0
- package/dist/scanners/create-scanner.d.ts +13 -0
- package/dist/scanners/create-scanner.d.ts.map +1 -0
- package/dist/scanners/create-scanner.js +11 -0
- package/dist/scanners/create-scanner.js.map +1 -0
- package/dist/scanners/engine/config-validator.d.ts +7 -0
- package/dist/scanners/engine/config-validator.d.ts.map +1 -0
- package/dist/scanners/engine/config-validator.js +16 -0
- package/dist/scanners/engine/config-validator.js.map +1 -0
- package/dist/scanners/engine/data-providers.d.ts +43 -0
- package/dist/scanners/engine/data-providers.d.ts.map +1 -0
- package/dist/scanners/engine/data-providers.js +8 -0
- package/dist/scanners/engine/data-providers.js.map +1 -0
- package/dist/scanners/engine/engine.d.ts +76 -0
- package/dist/scanners/engine/engine.d.ts.map +1 -0
- package/dist/scanners/engine/engine.js +399 -0
- package/dist/scanners/engine/engine.js.map +1 -0
- package/dist/scanners/engine/index.d.ts +9 -0
- package/dist/scanners/engine/index.d.ts.map +1 -0
- package/dist/scanners/engine/index.js +7 -0
- package/dist/scanners/engine/index.js.map +1 -0
- package/dist/scanners/engine/input-resolver.d.ts +21 -0
- package/dist/scanners/engine/input-resolver.d.ts.map +1 -0
- package/dist/scanners/engine/input-resolver.js +140 -0
- package/dist/scanners/engine/input-resolver.js.map +1 -0
- package/dist/scanners/engine/lifecycle.d.ts +21 -0
- package/dist/scanners/engine/lifecycle.d.ts.map +1 -0
- package/dist/scanners/engine/lifecycle.js +2 -0
- package/dist/scanners/engine/lifecycle.js.map +1 -0
- package/dist/scanners/engine/result-builder.d.ts +30 -0
- package/dist/scanners/engine/result-builder.d.ts.map +1 -0
- package/dist/scanners/engine/result-builder.js +129 -0
- package/dist/scanners/engine/result-builder.js.map +1 -0
- package/dist/scanners/engine/strategy-registry.d.ts +45 -0
- package/dist/scanners/engine/strategy-registry.d.ts.map +1 -0
- package/dist/scanners/engine/strategy-registry.js +93 -0
- package/dist/scanners/engine/strategy-registry.js.map +1 -0
- package/dist/scanners/events.d.ts +41 -0
- package/dist/scanners/events.d.ts.map +1 -0
- package/dist/scanners/events.js +33 -0
- package/dist/scanners/events.js.map +1 -0
- package/dist/scanners/implementations/emerging-movers.d.ts +212 -0
- package/dist/scanners/implementations/emerging-movers.d.ts.map +1 -0
- package/dist/scanners/implementations/emerging-movers.js +702 -0
- package/dist/scanners/implementations/emerging-movers.js.map +1 -0
- package/dist/scanners/implementations/index.d.ts +8 -0
- package/dist/scanners/implementations/index.d.ts.map +1 -0
- package/dist/scanners/implementations/index.js +8 -0
- package/dist/scanners/implementations/index.js.map +1 -0
- package/dist/scanners/implementations/indicators.d.ts +40 -0
- package/dist/scanners/implementations/indicators.d.ts.map +1 -0
- package/dist/scanners/implementations/indicators.js +114 -0
- package/dist/scanners/implementations/indicators.js.map +1 -0
- package/dist/scanners/implementations/market-regime.d.ts +52 -0
- package/dist/scanners/implementations/market-regime.d.ts.map +1 -0
- package/dist/scanners/implementations/market-regime.js +201 -0
- package/dist/scanners/implementations/market-regime.js.map +1 -0
- package/dist/scanners/implementations/momentum.d.ts +21 -0
- package/dist/scanners/implementations/momentum.d.ts.map +1 -0
- package/dist/scanners/implementations/momentum.js +128 -0
- package/dist/scanners/implementations/momentum.js.map +1 -0
- package/dist/scanners/implementations/oi-tracker.d.ts +13 -0
- package/dist/scanners/implementations/oi-tracker.d.ts.map +1 -0
- package/dist/scanners/implementations/oi-tracker.js +67 -0
- package/dist/scanners/implementations/oi-tracker.js.map +1 -0
- package/dist/scanners/implementations/opportunity.d.ts +104 -0
- package/dist/scanners/implementations/opportunity.d.ts.map +1 -0
- package/dist/scanners/implementations/opportunity.js +441 -0
- package/dist/scanners/implementations/opportunity.js.map +1 -0
- package/dist/scanners/implementations/prescreener.d.ts +20 -0
- package/dist/scanners/implementations/prescreener.d.ts.map +1 -0
- package/dist/scanners/implementations/prescreener.js +97 -0
- package/dist/scanners/implementations/prescreener.js.map +1 -0
- package/dist/scanners/implementations/sm-flip.d.ts +43 -0
- package/dist/scanners/implementations/sm-flip.d.ts.map +1 -0
- package/dist/scanners/implementations/sm-flip.js +200 -0
- package/dist/scanners/implementations/sm-flip.js.map +1 -0
- package/dist/scanners/index.d.ts +24 -0
- package/dist/scanners/index.d.ts.map +1 -0
- package/dist/scanners/index.js +17 -0
- package/dist/scanners/index.js.map +1 -0
- package/dist/scanners/input-descriptors.d.ts +49 -0
- package/dist/scanners/input-descriptors.d.ts.map +1 -0
- package/dist/scanners/input-descriptors.js +35 -0
- package/dist/scanners/input-descriptors.js.map +1 -0
- package/dist/scanners/protocol/context.d.ts +71 -0
- package/dist/scanners/protocol/context.d.ts.map +1 -0
- package/dist/scanners/protocol/context.js +2 -0
- package/dist/scanners/protocol/context.js.map +1 -0
- package/dist/scanners/protocol/hooks.d.ts +18 -0
- package/dist/scanners/protocol/hooks.d.ts.map +1 -0
- package/dist/scanners/protocol/hooks.js +12 -0
- package/dist/scanners/protocol/hooks.js.map +1 -0
- package/dist/scanners/protocol/index.d.ts +5 -0
- package/dist/scanners/protocol/index.d.ts.map +1 -0
- package/dist/scanners/protocol/index.js +2 -0
- package/dist/scanners/protocol/index.js.map +1 -0
- package/dist/scanners/protocol/scanner.d.ts +47 -0
- package/dist/scanners/protocol/scanner.d.ts.map +1 -0
- package/dist/scanners/protocol/scanner.js +2 -0
- package/dist/scanners/protocol/scanner.js.map +1 -0
- package/dist/scanners/providers/adapter.d.ts +23 -0
- package/dist/scanners/providers/adapter.d.ts.map +1 -0
- package/dist/scanners/providers/adapter.js +227 -0
- package/dist/scanners/providers/adapter.js.map +1 -0
- package/dist/scanners/providers/cache.d.ts +20 -0
- package/dist/scanners/providers/cache.d.ts.map +1 -0
- package/dist/scanners/providers/cache.js +61 -0
- package/dist/scanners/providers/cache.js.map +1 -0
- package/dist/scanners/providers/client.d.ts +53 -0
- package/dist/scanners/providers/client.d.ts.map +1 -0
- package/dist/scanners/providers/client.js +174 -0
- package/dist/scanners/providers/client.js.map +1 -0
- package/dist/scanners/providers/factory.d.ts +42 -0
- package/dist/scanners/providers/factory.d.ts.map +1 -0
- package/dist/scanners/providers/factory.js +156 -0
- package/dist/scanners/providers/factory.js.map +1 -0
- package/dist/scanners/providers/types.d.ts +61 -0
- package/dist/scanners/providers/types.d.ts.map +1 -0
- package/dist/scanners/providers/types.js +6 -0
- package/dist/scanners/providers/types.js.map +1 -0
- package/dist/scanners/runtime-module.d.ts +54 -0
- package/dist/scanners/runtime-module.d.ts.map +1 -0
- package/dist/scanners/runtime-module.js +94 -0
- package/dist/scanners/runtime-module.js.map +1 -0
- package/dist/scanners/scanner-definition.d.ts +58 -0
- package/dist/scanners/scanner-definition.d.ts.map +1 -0
- package/dist/scanners/scanner-definition.js +19 -0
- package/dist/scanners/scanner-definition.js.map +1 -0
- package/dist/scanners/schema-utils.d.ts +6 -0
- package/dist/scanners/schema-utils.d.ts.map +1 -0
- package/dist/scanners/schema-utils.js +14 -0
- package/dist/scanners/schema-utils.js.map +1 -0
- package/dist/scanners/score-utils.d.ts +19 -0
- package/dist/scanners/score-utils.d.ts.map +1 -0
- package/dist/scanners/score-utils.js +47 -0
- package/dist/scanners/score-utils.js.map +1 -0
- package/dist/scanners/store/fs-utils.d.ts +12 -0
- package/dist/scanners/store/fs-utils.d.ts.map +1 -0
- package/dist/scanners/store/fs-utils.js +47 -0
- package/dist/scanners/store/fs-utils.js.map +1 -0
- package/dist/scanners/store/index.d.ts +5 -0
- package/dist/scanners/store/index.d.ts.map +1 -0
- package/dist/scanners/store/index.js +5 -0
- package/dist/scanners/store/index.js.map +1 -0
- package/dist/scanners/store/runtime-store.d.ts +50 -0
- package/dist/scanners/store/runtime-store.d.ts.map +1 -0
- package/dist/scanners/store/runtime-store.js +91 -0
- package/dist/scanners/store/runtime-store.js.map +1 -0
- package/dist/scanners/store/shared-artifact-store.d.ts +31 -0
- package/dist/scanners/store/shared-artifact-store.d.ts.map +1 -0
- package/dist/scanners/store/shared-artifact-store.js +89 -0
- package/dist/scanners/store/shared-artifact-store.js.map +1 -0
- package/dist/scanners/store/signal-store.d.ts +36 -0
- package/dist/scanners/store/signal-store.d.ts.map +1 -0
- package/dist/scanners/store/signal-store.js +135 -0
- package/dist/scanners/store/signal-store.js.map +1 -0
- package/dist/scanners/types.d.ts +115 -0
- package/dist/scanners/types.d.ts.map +1 -0
- package/dist/scanners/types.js +3 -0
- package/dist/scanners/types.js.map +1 -0
- package/dist/scripts/run-dsl-standalone-live.d.ts +27 -0
- package/dist/scripts/run-dsl-standalone-live.d.ts.map +1 -0
- package/dist/scripts/run-dsl-standalone-live.js +207 -0
- package/dist/scripts/run-dsl-standalone-live.js.map +1 -0
- package/dist/scripts/run-dsl-standalone-mock.d.ts +13 -0
- package/dist/scripts/run-dsl-standalone-mock.d.ts.map +1 -0
- package/dist/scripts/run-dsl-standalone-mock.js +255 -0
- package/dist/scripts/run-dsl-standalone-mock.js.map +1 -0
- package/dist/senpi/client.d.ts +70 -0
- package/dist/senpi/client.d.ts.map +1 -0
- package/dist/senpi/client.js +482 -0
- package/dist/senpi/client.js.map +1 -0
- package/dist/senpi/constants.d.ts +7 -0
- package/dist/senpi/constants.d.ts.map +1 -0
- package/dist/senpi/constants.js +7 -0
- package/dist/senpi/constants.js.map +1 -0
- package/dist/senpi/index.d.ts +3 -0
- package/dist/senpi/index.d.ts.map +1 -0
- package/dist/senpi/index.js +2 -0
- package/dist/senpi/index.js.map +1 -0
- package/dist/senpi/types.d.ts +132 -0
- package/dist/senpi/types.d.ts.map +1 -0
- package/dist/senpi/types.js +3 -0
- package/dist/senpi/types.js.map +1 -0
- package/dist/state/index.d.ts +3 -0
- package/dist/state/index.d.ts.map +1 -0
- package/dist/state/index.js +3 -0
- package/dist/state/index.js.map +1 -0
- package/dist/state/paths.d.ts +8 -0
- package/dist/state/paths.d.ts.map +1 -0
- package/dist/state/paths.js +19 -0
- package/dist/state/paths.js.map +1 -0
- package/dist/state/state-manager.d.ts +56 -0
- package/dist/state/state-manager.d.ts.map +1 -0
- package/dist/state/state-manager.js +213 -0
- package/dist/state/state-manager.js.map +1 -0
- package/dist/strategy/index.d.ts +2 -0
- package/dist/strategy/index.d.ts.map +1 -0
- package/dist/strategy/index.js +2 -0
- package/dist/strategy/index.js.map +1 -0
- package/dist/strategy/strategy-state.d.ts +47 -0
- package/dist/strategy/strategy-state.d.ts.map +1 -0
- package/dist/strategy/strategy-state.js +179 -0
- package/dist/strategy/strategy-state.js.map +1 -0
- package/dist/types/action.d.ts +15 -0
- package/dist/types/action.d.ts.map +1 -0
- package/dist/types/action.js +2 -0
- package/dist/types/action.js.map +1 -0
- package/dist/types/dsl/index.d.ts +156 -0
- package/dist/types/dsl/index.d.ts.map +1 -0
- package/dist/types/dsl/index.js +5 -0
- package/dist/types/dsl/index.js.map +1 -0
- package/dist/types/event-bus.d.ts +11 -0
- package/dist/types/event-bus.d.ts.map +1 -0
- package/dist/types/event-bus.js +6 -0
- package/dist/types/event-bus.js.map +1 -0
- package/dist/types/hooks.d.ts +27 -0
- package/dist/types/hooks.d.ts.map +1 -0
- package/dist/types/hooks.js +15 -0
- package/dist/types/hooks.js.map +1 -0
- package/dist/types/index.d.ts +6 -0
- package/dist/types/index.d.ts.map +1 -0
- package/dist/types/index.js +6 -0
- package/dist/types/index.js.map +1 -0
- package/dist/types/notification.d.ts +4 -0
- package/dist/types/notification.d.ts.map +1 -0
- package/dist/types/notification.js +3 -0
- package/dist/types/notification.js.map +1 -0
- package/dist/types/runtime.d.ts +16 -0
- package/dist/types/runtime.d.ts.map +1 -0
- package/dist/types/runtime.js +2 -0
- package/dist/types/runtime.js.map +1 -0
- package/dist/types/scanner.d.ts +59 -0
- package/dist/types/scanner.d.ts.map +1 -0
- package/dist/types/scanner.js +13 -0
- package/dist/types/scanner.js.map +1 -0
- package/dist/types/strategy.d.ts +97 -0
- package/dist/types/strategy.d.ts.map +1 -0
- package/dist/types/strategy.js +2 -0
- package/dist/types/strategy.js.map +1 -0
- package/dist/utils/logger/dsl.d.ts +8 -0
- package/dist/utils/logger/dsl.d.ts.map +1 -0
- package/dist/utils/logger/dsl.js +7 -0
- package/dist/utils/logger/dsl.js.map +1 -0
- package/dist/utils/logger.d.ts +13 -0
- package/dist/utils/logger.d.ts.map +1 -0
- package/dist/utils/logger.js +65 -0
- package/dist/utils/logger.js.map +1 -0
- package/dist/utils/response.d.ts +14 -0
- package/dist/utils/response.d.ts.map +1 -0
- package/dist/utils/response.js +44 -0
- package/dist/utils/response.js.map +1 -0
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +2 -0
- package/dist/version.js.map +1 -0
- package/examples/.env.example +15 -0
- package/examples/README.md +75 -0
- package/examples/scanners-consumer-quickstart.mjs +134 -0
- package/examples/scanners-emerging-movers-v4.mjs +285 -0
- package/examples/scanners-live-mcp.mjs +208 -0
- package/examples/scanners-multi-strategy.mjs +165 -0
- package/examples/scanners-scheduled-events.mjs +95 -0
- package/examples/scanners-senpi-provider-client.mjs +182 -0
- package/examples/scanners-single-strategy.mjs +198 -0
- package/examples/strategies/README.md +45 -0
- package/examples/strategies/dsl-showcase.yaml +62 -0
- package/examples/strategies/fox.yaml +55 -0
- package/examples/strategies/minimal.yaml +41 -0
- package/examples/strategies/viper.yaml +63 -0
- package/examples/strategies/wolf.yaml +60 -0
- package/examples/yamls/sample-with-dsl-and-emerging-movers.yaml +111 -0
- package/openclaw.plugin.json +15 -0
- package/package.json +52 -0
|
@@ -0,0 +1,702 @@
|
|
|
1
|
+
import { Type } from "@sinclair/typebox";
|
|
2
|
+
import { createScanner } from "../create-scanner.js";
|
|
3
|
+
import { consumeSharedArtifact, MarketRegime, marketRegimeArtifact } from "../artifacts.js";
|
|
4
|
+
import { providerInput } from "../input-descriptors.js";
|
|
5
|
+
import { clamp01 } from "../score-utils.js";
|
|
6
|
+
import { PersistencePolicy, RetentionPolicy } from "../scanner-definition.js";
|
|
7
|
+
/**
|
|
8
|
+
* Numeric priority per signal type (1 = highest).
|
|
9
|
+
* Used to sort signals so that the action layer processes the most urgent first.
|
|
10
|
+
*/
|
|
11
|
+
const SIGNAL_PRIORITY = {
|
|
12
|
+
FIRST_JUMP: 1,
|
|
13
|
+
CONTRIB_EXPLOSION: 2,
|
|
14
|
+
IMMEDIATE_MOVER: 3,
|
|
15
|
+
NEW_ENTRY_DEEP: 4,
|
|
16
|
+
DEEP_CLIMBER: 5,
|
|
17
|
+
};
|
|
18
|
+
/**
|
|
19
|
+
* Default conviction score per signal type, mapped to the [0, 1] range
|
|
20
|
+
* consumed by the action layer. Higher conviction = stronger entry signal.
|
|
21
|
+
* These are used as the signal's `score` field so downstream consumers can
|
|
22
|
+
* sort/filter without knowing the signal type taxonomy.
|
|
23
|
+
*/
|
|
24
|
+
const SIGNAL_CONVICTION = {
|
|
25
|
+
FIRST_JUMP: 0.95,
|
|
26
|
+
CONTRIB_EXPLOSION: 0.90,
|
|
27
|
+
IMMEDIATE_MOVER: 0.85,
|
|
28
|
+
NEW_ENTRY_DEEP: 0.80,
|
|
29
|
+
DEEP_CLIMBER: 0.60,
|
|
30
|
+
};
|
|
31
|
+
// ─── Config ─────────────────────────────────────────────────────────────────
|
|
32
|
+
const emergingMoversConfigSchema = Type.Object({
|
|
33
|
+
topN: Type.Number({ minimum: 1, default: 50 }),
|
|
34
|
+
minRankJump: Type.Number({ minimum: 1, default: 5 }),
|
|
35
|
+
minTopTradersGain: Type.Number({ minimum: 0, default: 0.1 }),
|
|
36
|
+
minScansBeforeSignals: Type.Number({ minimum: 1, default: 2 }),
|
|
37
|
+
historyLimit: Type.Number({ minimum: 2, default: 60 }),
|
|
38
|
+
immediateJumpThreshold: Type.Optional(Type.Number({ minimum: 1, default: 10 })),
|
|
39
|
+
deepClimbRankThreshold: Type.Optional(Type.Number({ minimum: 1, default: 25 })),
|
|
40
|
+
contribExplosionMultiplier: Type.Optional(Type.Number({ minimum: 1, default: 3.0 })),
|
|
41
|
+
contribAccelThreshold: Type.Optional(Type.Number({ minimum: 0, default: 0.003 })),
|
|
42
|
+
minVelocityForDeepClimber: Type.Optional(Type.Number({ minimum: 0, default: 0.0003 })),
|
|
43
|
+
erraticReversalThreshold: Type.Optional(Type.Number({ minimum: 1, default: 5 })),
|
|
44
|
+
climbStreakScans: Type.Optional(Type.Number({ minimum: 2, default: 3 })),
|
|
45
|
+
rankClimbThreshold: Type.Optional(Type.Number({ minimum: 1, default: 3 })),
|
|
46
|
+
newEntryDeepMaxRank: Type.Optional(Type.Number({ minimum: 1, default: 20 })),
|
|
47
|
+
newEntryMaxRank: Type.Optional(Type.Number({ minimum: 1, default: 35 })),
|
|
48
|
+
firstJumpMinPrevRank: Type.Optional(Type.Number({ minimum: 1, default: 30 })),
|
|
49
|
+
/** Enable regime-aware score gating via market-regime artifact. Default: false (disabled). */
|
|
50
|
+
regimeScoreGating: Type.Optional(Type.Boolean({ default: false })),
|
|
51
|
+
/** Score threshold when market regime is NEUTRAL. Only used when regimeScoreGating is true. */
|
|
52
|
+
regimeScoreNeutral: Type.Optional(Type.Number({ minimum: 0, maximum: 1, default: 0.8 })),
|
|
53
|
+
/** Score threshold when market regime is BULLISH or BEARISH. Only used when regimeScoreGating is true. */
|
|
54
|
+
regimeScoreDefault: Type.Optional(Type.Number({ minimum: 0, maximum: 1, default: 0.7 })),
|
|
55
|
+
/** Minimum asset max leverage to emit signals. 0 = disabled (no filtering). */
|
|
56
|
+
minLeverage: Type.Optional(Type.Number({ minimum: 0, default: 0 })),
|
|
57
|
+
});
|
|
58
|
+
const emergingMoversStateSchema = Type.Object({
|
|
59
|
+
scans: Type.Number({ minimum: 0 }),
|
|
60
|
+
rankHistory: Type.Record(Type.String(), Type.Array(Type.Number())),
|
|
61
|
+
contribHistory: Type.Record(Type.String(), Type.Array(Type.Number())),
|
|
62
|
+
prevTopTokens: Type.Array(Type.String()),
|
|
63
|
+
});
|
|
64
|
+
// ─── Defaults ───────────────────────────────────────────────────────────────
|
|
65
|
+
const defaultConfig = {
|
|
66
|
+
topN: 50,
|
|
67
|
+
minRankJump: 5,
|
|
68
|
+
minTopTradersGain: 0.1,
|
|
69
|
+
minScansBeforeSignals: 2,
|
|
70
|
+
historyLimit: 60,
|
|
71
|
+
immediateJumpThreshold: 10,
|
|
72
|
+
deepClimbRankThreshold: 25,
|
|
73
|
+
contribExplosionMultiplier: 3.0,
|
|
74
|
+
contribAccelThreshold: 0.003,
|
|
75
|
+
minVelocityForDeepClimber: 0.0003,
|
|
76
|
+
erraticReversalThreshold: 5,
|
|
77
|
+
climbStreakScans: 3,
|
|
78
|
+
rankClimbThreshold: 3,
|
|
79
|
+
newEntryDeepMaxRank: 20,
|
|
80
|
+
newEntryMaxRank: 35,
|
|
81
|
+
firstJumpMinPrevRank: 30,
|
|
82
|
+
regimeScoreGating: false,
|
|
83
|
+
regimeScoreNeutral: 0.8,
|
|
84
|
+
regimeScoreDefault: 0.7,
|
|
85
|
+
minLeverage: 0,
|
|
86
|
+
};
|
|
87
|
+
const defaultState = {
|
|
88
|
+
scans: 0,
|
|
89
|
+
rankHistory: {},
|
|
90
|
+
contribHistory: {},
|
|
91
|
+
prevTopTokens: [],
|
|
92
|
+
};
|
|
93
|
+
// ─── Pure helpers (exported for testing) ────────────────────────────────────
|
|
94
|
+
/**
|
|
95
|
+
* Detects zigzag rank patterns that indicate market noise rather than a
|
|
96
|
+
* genuine trend.
|
|
97
|
+
*
|
|
98
|
+
* Scans consecutive rank deltas for direction reversals that exceed the
|
|
99
|
+
* threshold (e.g. improving 10 ranks then dropping 8). Such zigzags suggest
|
|
100
|
+
* the token is bouncing rather than sustainably climbing, and are used to
|
|
101
|
+
* downgrade IMMEDIATE_MOVER signals to the lower-priority DEEP_CLIMBER.
|
|
102
|
+
*
|
|
103
|
+
* @param excludeLast - When `true`, the final entry (this scan's rank) is
|
|
104
|
+
* removed before checking. This is critical for big-jump signals: the
|
|
105
|
+
* jump itself is the signal, not noise, so only the *pre-jump* history
|
|
106
|
+
* should be evaluated for erratic behaviour.
|
|
107
|
+
*/
|
|
108
|
+
export function isErraticHistory(rankHistory, erraticReversalThreshold, excludeLast = false) {
|
|
109
|
+
let nums = rankHistory.filter((r) => r !== null);
|
|
110
|
+
if (excludeLast && nums.length > 1) {
|
|
111
|
+
nums = nums.slice(0, -1);
|
|
112
|
+
}
|
|
113
|
+
if (nums.length < 3)
|
|
114
|
+
return false;
|
|
115
|
+
for (let i = 1; i < nums.length - 1; i++) {
|
|
116
|
+
const prevDelta = nums[i] - nums[i - 1];
|
|
117
|
+
const nextDelta = nums[i + 1] - nums[i];
|
|
118
|
+
if (prevDelta < 0 && nextDelta > erraticReversalThreshold)
|
|
119
|
+
return true;
|
|
120
|
+
if (prevDelta > 0 && nextDelta < -erraticReversalThreshold)
|
|
121
|
+
return true;
|
|
122
|
+
}
|
|
123
|
+
return false;
|
|
124
|
+
}
|
|
125
|
+
/**
|
|
126
|
+
* Computes average contribution change per scan over the given window.
|
|
127
|
+
*
|
|
128
|
+
* Uses (last - first) / (length - 1) rather than summing individual deltas,
|
|
129
|
+
* which is algebraically identical but avoids floating-point accumulation.
|
|
130
|
+
* A positive result means contribution is trending up; the velocity gate
|
|
131
|
+
* uses this to filter out tokens whose rank improvement is not backed by
|
|
132
|
+
* growing smart-money concentration.
|
|
133
|
+
*/
|
|
134
|
+
export function computeContribVelocity(contribs) {
|
|
135
|
+
if (contribs.length < 2)
|
|
136
|
+
return 0;
|
|
137
|
+
const total = contribs[contribs.length - 1] - contribs[0];
|
|
138
|
+
return total / (contribs.length - 1);
|
|
139
|
+
}
|
|
140
|
+
/**
|
|
141
|
+
* Classifies an alert candidate into a signal type and applies quality
|
|
142
|
+
* filters (erratic history, velocity gate).
|
|
143
|
+
*
|
|
144
|
+
* The classification pipeline is:
|
|
145
|
+
* 1. **Resolve signal type** from detection flags (priority order).
|
|
146
|
+
* 2. **Check erratic history** — zigzag rank patterns indicate noise.
|
|
147
|
+
* 3. **Check velocity gate** — contribution velocity must be positive
|
|
148
|
+
* (IMMEDIATE/FIRST_JUMP) or above a minimum (DEEP_CLIMBER).
|
|
149
|
+
* 4. **Apply downgrades** — IMMEDIATE_MOVER is demoted to DEEP_CLIMBER
|
|
150
|
+
* when erratic or low-velocity. FIRST_JUMP and CONTRIB_EXPLOSION are
|
|
151
|
+
* immune to downgrades because they represent decisive, one-scan events
|
|
152
|
+
* where historical noise is irrelevant.
|
|
153
|
+
*
|
|
154
|
+
* @returns Classification result, or `null` if the candidate has no reasons
|
|
155
|
+
* (should not happen if called from {@link analyzeMarket}).
|
|
156
|
+
*/
|
|
157
|
+
export function classifySignal(alert, config) {
|
|
158
|
+
if (alert.reasons.length === 0)
|
|
159
|
+
return null;
|
|
160
|
+
let signalType = resolveSignalType(alert);
|
|
161
|
+
const erratic = checkErratic(alert, config);
|
|
162
|
+
const lowVelocity = checkLowVelocity(alert, config);
|
|
163
|
+
// FIRST_JUMP and CONTRIB_EXPLOSION are never downgraded
|
|
164
|
+
if (signalType === "IMMEDIATE_MOVER" && (erratic || lowVelocity)) {
|
|
165
|
+
signalType = "DEEP_CLIMBER";
|
|
166
|
+
}
|
|
167
|
+
return {
|
|
168
|
+
signalType,
|
|
169
|
+
priority: SIGNAL_PRIORITY[signalType],
|
|
170
|
+
conviction: SIGNAL_CONVICTION[signalType],
|
|
171
|
+
erratic,
|
|
172
|
+
lowVelocity,
|
|
173
|
+
};
|
|
174
|
+
}
|
|
175
|
+
// ─── Signal type resolution ─────────────────────────────────────────────────
|
|
176
|
+
/**
|
|
177
|
+
* Maps detection flags to the highest-priority applicable signal type.
|
|
178
|
+
*
|
|
179
|
+
* The check order is deliberate:
|
|
180
|
+
* - FIRST_JUMP before everything — it's the strongest "catch it early" signal.
|
|
181
|
+
* - CONTRIB_EXPLOSION before IMMEDIATE — contribution spike is independent of rank.
|
|
182
|
+
* - NEW_ENTRY_DEEP before IMMEDIATE — a brand-new token appearing in the top 20
|
|
183
|
+
* is semantically different from an existing token jumping ranks.
|
|
184
|
+
* - IMMEDIATE_MOVER before DEEP_CLIMBER — larger, faster jumps rank higher.
|
|
185
|
+
* - DEEP_CLIMBER is the fallback for any remaining alert with reasons.
|
|
186
|
+
*/
|
|
187
|
+
function resolveSignalType(alert) {
|
|
188
|
+
if (alert.isFirstJump)
|
|
189
|
+
return "FIRST_JUMP";
|
|
190
|
+
if (alert.isContribExplosion)
|
|
191
|
+
return "CONTRIB_EXPLOSION";
|
|
192
|
+
if (alert.isDeepClimber && alert.reasons.some((r) => r.includes("NEW_ENTRY_DEEP"))) {
|
|
193
|
+
return "NEW_ENTRY_DEEP";
|
|
194
|
+
}
|
|
195
|
+
if (alert.isImmediate)
|
|
196
|
+
return "IMMEDIATE_MOVER";
|
|
197
|
+
return "DEEP_CLIMBER";
|
|
198
|
+
}
|
|
199
|
+
/**
|
|
200
|
+
* Determines whether the alert's rank history is erratic (zigzag).
|
|
201
|
+
*
|
|
202
|
+
* CONTRIB_EXPLOSION is exempt because the contribution spike itself is the
|
|
203
|
+
* signal — past rank noise doesn't diminish it. For big single-scan jumps
|
|
204
|
+
* (IMMEDIATE/FIRST_JUMP), only the *pre-jump* history is checked because
|
|
205
|
+
* the jump itself shouldn't count as a reversal.
|
|
206
|
+
*/
|
|
207
|
+
function checkErratic(alert, cfg) {
|
|
208
|
+
if (alert.isContribExplosion)
|
|
209
|
+
return false;
|
|
210
|
+
const excludeLast = alert.rankJumpThisScan >= cfg.immediateJumpThreshold || alert.isFirstJump;
|
|
211
|
+
return isErraticHistory(alert.rankHistoryWindow, cfg.erraticReversalThreshold, excludeLast);
|
|
212
|
+
}
|
|
213
|
+
/**
|
|
214
|
+
* Determines whether the alert's contribution velocity is too low.
|
|
215
|
+
*
|
|
216
|
+
* The threshold is split by urgency:
|
|
217
|
+
* - IMMEDIATE/FIRST_JUMP only require velocity > 0. These are one-scan
|
|
218
|
+
* signals where velocity hasn't had time to build; demanding more
|
|
219
|
+
* would filter out exactly the early entries the scanner is designed to catch.
|
|
220
|
+
* - DEEP_CLIMBER (slower, multi-scan) requires velocity >= `minVelocityForDeepClimber`
|
|
221
|
+
* to confirm that the rank improvement is backed by growing SM concentration.
|
|
222
|
+
*/
|
|
223
|
+
function checkLowVelocity(alert, cfg) {
|
|
224
|
+
if (alert.isImmediate || alert.isFirstJump)
|
|
225
|
+
return alert.contribVelocity <= 0;
|
|
226
|
+
return alert.contribVelocity < cfg.minVelocityForDeepClimber;
|
|
227
|
+
}
|
|
228
|
+
// ─── Detection rules ────────────────────────────────────────────────────────
|
|
229
|
+
// Each rule inspects the immutable MarketContext and appends reasons / flips
|
|
230
|
+
// flags in the mutable DetectionFlags. Rules are ordered to match the Python
|
|
231
|
+
// v4 detection pipeline so that flag interactions (e.g. isImmediate being set
|
|
232
|
+
// before FIRST_JUMP is checked) are preserved.
|
|
233
|
+
/**
|
|
234
|
+
* Rule 1 — Fresh entry detection.
|
|
235
|
+
*
|
|
236
|
+
* Fires when the token was not in the previous scan's top-N (`prevRank === null`).
|
|
237
|
+
* If the token lands directly in the top 20, it's flagged as NEW_ENTRY_DEEP
|
|
238
|
+
* with `isImmediate` (the market didn't gradually climb — it appeared strong).
|
|
239
|
+
* Tokens landing between #20 and #35 get a lower-priority NEW_ENTRY tag.
|
|
240
|
+
*/
|
|
241
|
+
function detectNewEntry(mctx, cfg, flags) {
|
|
242
|
+
if (mctx.prevRank !== null)
|
|
243
|
+
return;
|
|
244
|
+
if (mctx.rank <= cfg.newEntryDeepMaxRank) {
|
|
245
|
+
flags.reasons.push(`NEW_ENTRY_DEEP at rank #${mctx.rank}`);
|
|
246
|
+
flags.isDeepClimber = true;
|
|
247
|
+
flags.isImmediate = true;
|
|
248
|
+
}
|
|
249
|
+
else if (mctx.rank <= cfg.newEntryMaxRank) {
|
|
250
|
+
flags.reasons.push(`NEW_ENTRY at rank #${mctx.rank}`);
|
|
251
|
+
}
|
|
252
|
+
}
|
|
253
|
+
/**
|
|
254
|
+
* Rule 2 — Single-scan rank jump detection.
|
|
255
|
+
*
|
|
256
|
+
* Compares the token's rank in this scan vs. the previous one. Three tiers:
|
|
257
|
+
*
|
|
258
|
+
* - **FIRST_JUMP**: jump >= `immediateJumpThreshold` from >= `deepClimbRankThreshold`,
|
|
259
|
+
* AND the token was either absent from the previous top-N or ranked >= `firstJumpMinPrevRank`.
|
|
260
|
+
* This is the highest-priority signal — it catches tokens accelerating into
|
|
261
|
+
* the leaderboard before they're widely noticed.
|
|
262
|
+
*
|
|
263
|
+
* - **IMMEDIATE_MOVER**: same jump magnitude but the token was already tracked
|
|
264
|
+
* in the top-N at a rank < `firstJumpMinPrevRank` — still a strong signal
|
|
265
|
+
* but with less surprise value than FIRST_JUMP.
|
|
266
|
+
*
|
|
267
|
+
* - **DEEP_CLIMBER**: jump >= `minRankJump` from >= `deepClimbRankThreshold`.
|
|
268
|
+
* Moderate but notable improvement from deep in the list.
|
|
269
|
+
*
|
|
270
|
+
* Also records a RANK_UP reason for any jump >= 2 (informational, even if no
|
|
271
|
+
* signal type is triggered).
|
|
272
|
+
*/
|
|
273
|
+
function detectSingleScanJump(mctx, cfg, flags, prevTopTokenSet) {
|
|
274
|
+
if (mctx.prevRank === null)
|
|
275
|
+
return;
|
|
276
|
+
const jump = mctx.prevRank - mctx.rank;
|
|
277
|
+
flags.rankJumpThisScan = jump;
|
|
278
|
+
if (jump >= 2) {
|
|
279
|
+
flags.reasons.push(`RANK_UP +${jump} (#${mctx.prevRank}→#${mctx.rank})`);
|
|
280
|
+
}
|
|
281
|
+
if (jump >= cfg.immediateJumpThreshold && mctx.prevRank >= cfg.deepClimbRankThreshold) {
|
|
282
|
+
flags.isDeepClimber = true;
|
|
283
|
+
flags.isImmediate = true;
|
|
284
|
+
flags.reasons.push(`IMMEDIATE_MOVER +${jump} from #${mctx.prevRank} in ONE scan`);
|
|
285
|
+
const wasInPrev = prevTopTokenSet.has(mctx.tk);
|
|
286
|
+
if (!wasInPrev || mctx.prevRank >= cfg.firstJumpMinPrevRank) {
|
|
287
|
+
flags.isFirstJump = true;
|
|
288
|
+
flags.reasons.push(`FIRST_JUMP from #${mctx.prevRank}→#${mctx.rank} — highest priority`);
|
|
289
|
+
}
|
|
290
|
+
}
|
|
291
|
+
else if (jump >= cfg.minRankJump && mctx.prevRank >= cfg.deepClimbRankThreshold) {
|
|
292
|
+
flags.isDeepClimber = true;
|
|
293
|
+
flags.reasons.push(`DEEP_CLIMBER +${jump} from #${mctx.prevRank}`);
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
/**
|
|
297
|
+
* Rule 3 — Contribution explosion detection.
|
|
298
|
+
*
|
|
299
|
+
* Fires when the token's `pctOfTopTradersGain` increased by
|
|
300
|
+
* `contribExplosionMultiplier`x or more in a single scan. This indicates a
|
|
301
|
+
* sudden concentration of smart-money capital into the token — often the
|
|
302
|
+
* earliest sign of a coordinated move.
|
|
303
|
+
*
|
|
304
|
+
* When the explosion originates from a deep position (prevRank >= 20),
|
|
305
|
+
* the alert is also flagged as immediate/deep-climber to boost its priority.
|
|
306
|
+
*/
|
|
307
|
+
function detectContribExplosion(mctx, cfg, flags) {
|
|
308
|
+
if (mctx.prevContrib === null || mctx.prevContrib <= 0)
|
|
309
|
+
return;
|
|
310
|
+
const ratio = mctx.currentContrib / mctx.prevContrib;
|
|
311
|
+
if (ratio < cfg.contribExplosionMultiplier)
|
|
312
|
+
return;
|
|
313
|
+
const prev = (mctx.prevContrib * 100).toFixed(2);
|
|
314
|
+
const curr = (mctx.currentContrib * 100).toFixed(2);
|
|
315
|
+
flags.reasons.push(`CONTRIB_EXPLOSION ${ratio.toFixed(1)}x in one scan (${prev}→${curr})`);
|
|
316
|
+
flags.isContribExplosion = true;
|
|
317
|
+
if (mctx.prevRank !== null && mctx.prevRank >= cfg.newEntryDeepMaxRank) {
|
|
318
|
+
flags.isImmediate = true;
|
|
319
|
+
flags.isDeepClimber = true;
|
|
320
|
+
}
|
|
321
|
+
}
|
|
322
|
+
/**
|
|
323
|
+
* Rule 4 — Multi-scan cumulative climb detection.
|
|
324
|
+
*
|
|
325
|
+
* Looks back up to 5 scans and compares the oldest available rank to the
|
|
326
|
+
* current one. A total climb >= `rankClimbThreshold` adds a CLIMBING reason.
|
|
327
|
+
*
|
|
328
|
+
* If the cumulative climb is large enough (>= `immediateJumpThreshold`) and
|
|
329
|
+
* started from rank >= 30, it additionally flags as DEEP_CLIMBER — the token
|
|
330
|
+
* may not have jumped dramatically in any single scan but has been steadily
|
|
331
|
+
* rising from the depths.
|
|
332
|
+
*/
|
|
333
|
+
function detectMultiScanClimb(mctx, cfg, flags) {
|
|
334
|
+
const lookback = Math.min(mctx.prevRankHistory.length, 5);
|
|
335
|
+
if (lookback < 1)
|
|
336
|
+
return;
|
|
337
|
+
const oldRank = mctx.prevRankHistory[mctx.prevRankHistory.length - lookback];
|
|
338
|
+
if (oldRank === undefined)
|
|
339
|
+
return;
|
|
340
|
+
const totalClimb = oldRank - mctx.rank;
|
|
341
|
+
if (totalClimb >= cfg.rankClimbThreshold) {
|
|
342
|
+
flags.reasons.push(`CLIMBING +${totalClimb} over ${lookback} scans`);
|
|
343
|
+
}
|
|
344
|
+
if (totalClimb >= cfg.immediateJumpThreshold && oldRank >= 30) {
|
|
345
|
+
flags.isDeepClimber = true;
|
|
346
|
+
const alreadyTagged = flags.reasons.some((r) => r.includes("DEEP_CLIMBER") || r.includes("IMMEDIATE"));
|
|
347
|
+
if (!alreadyTagged) {
|
|
348
|
+
flags.reasons.push(`DEEP_CLIMBER +${totalClimb} from #${oldRank} over ${lookback} scans`);
|
|
349
|
+
}
|
|
350
|
+
}
|
|
351
|
+
}
|
|
352
|
+
/**
|
|
353
|
+
* Rule 5 — Single-scan contribution acceleration.
|
|
354
|
+
*
|
|
355
|
+
* Fires when the contribution delta (current - previous) exceeds
|
|
356
|
+
* `contribAccelThreshold`. Unlike the explosion rule (which checks
|
|
357
|
+
* *ratios*), this catches absolute contribution increases — relevant
|
|
358
|
+
* when the previous contribution was already substantial.
|
|
359
|
+
*/
|
|
360
|
+
function detectContribAccel(mctx, cfg, flags) {
|
|
361
|
+
if (mctx.prevContrib === null)
|
|
362
|
+
return;
|
|
363
|
+
const delta = mctx.currentContrib - mctx.prevContrib;
|
|
364
|
+
if (delta >= cfg.contribAccelThreshold) {
|
|
365
|
+
flags.reasons.push(`ACCEL +${delta.toFixed(3)} contribution`);
|
|
366
|
+
}
|
|
367
|
+
}
|
|
368
|
+
/**
|
|
369
|
+
* Rule 6 — Consistent climb streak detection.
|
|
370
|
+
*
|
|
371
|
+
* Checks whether the token's rank has improved (or stayed equal) in every
|
|
372
|
+
* scan over the last `climbStreakScans + 1` data points (including the current
|
|
373
|
+
* scan). A monotonically improving rank over multiple scans is a strong
|
|
374
|
+
* trend signal even when no single scan's jump is dramatic.
|
|
375
|
+
*/
|
|
376
|
+
function detectClimbStreak(mctx, cfg, flags) {
|
|
377
|
+
if (mctx.prevRankHistory.length < cfg.climbStreakScans)
|
|
378
|
+
return;
|
|
379
|
+
const window = [...mctx.prevRankHistory.slice(-cfg.climbStreakScans), mctx.rank];
|
|
380
|
+
const isMonotonic = window.every((r, i) => i === 0 || window[i - 1] >= r);
|
|
381
|
+
const totalGain = window[0] - window[window.length - 1];
|
|
382
|
+
if (isMonotonic && totalGain >= 2) {
|
|
383
|
+
flags.reasons.push(`STREAK climbing ${totalGain} ranks over ${window.length} checks`);
|
|
384
|
+
}
|
|
385
|
+
}
|
|
386
|
+
/**
|
|
387
|
+
* Rule 7 — Contribution velocity as a standalone signal.
|
|
388
|
+
*
|
|
389
|
+
* Computes the average contribution change per scan over the last 5 scans.
|
|
390
|
+
* If velocity exceeds 0.2%/scan, at least 3 data points are available, and
|
|
391
|
+
* *no other detection rule has fired*, a VELOCITY reason is emitted.
|
|
392
|
+
*
|
|
393
|
+
* This acts as a catch-all for tokens that are gaining SM concentration
|
|
394
|
+
* gradually — not enough for an explosion or a big rank jump, but sustained
|
|
395
|
+
* enough to be noteworthy.
|
|
396
|
+
*
|
|
397
|
+
* @returns The computed velocity value (always returned, regardless of whether
|
|
398
|
+
* a reason was added) so that the caller can attach it to the alert.
|
|
399
|
+
*/
|
|
400
|
+
function detectVelocitySignal(mctx, flags) {
|
|
401
|
+
const recent = [...mctx.prevContribHistory.slice(-5), mctx.currentContrib];
|
|
402
|
+
const velocity = computeContribVelocity(recent);
|
|
403
|
+
if (velocity > 0.002 && recent.length >= 3 && flags.reasons.length === 0) {
|
|
404
|
+
flags.reasons.push(`VELOCITY +${(velocity * 100).toFixed(3)}%/scan sustained`);
|
|
405
|
+
}
|
|
406
|
+
return velocity;
|
|
407
|
+
}
|
|
408
|
+
// ─── Market analysis pipeline ───────────────────────────────────────────────
|
|
409
|
+
/**
|
|
410
|
+
* Runs all detection rules against a single ranked market.
|
|
411
|
+
*
|
|
412
|
+
* This is the core of the scanner: it initialises empty detection flags,
|
|
413
|
+
* runs each rule in sequence (order matters — flags set by earlier rules
|
|
414
|
+
* affect later ones), computes contribution velocity, and assembles the
|
|
415
|
+
* full {@link AlertCandidate} if any reasons were found.
|
|
416
|
+
*
|
|
417
|
+
* @returns An alert candidate, or `null` if no detection rule fired.
|
|
418
|
+
*/
|
|
419
|
+
function analyzeMarket(mctx, cfg, prevTopTokenSet) {
|
|
420
|
+
const flags = {
|
|
421
|
+
reasons: [],
|
|
422
|
+
isDeepClimber: false,
|
|
423
|
+
isImmediate: false,
|
|
424
|
+
isFirstJump: false,
|
|
425
|
+
isContribExplosion: false,
|
|
426
|
+
rankJumpThisScan: 0,
|
|
427
|
+
};
|
|
428
|
+
detectNewEntry(mctx, cfg, flags);
|
|
429
|
+
detectSingleScanJump(mctx, cfg, flags, prevTopTokenSet);
|
|
430
|
+
detectContribExplosion(mctx, cfg, flags);
|
|
431
|
+
detectMultiScanClimb(mctx, cfg, flags);
|
|
432
|
+
detectContribAccel(mctx, cfg, flags);
|
|
433
|
+
detectClimbStreak(mctx, cfg, flags);
|
|
434
|
+
const contribVelocity = detectVelocitySignal(mctx, flags);
|
|
435
|
+
if (flags.reasons.length === 0)
|
|
436
|
+
return null;
|
|
437
|
+
return {
|
|
438
|
+
token: mctx.market.token,
|
|
439
|
+
dex: mctx.market.dex,
|
|
440
|
+
direction: mctx.market.direction,
|
|
441
|
+
currentRank: mctx.rank,
|
|
442
|
+
currentContrib: mctx.currentContrib,
|
|
443
|
+
priceChg4h: mctx.market.tokenPriceChangePct4h,
|
|
444
|
+
traderCount: mctx.market.traderCount,
|
|
445
|
+
...flags,
|
|
446
|
+
contribVelocity,
|
|
447
|
+
rankHistoryWindow: [...mctx.prevRankHistory.slice(-5), mctx.rank],
|
|
448
|
+
contribHistoryWindow: [
|
|
449
|
+
...mctx.prevContribHistory.slice(-5).map((c) => Math.round(c * 10000) / 100),
|
|
450
|
+
Math.round(mctx.currentContrib * 10000) / 100,
|
|
451
|
+
],
|
|
452
|
+
};
|
|
453
|
+
}
|
|
454
|
+
// ─── Alert → Signal conversion ──────────────────────────────────────────────
|
|
455
|
+
/**
|
|
456
|
+
* Converts a classified alert candidate into the canonical {@link Signal} shape.
|
|
457
|
+
*
|
|
458
|
+
* Handles DEX-qualified asset naming (`xyz:TOKEN`), direction normalisation
|
|
459
|
+
* (the SM data uses lowercase strings, signals require "LONG" | "SHORT"),
|
|
460
|
+
* and packs all detection metadata into the signal's `factors` and `meta`
|
|
461
|
+
* fields for downstream transparency.
|
|
462
|
+
*
|
|
463
|
+
* @returns A signal, or `null` if classification returned null (defensive).
|
|
464
|
+
*/
|
|
465
|
+
function alertToSignal(alert, cfg, ctx) {
|
|
466
|
+
const classification = classifySignal(alert, cfg);
|
|
467
|
+
if (classification === null)
|
|
468
|
+
return null;
|
|
469
|
+
return {
|
|
470
|
+
address: ctx.address,
|
|
471
|
+
scannerId: ctx.scannerId,
|
|
472
|
+
signalType: classification.signalType,
|
|
473
|
+
asset: alert.dex === "xyz" ? `xyz:${alert.token}` : alert.token,
|
|
474
|
+
direction: alert.direction.toUpperCase() === "SHORT" ? "SHORT" : "LONG",
|
|
475
|
+
score: clamp01(classification.conviction),
|
|
476
|
+
timestamp: ctx.timestamp,
|
|
477
|
+
factors: {
|
|
478
|
+
rank_jump: alert.rankJumpThisScan > 0,
|
|
479
|
+
new_entry: alert.reasons.some((r) => r.startsWith("NEW_ENTRY")),
|
|
480
|
+
contrib_explosion: alert.isContribExplosion,
|
|
481
|
+
deep_climber: alert.isDeepClimber,
|
|
482
|
+
immediate_mover: alert.isImmediate,
|
|
483
|
+
first_jump: alert.isFirstJump,
|
|
484
|
+
erratic: classification.erratic,
|
|
485
|
+
low_velocity: classification.lowVelocity,
|
|
486
|
+
},
|
|
487
|
+
meta: {
|
|
488
|
+
signalPriority: classification.priority,
|
|
489
|
+
rank: alert.currentRank,
|
|
490
|
+
prevRank: alert.rankHistoryWindow.length > 1
|
|
491
|
+
? alert.rankHistoryWindow[alert.rankHistoryWindow.length - 2]
|
|
492
|
+
: null,
|
|
493
|
+
jump: alert.rankJumpThisScan,
|
|
494
|
+
dex: alert.dex ?? null,
|
|
495
|
+
direction: alert.direction,
|
|
496
|
+
topTradersGain: alert.currentContrib,
|
|
497
|
+
priceChg4h: alert.priceChg4h,
|
|
498
|
+
traderCount: alert.traderCount,
|
|
499
|
+
contribVelocity: Math.round(alert.contribVelocity * 10000) / 10000,
|
|
500
|
+
rankHistory: alert.rankHistoryWindow,
|
|
501
|
+
contribHistory: alert.contribHistoryWindow,
|
|
502
|
+
reasons: alert.reasons,
|
|
503
|
+
marketRegime: ctx.marketRegime ?? "UNKNOWN",
|
|
504
|
+
},
|
|
505
|
+
};
|
|
506
|
+
}
|
|
507
|
+
/**
|
|
508
|
+
* Sorts signals in-place by descending urgency.
|
|
509
|
+
*
|
|
510
|
+
* Primary key: signal priority (1 = most urgent).
|
|
511
|
+
* Secondary key: absolute contribution velocity (higher = more active).
|
|
512
|
+
* Tertiary key: number of detection reasons (more reasons = richer signal).
|
|
513
|
+
*
|
|
514
|
+
* This ordering ensures the action layer processes the most time-sensitive
|
|
515
|
+
* signals first when iterating through the FIFO queue.
|
|
516
|
+
*/
|
|
517
|
+
function sortByPriority(signals) {
|
|
518
|
+
signals.sort((a, b) => {
|
|
519
|
+
const pa = a.meta.signalPriority ?? 99;
|
|
520
|
+
const pb = b.meta.signalPriority ?? 99;
|
|
521
|
+
if (pa !== pb)
|
|
522
|
+
return pa - pb;
|
|
523
|
+
const va = Math.abs(a.meta.contribVelocity ?? 0);
|
|
524
|
+
const vb = Math.abs(b.meta.contribVelocity ?? 0);
|
|
525
|
+
if (va !== vb)
|
|
526
|
+
return vb - va;
|
|
527
|
+
return (b.meta.reasons ?? []).length - (a.meta.reasons ?? []).length;
|
|
528
|
+
});
|
|
529
|
+
}
|
|
530
|
+
/**
|
|
531
|
+
* Builds the scan result summary with per-signal-type counts.
|
|
532
|
+
*
|
|
533
|
+
* Exposed in the scan result's `summary` field for observability dashboards
|
|
534
|
+
* and health checks — operators can see at a glance which signal categories
|
|
535
|
+
* fired in a given scan without parsing individual signals.
|
|
536
|
+
*/
|
|
537
|
+
function buildSummary(signals, rankedCount, scans) {
|
|
538
|
+
return {
|
|
539
|
+
rankedCount,
|
|
540
|
+
scans,
|
|
541
|
+
signalBreakdown: {
|
|
542
|
+
firstJumps: signals.filter((s) => s.signalType === "FIRST_JUMP").length,
|
|
543
|
+
contribExplosions: signals.filter((s) => s.signalType === "CONTRIB_EXPLOSION").length,
|
|
544
|
+
immediateMovers: signals.filter((s) => s.signalType === "IMMEDIATE_MOVER").length,
|
|
545
|
+
newEntryDeep: signals.filter((s) => s.signalType === "NEW_ENTRY_DEEP").length,
|
|
546
|
+
deepClimbers: signals.filter((s) => s.signalType === "DEEP_CLIMBER").length,
|
|
547
|
+
},
|
|
548
|
+
};
|
|
549
|
+
}
|
|
550
|
+
// ─── Utility ────────────────────────────────────────────────────────────────
|
|
551
|
+
/**
|
|
552
|
+
* Fills in default values for optional config fields.
|
|
553
|
+
*
|
|
554
|
+
* v4 added many config knobs that are optional in the TypeBox schema (so
|
|
555
|
+
* existing YAML configs don't break). This function merges them with
|
|
556
|
+
* defaults so detection rules always see a fully populated config.
|
|
557
|
+
*/
|
|
558
|
+
function resolveConfig(config) {
|
|
559
|
+
return {
|
|
560
|
+
...defaultConfig,
|
|
561
|
+
...Object.fromEntries(Object.entries(config).filter(([, v]) => v !== undefined)),
|
|
562
|
+
};
|
|
563
|
+
}
|
|
564
|
+
/**
|
|
565
|
+
* Produces a unique key for a market, incorporating the DEX when present.
|
|
566
|
+
*
|
|
567
|
+
* The same token symbol can appear on multiple DEXes (e.g. "SOL" on main
|
|
568
|
+
* vs. "SOL" on xyz). Keying history by `dex:token` prevents cross-DEX
|
|
569
|
+
* rank comparisons that would produce spurious signals.
|
|
570
|
+
*/
|
|
571
|
+
function tokenKey(market) {
|
|
572
|
+
return market.dex ? `${market.dex}:${market.token}` : market.token;
|
|
573
|
+
}
|
|
574
|
+
/**
|
|
575
|
+
* Filters out malformed market rows that would cause NaN in scoring.
|
|
576
|
+
*
|
|
577
|
+
* The SM data provider may return rows with missing or non-numeric fields
|
|
578
|
+
* (e.g. newly listed tokens with incomplete data). These are silently
|
|
579
|
+
* dropped to avoid polluting rank calculations.
|
|
580
|
+
*/
|
|
581
|
+
function normalizeMarkets(markets) {
|
|
582
|
+
return markets.filter((m) => typeof m.token === "string" && m.token.length > 0 &&
|
|
583
|
+
Number.isFinite(m.pctOfTopTradersGain) && Number.isFinite(m.traderCount));
|
|
584
|
+
}
|
|
585
|
+
/** Returns the last element of an array, or `null` if empty. */
|
|
586
|
+
function lastOf(arr) {
|
|
587
|
+
return arr.length > 0 ? arr[arr.length - 1] : null;
|
|
588
|
+
}
|
|
589
|
+
// ─── Scanner factory ────────────────────────────────────────────────────────
|
|
590
|
+
/**
|
|
591
|
+
* Creates the emerging-movers scanner instance (v4).
|
|
592
|
+
*
|
|
593
|
+
* This is the entry point registered in the scanner registry. It produces a
|
|
594
|
+
* stateful scanner that:
|
|
595
|
+
* 1. Ranks markets by `pctOfTopTradersGain` and tracks the top N.
|
|
596
|
+
* 2. Maintains per-token rank and contribution history across scans.
|
|
597
|
+
* 3. Runs 7 detection rules per market to identify emerging movers.
|
|
598
|
+
* 4. Classifies alerts into 5 signal types with conviction scoring.
|
|
599
|
+
* 5. Applies erratic-history and velocity-gate filters to reduce noise.
|
|
600
|
+
*/
|
|
601
|
+
export function emergingMoversScanner() {
|
|
602
|
+
return createScanner({
|
|
603
|
+
id: "emerging-movers",
|
|
604
|
+
signalType: "EMERGING_MOVER",
|
|
605
|
+
description: "Detects top-trader rank movers with persistent local history (v4: multi-signal).",
|
|
606
|
+
intervalSeconds: 180,
|
|
607
|
+
retention: RetentionPolicy.ROLLING_WINDOW,
|
|
608
|
+
retentionMaxRuns: 300,
|
|
609
|
+
persistence: PersistencePolicy.ACTIONABLE_ONLY,
|
|
610
|
+
inputs: [
|
|
611
|
+
providerInput.smData(),
|
|
612
|
+
providerInput.instruments(),
|
|
613
|
+
consumeSharedArtifact(marketRegimeArtifact, {
|
|
614
|
+
required: false,
|
|
615
|
+
maxAgeMs: 7_200_000,
|
|
616
|
+
}),
|
|
617
|
+
],
|
|
618
|
+
configSchema: emergingMoversConfigSchema,
|
|
619
|
+
defaultConfig,
|
|
620
|
+
stateSchema: emergingMoversStateSchema,
|
|
621
|
+
defaultState,
|
|
622
|
+
hooks: {
|
|
623
|
+
isActionable: (signal, ctx) => {
|
|
624
|
+
const cfg = resolveConfig(ctx.config);
|
|
625
|
+
// 2A: Regime-aware score gating (disabled by default)
|
|
626
|
+
if (cfg.regimeScoreGating) {
|
|
627
|
+
const regime = ctx.inputs.artifacts?.["market-regime"];
|
|
628
|
+
const threshold = regime?.regime === MarketRegime.NEUTRAL
|
|
629
|
+
? cfg.regimeScoreNeutral
|
|
630
|
+
: cfg.regimeScoreDefault;
|
|
631
|
+
if (signal.score < threshold)
|
|
632
|
+
return false;
|
|
633
|
+
}
|
|
634
|
+
// 2D: Leverage floor (disabled when minLeverage = 0)
|
|
635
|
+
if (cfg.minLeverage > 0 && ctx.inputs.instruments) {
|
|
636
|
+
const instrument = ctx.inputs.instruments.find((i) => i.name === signal.asset);
|
|
637
|
+
if (instrument && instrument.maxLeverage < cfg.minLeverage) {
|
|
638
|
+
return false;
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
return true;
|
|
642
|
+
},
|
|
643
|
+
},
|
|
644
|
+
scan: async (ctx) => {
|
|
645
|
+
const now = ctx.now();
|
|
646
|
+
const state = ctx.state;
|
|
647
|
+
const cfg = resolveConfig(ctx.config);
|
|
648
|
+
const ranked = normalizeMarkets(ctx.inputs.smData ?? [])
|
|
649
|
+
.sort((a, b) => b.pctOfTopTradersGain - a.pctOfTopTradersGain)
|
|
650
|
+
.slice(0, cfg.topN);
|
|
651
|
+
const prevTopTokenSet = new Set(state.prevTopTokens);
|
|
652
|
+
const nextState = {
|
|
653
|
+
scans: state.scans + 1,
|
|
654
|
+
rankHistory: { ...state.rankHistory },
|
|
655
|
+
contribHistory: { ...state.contribHistory },
|
|
656
|
+
prevTopTokens: ranked.map(tokenKey),
|
|
657
|
+
};
|
|
658
|
+
// Analyze each ranked market and collect alerts
|
|
659
|
+
const alerts = [];
|
|
660
|
+
for (let i = 0; i < ranked.length; i++) {
|
|
661
|
+
const market = ranked[i];
|
|
662
|
+
const tk = tokenKey(market);
|
|
663
|
+
const prevRankHistory = state.rankHistory[tk] ?? [];
|
|
664
|
+
const prevContribHistory = state.contribHistory[tk] ?? [];
|
|
665
|
+
const rank = i + 1;
|
|
666
|
+
// Update state histories
|
|
667
|
+
nextState.rankHistory[tk] = [...prevRankHistory, rank].slice(-cfg.historyLimit);
|
|
668
|
+
nextState.contribHistory[tk] = [...prevContribHistory, market.pctOfTopTradersGain].slice(-cfg.historyLimit);
|
|
669
|
+
if (nextState.scans < cfg.minScansBeforeSignals)
|
|
670
|
+
continue;
|
|
671
|
+
const mctx = {
|
|
672
|
+
market,
|
|
673
|
+
tk,
|
|
674
|
+
rank,
|
|
675
|
+
currentContrib: market.pctOfTopTradersGain,
|
|
676
|
+
prevRank: lastOf(prevRankHistory),
|
|
677
|
+
prevContrib: lastOf(prevContribHistory),
|
|
678
|
+
prevRankHistory,
|
|
679
|
+
prevContribHistory,
|
|
680
|
+
};
|
|
681
|
+
const alert = analyzeMarket(mctx, cfg, prevTopTokenSet);
|
|
682
|
+
if (alert)
|
|
683
|
+
alerts.push(alert);
|
|
684
|
+
}
|
|
685
|
+
// Convert alerts to signals, sort, persist
|
|
686
|
+
const regimeArtifact = ctx.inputs.artifacts?.["market-regime"];
|
|
687
|
+
const marketRegime = regimeArtifact?.regime;
|
|
688
|
+
const signals = alerts
|
|
689
|
+
.map((a) => alertToSignal(a, cfg, { address: ctx.address, scannerId: ctx.scannerId, timestamp: now, marketRegime }))
|
|
690
|
+
.filter((s) => s !== null);
|
|
691
|
+
sortByPriority(signals);
|
|
692
|
+
await ctx.setState(nextState);
|
|
693
|
+
return {
|
|
694
|
+
timestamp: now,
|
|
695
|
+
scannedCount: ranked.length,
|
|
696
|
+
signals,
|
|
697
|
+
summary: buildSummary(signals, ranked.length, nextState.scans),
|
|
698
|
+
};
|
|
699
|
+
},
|
|
700
|
+
});
|
|
701
|
+
}
|
|
702
|
+
//# sourceMappingURL=emerging-movers.js.map
|