dexbot 1.4.17 → 1.4.18
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/CHANGELOG.md +6 -0
- package/analysis/README.md +43 -43
- package/analysis/ama_fitting/README.md +8 -8
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/bot_fitting/README.md +4 -4
- package/analysis/bot_fitting/backtest_ama_sweep.ts +1 -1
- package/analysis/bot_usage/discover_bot_accounts.ts +11 -11
- package/analysis/tradingview/README.md +4 -4
- package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +2 -2
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +1 -1
- package/analysis/trend_detection/package.json +1 -1
- package/claw/README.md +45 -45
- package/claw/docs/AI_BOT_LIBRARY_API.md +6 -6
- package/claw/examples/short_mpa_bts_strategy.ts +3 -3
- package/claw/package.json +21 -21
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/scripts/claw_bridge.ts +2 -2
- package/claw/scripts/claw_skill_md.ts +1 -1
- package/claw/skills/launcher-ops/references/launcher-workflow.md +5 -5
- package/claw/skills/margin-trading/references/honest-asset-list.md +1 -1
- package/claw/skills/memu-memory/SKILL.md +3 -3
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts +3 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.js +148 -0
- package/dist/analysis/ama_fitting/analyze_ama_price_changes.js.map +1 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts +12 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +389 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts +3 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.js +200 -0
- package/dist/analysis/ama_fitting/calibrate_convergence_er.js.map +1 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts +3 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.js +213 -0
- package/dist/analysis/ama_fitting/fetch_lp_candles.js.map +1 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts +25 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js +247 -0
- package/dist/analysis/ama_fitting/generate_unified_comparison_chart.js.map +1 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts +35 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.d.ts.map +1 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.js +782 -0
- package/dist/analysis/ama_fitting/optimizer_high_resolution.js.map +1 -0
- package/dist/analysis/analyze_derivatives.d.ts +86 -0
- package/dist/analysis/analyze_derivatives.d.ts.map +1 -0
- package/dist/analysis/analyze_derivatives.js +285 -0
- package/dist/analysis/analyze_derivatives.js.map +1 -0
- package/dist/analysis/analyze_dynamic_weight.d.ts +14 -0
- package/dist/analysis/analyze_dynamic_weight.d.ts.map +1 -0
- package/dist/analysis/analyze_dynamic_weight.js +197 -0
- package/dist/analysis/analyze_dynamic_weight.js.map +1 -0
- package/dist/analysis/analyze_kalman.d.ts +13 -0
- package/dist/analysis/analyze_kalman.d.ts.map +1 -0
- package/dist/analysis/analyze_kalman.js +118 -0
- package/dist/analysis/analyze_kalman.js.map +1 -0
- package/dist/analysis/analyze_regime.d.ts +20 -0
- package/dist/analysis/analyze_regime.d.ts.map +1 -0
- package/dist/analysis/analyze_regime.js +139 -0
- package/dist/analysis/analyze_regime.js.map +1 -0
- package/dist/analysis/analyze_regime_windows.d.ts +19 -0
- package/dist/analysis/analyze_regime_windows.d.ts.map +1 -0
- package/dist/analysis/analyze_regime_windows.js +414 -0
- package/dist/analysis/analyze_regime_windows.js.map +1 -0
- package/dist/analysis/analyze_risk_profile.d.ts +3 -0
- package/dist/analysis/analyze_risk_profile.d.ts.map +1 -0
- package/dist/analysis/analyze_risk_profile.js +186 -0
- package/dist/analysis/analyze_risk_profile.js.map +1 -0
- package/dist/analysis/analyze_trade_heatmap.d.ts +3 -0
- package/dist/analysis/analyze_trade_heatmap.d.ts.map +1 -0
- package/dist/analysis/analyze_trade_heatmap.js +358 -0
- package/dist/analysis/analyze_trade_heatmap.js.map +1 -0
- package/dist/analysis/analyze_volatility.d.ts +22 -0
- package/dist/analysis/analyze_volatility.d.ts.map +1 -0
- package/dist/analysis/analyze_volatility.js +165 -0
- package/dist/analysis/analyze_volatility.js.map +1 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts +91 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.js +792 -0
- package/dist/analysis/bot_fitting/backtest_ama_sweep.js.map +1 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts +2 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.js +380 -0
- package/dist/analysis/bot_fitting/backtest_bot_fitting.js.map +1 -0
- package/dist/analysis/bot_fitting/shared_utils.d.ts +26 -0
- package/dist/analysis/bot_fitting/shared_utils.d.ts.map +1 -0
- package/dist/analysis/bot_fitting/shared_utils.js +42 -0
- package/dist/analysis/bot_fitting/shared_utils.js.map +1 -0
- package/dist/analysis/bot_key_utils.d.ts +11 -0
- package/dist/analysis/bot_key_utils.d.ts.map +1 -0
- package/dist/analysis/bot_key_utils.js +153 -0
- package/dist/analysis/bot_key_utils.js.map +1 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.d.ts +3 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.d.ts.map +1 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.js +499 -0
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +531 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.js +399 -0
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -0
- package/dist/analysis/chart_css.d.ts +17 -0
- package/dist/analysis/chart_css.d.ts.map +1 -0
- package/dist/analysis/chart_css.js +49 -0
- package/dist/analysis/chart_css.js.map +1 -0
- package/dist/analysis/chart_ui.d.ts +45 -0
- package/dist/analysis/chart_ui.d.ts.map +1 -0
- package/dist/analysis/chart_ui.js +97 -0
- package/dist/analysis/chart_ui.js.map +1 -0
- package/dist/analysis/chart_utils.d.ts +27 -0
- package/dist/analysis/chart_utils.d.ts.map +1 -0
- package/dist/analysis/chart_utils.js +153 -0
- package/dist/analysis/chart_utils.js.map +1 -0
- package/dist/analysis/derivative_chart_generator.d.ts +12 -0
- package/dist/analysis/derivative_chart_generator.d.ts.map +1 -0
- package/dist/analysis/derivative_chart_generator.js +891 -0
- package/dist/analysis/derivative_chart_generator.js.map +1 -0
- package/dist/analysis/math_utils.d.ts +22 -0
- package/dist/analysis/math_utils.d.ts.map +1 -0
- package/dist/analysis/math_utils.js +43 -0
- package/dist/analysis/math_utils.js.map +1 -0
- package/dist/analysis/price_sources.d.ts +40 -0
- package/dist/analysis/price_sources.d.ts.map +1 -0
- package/dist/analysis/price_sources.js +94 -0
- package/dist/analysis/price_sources.js.map +1 -0
- package/dist/analysis/resolve_source.d.ts +25 -0
- package/dist/analysis/resolve_source.d.ts.map +1 -0
- package/dist/analysis/resolve_source.js +69 -0
- package/dist/analysis/resolve_source.js.map +1 -0
- package/dist/analysis/trade_profitability.d.ts +115 -0
- package/dist/analysis/trade_profitability.d.ts.map +1 -0
- package/dist/analysis/trade_profitability.js +1240 -0
- package/dist/analysis/trade_profitability.js.map +1 -0
- package/dist/analysis/tradingview/analyze_tradingview.d.ts +31 -0
- package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -0
- package/dist/analysis/tradingview/analyze_tradingview.js +146 -0
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts +5 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +1714 -0
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +247 -0
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/derivative_analyzer.js +901 -0
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +1361 -0
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/hurst_analyzer.d.ts +6 -0
- package/dist/analysis/trend_detection/hurst_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/hurst_analyzer.js +7 -0
- package/dist/analysis/trend_detection/hurst_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.js +360 -0
- package/dist/analysis/trend_detection/kalman_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts +8 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.js +8 -0
- package/dist/analysis/trend_detection/kalman_trend_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts +6 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.d.ts.map +1 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.js +7 -0
- package/dist/analysis/trend_detection/kalman_velocity_smoothing.js.map +1 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts +6 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.d.ts.map +1 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.js +7 -0
- package/dist/analysis/trend_detection/permutation_entropy_analyzer.js.map +1 -0
- package/dist/analysis/trend_detection/regime_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/regime_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/regime_chart_generator.js +310 -0
- package/dist/analysis/trend_detection/regime_chart_generator.js.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts +2 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.d.ts.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.js +128 -0
- package/dist/analysis/trend_detection/tests/test_kalman_trend.js.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts +2 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.d.ts.map +1 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js +37 -0
- package/dist/analysis/trend_detection/tests/test_kalman_velocity_smoothing.js.map +1 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.d.ts +3 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.d.ts.map +1 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.js +646 -0
- package/dist/analysis/trend_detection/volatility_chart_generator.js.map +1 -0
- package/dist/bot.js +1 -1
- package/dist/bot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +1 -1
- package/dist/market_adapter/ama_signal_runner.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +1 -1
- package/dist/scripts/generate_lp_chart.js +1 -1
- package/dist/scripts/test-credit-renewal.js +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +5 -4
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/GRID_RECALCULATION.md +2 -2
- package/docs/README.md +1 -1
- package/market_adapter/README.md +23 -23
- package/package.json +17 -21
- package/scripts/README.md +12 -12
- package/scripts/bots +5 -7
- package/scripts/clear-market-adapter.sh +1 -1
- package/scripts/dexbot +5 -7
- package/scripts/keys +5 -7
- package/scripts/pm2 +5 -7
- package/scripts/unlock +5 -7
- package/scripts/update.js +6 -7
|
@@ -0,0 +1,1361 @@
|
|
|
1
|
+
import { DEFAULT_CONFIG, MARKET_ADAPTER } from '../../modules/constants.js';
|
|
2
|
+
import { getAmaWarmupBars } from '../../market_adapter/core/strategies/ama.js';
|
|
3
|
+
import { bilinearInterpolate } from '../../market_adapter/core/strategies/regime_interp.js';
|
|
4
|
+
import { computeDynamicWeightSeries, computeAverageAmaSlopePct, echoLatchSeries, roundToN } from '../../market_adapter/core/strategies/dynamic_weight_series.js';
|
|
5
|
+
import { buildKalmanVelocitySeries, computeAbsolutePercentileThreshold, resolveKalmanVelocitySmoothingConfig, smoothKalmanVelocityPoint, } from '../../market_adapter/core/signals/kalman_velocity_smoothing.js';
|
|
6
|
+
import { embedFunctionSources, escapeHtml, serializeJsonForScript, toEpochSeconds, UPLOT_SHARED_SCRIPT } from '../chart_utils.js';
|
|
7
|
+
import { sharedChartCSS } from '../chart_css.js';
|
|
8
|
+
import { Y_AXIS_SIZE, makeCursorConfig, bindHoverStateFn, wireChartEvents, zoomResetScript, sizeChartsFn } from '../chart_ui.js';
|
|
9
|
+
'use strict';
|
|
10
|
+
// Browser-embedded shared functions — the interactive chart runs the exact same
|
|
11
|
+
// pure logic as the live market adapter service instead of a hand-copied copy.
|
|
12
|
+
const EMBEDDED_SHARED_FUNCS = embedFunctionSources([
|
|
13
|
+
computeAverageAmaSlopePct,
|
|
14
|
+
bilinearInterpolate,
|
|
15
|
+
echoLatchSeries,
|
|
16
|
+
buildKalmanVelocitySeries,
|
|
17
|
+
resolveKalmanVelocitySmoothingConfig,
|
|
18
|
+
smoothKalmanVelocityPoint,
|
|
19
|
+
computeAbsolutePercentileThreshold,
|
|
20
|
+
computeDynamicWeightSeries,
|
|
21
|
+
roundToN,
|
|
22
|
+
]);
|
|
23
|
+
function generateHTML(data, title = 'Dynamic Weight Research') {
|
|
24
|
+
const results = data.allResults || [];
|
|
25
|
+
if (results.length === 0)
|
|
26
|
+
throw new Error('No analysis results in input');
|
|
27
|
+
const ma = data.marketAdapter || {};
|
|
28
|
+
const amaWeightConfig = data.amaWeightConfig || {};
|
|
29
|
+
const defaultAlpha = data.alpha ?? ma.alpha ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_ALPHA;
|
|
30
|
+
const defaultGain = data.gain ?? ma.gain ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_GAIN;
|
|
31
|
+
const defaultNeutralZone = amaWeightConfig.neutralZonePct ?? ma.amaNeutralZonePct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_AMA_NEUTRAL_ZONE_PCT;
|
|
32
|
+
const defaultDispWeight = data.dispWeight ?? ma.dispWeight ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_DW;
|
|
33
|
+
const defaultAmaMaxSlopePct = amaWeightConfig.amaMaxSlopePct
|
|
34
|
+
?? ma.amaMaxSlopePct
|
|
35
|
+
?? MARKET_ADAPTER.DYNAMIC_WEIGHT_AMA_MAX_SLOPE_PCT;
|
|
36
|
+
const defaultKalmanMaxSlopePct = amaWeightConfig.kalmanMaxSlopePct
|
|
37
|
+
?? ma.kalmanMaxSlopePct
|
|
38
|
+
?? defaultAmaMaxSlopePct
|
|
39
|
+
?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_MAX_SLOPE_PCT;
|
|
40
|
+
const defaultClipPct = data.clipPct ?? ma.clipPercentile ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_CLIP_PERCENTILE;
|
|
41
|
+
const defaultMinOutputThresholdRaw = data.minOutputThreshold ?? ma.minOutputThreshold ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_ASYMMETRIC_TREND_THRESHOLD;
|
|
42
|
+
const defaultMinOutputThreshold = Math.min(Math.max(defaultMinOutputThresholdRaw, 0), 0.5);
|
|
43
|
+
const defaultOutputClamp = data.outputClamp ?? ma.outputClamp ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_ASYMMETRIC_OFFSET_CLAMP;
|
|
44
|
+
const lookbackBarsRaw = amaWeightConfig.lookbackBars ?? ma.amaLookbackBars ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_AMA_LOOKBACK_BARS;
|
|
45
|
+
const lookbackBars = Math.ceil(Math.max(1, Math.min(32, Number.isFinite(lookbackBarsRaw) ? lookbackBarsRaw : 1)));
|
|
46
|
+
// Log mapping for lookback slider (1 to 32 bars)
|
|
47
|
+
const LB_LOG_MIN_N = Math.log(1);
|
|
48
|
+
const LB_LOG_MAX_N = Math.log(32);
|
|
49
|
+
const clampedLb = Math.min(Math.max(lookbackBars, 1), 32);
|
|
50
|
+
const lbInitSlider = Math.round((Math.log(clampedLb) - LB_LOG_MIN_N) / (LB_LOG_MAX_N - LB_LOG_MIN_N) * 1000);
|
|
51
|
+
// Log mapping for gain slider
|
|
52
|
+
const GAIN_LOG_MIN_N = Math.log(0.5);
|
|
53
|
+
const GAIN_LOG_MAX_N = Math.log(2.0);
|
|
54
|
+
const clampedGain = Math.min(Math.max(defaultGain, 0.5), 2.0);
|
|
55
|
+
const gainInitSlider = Math.round((Math.log(clampedGain) - GAIN_LOG_MIN_N) / (GAIN_LOG_MAX_N - GAIN_LOG_MIN_N) * 1000);
|
|
56
|
+
// Log mapping for slope saturation sliders
|
|
57
|
+
const AMA_MS_MIN = 0.04;
|
|
58
|
+
const AMA_MS_MAX = 0.12;
|
|
59
|
+
const AMA_MS_LOG_MIN_N = Math.log(AMA_MS_MIN);
|
|
60
|
+
const AMA_MS_LOG_MAX_N = Math.log(AMA_MS_MAX);
|
|
61
|
+
const KAL_MS_MIN = 0.15;
|
|
62
|
+
const KAL_MS_MAX = 1.5;
|
|
63
|
+
const KAL_MS_LOG_MIN_N = Math.log(KAL_MS_MIN);
|
|
64
|
+
const KAL_MS_LOG_MAX_N = Math.log(KAL_MS_MAX);
|
|
65
|
+
const clampedAmaMs = Math.min(Math.max(defaultAmaMaxSlopePct, AMA_MS_MIN), AMA_MS_MAX);
|
|
66
|
+
const clampedKalMs = Math.min(Math.max(defaultKalmanMaxSlopePct, KAL_MS_MIN), KAL_MS_MAX);
|
|
67
|
+
const amaMsInitSlider = Math.round((Math.log(clampedAmaMs) - AMA_MS_LOG_MIN_N) / (AMA_MS_LOG_MAX_N - AMA_MS_LOG_MIN_N) * 1000);
|
|
68
|
+
const kalMsInitSlider = Math.round((Math.log(clampedKalMs) - KAL_MS_LOG_MIN_N) / (KAL_MS_LOG_MAX_N - KAL_MS_LOG_MIN_N) * 1000);
|
|
69
|
+
const defaultRegimeSensitivity = data.regimeSensitivity ?? ma.regimeSensitivity ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_REGIME_SENSITIVITY;
|
|
70
|
+
const defaultAbsoluteThreshold = ma.absoluteThreshold ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_ABSOLUTE_THRESHOLD_DEFAULT;
|
|
71
|
+
const defaultDispScaleMinPct = data.dispScaleMinPct ?? ma.dispScaleMinPct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_DISP_SCALE_MIN_PCT;
|
|
72
|
+
const defaultKalmanSmoothPct = data.kalmanSmoothPct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_SMOOTH_PCT_DEFAULT;
|
|
73
|
+
const defaultKalmanDispScaleMult = data.kalmanDispScaleMult ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_DISP_SCALE_MULT_DEFAULT;
|
|
74
|
+
const defaultKalmanDispThresholdMult = data.kalmanDispThresholdMult ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_DISP_THRESHOLD_MULT_DEFAULT;
|
|
75
|
+
const defaultKalmanSmoothSpanPct = data.kalmanSmoothSpanPct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_SMOOTH_SPAN_PCT_DEFAULT;
|
|
76
|
+
const defaultSignalConfirmBars = data.signalConfirmBars ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_SIGNAL_CONFIRM_BARS_DEFAULT;
|
|
77
|
+
const regimeInitSlider = Math.round(defaultRegimeSensitivity * 100);
|
|
78
|
+
const interval = results.length > 1 ?
|
|
79
|
+
(new Date(results[1].timestamp).getTime() - new Date(results[0].timestamp).getTime()) / 1000 : 3600;
|
|
80
|
+
const dates = results.map((r, idx) => toEpochSeconds(r.timestamp || Date.now(), idx));
|
|
81
|
+
const prices = results.map((r) => r.price);
|
|
82
|
+
const hurstArr = results.map((r) => r.hurst ?? null);
|
|
83
|
+
const peArr = results.map((r) => r.pe ?? null);
|
|
84
|
+
const hurstSegments = results.map((r) => r.hurstSegment ?? null);
|
|
85
|
+
const peSegments = results.map((r) => r.peSegment ?? null);
|
|
86
|
+
const amaSlopePct = results.map((r) => r.amaSlopePct ?? null);
|
|
87
|
+
const kalmanVelocityPctRaw = results.map((r) => r.velocityRawPct ?? r.velocityPct ?? null);
|
|
88
|
+
const kalmanDisplacementPct = results.map((r) => r.displacementRawPct ?? r.displacementPct ?? null);
|
|
89
|
+
const kalmanVelocityPct = buildKalmanVelocitySeries(results, {
|
|
90
|
+
kalmanSmoothPct: defaultKalmanSmoothPct,
|
|
91
|
+
kalmanDispScaleMult: defaultKalmanDispScaleMult,
|
|
92
|
+
kalmanDispThresholdMult: defaultKalmanDispThresholdMult,
|
|
93
|
+
kalmanSmoothSpanPct: defaultKalmanSmoothSpanPct,
|
|
94
|
+
});
|
|
95
|
+
const kalmanIsReady = results.map((r) => r.isReady ?? false);
|
|
96
|
+
const signals = results.map((r) => r.signal);
|
|
97
|
+
const amaLabel = data.amaKey || 'AMA3';
|
|
98
|
+
const ama3Prices = results.map((r) => r.ama3Price ?? null);
|
|
99
|
+
const defaultAmaKey = MARKET_ADAPTER.DEFAULT_AMA_KEY;
|
|
100
|
+
const amaErPeriod = data.amaConfig?.erPeriod ?? MARKET_ADAPTER.AMAS[defaultAmaKey].erPeriod;
|
|
101
|
+
const amaSlowPeriod = data.amaConfig?.slowPeriod ?? MARKET_ADAPTER.AMAS[defaultAmaKey].slowPeriod;
|
|
102
|
+
const amaFastPeriod = data.amaConfig?.fastPeriod ?? MARKET_ADAPTER.AMAS[defaultAmaKey].fastPeriod;
|
|
103
|
+
const amaErSmoothPeriod = data.amaConfig?.erSmoothPeriod ?? 0;
|
|
104
|
+
const amaWarmupBars = getAmaWarmupBars(amaErPeriod, amaSlowPeriod, lookbackBars, amaFastPeriod, amaErSmoothPeriod);
|
|
105
|
+
const amaSlopeReadyBars = Math.ceil(amaErPeriod) + lookbackBars;
|
|
106
|
+
const amaSeedSmaLine = new Array(results.length).fill(null);
|
|
107
|
+
const amaSeedWindowEnd = Math.min(results.length - 1, Math.ceil(amaErPeriod));
|
|
108
|
+
if (amaSeedWindowEnd >= 0) {
|
|
109
|
+
let seedSum = 0;
|
|
110
|
+
let seedCount = 0;
|
|
111
|
+
for (let i = 0; i <= amaSeedWindowEnd; i++) {
|
|
112
|
+
if (Number.isFinite(prices[i])) {
|
|
113
|
+
seedSum += prices[i];
|
|
114
|
+
seedCount++;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
if (seedCount > 0) {
|
|
118
|
+
const seedSma = seedSum / seedCount;
|
|
119
|
+
for (let i = 0; i <= amaSeedWindowEnd; i++) {
|
|
120
|
+
amaSeedSmaLine[i] = seedSma;
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
const lastDate = dates[dates.length - 1];
|
|
125
|
+
for (let i = 1; i <= 150; i++) {
|
|
126
|
+
dates.push(lastDate + (i * interval));
|
|
127
|
+
prices.push(null);
|
|
128
|
+
hurstArr.push(null);
|
|
129
|
+
peArr.push(null);
|
|
130
|
+
hurstSegments.push(null);
|
|
131
|
+
peSegments.push(null);
|
|
132
|
+
amaSlopePct.push(null);
|
|
133
|
+
kalmanVelocityPctRaw.push(null);
|
|
134
|
+
kalmanVelocityPct.push(null);
|
|
135
|
+
kalmanDisplacementPct.push(null);
|
|
136
|
+
kalmanIsReady.push(null);
|
|
137
|
+
signals.push(null);
|
|
138
|
+
ama3Prices.push(null);
|
|
139
|
+
amaSeedSmaLine.push(null);
|
|
140
|
+
}
|
|
141
|
+
const realBarCount = results.length;
|
|
142
|
+
function maxAbsPct(arr) {
|
|
143
|
+
let m = 0;
|
|
144
|
+
for (let i = 0; i < realBarCount; i++) {
|
|
145
|
+
if (arr[i] != null && Math.abs(arr[i]) > m)
|
|
146
|
+
m = Math.abs(arr[i]);
|
|
147
|
+
}
|
|
148
|
+
return m || 4;
|
|
149
|
+
}
|
|
150
|
+
const amaPctMax = Math.ceil(maxAbsPct(amaSlopePct) * 1.15) || 5;
|
|
151
|
+
const kalPctMax = Math.ceil(Math.max(maxAbsPct(kalmanVelocityPct), maxAbsPct(kalmanDisplacementPct)) * 1.15) || 5;
|
|
152
|
+
function buildPercentiles(arr, startIndex = 0) {
|
|
153
|
+
const safeStartIndex = Math.max(0, Math.min(realBarCount, Math.ceil(startIndex)));
|
|
154
|
+
const sorted = [];
|
|
155
|
+
for (let i = safeStartIndex; i < realBarCount; i++) {
|
|
156
|
+
if (arr[i] != null)
|
|
157
|
+
sorted.push(Math.abs(arr[i]));
|
|
158
|
+
}
|
|
159
|
+
sorted.sort((a, b) => a - b);
|
|
160
|
+
const pcts = [];
|
|
161
|
+
for (let p = 0; p <= 100; p++) {
|
|
162
|
+
const idx = Math.min(Math.floor(sorted.length * p / 100), sorted.length - 1);
|
|
163
|
+
pcts.push(sorted[idx] || 0);
|
|
164
|
+
}
|
|
165
|
+
return pcts;
|
|
166
|
+
}
|
|
167
|
+
const amaPercentiles = buildPercentiles(amaSlopePct, amaSlopeReadyBars);
|
|
168
|
+
const kalPercentiles = buildPercentiles(kalmanVelocityPct);
|
|
169
|
+
const maxDispPct = Math.ceil(maxAbsPct(kalmanDisplacementPct) * 1.15) || 5;
|
|
170
|
+
return `<!DOCTYPE html>
|
|
171
|
+
<html lang="en">
|
|
172
|
+
<head>
|
|
173
|
+
<meta charset="UTF-8">
|
|
174
|
+
<title>${escapeHtml(title)}</title>
|
|
175
|
+
<link rel="stylesheet" href="../uplot/uPlot.min.css">
|
|
176
|
+
<script src="../uplot/uPlot.iife.min.js"></script>
|
|
177
|
+
<style>
|
|
178
|
+
${sharedChartCSS()}
|
|
179
|
+
#price-panel { flex: 0 0 34%; min-height: 0; position: relative; border-bottom: 1px solid #30363d; }
|
|
180
|
+
#ama-panel { flex: 0 0 14%; min-height: 0; position: relative; border-bottom: 1px solid #30363d; }
|
|
181
|
+
#kalman-panel { flex: 0 0 21%; min-height: 0; position: relative; border-bottom: 1px solid #30363d; }
|
|
182
|
+
#output-panel { flex: 1 1 0; min-height: 0; position: relative; }
|
|
183
|
+
.legend { left: 70px; flex-wrap: wrap; gap: 4px 8px; color: #8b949e; max-width: calc(100% - 86px); }
|
|
184
|
+
.legend-item { gap: 3px; }
|
|
185
|
+
.legend-val { font-family: monospace; display: inline-block; text-align: right; min-width: 45px; }
|
|
186
|
+
#l-price, #l-ama3 { min-width: 62px; }
|
|
187
|
+
#l-ama-slope, #l-kal-vel, #l-kal-disp { min-width: 54px; }
|
|
188
|
+
#l-signal, #l-signal-latched { min-width: 70px; text-align: left; }
|
|
189
|
+
#l-combined-raw, #l-combined-echo { min-width: 42px; }
|
|
190
|
+
#l-mult { min-width: 38px; }
|
|
191
|
+
#l-sell, #l-buy { min-width: 30px; }
|
|
192
|
+
.ctrl { pointer-events: auto; display: inline-flex; align-items: center; gap: 2px; margin-left: 4px; }
|
|
193
|
+
.ctrl label { color: #8b949e; font-size: 10px; min-width: 24px; text-align: right; margin-right: 2px; }
|
|
194
|
+
.ctrl.alpha label { min-width: 8px; }
|
|
195
|
+
.ctrl.ms label { min-width: 32px; }
|
|
196
|
+
.ctrl.clip label { min-width: 30px; }
|
|
197
|
+
.ctrl.off label { min-width: 24px; }
|
|
198
|
+
.ctrl.regime label { min-width: 24px; }
|
|
199
|
+
.ctrl input[type="range"] { width: 80px; height: 3px; }
|
|
200
|
+
.ctrl .val { font-weight: bold; font-size: 10px; min-width: 30px; display: inline-block; text-align: right; }
|
|
201
|
+
.group-sep { border-left: 1px solid #30363d; margin-left: 6px; padding-left: 4px; display: inline-flex; align-items: center; height: 16px; }
|
|
202
|
+
.group-label { font-size: 9px; color: #484f58; text-transform: uppercase; letter-spacing: 0.5px; margin-left: 8px; font-weight: bold; }
|
|
203
|
+
.ctrl.alpha input[type="range"] { accent-color: #58a6ff; width: 80px; }
|
|
204
|
+
.ctrl.alpha .val { color: #58a6ff; }
|
|
205
|
+
.ctrl.dw label { min-width: 20px; }
|
|
206
|
+
.ctrl.dw input[type="range"] { accent-color: #a371f7; width: 80px; }
|
|
207
|
+
.ctrl.dw .val { color: #a371f7; }
|
|
208
|
+
.ctrl.kf label { min-width: 20px; }
|
|
209
|
+
.ctrl.kf input[type="range"] { accent-color: #79c0ff; width: 80px; }
|
|
210
|
+
.ctrl.kf .val { color: #79c0ff; }
|
|
211
|
+
.ctrl.kfd label { min-width: 20px; }
|
|
212
|
+
.ctrl.kfd input[type="range"] { accent-color: #58a6ff; width: 80px; }
|
|
213
|
+
.ctrl.kfd .val { color: #58a6ff; }
|
|
214
|
+
.ctrl.kdt label { min-width: 20px; }
|
|
215
|
+
.ctrl.kdt input[type="range"] { accent-color: #f78166; width: 80px; }
|
|
216
|
+
.ctrl.kdt .val { color: #f78166; }
|
|
217
|
+
.ctrl.kfs label { min-width: 20px; }
|
|
218
|
+
.ctrl.kfs input[type="range"] { accent-color: #ffab70; width: 80px; }
|
|
219
|
+
.ctrl.kfs .val { color: #ffab70; }
|
|
220
|
+
.ctrl.ms-ama label { min-width: 32px; }
|
|
221
|
+
.ctrl.ms-ama input[type="range"] { accent-color: #e3b341; width: 80px; }
|
|
222
|
+
.ctrl.ms-ama .val { color: #e3b341; }
|
|
223
|
+
.ctrl.ms-kal label { min-width: 32px; }
|
|
224
|
+
.ctrl.ms-kal input[type="range"] { accent-color: #f0883e; width: 80px; }
|
|
225
|
+
.ctrl.ms-kal .val { color: #f0883e; }
|
|
226
|
+
.ctrl.cf label { min-width: 20px; }
|
|
227
|
+
.ctrl.cf input[type="range"] { accent-color: #d2a8ff; width: 80px; }
|
|
228
|
+
.ctrl.cf .val { color: #d2a8ff; }
|
|
229
|
+
.ctrl.lb label { min-width: 20px; }
|
|
230
|
+
.ctrl.lb input[type="range"] { accent-color: #39d0d8; width: 80px; }
|
|
231
|
+
.ctrl.lb .val { color: #39d0d8; }
|
|
232
|
+
.ctrl.off input[type="range"] { accent-color: #3fb950; }
|
|
233
|
+
.ctrl.off .val { color: #3fb950; }
|
|
234
|
+
.ctrl.nz input[type="range"] { accent-color: #8b949e; }
|
|
235
|
+
.ctrl.nz .val { color: #8b949e; }
|
|
236
|
+
.copy-btn, .paste-btn { pointer-events: auto; margin-left: 6px; padding: 2px 10px; font-size: 10px; background: #21262d; color: #8b949e; border: 1px solid #30363d; border-radius: 4px; cursor: pointer; }
|
|
237
|
+
.copy-btn:hover, .paste-btn:hover { background: #30363d; color: #e6edf3; }
|
|
238
|
+
.copy-btn.copied { color: #3fb950; border-color: #3fb950; }
|
|
239
|
+
.paste-btn.pasted { color: #58a6ff; border-color: #58a6ff; }
|
|
240
|
+
.paste-btn.error { color: #f85149; border-color: #f85149; }
|
|
241
|
+
#paste-confirm { display: none; position: fixed; background: #161b22; border: 1px solid #30363d; border-radius: 6px; padding: 14px 16px; z-index: 1000; box-shadow: 0 4px 24px rgba(0,0,0,0.7); min-width: 200px; }
|
|
242
|
+
#paste-confirm-title { font-size: 10px; color: #8b949e; margin-bottom: 8px; text-transform: uppercase; letter-spacing: 0.5px; }
|
|
243
|
+
#paste-confirm-vals { font-size: 11px; font-family: monospace; color: #e6edf3; line-height: 1.7; margin-bottom: 12px; }
|
|
244
|
+
#paste-confirm-vals span { color: #58a6ff; }
|
|
245
|
+
#paste-confirm-btns { display: flex; gap: 6px; justify-content: flex-end; }
|
|
246
|
+
#paste-confirm-btns button { padding: 3px 12px; font-size: 10px; border-radius: 4px; cursor: pointer; border: 1px solid #30363d; background: #21262d; color: #8b949e; }
|
|
247
|
+
#paste-confirm-apply { background: #1f4e23 !important; color: #3fb950 !important; border-color: #3fb950 !important; }
|
|
248
|
+
#paste-confirm-apply:hover { background: #2ea043 !important; color: #fff !important; }
|
|
249
|
+
|
|
250
|
+
.ctrl.clip input[type="range"] { accent-color: #da3633; }
|
|
251
|
+
.ctrl.clip .val { color: #da3633; }
|
|
252
|
+
.ctrl.regime input[type="range"] { accent-color: #d2a8ff; }
|
|
253
|
+
.ctrl.regime .val { color: #d2a8ff; }
|
|
254
|
+
.row-break { flex-basis: 100%; height: 0; }
|
|
255
|
+
</style>
|
|
256
|
+
</head>
|
|
257
|
+
<body>
|
|
258
|
+
<div id="header">
|
|
259
|
+
<div style="font-weight:bold;color:#fff;">${escapeHtml(title)}</div>
|
|
260
|
+
<div style="font-size:11px;color:#8b949e;text-transform:uppercase;">Log Price \u2192 AMA Slope \u2192 Kalman Composite \u2192 Dynamic Weight | Scroll \u00b7 Drag \u00b7 Ctrl+0</div>
|
|
261
|
+
</div>
|
|
262
|
+
|
|
263
|
+
<div id="panels">
|
|
264
|
+
<div id="price-panel">
|
|
265
|
+
<div class="section-label">PRICE (LOG)</div>
|
|
266
|
+
<div class="legend">
|
|
267
|
+
<div class="legend-item"><div class="dot" style="background:#58a6ff;"></div>Price: <span id="l-price" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
268
|
+
<div class="legend-item"><div class="dot" style="background:#e3b341;"></div>${amaLabel}: <span id="l-ama3" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
269
|
+
<div class="legend-item"><div class="dot" style="background:#8b949e;"></div>Seed SMA: <span id="l-seed-sma" class="legend-val">-</span></div>
|
|
270
|
+
</div>
|
|
271
|
+
<div id="price-chart"></div>
|
|
272
|
+
</div>
|
|
273
|
+
<div id="ama-panel">
|
|
274
|
+
<div class="section-label">AMA SLOPE INPUT</div>
|
|
275
|
+
<div class="legend">
|
|
276
|
+
<div class="legend-item"><div class="dot" style="background:#f0a000;"></div>Slope‰: <span id="l-ama-slope" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
277
|
+
</div>
|
|
278
|
+
<div id="ama-chart"></div>
|
|
279
|
+
</div>
|
|
280
|
+
<div id="kalman-panel">
|
|
281
|
+
<div class="section-label">KALMAN COMPOSITE INPUT</div>
|
|
282
|
+
<div class="legend">
|
|
283
|
+
<div class="legend-item"><div class="dot" style="background:#d2a8ff;"></div>Vel%: <span id="l-kal-vel" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
284
|
+
<div class="legend-item"><div class="dot" style="background:#79c0ff;"></div>Disp%: <span id="l-kal-disp" class="legend-val">-</span></div>
|
|
285
|
+
<div class="legend-item">Raw: <span id="l-signal" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
286
|
+
<div class="legend-item">Echo: <span id="l-signal-echo" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
287
|
+
<div class="legend-item">Latched: <span id="l-signal-latched" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
288
|
+
</div>
|
|
289
|
+
<div id="kalman-chart"></div>
|
|
290
|
+
</div>
|
|
291
|
+
<div id="output-panel">
|
|
292
|
+
<div class="section-label">COMBINED WEIGHT OUTPUT</div>
|
|
293
|
+
<div class="legend" style="gap: 4px;">
|
|
294
|
+
<div class="legend-item"><div class="dot" style="background:linear-gradient(to bottom,#8b949e,#8b949e);"></div>Raw: <span id="l-combined-raw" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
295
|
+
<div class="legend-item"><div class="dot" style="background:linear-gradient(to bottom,#2ea043,#f85149);"></div>Echo: <span id="l-combined-echo" class="legend-val" style="font-weight:bold;">-</span></div>
|
|
296
|
+
<div class="legend-item">xMult: <span id="l-mult" class="legend-val" style="font-weight:bold;color:#d2a8ff;">-</span></div>
|
|
297
|
+
<div class="legend-item">S: <span id="l-sell" class="legend-val" style="color:#ff7b72;">-</span></div>
|
|
298
|
+
<div class="legend-item">B: <span id="l-buy" class="legend-val" style="color:#58a6ff;">-</span></div>
|
|
299
|
+
|
|
300
|
+
<div class="group-sep"></div>
|
|
301
|
+
<div class="ctrl alpha"><label for="alpha-slider">α</label><input type="range" id="alpha-slider" min="0" max="100" value="${Math.round(defaultAlpha * 100)}" title="Alpha Mix (AMA vs Kalman)"><span class="val" id="alpha-value">${defaultAlpha.toFixed(2)}</span></div>
|
|
302
|
+
<div class="ctrl dw"><label for="dw-slider">dw</label><input type="range" id="dw-slider" min="0" max="100" value="${Math.round(defaultDispWeight * 100)}" title="Displacement Weight"><span class="val" id="dw-value">${defaultDispWeight.toFixed(2)}</span></div>
|
|
303
|
+
|
|
304
|
+
<div class="group-sep"></div>
|
|
305
|
+
<div class="ctrl nz"><label for="nz-slider">nz%</label><input type="range" id="nz-slider" min="0" max="100" value="${Math.round(defaultNeutralZone * 100)}" title="Neutral Zone %"><span class="val" id="nz-value">${defaultNeutralZone.toFixed(2)}</span></div>
|
|
306
|
+
<div class="ctrl lb"><label for="lb-slider">lb</label><input type="range" id="lb-slider" min="0" max="1000" value="${lbInitSlider}" title="Lookback Bars (1-32)"><span class="val" id="lb-value">${lookbackBars}</span></div>
|
|
307
|
+
<div class="ctrl ms-ama"><label for="ama-ms-slider">amaS%</label><input type="range" id="ama-ms-slider" min="0" max="1000" value="${amaMsInitSlider}" title="AMA Max Slope % per bar (0.04-0.12)"><span class="val" id="ama-ms-value">${defaultAmaMaxSlopePct.toFixed(4)}</span></div>
|
|
308
|
+
<div class="ctrl clip"><label for="clip-slider">clip%</label><input type="range" id="clip-slider" min="0" max="55" value="${Math.min(defaultClipPct, 55)}" title="Outlier Clip %"><span class="val" id="clip-value">${Math.min(defaultClipPct, 55)}%</span></div>
|
|
309
|
+
|
|
310
|
+
<div class="group-sep"></div>
|
|
311
|
+
<div class="ctrl th"><label for="th-slider">th%</label><input type="range" id="th-slider" min="0" max="50" value="${Math.round(defaultMinOutputThreshold * 100)}" title="Minimum pre-gain blended output required before the signal is allowed through (0.00-0.50)"><span class="val" id="th-value">${defaultMinOutputThreshold.toFixed(2)}</span></div>
|
|
312
|
+
|
|
313
|
+
<div class="group-sep"></div>
|
|
314
|
+
<div class="row-break"></div>
|
|
315
|
+
<div class="ctrl ms-kal"><label for="kal-ms-slider">kalS%</label><input type="range" id="kal-ms-slider" min="0" max="1000" value="${kalMsInitSlider}" title="Kalman Max Slope % (0.15-1.5)"><span class="val" id="kal-ms-value">${clampedKalMs.toFixed(2)}</span></div>
|
|
316
|
+
<div class="ctrl kf"><label for="kf-slider">kf</label><input type="range" id="kf-slider" min="0" max="200" value="${defaultKalmanSmoothPct}" title="Kalman smoothing blend (0 = raw, 100 = current adaptive smoothing, 200 = stronger smoothing)"><span class="val" id="kf-value">${defaultKalmanSmoothPct}%</span></div>
|
|
317
|
+
<div class="ctrl kfd"><label for="kfd-slider">kfd</label><input type="range" id="kfd-slider" min="100" max="300" value="${Math.round(defaultKalmanDispScaleMult * 100)}" title="Kalman displacement scale multiplier (1x to 3x)"><span class="val" id="kfd-value">${defaultKalmanDispScaleMult.toFixed(2)}x</span></div>
|
|
318
|
+
<div class="ctrl dsp"><label for="dsp-slider">dsp</label><input type="range" id="dsp-slider" min="25" max="400" value="${Math.round(defaultDispScaleMinPct * 100)}" title="Minimum displacement scale floor (0.25x to 4.0x)"><span class="val" id="dsp-value">${defaultDispScaleMinPct.toFixed(2)}x</span></div>
|
|
319
|
+
<div class="ctrl kdt"><label for="kdt-slider">kdt</label><input type="range" id="kdt-slider" min="25" max="300" value="${Math.round(defaultKalmanDispThresholdMult * 100)}" title="Kalman displacement threshold multiplier (0.25x to 3x)"><span class="val" id="kdt-value">${defaultKalmanDispThresholdMult.toFixed(2)}x</span></div>
|
|
320
|
+
<div class="ctrl kfs"><label for="kfs-slider">kfs</label><input type="range" id="kfs-slider" min="20" max="200" value="${defaultKalmanSmoothSpanPct}" title="Adaptive EMA span ratio (20% span / 200% span; floor fixed at 0)"><span class="val" id="kfs-value">${defaultKalmanSmoothSpanPct}%</span></div>
|
|
321
|
+
<div class="ctrl cf"><label for="cf-slider">cf</label><input type="range" id="cf-slider" min="0" max="5" value="${defaultSignalConfirmBars}" title="Signal confirmation bars (0 disables latching; otherwise flips after N opposite echo bars)"><span class="val" id="cf-value">${defaultSignalConfirmBars}</span></div>
|
|
322
|
+
<div class="ctrl off"><label for="gain-slider">gain</label><input type="range" id="gain-slider" min="0" max="1000" value="${gainInitSlider}" title="Output Gain (0.5-2.0)"><span class="val" id="gain-value">${clampedGain.toFixed(3)}</span></div>
|
|
323
|
+
<div class="ctrl regime"><label for="regime-slider">regi</label><input type="range" id="regime-slider" min="0" max="200" value="${regimeInitSlider}" title="Regime Sensitivity"><span class="val" id="regime-value">${defaultRegimeSensitivity.toFixed(2)}</span></div>
|
|
324
|
+
<button class="copy-btn" id="copy-params-btn">copy</button>
|
|
325
|
+
<button class="paste-btn" id="paste-params-btn">paste</button>
|
|
326
|
+
</div>
|
|
327
|
+
<div id="output-chart"></div>
|
|
328
|
+
</div>
|
|
329
|
+
</div>
|
|
330
|
+
|
|
331
|
+
<div id="paste-confirm">
|
|
332
|
+
<div id="paste-confirm-title">Confirm paste parameters</div>
|
|
333
|
+
<div id="paste-confirm-vals"></div>
|
|
334
|
+
<div id="paste-confirm-btns">
|
|
335
|
+
<button id="paste-confirm-cancel">Cancel</button>
|
|
336
|
+
<button id="paste-confirm-apply">Apply</button>
|
|
337
|
+
</div>
|
|
338
|
+
</div>
|
|
339
|
+
|
|
340
|
+
<script id="payload" type="application/json">${serializeJsonForScript({ dates, prices, hurstArr, peArr, hurstSegments, peSegments, ama3Prices, amaSeedSmaLine, amaSlopePct, kalmanVelocityPctRaw, kalmanVelocityPct, kalmanDisplacementPct, kalmanIsReady, signals, alpha: defaultAlpha, gain: defaultGain, kalmanSmoothPct: defaultKalmanSmoothPct, kalmanDispScaleMult: defaultKalmanDispScaleMult, kalmanDispThresholdMult: defaultKalmanDispThresholdMult, kalmanSmoothSpanPct: defaultKalmanSmoothSpanPct, signalConfirmBars: defaultSignalConfirmBars, neutralZonePct: defaultNeutralZone, dispWeight: defaultDispWeight, amaMaxSlopePct: defaultAmaMaxSlopePct, kalmanMaxSlopePct: defaultKalmanMaxSlopePct, maxDispPct, clipPct: defaultClipPct, minOutputThreshold: defaultMinOutputThreshold, outputClamp: defaultOutputClamp, regimeSensitivity: defaultRegimeSensitivity, absoluteThreshold: defaultAbsoluteThreshold, lookbackBars, amaErPeriod, amaSlowPeriod, amaWarmupBars, amaSlopeReadyBars, realBarCount, amaPctMax, kalPctMax, amaPercentiles, kalPercentiles, amaSlopeLogMin: AMA_MS_LOG_MIN_N, amaSlopeLogMax: AMA_MS_LOG_MAX_N, kalSlopeLogMin: KAL_MS_LOG_MIN_N, kalSlopeLogMax: KAL_MS_LOG_MAX_N, lbLogMin: LB_LOG_MIN_N, lbLogMax: LB_LOG_MAX_N, gainLogMin: GAIN_LOG_MIN_N, gainLogMax: GAIN_LOG_MAX_N, dispScaleMinPct: defaultDispScaleMinPct, weightMin: MARKET_ADAPTER.DYNAMIC_WEIGHT_MIN_WEIGHT, weightMax: MARKET_ADAPTER.DYNAMIC_WEIGHT_MAX_WEIGHT, marketAdapter: ma, amaWeightConfig, hNodes: [0.5 + MARKET_ADAPTER.HURST_ZONE_BAND, 0.5, 0.5 - MARKET_ADAPTER.HURST_ZONE_BAND], pNodes: MARKET_ADAPTER.PE_NODES, regimeTable: MARKET_ADAPTER.REGIME_TABLE })}</script>
|
|
341
|
+
|
|
342
|
+
<script>
|
|
343
|
+
const data = JSON.parse(document.getElementById('payload').textContent);
|
|
344
|
+
|
|
345
|
+
${EMBEDDED_SHARED_FUNCS}
|
|
346
|
+
|
|
347
|
+
const SYNC_KEY = "dyn-wt-res-v4";
|
|
348
|
+
const Y_AXIS_SIZE = ${Y_AXIS_SIZE};
|
|
349
|
+
const ma = data.marketAdapter || {};
|
|
350
|
+
const amaWeightConfig = data.amaWeightConfig || {};
|
|
351
|
+
|
|
352
|
+
const ABSOLUTE_THRESHOLD = data.absoluteThreshold ?? ${JSON.stringify(defaultAbsoluteThreshold)};
|
|
353
|
+
const WEIGHT_MIN = data.weightMin ?? ${JSON.stringify(MARKET_ADAPTER.DYNAMIC_WEIGHT_MIN_WEIGHT)};
|
|
354
|
+
const WEIGHT_MAX = data.weightMax ?? ${JSON.stringify(MARKET_ADAPTER.DYNAMIC_WEIGHT_MAX_WEIGHT)};
|
|
355
|
+
const OUTPUT_CLAMP = data.outputClamp ?? ${JSON.stringify(defaultOutputClamp)};
|
|
356
|
+
const weightDistribution = ${serializeJsonForScript(DEFAULT_CONFIG.weightDistribution)};
|
|
357
|
+
const STATIC_SELL = Number.isFinite(weightDistribution.sell) ? weightDistribution.sell : ${JSON.stringify(DEFAULT_CONFIG.weightDistribution.sell)};
|
|
358
|
+
const STATIC_BUY = Number.isFinite(weightDistribution.buy) ? weightDistribution.buy : ${JSON.stringify(DEFAULT_CONFIG.weightDistribution.buy)};
|
|
359
|
+
|
|
360
|
+
let currentAlpha = data.alpha ?? ${JSON.stringify(defaultAlpha)};
|
|
361
|
+
let currentGain = data.gain ?? ${JSON.stringify(defaultGain)};
|
|
362
|
+
let currentMinOutputThreshold = Math.min(Math.max(data.minOutputThreshold ?? ${JSON.stringify(defaultMinOutputThreshold)}, 0), 0.5);
|
|
363
|
+
let currentKalmanSmoothPct = data.kalmanSmoothPct ?? ${JSON.stringify(defaultKalmanSmoothPct)};
|
|
364
|
+
let currentKalmanDispScaleMult = data.kalmanDispScaleMult ?? ${JSON.stringify(defaultKalmanDispScaleMult)};
|
|
365
|
+
let currentKalmanDispThresholdMult = data.kalmanDispThresholdMult ?? ${JSON.stringify(defaultKalmanDispThresholdMult)};
|
|
366
|
+
let currentKalmanSmoothSpanPct = data.kalmanSmoothSpanPct ?? ${JSON.stringify(defaultKalmanSmoothSpanPct)};
|
|
367
|
+
let currentDispScaleMinPct = data.dispScaleMinPct ?? ${JSON.stringify(defaultDispScaleMinPct)};
|
|
368
|
+
let currentSignalConfirmBars = data.signalConfirmBars ?? ${JSON.stringify(defaultSignalConfirmBars)};
|
|
369
|
+
let currentNz = data.neutralZonePct ?? ${JSON.stringify(defaultNeutralZone)};
|
|
370
|
+
let currentDw = data.dispWeight ?? ${JSON.stringify(defaultDispWeight)};
|
|
371
|
+
let currentAmaMaxSlopePct = data.amaMaxSlopePct ?? ${JSON.stringify(defaultAmaMaxSlopePct)};
|
|
372
|
+
let currentKalmanMaxSlopePct = data.kalmanMaxSlopePct ?? ${JSON.stringify(defaultKalmanMaxSlopePct)};
|
|
373
|
+
const AMA_MS_LOG_MIN = data.amaSlopeLogMin;
|
|
374
|
+
const AMA_MS_LOG_MAX = data.amaSlopeLogMax;
|
|
375
|
+
const KAL_MS_LOG_MIN = data.kalSlopeLogMin;
|
|
376
|
+
const KAL_MS_LOG_MAX = data.kalSlopeLogMax;
|
|
377
|
+
const amaMsSliderToVal = (pos) => Math.exp(AMA_MS_LOG_MIN + (pos / 1000) * (AMA_MS_LOG_MAX - AMA_MS_LOG_MIN));
|
|
378
|
+
const kalMsSliderToVal = (pos) => Math.exp(KAL_MS_LOG_MIN + (pos / 1000) * (KAL_MS_LOG_MAX - KAL_MS_LOG_MIN));
|
|
379
|
+
|
|
380
|
+
let currentLookbackBars = data.lookbackBars ?? ${JSON.stringify(lookbackBars)};
|
|
381
|
+
const LB_LOG_MIN = data.lbLogMin;
|
|
382
|
+
const LB_LOG_MAX = data.lbLogMax;
|
|
383
|
+
const lbSliderToVal = (pos) => Math.round(Math.exp(LB_LOG_MIN + (pos / 1000) * (LB_LOG_MAX - LB_LOG_MIN)));
|
|
384
|
+
const lbValToSlider = (val) => Math.round((Math.log(Math.max(1, val)) - LB_LOG_MIN) / (LB_LOG_MAX - LB_LOG_MIN) * 1000);
|
|
385
|
+
|
|
386
|
+
const GAIN_LOG_MIN = data.gainLogMin;
|
|
387
|
+
const GAIN_LOG_MAX = data.gainLogMax;
|
|
388
|
+
const gainSliderToVal = (pos) => Math.exp(GAIN_LOG_MIN + (pos / 1000) * (GAIN_LOG_MAX - GAIN_LOG_MIN));
|
|
389
|
+
const gainValToSlider = (val) => Math.round((Math.log(Math.max(Math.exp(GAIN_LOG_MIN), val)) - GAIN_LOG_MIN) / (GAIN_LOG_MAX - GAIN_LOG_MIN) * 1000);
|
|
390
|
+
|
|
391
|
+
let currentClipPct = data.clipPct ?? ${JSON.stringify(defaultClipPct)};
|
|
392
|
+
const maxAmaSlope = data.amaPercentiles[data.amaPercentiles.length - 1];
|
|
393
|
+
let currentAmaClipThreshold = currentClipPct === 0 ? maxAmaSlope : data.amaPercentiles[100 - currentClipPct];
|
|
394
|
+
let currentKalClipThreshold = currentClipPct === 0 ? Infinity : data.kalPercentiles[100 - currentClipPct];
|
|
395
|
+
|
|
396
|
+
// Regime table and axis nodes from payload — sourced from MARKET_ADAPTER in constants.ts
|
|
397
|
+
const REGIME_TABLE = data.regimeTable;
|
|
398
|
+
const H_NODES = data.hNodes; // [0.5+band, 0.5, 0.5-band]
|
|
399
|
+
const P_NODES = data.pNodes; // PE_NODES
|
|
400
|
+
|
|
401
|
+
/**
|
|
402
|
+
* Regime multiplier for smooth gating — canonical bilinear interpolation
|
|
403
|
+
* (bilinearInterpolate injected above) plus the dead-band / clamp logic.
|
|
404
|
+
*/
|
|
405
|
+
function getRegimeMultiplier(H, PE) {
|
|
406
|
+
if (H == null || PE == null) return 1.0;
|
|
407
|
+
return bilinearInterpolate(H, PE, REGIME_TABLE, { hNodes: H_NODES, pNodes: P_NODES });
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
let currentRegimeSensitivity = data.regimeSensitivity ?? ma.regimeSensitivity;
|
|
411
|
+
|
|
412
|
+
const currentKalmanVelocityPct = new Array(data.dates.length).fill(null);
|
|
413
|
+
const currentLatchedSignals = new Array(data.dates.length).fill(null);
|
|
414
|
+
const dynamicAmaOff = new Array(data.dates.length).fill(null);
|
|
415
|
+
const dynamicAmaSlopePct = new Array(data.dates.length).fill(null);
|
|
416
|
+
const dynamicKalOff = new Array(data.dates.length).fill(null);
|
|
417
|
+
const combinedOff = new Array(data.dates.length).fill(null);
|
|
418
|
+
const combinedSell = new Array(data.dates.length).fill(null);
|
|
419
|
+
const combinedBuy = new Array(data.dates.length).fill(null);
|
|
420
|
+
const echoCombinedOff = new Array(data.dates.length).fill(null);
|
|
421
|
+
const echoCombinedSell = new Array(data.dates.length).fill(null);
|
|
422
|
+
const echoCombinedBuy = new Array(data.dates.length).fill(null);
|
|
423
|
+
const currentMults = new Array(data.dates.length).fill(null);
|
|
424
|
+
let currentOutputAxisMax = 0.5;
|
|
425
|
+
let dynamicClipThreshold = Infinity;
|
|
426
|
+
|
|
427
|
+
// Shared Kalman velocity smoothing (smoothKalmanVelocityPoint /
|
|
428
|
+
// buildKalmanVelocitySeries / resolveKalmanVelocitySmoothingConfig /
|
|
429
|
+
// computeAbsolutePercentileThreshold injected above).
|
|
430
|
+
function recalcKalmanVelocity() {
|
|
431
|
+
const rawSeries = data.kalmanVelocityPctRaw || data.kalmanVelocityPct;
|
|
432
|
+
const dispSeries = data.kalmanDisplacementPct;
|
|
433
|
+
const points = new Array(data.dates.length).fill(null);
|
|
434
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
435
|
+
points[i] = { velocityPct: rawSeries[i], displacementPct: dispSeries[i] };
|
|
436
|
+
}
|
|
437
|
+
const smoothed = buildKalmanVelocitySeries(points, {
|
|
438
|
+
kalmanSmoothPct: currentKalmanSmoothPct,
|
|
439
|
+
kalmanDispScaleMult: currentKalmanDispScaleMult,
|
|
440
|
+
kalmanDispThresholdMult: currentKalmanDispThresholdMult,
|
|
441
|
+
kalmanSmoothSpanPct: currentKalmanSmoothSpanPct,
|
|
442
|
+
});
|
|
443
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
444
|
+
currentKalmanVelocityPct[i] = smoothed[i] ?? null;
|
|
445
|
+
}
|
|
446
|
+
}
|
|
447
|
+
|
|
448
|
+
function recalcKalmanClipThreshold() {
|
|
449
|
+
currentKalClipThreshold = computeAbsolutePercentileThreshold(
|
|
450
|
+
currentKalmanVelocityPct,
|
|
451
|
+
currentClipPct,
|
|
452
|
+
Infinity
|
|
453
|
+
);
|
|
454
|
+
}
|
|
455
|
+
|
|
456
|
+
function signalDirectionForIndex(i) {
|
|
457
|
+
const sig = data.signals[i];
|
|
458
|
+
if (!sig) return 0;
|
|
459
|
+
if (sig.includes('BULL')) return 1;
|
|
460
|
+
if (sig.includes('BEAR')) return -1;
|
|
461
|
+
return 0;
|
|
462
|
+
}
|
|
463
|
+
|
|
464
|
+
function directionToSignal(dir) {
|
|
465
|
+
if (dir > 0) return 'BULLISH_DISPLACEMENT';
|
|
466
|
+
if (dir < 0) return 'BEARISH_DISPLACEMENT';
|
|
467
|
+
return 'NEUTRAL';
|
|
468
|
+
}
|
|
469
|
+
|
|
470
|
+
function recalcLatchedSignals() {
|
|
471
|
+
const confirmBars = Math.max(0, Math.min(5, currentSignalConfirmBars));
|
|
472
|
+
if (confirmBars === 0) {
|
|
473
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
474
|
+
currentLatchedSignals[i] = data.signals[i] || null;
|
|
475
|
+
}
|
|
476
|
+
return;
|
|
477
|
+
}
|
|
478
|
+
let latchedDir = 0;
|
|
479
|
+
let pendingDir = 0;
|
|
480
|
+
let pendingCount = 0;
|
|
481
|
+
|
|
482
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
483
|
+
const raw = data.signals[i];
|
|
484
|
+
const echoDir = signalDirectionForIndex(i);
|
|
485
|
+
|
|
486
|
+
if (raw == null) {
|
|
487
|
+
currentLatchedSignals[i] = null;
|
|
488
|
+
pendingDir = 0;
|
|
489
|
+
pendingCount = 0;
|
|
490
|
+
continue;
|
|
491
|
+
}
|
|
492
|
+
|
|
493
|
+
if (echoDir === 0) {
|
|
494
|
+
currentLatchedSignals[i] = directionToSignal(latchedDir);
|
|
495
|
+
continue;
|
|
496
|
+
}
|
|
497
|
+
|
|
498
|
+
if (latchedDir === 0) {
|
|
499
|
+
latchedDir = echoDir;
|
|
500
|
+
pendingDir = 0;
|
|
501
|
+
pendingCount = 0;
|
|
502
|
+
currentLatchedSignals[i] = directionToSignal(latchedDir);
|
|
503
|
+
continue;
|
|
504
|
+
}
|
|
505
|
+
|
|
506
|
+
if (echoDir === latchedDir) {
|
|
507
|
+
pendingDir = 0;
|
|
508
|
+
pendingCount = 0;
|
|
509
|
+
currentLatchedSignals[i] = directionToSignal(latchedDir);
|
|
510
|
+
continue;
|
|
511
|
+
}
|
|
512
|
+
|
|
513
|
+
if (pendingDir !== echoDir) {
|
|
514
|
+
pendingDir = echoDir;
|
|
515
|
+
pendingCount = 1;
|
|
516
|
+
} else {
|
|
517
|
+
pendingCount++;
|
|
518
|
+
}
|
|
519
|
+
|
|
520
|
+
if (pendingCount >= confirmBars) {
|
|
521
|
+
latchedDir = echoDir;
|
|
522
|
+
pendingDir = 0;
|
|
523
|
+
pendingCount = 0;
|
|
524
|
+
}
|
|
525
|
+
|
|
526
|
+
currentLatchedSignals[i] = directionToSignal(latchedDir);
|
|
527
|
+
}
|
|
528
|
+
}
|
|
529
|
+
|
|
530
|
+
function computeSlopeAtIndex(idx, lb) {
|
|
531
|
+
// Canonical AMA slope % (computeAverageAmaSlopePct injected above).
|
|
532
|
+
const sp = computeAverageAmaSlopePct(data.ama3Prices[idx], data.ama3Prices[idx - lb], lb);
|
|
533
|
+
return sp == null ? 0 : sp;
|
|
534
|
+
}
|
|
535
|
+
|
|
536
|
+
function recalcInputs() {
|
|
537
|
+
recalcKalmanVelocity();
|
|
538
|
+
recalcKalmanClipThreshold();
|
|
539
|
+
recalcLatchedSignals();
|
|
540
|
+
const lb = currentLookbackBars;
|
|
541
|
+
const amaErWarmup = Math.max(0, Number.isFinite(data.amaErPeriod) ? Math.ceil(data.amaErPeriod) : ${JSON.stringify(MARKET_ADAPTER.AMAS[MARKET_ADAPTER.DEFAULT_AMA_KEY].erPeriod)});
|
|
542
|
+
const amaReadyBar = Math.max(lb, amaErWarmup + lb);
|
|
543
|
+
|
|
544
|
+
// Recompute clip threshold based on current lookback
|
|
545
|
+
dynamicClipThreshold = Infinity;
|
|
546
|
+
if (currentClipPct > 0) {
|
|
547
|
+
const slopes = [];
|
|
548
|
+
for (let i = amaReadyBar; i < data.realBarCount; i++) {
|
|
549
|
+
const s = computeSlopeAtIndex(i, lb);
|
|
550
|
+
if (s !== 0) slopes.push(Math.abs(s));
|
|
551
|
+
}
|
|
552
|
+
if (slopes.length > 0) {
|
|
553
|
+
slopes.sort((a, b) => a - b);
|
|
554
|
+
const idx = Math.min(Math.floor((100 - currentClipPct) / 100 * slopes.length), slopes.length - 1);
|
|
555
|
+
dynamicClipThreshold = slopes[idx];
|
|
556
|
+
}
|
|
557
|
+
}
|
|
558
|
+
|
|
559
|
+
for (let i = 0; i < data.realBarCount; i++) {
|
|
560
|
+
// AMA raw slope % for the display panel (offsets computed in recalcWeights)
|
|
561
|
+
dynamicAmaSlopePct[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb);
|
|
562
|
+
}
|
|
563
|
+
for (let i = data.realBarCount; i < data.dates.length; i++) {
|
|
564
|
+
dynamicAmaSlopePct[i] = null;
|
|
565
|
+
}
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
function recalcWeights() {
|
|
569
|
+
// Normalize each channel by the configured clamp so alpha remains a pure ratio knob.
|
|
570
|
+
// Gain then scales the blended output linearly after the dead-band decision.
|
|
571
|
+
currentOutputAxisMax = Math.max(0.5, OUTPUT_CLAMP);
|
|
572
|
+
const channelNorm = Math.max(Math.abs(OUTPUT_CLAMP), 1e-9);
|
|
573
|
+
const outputThreshold = currentMinOutputThreshold;
|
|
574
|
+
const n = data.realBarCount;
|
|
575
|
+
|
|
576
|
+
// Per-bar regime multiplier (display + pipeline input)
|
|
577
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
578
|
+
if (i >= n) { currentMults[i] = null; continue; }
|
|
579
|
+
const baseMult = getRegimeMultiplier(data.hurstArr[i], data.peArr[i]);
|
|
580
|
+
// Use power for sensitivity: pushes away from 1.0 in both directions without flipping sign
|
|
581
|
+
const rawMult = Math.pow(baseMult, currentRegimeSensitivity);
|
|
582
|
+
// Dead-band: only apply regime multiplier when |mult - 1.0| >= absoluteThreshold
|
|
583
|
+
// Clamp to 1.0 max: regime only dampens, never amplifies
|
|
584
|
+
currentMults[i] = Math.abs(rawMult - 1.0) >= ABSOLUTE_THRESHOLD ? Math.min(rawMult, 1.0) : 1.0;
|
|
585
|
+
}
|
|
586
|
+
|
|
587
|
+
// Per-bar offset pipeline — canonical implementation (computeDynamicWeightSeries
|
|
588
|
+
// injected above), shared with the live market adapter service.
|
|
589
|
+
const res = computeDynamicWeightSeries({
|
|
590
|
+
amaValues: data.ama3Prices,
|
|
591
|
+
kalmanVelocityPct: currentKalmanVelocityPct,
|
|
592
|
+
kalmanDisplacementPct: data.kalmanDisplacementPct,
|
|
593
|
+
kalmanIsReady: data.kalmanIsReady,
|
|
594
|
+
regimeMultipliers: currentMults,
|
|
595
|
+
lookbackBars: currentLookbackBars,
|
|
596
|
+
amaErPeriod: data.amaErPeriod,
|
|
597
|
+
amaClipThreshold: currentClipPct > 0 ? dynamicClipThreshold : currentAmaClipThreshold,
|
|
598
|
+
kalClipThreshold: currentKalClipThreshold,
|
|
599
|
+
neutralZonePct: currentNz,
|
|
600
|
+
amaMaxSlopePct: currentAmaMaxSlopePct,
|
|
601
|
+
kalmanMaxSlopePct: currentKalmanMaxSlopePct,
|
|
602
|
+
offsetClamp: OUTPUT_CLAMP,
|
|
603
|
+
dispScaleMinPct: currentDispScaleMinPct,
|
|
604
|
+
alpha: currentAlpha,
|
|
605
|
+
dw: currentDw,
|
|
606
|
+
gain: currentGain,
|
|
607
|
+
minOutputThreshold: currentMinOutputThreshold,
|
|
608
|
+
signalConfirmBars: currentSignalConfirmBars,
|
|
609
|
+
clampFinalOutput: false,
|
|
610
|
+
});
|
|
611
|
+
|
|
612
|
+
for (let i = 0; i < data.dates.length; i++) {
|
|
613
|
+
if (i >= n) {
|
|
614
|
+
dynamicAmaOff[i] = null;
|
|
615
|
+
dynamicKalOff[i] = null;
|
|
616
|
+
combinedOff[i] = null;
|
|
617
|
+
combinedSell[i] = null;
|
|
618
|
+
combinedBuy[i] = null;
|
|
619
|
+
echoCombinedOff[i] = null;
|
|
620
|
+
echoCombinedSell[i] = null;
|
|
621
|
+
echoCombinedBuy[i] = null;
|
|
622
|
+
continue;
|
|
623
|
+
}
|
|
624
|
+
const aOff = res.amaOffsets[i];
|
|
625
|
+
const kOff = res.kalmanOffsets[i];
|
|
626
|
+
const cOff = res.combinedOffSeries[i];
|
|
627
|
+
const eOff = res.echoedOffSeries[i];
|
|
628
|
+
dynamicAmaOff[i] = aOff;
|
|
629
|
+
// Keep pre-warmup bars as gaps (not flat 0) to match the historical
|
|
630
|
+
// research-chart rendering: the Kalman channel contributes nothing
|
|
631
|
+
// until the analyzer reports ready with a measurable velocity.
|
|
632
|
+
const kalReady = data.kalmanIsReady[i] === true
|
|
633
|
+
&& currentKalmanVelocityPct[i] != null
|
|
634
|
+
&& data.kalmanDisplacementPct[i] != null;
|
|
635
|
+
if (!kalReady) {
|
|
636
|
+
dynamicKalOff[i] = null;
|
|
637
|
+
combinedOff[i] = null;
|
|
638
|
+
combinedSell[i] = null;
|
|
639
|
+
combinedBuy[i] = null;
|
|
640
|
+
echoCombinedOff[i] = null;
|
|
641
|
+
echoCombinedSell[i] = null;
|
|
642
|
+
echoCombinedBuy[i] = null;
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
dynamicKalOff[i] = kOff;
|
|
646
|
+
combinedOff[i] = cOff;
|
|
647
|
+
combinedSell[i] = Math.max(WEIGHT_MIN, Math.min(WEIGHT_MAX, Math.round((STATIC_SELL - cOff) * 100) / 100));
|
|
648
|
+
combinedBuy[i] = Math.max(WEIGHT_MIN, Math.min(WEIGHT_MAX, Math.round((STATIC_BUY + cOff) * 100) / 100));
|
|
649
|
+
echoCombinedOff[i] = eOff;
|
|
650
|
+
echoCombinedSell[i] = Math.max(WEIGHT_MIN, Math.min(WEIGHT_MAX, Math.round((STATIC_SELL - eOff) * 100) / 100));
|
|
651
|
+
echoCombinedBuy[i] = Math.max(WEIGHT_MIN, Math.min(WEIGHT_MAX, Math.round((STATIC_BUY + eOff) * 100) / 100));
|
|
652
|
+
currentOutputAxisMax = Math.max(currentOutputAxisMax, Math.abs(cOff), Math.abs(eOff));
|
|
653
|
+
}
|
|
654
|
+
}
|
|
655
|
+
recalcInputs();
|
|
656
|
+
recalcWeights();
|
|
657
|
+
|
|
658
|
+
const xMin = data.dates[0];
|
|
659
|
+
const xMax = data.dates[data.dates.length - 1];
|
|
660
|
+
let pendingRange = null;
|
|
661
|
+
let pendingRangeRaf = 0;
|
|
662
|
+
let priceChart, amaChart, kalmanChart, outputChart;
|
|
663
|
+
let charts;
|
|
664
|
+
|
|
665
|
+
${UPLOT_SHARED_SCRIPT}
|
|
666
|
+
|
|
667
|
+
function refreshChartsPreservingZoom() {
|
|
668
|
+
const xs = outputChart?.scales?.x;
|
|
669
|
+
const savedX = xs ? {
|
|
670
|
+
min: Number.isFinite(xs.min) ? xs.min : xMin,
|
|
671
|
+
max: Number.isFinite(xs.max) ? xs.max : xMax,
|
|
672
|
+
} : null;
|
|
673
|
+
|
|
674
|
+
amaChart.setData([data.dates, dynamicAmaSlopePct], false);
|
|
675
|
+
kalmanChart.setData([data.dates, currentKalmanVelocityPct, data.kalmanDisplacementPct], false);
|
|
676
|
+
outputChart.setData([data.dates, combinedOff, echoCombinedOff], false);
|
|
677
|
+
|
|
678
|
+
if (savedX) {
|
|
679
|
+
charts.forEach(c => c.batch(() => c.setScale('x', savedX)));
|
|
680
|
+
}
|
|
681
|
+
}
|
|
682
|
+
|
|
683
|
+
let lastLiveIdx = data.realBarCount - 1;
|
|
684
|
+
|
|
685
|
+
function updateLegend(idx) {
|
|
686
|
+
if (idx == null) return;
|
|
687
|
+
lastLiveIdx = idx;
|
|
688
|
+
|
|
689
|
+
const price = data.prices[idx];
|
|
690
|
+
const priceEl = document.getElementById('l-price');
|
|
691
|
+
if (price == null) { priceEl.textContent = '-'; priceEl.style.color = '#8b949e'; }
|
|
692
|
+
else { priceEl.textContent = price.toFixed(4); priceEl.style.color = '#58a6ff'; }
|
|
693
|
+
|
|
694
|
+
const ama3 = data.ama3Prices[idx];
|
|
695
|
+
const ama3El = document.getElementById('l-ama3');
|
|
696
|
+
if (ama3 == null) { ama3El.textContent = '-'; ama3El.style.color = '#8b949e'; }
|
|
697
|
+
else { ama3El.textContent = ama3.toFixed(4); ama3El.style.color = '#e3b341'; }
|
|
698
|
+
|
|
699
|
+
const seedSma = data.amaSeedSmaLine[idx];
|
|
700
|
+
const seedSmaEl = document.getElementById('l-seed-sma');
|
|
701
|
+
if (seedSma == null) { seedSmaEl.textContent = '-'; seedSmaEl.style.color = '#8b949e'; }
|
|
702
|
+
else { seedSmaEl.textContent = seedSma.toFixed(4); seedSmaEl.style.color = '#8b949e'; }
|
|
703
|
+
|
|
704
|
+
const sp = dynamicAmaSlopePct[idx];
|
|
705
|
+
const spEl = document.getElementById('l-ama-slope');
|
|
706
|
+
if (sp == null) { spEl.textContent = '-'; spEl.style.color = '#8b949e'; }
|
|
707
|
+
else { spEl.textContent = (sp >= 0 ? '+' : '') + (sp * 10).toFixed(3) + '‰'; spEl.style.color = sp > 0.01 ? '#2ea043' : sp < -0.01 ? '#f85149' : '#8b949e'; }
|
|
708
|
+
|
|
709
|
+
const vp = currentKalmanVelocityPct[idx];
|
|
710
|
+
const vpEl = document.getElementById('l-kal-vel');
|
|
711
|
+
if (vp == null) { vpEl.textContent = '-'; vpEl.style.color = '#8b949e'; }
|
|
712
|
+
else { vpEl.textContent = (vp >= 0 ? '+' : '') + vp.toFixed(3) + '%'; vpEl.style.color = vp > 0.01 ? '#2ea043' : vp < -0.01 ? '#f85149' : '#8b949e'; }
|
|
713
|
+
|
|
714
|
+
const dp = data.kalmanDisplacementPct[idx];
|
|
715
|
+
const dpEl = document.getElementById('l-kal-disp');
|
|
716
|
+
if (dp == null) { dpEl.textContent = '-'; dpEl.style.color = '#8b949e'; }
|
|
717
|
+
else { dpEl.textContent = (dp >= 0 ? '+' : '') + dp.toFixed(2) + '%'; dpEl.style.color = dp > 0.3 ? '#2ea043' : dp < -0.3 ? '#f85149' : '#8b949e'; }
|
|
718
|
+
|
|
719
|
+
const sig = data.signals[idx];
|
|
720
|
+
const echoSig = directionToSignal(signalDirectionForIndex(idx));
|
|
721
|
+
const latchedSig = currentLatchedSignals[idx];
|
|
722
|
+
const sigEl = document.getElementById('l-signal');
|
|
723
|
+
if (!sig) { sigEl.textContent = '-'; sigEl.style.color = '#8b949e'; }
|
|
724
|
+
else if (sig.includes('BULL')) { sigEl.textContent = '\u25b2 BULL'; sigEl.style.color = '#2ea043'; }
|
|
725
|
+
else if (sig.includes('BEAR')) { sigEl.textContent = '\u25bc BEAR'; sigEl.style.color = '#f85149'; }
|
|
726
|
+
else if (sig === 'EQUILIBRIUM') { sigEl.textContent = '\u2248 EQ'; sigEl.style.color = '#58a6ff'; }
|
|
727
|
+
else { sigEl.textContent = '\u25cb NEU'; sigEl.style.color = '#8b949e'; }
|
|
728
|
+
const echoEl = document.getElementById('l-signal-echo');
|
|
729
|
+
if (!echoSig) { echoEl.textContent = '-'; echoEl.style.color = '#8b949e'; }
|
|
730
|
+
else if (echoSig.includes('BULL')) { echoEl.textContent = '\u25b2 BULL'; echoEl.style.color = '#3fb950'; }
|
|
731
|
+
else if (echoSig.includes('BEAR')) { echoEl.textContent = '\u25bc BEAR'; echoEl.style.color = '#ff7b72'; }
|
|
732
|
+
else { echoEl.textContent = '\u25cb NEU'; echoEl.style.color = '#8b949e'; }
|
|
733
|
+
const latchedEl = document.getElementById('l-signal-latched');
|
|
734
|
+
if (!latchedSig) { latchedEl.textContent = '-'; latchedEl.style.color = '#8b949e'; }
|
|
735
|
+
else if (latchedSig.includes('BULL')) { latchedEl.textContent = '\u25b2 BULL'; latchedEl.style.color = '#3fb950'; }
|
|
736
|
+
else if (latchedSig.includes('BEAR')) { latchedEl.textContent = '\u25bc BEAR'; latchedEl.style.color = '#ff7b72'; }
|
|
737
|
+
else if (latchedSig === 'EQUILIBRIUM') { latchedEl.textContent = '\u2248 EQ'; latchedEl.style.color = '#79c0ff'; }
|
|
738
|
+
else { latchedEl.textContent = '\u25cb NEU'; latchedEl.style.color = '#8b949e'; }
|
|
739
|
+
|
|
740
|
+
const cRaw = combinedOff[idx];
|
|
741
|
+
const cRawEl = document.getElementById('l-combined-raw');
|
|
742
|
+
if (cRaw == null) { cRawEl.textContent = '-'; cRawEl.style.color = '#8b949e'; }
|
|
743
|
+
else { cRawEl.textContent = (cRaw >= 0 ? '+' : '') + cRaw.toFixed(3); cRawEl.style.color = cRaw > 0.01 ? '#8b949e' : cRaw < -0.01 ? '#8b949e' : '#8b949e'; }
|
|
744
|
+
|
|
745
|
+
const cEcho = echoCombinedOff[idx];
|
|
746
|
+
const cEchoEl = document.getElementById('l-combined-echo');
|
|
747
|
+
if (cEcho == null) { cEchoEl.textContent = '-'; cEchoEl.style.color = '#8b949e'; }
|
|
748
|
+
else { cEchoEl.textContent = (cEcho >= 0 ? '+' : '') + cEcho.toFixed(3); cEchoEl.style.color = cEcho > 0.01 ? '#2ea043' : cEcho < -0.01 ? '#f85149' : '#8b949e'; }
|
|
749
|
+
|
|
750
|
+
const mult = currentMults[idx];
|
|
751
|
+
const multEl = document.getElementById('l-mult');
|
|
752
|
+
if (mult == null) { multEl.textContent = '-'; }
|
|
753
|
+
else { multEl.textContent = 'x' + mult.toFixed(2); multEl.style.color = mult > 1.05 ? '#3fb950' : mult < 0.95 ? '#f85149' : '#d2a8ff'; }
|
|
754
|
+
|
|
755
|
+
document.getElementById('l-sell').textContent = echoCombinedSell[idx] != null ? echoCombinedSell[idx].toFixed(2) : '-';
|
|
756
|
+
document.getElementById('l-buy').textContent = echoCombinedBuy[idx] != null ? echoCombinedBuy[idx].toFixed(2) : '-';
|
|
757
|
+
}
|
|
758
|
+
|
|
759
|
+
function makeSignalBgHook(scaleKey) {
|
|
760
|
+
return u => {
|
|
761
|
+
const { ctx, bbox } = u;
|
|
762
|
+
ctx.save();
|
|
763
|
+
ctx.beginPath(); ctx.rect(bbox.left, bbox.top, bbox.width, bbox.height); ctx.clip();
|
|
764
|
+
ctx.globalCompositeOperation = 'destination-over';
|
|
765
|
+
let i = 0;
|
|
766
|
+
while (i < data.signals.length) {
|
|
767
|
+
const s = currentLatchedSignals[i] || data.signals[i];
|
|
768
|
+
if (!s || i >= data.realBarCount) { i++; continue; }
|
|
769
|
+
const isBull = s.includes('BULL');
|
|
770
|
+
const isBear = s.includes('BEAR');
|
|
771
|
+
let j = i + 1;
|
|
772
|
+
while (j < data.realBarCount) {
|
|
773
|
+
const sj = currentLatchedSignals[j] || data.signals[j];
|
|
774
|
+
if (sj !== s) break;
|
|
775
|
+
j++;
|
|
776
|
+
}
|
|
777
|
+
const x0 = u.valToPos(data.dates[i], 'x', true);
|
|
778
|
+
const x1 = j < data.realBarCount ? u.valToPos(data.dates[j], 'x', true) : bbox.left + bbox.width;
|
|
779
|
+
if (isBull) { ctx.fillStyle = 'rgba(46,160,67,0.06)'; ctx.fillRect(x0, bbox.top, x1 - x0, bbox.height); }
|
|
780
|
+
else if (isBear) { ctx.fillStyle = 'rgba(248,81,73,0.06)'; ctx.fillRect(x0, bbox.top, x1 - x0, bbox.height); }
|
|
781
|
+
i = j;
|
|
782
|
+
}
|
|
783
|
+
// Neutral zone band
|
|
784
|
+
if (scaleKey) {
|
|
785
|
+
const nzY = u.valToPos(currentNz, scaleKey, true);
|
|
786
|
+
const nzYNeg = u.valToPos(-currentNz, scaleKey, true);
|
|
787
|
+
ctx.globalCompositeOperation = 'source-over';
|
|
788
|
+
ctx.fillStyle = 'rgba(139,148,158,0.06)';
|
|
789
|
+
ctx.fillRect(bbox.left, Math.min(nzY, nzYNeg), bbox.width, Math.abs(nzYNeg - nzY));
|
|
790
|
+
}
|
|
791
|
+
ctx.restore();
|
|
792
|
+
};
|
|
793
|
+
}
|
|
794
|
+
|
|
795
|
+
function makePctFillHook(values, scaleKey, posColor, negColor, strokeStyle = 'rgba(255,255,255,0.35)') {
|
|
796
|
+
return u => {
|
|
797
|
+
const { ctx, bbox } = u;
|
|
798
|
+
ctx.save();
|
|
799
|
+
ctx.beginPath(); ctx.rect(bbox.left, bbox.top, bbox.width, bbox.height); ctx.clip();
|
|
800
|
+
const zeroY = u.valToPos(0, scaleKey, true);
|
|
801
|
+
const path = new Path2D();
|
|
802
|
+
let inSeg = false, lastX = 0;
|
|
803
|
+
for (let i = 0; i < values.length; i++) {
|
|
804
|
+
const v = values[i];
|
|
805
|
+
if (v === null) { if (inSeg) { path.lineTo(lastX, zeroY); inSeg = false; } continue; }
|
|
806
|
+
const x = u.valToPos(data.dates[i], 'x', true);
|
|
807
|
+
const y = u.valToPos(v, scaleKey, true);
|
|
808
|
+
if (!inSeg) { path.moveTo(x, zeroY); path.lineTo(x, y); inSeg = true; }
|
|
809
|
+
else path.lineTo(x, y);
|
|
810
|
+
lastX = x;
|
|
811
|
+
}
|
|
812
|
+
if (inSeg) path.lineTo(lastX, zeroY);
|
|
813
|
+
ctx.save();
|
|
814
|
+
ctx.beginPath(); ctx.rect(bbox.left, bbox.top, bbox.width, zeroY - bbox.top); ctx.clip();
|
|
815
|
+
ctx.fillStyle = posColor; ctx.fill(path);
|
|
816
|
+
ctx.restore();
|
|
817
|
+
ctx.save();
|
|
818
|
+
ctx.beginPath(); ctx.rect(bbox.left, zeroY, bbox.width, bbox.top + bbox.height - zeroY); ctx.clip();
|
|
819
|
+
ctx.fillStyle = negColor; ctx.fill(path);
|
|
820
|
+
ctx.restore();
|
|
821
|
+
if (strokeStyle) {
|
|
822
|
+
ctx.strokeStyle = strokeStyle; ctx.lineWidth = 1; ctx.stroke(path);
|
|
823
|
+
ctx.strokeStyle = 'rgba(255,255,255,0.15)'; ctx.lineWidth = 1;
|
|
824
|
+
ctx.beginPath(); ctx.moveTo(bbox.left, zeroY); ctx.lineTo(bbox.left + bbox.width, zeroY); ctx.stroke();
|
|
825
|
+
}
|
|
826
|
+
ctx.restore();
|
|
827
|
+
};
|
|
828
|
+
}
|
|
829
|
+
|
|
830
|
+
function makeClampLineHook(scaleKey, clampValue, label = 'clamp') {
|
|
831
|
+
return u => {
|
|
832
|
+
const { ctx, bbox } = u;
|
|
833
|
+
ctx.save();
|
|
834
|
+
ctx.beginPath(); ctx.rect(bbox.left, bbox.top, bbox.width, bbox.height); ctx.clip();
|
|
835
|
+
|
|
836
|
+
const y = u.valToPos(clampValue, scaleKey, true);
|
|
837
|
+
ctx.strokeStyle = '#a371f7';
|
|
838
|
+
ctx.shadowColor = 'rgba(163,113,247,0.65)';
|
|
839
|
+
ctx.shadowBlur = 5;
|
|
840
|
+
ctx.lineWidth = 1.5;
|
|
841
|
+
ctx.setLineDash([5, 4]);
|
|
842
|
+
ctx.beginPath();
|
|
843
|
+
ctx.moveTo(bbox.left, y);
|
|
844
|
+
ctx.lineTo(bbox.left + bbox.width, y);
|
|
845
|
+
ctx.stroke();
|
|
846
|
+
ctx.setLineDash([]);
|
|
847
|
+
ctx.shadowBlur = 0;
|
|
848
|
+
ctx.shadowColor = 'transparent';
|
|
849
|
+
|
|
850
|
+
if (label) {
|
|
851
|
+
ctx.font = 'bold 10px Segoe UI, sans-serif';
|
|
852
|
+
ctx.textBaseline = 'middle';
|
|
853
|
+
const textW = ctx.measureText(label).width + 8;
|
|
854
|
+
const textX = Math.max(bbox.left + 4, bbox.left + bbox.width - textW - 2);
|
|
855
|
+
const textY = y - 8;
|
|
856
|
+
ctx.fillStyle = 'rgba(11,14,20,0.82)';
|
|
857
|
+
ctx.fillRect(textX - 4, textY - 7, textW, 14);
|
|
858
|
+
ctx.fillStyle = '#a371f7';
|
|
859
|
+
ctx.fillText(label, textX, textY);
|
|
860
|
+
}
|
|
861
|
+
|
|
862
|
+
ctx.restore();
|
|
863
|
+
};
|
|
864
|
+
}
|
|
865
|
+
|
|
866
|
+
function makeClampPairHooks(scaleKey, clampValue) {
|
|
867
|
+
return [
|
|
868
|
+
makeClampLineHook(scaleKey, clampValue, 'clamp ' + clampValue.toFixed(2)),
|
|
869
|
+
makeClampLineHook(scaleKey, -clampValue, 'clamp -' + clampValue.toFixed(2)),
|
|
870
|
+
];
|
|
871
|
+
}
|
|
872
|
+
|
|
873
|
+
const cursorCfg = ${makeCursorConfig()};
|
|
874
|
+
|
|
875
|
+
const TIME_MONTHS = ['Jan', 'Feb', 'Mar', 'Apr', 'May', 'Jun', 'Jul', 'Aug', 'Sep', 'Oct', 'Nov', 'Dec'];
|
|
876
|
+
|
|
877
|
+
function pad2(n) {
|
|
878
|
+
return String(n).padStart(2, '0');
|
|
879
|
+
}
|
|
880
|
+
|
|
881
|
+
function formatTimeLabel(tsSec, spanSec) {
|
|
882
|
+
const d = new Date(tsSec * 1000);
|
|
883
|
+
if (!Number.isFinite(spanSec)) spanSec = 0;
|
|
884
|
+
|
|
885
|
+
if (spanSec >= 365 * 24 * 3600 * 2) {
|
|
886
|
+
return String(d.getUTCFullYear());
|
|
887
|
+
}
|
|
888
|
+
if (spanSec >= 90 * 24 * 3600) {
|
|
889
|
+
return TIME_MONTHS[d.getUTCMonth()] + ' ' + d.getUTCFullYear();
|
|
890
|
+
}
|
|
891
|
+
if (spanSec >= 14 * 24 * 3600) {
|
|
892
|
+
return TIME_MONTHS[d.getUTCMonth()] + ' ' + d.getUTCDate();
|
|
893
|
+
}
|
|
894
|
+
return TIME_MONTHS[d.getUTCMonth()] + ' ' + d.getUTCDate() + ' ' + pad2(d.getUTCHours()) + ':' + pad2(d.getUTCMinutes());
|
|
895
|
+
}
|
|
896
|
+
|
|
897
|
+
function makeTimeAxis(showLabels = false) {
|
|
898
|
+
return {
|
|
899
|
+
show: true,
|
|
900
|
+
stroke: '#ffffff',
|
|
901
|
+
grid: { stroke: '#1c2128' },
|
|
902
|
+
ticks: { stroke: '#30363d', width: 1 },
|
|
903
|
+
size: showLabels ? 24 : 0,
|
|
904
|
+
font: '11px Segoe UI, sans-serif',
|
|
905
|
+
values: showLabels ? (u, vals) => {
|
|
906
|
+
const xScale = u.scales.x || {};
|
|
907
|
+
const spanSec = Number.isFinite(xScale.min) && Number.isFinite(xScale.max)
|
|
908
|
+
? Math.max(0, xScale.max - xScale.min)
|
|
909
|
+
: (xMax - xMin);
|
|
910
|
+
return vals.map((ts) => formatTimeLabel(ts, spanSec));
|
|
911
|
+
} : () => [],
|
|
912
|
+
};
|
|
913
|
+
}
|
|
914
|
+
|
|
915
|
+
function init() {
|
|
916
|
+
// Force all sliders to match JS state — overrides browser form-restore memory
|
|
917
|
+
document.getElementById('alpha-slider').value = Math.round(currentAlpha * 100);
|
|
918
|
+
document.getElementById('ama-ms-slider').value = Math.round((Math.log(currentAmaMaxSlopePct) - AMA_MS_LOG_MIN) / (AMA_MS_LOG_MAX - AMA_MS_LOG_MIN) * 1000);
|
|
919
|
+
document.getElementById('kal-ms-slider').value = Math.round((Math.log(currentKalmanMaxSlopePct) - KAL_MS_LOG_MIN) / (KAL_MS_LOG_MAX - KAL_MS_LOG_MIN) * 1000);
|
|
920
|
+
document.getElementById('th-slider').value = Math.round(currentMinOutputThreshold * 100);
|
|
921
|
+
document.getElementById('th-value').textContent = currentMinOutputThreshold.toFixed(2);
|
|
922
|
+
document.getElementById('gain-slider').value = gainValToSlider(currentGain);
|
|
923
|
+
document.getElementById('kf-slider').value = currentKalmanSmoothPct;
|
|
924
|
+
document.getElementById('kfd-slider').value = Math.round(currentKalmanDispScaleMult * 100);
|
|
925
|
+
document.getElementById('dsp-slider').value = Math.round(currentDispScaleMinPct * 100);
|
|
926
|
+
document.getElementById('kdt-slider').value = Math.round(currentKalmanDispThresholdMult * 100);
|
|
927
|
+
document.getElementById('kfs-slider').value = currentKalmanSmoothSpanPct;
|
|
928
|
+
document.getElementById('cf-slider').value = currentSignalConfirmBars;
|
|
929
|
+
document.getElementById('clip-slider').value = currentClipPct;
|
|
930
|
+
document.getElementById('nz-slider').value = Math.round(currentNz * 100);
|
|
931
|
+
document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
|
|
932
|
+
|
|
933
|
+
const priceEl = document.getElementById('price-panel');
|
|
934
|
+
const amaEl = document.getElementById('ama-panel');
|
|
935
|
+
const kalmanEl = document.getElementById('kalman-panel');
|
|
936
|
+
const outputEl = document.getElementById('output-panel');
|
|
937
|
+
|
|
938
|
+
|
|
939
|
+
priceChart = new uPlot({
|
|
940
|
+
width: priceEl.offsetWidth, height: priceEl.offsetHeight,
|
|
941
|
+
padding: [4, 8, 0, 4], select: { show: false }, legend: { show: false },
|
|
942
|
+
scales: {
|
|
943
|
+
x: { time: true },
|
|
944
|
+
y: { auto: true, distr: 3, log: 10, range: (u, min, max) => [min * 0.9, max * 1.1] }
|
|
945
|
+
},
|
|
946
|
+
series: [
|
|
947
|
+
{ label: 'Time' },
|
|
948
|
+
{ label: 'Price', stroke: '#58a6ff', width: 1.5, scale: 'y', points: { show: false } },
|
|
949
|
+
{ label: '${amaLabel}', stroke: '#e3b341', width: 1.5, scale: 'y', points: { show: false } },
|
|
950
|
+
{ label: 'Seed SMA', stroke: '#8b949e', width: 1.5, dash: [4, 4], scale: 'y', points: { show: false } },
|
|
951
|
+
],
|
|
952
|
+
axes: [
|
|
953
|
+
makeTimeAxis(false),
|
|
954
|
+
{ scale: 'y', stroke: '#ffffff', grid: { stroke: '#1c2128' }, ticks: { stroke: '#30363d', width: 1 }, size: Y_AXIS_SIZE, font: '11px Segoe UI, sans-serif',
|
|
955
|
+
values: (u, v) => v.map(x => x != null ? x.toFixed(4) : ''), }
|
|
956
|
+
],
|
|
957
|
+
cursor: cursorCfg,
|
|
958
|
+
hooks: { draw: [makeSignalBgHook('y')] }
|
|
959
|
+
}, [data.dates, data.prices, data.ama3Prices, data.amaSeedSmaLine], document.getElementById('price-chart'));
|
|
960
|
+
|
|
961
|
+
amaChart = new uPlot({
|
|
962
|
+
width: amaEl.offsetWidth, height: amaEl.offsetHeight,
|
|
963
|
+
padding: [4, 8, 0, 4], select: { show: false }, legend: { show: false },
|
|
964
|
+
scales: { x: { time: true }, p: { auto: true } },
|
|
965
|
+
series: [
|
|
966
|
+
{ label: 'Time' },
|
|
967
|
+
{ label: 'Slope‰', stroke: '#f0a000', width: 2, scale: 'p', points: { show: false } },
|
|
968
|
+
],
|
|
969
|
+
axes: [
|
|
970
|
+
makeTimeAxis(false),
|
|
971
|
+
{ scale: 'p', stroke: '#ffffff', grid: { stroke: '#1c2128', dash: [4, 4] }, ticks: { stroke: '#30303d', width: 1 }, size: Y_AXIS_SIZE, font: '11px Segoe UI, sans-serif',
|
|
972
|
+
values: (u, v) => v.map(x => x != null ? (x >= 0 ? '+' : '') + (x * 10).toFixed(2) + '‰' : '') }
|
|
973
|
+
],
|
|
974
|
+
cursor: cursorCfg,
|
|
975
|
+
hooks: { draw: [makePctFillHook(dynamicAmaSlopePct, 'p', 'rgba(46,160,67,0.20)', 'rgba(248,81,73,0.20)'), makeSignalBgHook('p')] }
|
|
976
|
+
}, [data.dates, dynamicAmaSlopePct], document.getElementById('ama-chart'));
|
|
977
|
+
|
|
978
|
+
kalmanChart = new uPlot({
|
|
979
|
+
width: kalmanEl.offsetWidth, height: kalmanEl.offsetHeight,
|
|
980
|
+
padding: [4, 8, 0, 4], select: { show: false }, legend: { show: false },
|
|
981
|
+
scales: { x: { time: true }, v: { auto: true } },
|
|
982
|
+
series: [
|
|
983
|
+
{ label: 'Time' },
|
|
984
|
+
{ label: 'Vel%', stroke: '#d2a8ff', width: 2, scale: 'v', points: { show: false } },
|
|
985
|
+
{ label: 'Disp%', stroke: '#79c0ff', width: 1.5, dash: [6, 3], scale: 'v', points: { show: false } },
|
|
986
|
+
],
|
|
987
|
+
axes: [
|
|
988
|
+
makeTimeAxis(false),
|
|
989
|
+
{ scale: 'v', stroke: '#ffffff', grid: { stroke: '#1c2128', dash: [4, 4] }, ticks: { stroke: '#30303d', width: 1 }, size: Y_AXIS_SIZE, font: '11px Segoe UI, sans-serif',
|
|
990
|
+
values: (u, v) => v.map(x => x != null ? (x >= 0 ? '+' : '') + x.toFixed(1) + '%' : '') }
|
|
991
|
+
],
|
|
992
|
+
cursor: cursorCfg,
|
|
993
|
+
hooks: { draw: [
|
|
994
|
+
makePctFillHook(currentKalmanVelocityPct, 'v', 'rgba(210,168,255,0.18)', 'rgba(210,168,255,0.18)'),
|
|
995
|
+
makeSignalBgHook('v')
|
|
996
|
+
] }
|
|
997
|
+
}, [data.dates, currentKalmanVelocityPct, data.kalmanDisplacementPct], document.getElementById('kalman-chart'));
|
|
998
|
+
|
|
999
|
+
outputChart = new uPlot({
|
|
1000
|
+
width: outputEl.offsetWidth, height: outputEl.offsetHeight,
|
|
1001
|
+
padding: [20, 8, 0, 4], select: { show: false }, legend: { show: false },
|
|
1002
|
+
scales: { x: { time: true }, ow: { auto: true, range: (u, min, max) => {
|
|
1003
|
+
const m = 0.5;
|
|
1004
|
+
const pad = (max - min) * 0.1 || 0.02;
|
|
1005
|
+
return [Math.min(min - pad, -m), Math.max(max + pad, m)];
|
|
1006
|
+
}}},
|
|
1007
|
+
series: [
|
|
1008
|
+
{ label: 'Time' },
|
|
1009
|
+
{ label: 'RawOff', stroke: 'transparent', width: 0, scale: 'ow', points: { show: false } },
|
|
1010
|
+
{ label: 'EchoOff', stroke: 'transparent', width: 0, scale: 'ow', points: { show: false } }
|
|
1011
|
+
],
|
|
1012
|
+
axes: [
|
|
1013
|
+
makeTimeAxis(true),
|
|
1014
|
+
{ scale: 'ow', stroke: '#ffffff', grid: { stroke: '#1c2128', dash: [4, 4] }, ticks: { stroke: '#30303d', width: 1 }, size: Y_AXIS_SIZE, font: '11px Segoe UI, sans-serif',
|
|
1015
|
+
values: (u, v) => v.map(x => x != null ? (x >= 0 ? '+' : '') + x.toFixed(2) : ''),
|
|
1016
|
+
splits: () => { const m = Math.max(0.5, currentOutputAxisMax || 0.5); return [-m, -m/2, 0, m/2, m]; } }
|
|
1017
|
+
],
|
|
1018
|
+
cursor: cursorCfg,
|
|
1019
|
+
hooks: { draw: [
|
|
1020
|
+
makePctFillHook(combinedOff, 'ow', 'rgba(108,117,125,0.08)', 'rgba(108,117,125,0.06)', null),
|
|
1021
|
+
makePctFillHook(echoCombinedOff, 'ow', 'rgba(46,160,67,0.18)', 'rgba(248,81,73,0.18)', null),
|
|
1022
|
+
...makeClampPairHooks('ow', OUTPUT_CLAMP)
|
|
1023
|
+
] }
|
|
1024
|
+
}, [data.dates, combinedOff, echoCombinedOff], document.getElementById('output-chart'));
|
|
1025
|
+
|
|
1026
|
+
charts = [priceChart, amaChart, kalmanChart, outputChart];
|
|
1027
|
+
${bindHoverStateFn()}
|
|
1028
|
+
${wireChartEvents('charts', 'updateLegend', 'lastLiveIdx')}
|
|
1029
|
+
|
|
1030
|
+
function onSliderChange() {
|
|
1031
|
+
recalcInputs();
|
|
1032
|
+
recalcWeights();
|
|
1033
|
+
refreshChartsPreservingZoom();
|
|
1034
|
+
}
|
|
1035
|
+
|
|
1036
|
+
document.getElementById('alpha-slider').addEventListener('input', (e) => {
|
|
1037
|
+
currentAlpha = parseInt(e.target.value, 10) / 100;
|
|
1038
|
+
document.getElementById('alpha-value').textContent = currentAlpha.toFixed(2);
|
|
1039
|
+
recalcWeights();
|
|
1040
|
+
refreshChartsPreservingZoom();
|
|
1041
|
+
});
|
|
1042
|
+
|
|
1043
|
+
document.getElementById('dw-slider').addEventListener('input', (e) => {
|
|
1044
|
+
currentDw = parseInt(e.target.value, 10) / 100;
|
|
1045
|
+
document.getElementById('dw-value').textContent = currentDw.toFixed(2);
|
|
1046
|
+
onSliderChange();
|
|
1047
|
+
});
|
|
1048
|
+
|
|
1049
|
+
document.getElementById('ama-ms-slider').addEventListener('input', (e) => {
|
|
1050
|
+
currentAmaMaxSlopePct = amaMsSliderToVal(parseInt(e.target.value, 10));
|
|
1051
|
+
document.getElementById('ama-ms-value').textContent = currentAmaMaxSlopePct.toFixed(4);
|
|
1052
|
+
onSliderChange();
|
|
1053
|
+
});
|
|
1054
|
+
|
|
1055
|
+
document.getElementById('kal-ms-slider').addEventListener('input', (e) => {
|
|
1056
|
+
currentKalmanMaxSlopePct = kalMsSliderToVal(parseInt(e.target.value, 10));
|
|
1057
|
+
document.getElementById('kal-ms-value').textContent = currentKalmanMaxSlopePct.toFixed(2);
|
|
1058
|
+
onSliderChange();
|
|
1059
|
+
});
|
|
1060
|
+
|
|
1061
|
+
document.getElementById('gain-slider').addEventListener('input', (e) => {
|
|
1062
|
+
currentGain = gainSliderToVal(parseInt(e.target.value, 10));
|
|
1063
|
+
document.getElementById('gain-value').textContent = currentGain.toFixed(3);
|
|
1064
|
+
onSliderChange();
|
|
1065
|
+
});
|
|
1066
|
+
|
|
1067
|
+
document.getElementById('th-slider').addEventListener('input', (e) => {
|
|
1068
|
+
currentMinOutputThreshold = Math.min(Math.max(parseInt(e.target.value, 10) / 100, 0), 0.5);
|
|
1069
|
+
document.getElementById('th-value').textContent = currentMinOutputThreshold.toFixed(2);
|
|
1070
|
+
recalcWeights();
|
|
1071
|
+
refreshChartsPreservingZoom();
|
|
1072
|
+
});
|
|
1073
|
+
|
|
1074
|
+
document.getElementById('kf-slider').addEventListener('input', (e) => {
|
|
1075
|
+
currentKalmanSmoothPct = parseInt(e.target.value, 10);
|
|
1076
|
+
document.getElementById('kf-value').textContent = currentKalmanSmoothPct + '%';
|
|
1077
|
+
onSliderChange();
|
|
1078
|
+
});
|
|
1079
|
+
|
|
1080
|
+
document.getElementById('kfd-slider').addEventListener('input', (e) => {
|
|
1081
|
+
currentKalmanDispScaleMult = parseInt(e.target.value, 10) / 100;
|
|
1082
|
+
document.getElementById('kfd-value').textContent = currentKalmanDispScaleMult.toFixed(2) + 'x';
|
|
1083
|
+
onSliderChange();
|
|
1084
|
+
});
|
|
1085
|
+
|
|
1086
|
+
document.getElementById('dsp-slider').addEventListener('input', (e) => {
|
|
1087
|
+
currentDispScaleMinPct = parseInt(e.target.value, 10) / 100;
|
|
1088
|
+
document.getElementById('dsp-value').textContent = currentDispScaleMinPct.toFixed(2) + 'x';
|
|
1089
|
+
onSliderChange();
|
|
1090
|
+
});
|
|
1091
|
+
|
|
1092
|
+
document.getElementById('kdt-slider').addEventListener('input', (e) => {
|
|
1093
|
+
currentKalmanDispThresholdMult = parseInt(e.target.value, 10) / 100;
|
|
1094
|
+
document.getElementById('kdt-value').textContent = currentKalmanDispThresholdMult.toFixed(2) + 'x';
|
|
1095
|
+
onSliderChange();
|
|
1096
|
+
});
|
|
1097
|
+
|
|
1098
|
+
document.getElementById('kfs-slider').addEventListener('input', (e) => {
|
|
1099
|
+
currentKalmanSmoothSpanPct = parseInt(e.target.value, 10);
|
|
1100
|
+
document.getElementById('kfs-value').textContent = currentKalmanSmoothSpanPct + '%';
|
|
1101
|
+
onSliderChange();
|
|
1102
|
+
});
|
|
1103
|
+
|
|
1104
|
+
document.getElementById('cf-slider').addEventListener('input', (e) => {
|
|
1105
|
+
currentSignalConfirmBars = parseInt(e.target.value, 10);
|
|
1106
|
+
document.getElementById('cf-value').textContent = currentSignalConfirmBars;
|
|
1107
|
+
onSliderChange();
|
|
1108
|
+
});
|
|
1109
|
+
|
|
1110
|
+
document.getElementById('clip-slider').addEventListener('input', (e) => {
|
|
1111
|
+
currentClipPct = parseInt(e.target.value, 10);
|
|
1112
|
+
if (currentClipPct === 0) {
|
|
1113
|
+
currentAmaClipThreshold = data.amaPercentiles[data.amaPercentiles.length - 1];
|
|
1114
|
+
currentKalClipThreshold = Infinity;
|
|
1115
|
+
} else {
|
|
1116
|
+
currentAmaClipThreshold = data.amaPercentiles[100 - currentClipPct];
|
|
1117
|
+
recalcKalmanClipThreshold();
|
|
1118
|
+
}
|
|
1119
|
+
document.getElementById('clip-value').textContent = currentClipPct + '%';
|
|
1120
|
+
onSliderChange();
|
|
1121
|
+
});
|
|
1122
|
+
|
|
1123
|
+
|
|
1124
|
+
document.getElementById('nz-slider').addEventListener('input', (e) => {
|
|
1125
|
+
currentNz = parseInt(e.target.value, 10) / 100;
|
|
1126
|
+
document.getElementById('nz-value').textContent = currentNz.toFixed(2);
|
|
1127
|
+
onSliderChange();
|
|
1128
|
+
});
|
|
1129
|
+
|
|
1130
|
+
document.getElementById('lb-slider').addEventListener('input', (e) => {
|
|
1131
|
+
currentLookbackBars = lbSliderToVal(parseInt(e.target.value, 10));
|
|
1132
|
+
document.getElementById('lb-value').textContent = currentLookbackBars;
|
|
1133
|
+
onSliderChange();
|
|
1134
|
+
});
|
|
1135
|
+
|
|
1136
|
+
document.getElementById('regime-slider').addEventListener('input', (e) => {
|
|
1137
|
+
currentRegimeSensitivity = parseInt(e.target.value, 10) / 100;
|
|
1138
|
+
document.getElementById('regime-value').textContent = currentRegimeSensitivity.toFixed(2);
|
|
1139
|
+
recalcWeights();
|
|
1140
|
+
refreshChartsPreservingZoom();
|
|
1141
|
+
});
|
|
1142
|
+
|
|
1143
|
+
function applyParams(p, btn) {
|
|
1144
|
+
if (typeof p !== 'object' || p === null) throw new Error('not an object');
|
|
1145
|
+
if (p.alpha != null) {
|
|
1146
|
+
currentAlpha = Math.max(0, Math.min(1, p.alpha));
|
|
1147
|
+
document.getElementById('alpha-slider').value = Math.round(currentAlpha * 100);
|
|
1148
|
+
document.getElementById('alpha-value').textContent = currentAlpha.toFixed(2);
|
|
1149
|
+
}
|
|
1150
|
+
if (p.dispWeight != null) {
|
|
1151
|
+
currentDw = Math.max(0, Math.min(1, p.dispWeight));
|
|
1152
|
+
document.getElementById('dw-slider').value = Math.round(currentDw * 100);
|
|
1153
|
+
document.getElementById('dw-value').textContent = currentDw.toFixed(2);
|
|
1154
|
+
}
|
|
1155
|
+
if (p.amaMaxSlopePct != null) {
|
|
1156
|
+
currentAmaMaxSlopePct = Math.max(0.01, Math.min(0.5, p.amaMaxSlopePct));
|
|
1157
|
+
document.getElementById('ama-ms-slider').value = Math.round((Math.log(currentAmaMaxSlopePct) - AMA_MS_LOG_MIN) / (AMA_MS_LOG_MAX - AMA_MS_LOG_MIN) * 1000);
|
|
1158
|
+
document.getElementById('ama-ms-value').textContent = currentAmaMaxSlopePct.toFixed(4);
|
|
1159
|
+
}
|
|
1160
|
+
if (p.kalmanMaxSlopePct != null) {
|
|
1161
|
+
currentKalmanMaxSlopePct = Math.max(0.15, Math.min(1.5, p.kalmanMaxSlopePct));
|
|
1162
|
+
document.getElementById('kal-ms-slider').value = Math.round((Math.log(currentKalmanMaxSlopePct) - KAL_MS_LOG_MIN) / (KAL_MS_LOG_MAX - KAL_MS_LOG_MIN) * 1000);
|
|
1163
|
+
document.getElementById('kal-ms-value').textContent = currentKalmanMaxSlopePct.toFixed(2);
|
|
1164
|
+
}
|
|
1165
|
+
if (p.gain != null) {
|
|
1166
|
+
currentGain = Math.max(0.5, Math.min(2.0, p.gain));
|
|
1167
|
+
document.getElementById('gain-slider').value = gainValToSlider(currentGain);
|
|
1168
|
+
document.getElementById('gain-value').textContent = currentGain.toFixed(3);
|
|
1169
|
+
}
|
|
1170
|
+
if (p.minOutputThreshold != null) {
|
|
1171
|
+
currentMinOutputThreshold = Math.max(0, Math.min(0.5, p.minOutputThreshold));
|
|
1172
|
+
document.getElementById('th-slider').value = Math.round(currentMinOutputThreshold * 100);
|
|
1173
|
+
document.getElementById('th-value').textContent = currentMinOutputThreshold.toFixed(2);
|
|
1174
|
+
}
|
|
1175
|
+
if (p.kalmanSmoothPct != null) {
|
|
1176
|
+
currentKalmanSmoothPct = Math.max(0, Math.min(200, Math.round(p.kalmanSmoothPct)));
|
|
1177
|
+
document.getElementById('kf-slider').value = currentKalmanSmoothPct;
|
|
1178
|
+
document.getElementById('kf-value').textContent = currentKalmanSmoothPct + '%';
|
|
1179
|
+
}
|
|
1180
|
+
if (p.kalmanDispScaleMult != null) {
|
|
1181
|
+
currentKalmanDispScaleMult = Math.max(1.0, Math.min(3.0, p.kalmanDispScaleMult));
|
|
1182
|
+
document.getElementById('kfd-slider').value = Math.round(currentKalmanDispScaleMult * 100);
|
|
1183
|
+
document.getElementById('kfd-value').textContent = currentKalmanDispScaleMult.toFixed(2) + 'x';
|
|
1184
|
+
}
|
|
1185
|
+
if (p.dispScaleMinPct != null) {
|
|
1186
|
+
currentDispScaleMinPct = Math.max(0.25, Math.min(4.0, p.dispScaleMinPct));
|
|
1187
|
+
document.getElementById('dsp-slider').value = Math.round(currentDispScaleMinPct * 100);
|
|
1188
|
+
document.getElementById('dsp-value').textContent = currentDispScaleMinPct.toFixed(2) + 'x';
|
|
1189
|
+
}
|
|
1190
|
+
if (p.kalmanDispThresholdMult != null) {
|
|
1191
|
+
currentKalmanDispThresholdMult = Math.max(0.25, Math.min(3.0, p.kalmanDispThresholdMult));
|
|
1192
|
+
document.getElementById('kdt-slider').value = Math.round(currentKalmanDispThresholdMult * 100);
|
|
1193
|
+
document.getElementById('kdt-value').textContent = currentKalmanDispThresholdMult.toFixed(2) + 'x';
|
|
1194
|
+
}
|
|
1195
|
+
if (p.kalmanSmoothSpanPct != null) {
|
|
1196
|
+
currentKalmanSmoothSpanPct = Math.max(20, Math.min(200, Math.round(p.kalmanSmoothSpanPct)));
|
|
1197
|
+
document.getElementById('kfs-slider').value = currentKalmanSmoothSpanPct;
|
|
1198
|
+
document.getElementById('kfs-value').textContent = currentKalmanSmoothSpanPct + '%';
|
|
1199
|
+
}
|
|
1200
|
+
if (p.signalConfirmBars != null) {
|
|
1201
|
+
currentSignalConfirmBars = Math.max(0, Math.min(5, Math.round(p.signalConfirmBars)));
|
|
1202
|
+
document.getElementById('cf-slider').value = currentSignalConfirmBars;
|
|
1203
|
+
document.getElementById('cf-value').textContent = currentSignalConfirmBars;
|
|
1204
|
+
}
|
|
1205
|
+
if (p.clipPct != null) {
|
|
1206
|
+
currentClipPct = Math.max(0, Math.min(55, Math.round(p.clipPct)));
|
|
1207
|
+
document.getElementById('clip-slider').value = currentClipPct;
|
|
1208
|
+
document.getElementById('clip-value').textContent = currentClipPct + '%';
|
|
1209
|
+
if (currentClipPct === 0) {
|
|
1210
|
+
currentAmaClipThreshold = data.amaPercentiles[data.amaPercentiles.length - 1];
|
|
1211
|
+
currentKalClipThreshold = Infinity;
|
|
1212
|
+
} else {
|
|
1213
|
+
currentAmaClipThreshold = data.amaPercentiles[100 - currentClipPct];
|
|
1214
|
+
recalcKalmanClipThreshold();
|
|
1215
|
+
}
|
|
1216
|
+
}
|
|
1217
|
+
if (p.neutralZonePct != null) {
|
|
1218
|
+
currentNz = Math.max(0, Math.min(1, p.neutralZonePct));
|
|
1219
|
+
document.getElementById('nz-slider').value = Math.round(currentNz * 100);
|
|
1220
|
+
document.getElementById('nz-value').textContent = currentNz.toFixed(2);
|
|
1221
|
+
}
|
|
1222
|
+
if (p.regimeSensitivity != null) {
|
|
1223
|
+
currentRegimeSensitivity = Math.max(0, Math.min(2, p.regimeSensitivity));
|
|
1224
|
+
document.getElementById('regime-slider').value = Math.round(currentRegimeSensitivity * 100);
|
|
1225
|
+
document.getElementById('regime-value').textContent = currentRegimeSensitivity.toFixed(2);
|
|
1226
|
+
}
|
|
1227
|
+
if (p.lookbackBars != null) {
|
|
1228
|
+
currentLookbackBars = Math.max(1, Math.min(32, Math.round(p.lookbackBars)));
|
|
1229
|
+
document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
|
|
1230
|
+
document.getElementById('lb-value').textContent = currentLookbackBars;
|
|
1231
|
+
}
|
|
1232
|
+
recalcInputs();
|
|
1233
|
+
recalcWeights();
|
|
1234
|
+
refreshChartsPreservingZoom();
|
|
1235
|
+
if (btn) { btn.textContent = 'pasted!'; btn.classList.add('pasted'); setTimeout(() => { btn.textContent = 'paste'; btn.classList.remove('pasted'); }, 1500); }
|
|
1236
|
+
}
|
|
1237
|
+
|
|
1238
|
+
let _confirmPending = null;
|
|
1239
|
+
function showConfirm(p, btn) {
|
|
1240
|
+
_confirmPending = { p, btn };
|
|
1241
|
+
const labels = {
|
|
1242
|
+
alpha: 'alpha',
|
|
1243
|
+
amaMaxSlopePct: 'amaS%',
|
|
1244
|
+
kalmanMaxSlopePct: 'kalS%',
|
|
1245
|
+
minOutputThreshold: 'th%',
|
|
1246
|
+
gain: 'gain',
|
|
1247
|
+
kalmanSmoothPct: 'kf',
|
|
1248
|
+
kalmanDispScaleMult: 'kfd',
|
|
1249
|
+
dispScaleMinPct: 'dsp',
|
|
1250
|
+
kalmanDispThresholdMult: 'kdt',
|
|
1251
|
+
kalmanSmoothSpanPct: 'kfs',
|
|
1252
|
+
signalConfirmBars: 'cf',
|
|
1253
|
+
clipPct: 'clip%',
|
|
1254
|
+
neutralZonePct: 'nz%',
|
|
1255
|
+
regimeSensitivity: 'regime',
|
|
1256
|
+
dispWeight: 'dw',
|
|
1257
|
+
lookbackBars: 'lb',
|
|
1258
|
+
};
|
|
1259
|
+
document.getElementById('paste-confirm-vals').innerHTML = Object.entries(labels)
|
|
1260
|
+
.filter(([k]) => p[k] != null)
|
|
1261
|
+
.map(([k, label]) => label + ': <span>' + p[k] + '</span>')
|
|
1262
|
+
.join('<br>');
|
|
1263
|
+
const popup = document.getElementById('paste-confirm');
|
|
1264
|
+
popup.style.display = 'block';
|
|
1265
|
+
const r = btn.getBoundingClientRect();
|
|
1266
|
+
const pw = popup.offsetWidth, ph = popup.offsetHeight;
|
|
1267
|
+
let top = r.top - ph - 6;
|
|
1268
|
+
let left = r.right - pw;
|
|
1269
|
+
if (top < 4) top = r.bottom + 6;
|
|
1270
|
+
if (left < 4) left = 4;
|
|
1271
|
+
popup.style.top = top + 'px';
|
|
1272
|
+
popup.style.left = left + 'px';
|
|
1273
|
+
}
|
|
1274
|
+
|
|
1275
|
+
document.getElementById('paste-confirm-apply').addEventListener('click', () => {
|
|
1276
|
+
document.getElementById('paste-confirm').style.display = 'none';
|
|
1277
|
+
if (_confirmPending) { applyParams(_confirmPending.p, _confirmPending.btn); _confirmPending = null; }
|
|
1278
|
+
});
|
|
1279
|
+
document.getElementById('paste-confirm-cancel').addEventListener('click', () => {
|
|
1280
|
+
document.getElementById('paste-confirm').style.display = 'none';
|
|
1281
|
+
_confirmPending = null;
|
|
1282
|
+
});
|
|
1283
|
+
document.addEventListener('keydown', (e) => {
|
|
1284
|
+
if (e.key === 'Escape' && document.getElementById('paste-confirm').style.display !== 'none') {
|
|
1285
|
+
document.getElementById('paste-confirm').style.display = 'none';
|
|
1286
|
+
_confirmPending = null;
|
|
1287
|
+
}
|
|
1288
|
+
});
|
|
1289
|
+
|
|
1290
|
+
const LS_KEY = 'dw_research_params';
|
|
1291
|
+
|
|
1292
|
+
document.getElementById('copy-params-btn').addEventListener('click', () => {
|
|
1293
|
+
const btn = document.getElementById('copy-params-btn');
|
|
1294
|
+
const params = {
|
|
1295
|
+
alpha: +currentAlpha.toFixed(2),
|
|
1296
|
+
amaMaxSlopePct: +currentAmaMaxSlopePct.toFixed(4),
|
|
1297
|
+
kalmanMaxSlopePct: +currentKalmanMaxSlopePct.toFixed(2),
|
|
1298
|
+
minOutputThreshold: +currentMinOutputThreshold.toFixed(2),
|
|
1299
|
+
gain: +currentGain.toFixed(3),
|
|
1300
|
+
kalmanSmoothPct: currentKalmanSmoothPct,
|
|
1301
|
+
kalmanDispScaleMult: +currentKalmanDispScaleMult.toFixed(2),
|
|
1302
|
+
dispScaleMinPct: +currentDispScaleMinPct.toFixed(2),
|
|
1303
|
+
kalmanDispThresholdMult: +currentKalmanDispThresholdMult.toFixed(2),
|
|
1304
|
+
kalmanSmoothSpanPct: currentKalmanSmoothSpanPct,
|
|
1305
|
+
signalConfirmBars: currentSignalConfirmBars,
|
|
1306
|
+
clipPct: currentClipPct,
|
|
1307
|
+
neutralZonePct: +currentNz.toFixed(3),
|
|
1308
|
+
regimeSensitivity: +currentRegimeSensitivity.toFixed(2),
|
|
1309
|
+
dispWeight: +currentDw.toFixed(2),
|
|
1310
|
+
lookbackBars: currentLookbackBars,
|
|
1311
|
+
};
|
|
1312
|
+
const json = JSON.stringify(params, null, 2);
|
|
1313
|
+
localStorage.setItem(LS_KEY, json);
|
|
1314
|
+
navigator.clipboard.writeText(json).catch(() => {});
|
|
1315
|
+
btn.textContent = 'copied!';
|
|
1316
|
+
btn.classList.add('copied');
|
|
1317
|
+
setTimeout(() => { btn.textContent = 'copy'; btn.classList.remove('copied'); }, 1500);
|
|
1318
|
+
});
|
|
1319
|
+
|
|
1320
|
+
document.getElementById('paste-params-btn').addEventListener('click', () => {
|
|
1321
|
+
const btn = document.getElementById('paste-params-btn');
|
|
1322
|
+
const tryParse = (text) => {
|
|
1323
|
+
try { showConfirm(JSON.parse(text), btn); }
|
|
1324
|
+
catch { btn.textContent = 'error'; btn.classList.add('error'); setTimeout(() => { btn.textContent = 'paste'; btn.classList.remove('error'); }, 1500); }
|
|
1325
|
+
};
|
|
1326
|
+
// Use localStorage first (same-tab copy/paste), fallback to Ctrl+V
|
|
1327
|
+
const stored = localStorage.getItem(LS_KEY);
|
|
1328
|
+
if (stored) {
|
|
1329
|
+
try { showConfirm(JSON.parse(stored), btn); return; } catch {}
|
|
1330
|
+
}
|
|
1331
|
+
// No localStorage data - use Ctrl+V method
|
|
1332
|
+
btn.textContent = 'Ctrl+V…';
|
|
1333
|
+
btn.classList.add('pasted');
|
|
1334
|
+
const ta = document.createElement('textarea');
|
|
1335
|
+
ta.style.cssText = 'position:fixed;opacity:0;top:0;left:0;width:1px;height:1px;font-size:16px;';
|
|
1336
|
+
document.body.appendChild(ta);
|
|
1337
|
+
ta.focus();
|
|
1338
|
+
const cleanup = () => { document.body.removeChild(ta); btn.textContent = 'paste'; btn.classList.remove('pasted'); };
|
|
1339
|
+
ta.addEventListener('paste', (e) => {
|
|
1340
|
+
const text = (e.clipboardData || window.clipboardData).getData('text');
|
|
1341
|
+
cleanup();
|
|
1342
|
+
tryParse(text);
|
|
1343
|
+
}, { once: true });
|
|
1344
|
+
ta.addEventListener('keydown', (e) => { if (e.key === 'Escape') cleanup(); });
|
|
1345
|
+
setTimeout(cleanup, 10000);
|
|
1346
|
+
});
|
|
1347
|
+
}
|
|
1348
|
+
|
|
1349
|
+
${sizeChartsFn([['priceChart', 'price-panel'], ['amaChart', 'ama-panel'], ['kalmanChart', 'kalman-panel'], ['outputChart', 'output-panel']])}
|
|
1350
|
+
|
|
1351
|
+
window.addEventListener('resize', sizeCharts);
|
|
1352
|
+
${zoomResetScript()}
|
|
1353
|
+
|
|
1354
|
+
init();
|
|
1355
|
+
requestAnimationFrame(() => { sizeCharts(); updateLegend(lastLiveIdx); });
|
|
1356
|
+
</script>
|
|
1357
|
+
</body>
|
|
1358
|
+
</html>`;
|
|
1359
|
+
}
|
|
1360
|
+
export { generateHTML };
|
|
1361
|
+
//# sourceMappingURL=dynamic_weight_chart_generator.js.map
|