dexbot 1.6.4 → 1.6.6
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 +75 -3
- package/README.md +109 -40
- package/analysis/README.md +19 -17
- package/analysis/account_resolver.ts +1 -11
- package/analysis/analyze_dynamic_weight.ts +9 -3
- package/analysis/bot_usage/kibana_bot_queries.ts +1 -1
- package/analysis/chain_pool.ts +1 -1
- package/analysis/fills_source.ts +0 -6
- package/analysis/grid_correction_check.ts +20 -26
- package/analysis/resolve_source.ts +1 -1
- package/analysis/trade_profitability.ts +2 -5
- package/analysis/tradingview/analyze_tradingview.ts +5 -0
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +2 -2
- package/analysis/trend_detection/DYNAMIC_WEIGHT_RESEARCH.md +35 -2
- package/analysis/trend_detection/README.md +1 -3
- package/analysis/trend_detection/dynamic_weight_chart_generator.ts +82 -10
- package/analysis/trend_detection/package.json +1 -1
- package/claw/modules/dexbot_profiles.ts +4 -3
- package/claw/package.json +1 -1
- package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
- package/claw/runtimes/openclaw-plugin/package.json +1 -1
- package/claw/tests/test_claw_mcp_transport.ts +2 -2
- package/claw/tests/test_dexbot_profiles.ts +18 -1
- package/dist/analysis/account_resolver.d.ts +1 -4
- package/dist/analysis/account_resolver.d.ts.map +1 -1
- package/dist/analysis/account_resolver.js +1 -1
- package/dist/analysis/account_resolver.js.map +1 -1
- package/dist/analysis/analyze_dynamic_weight.js +9 -3
- package/dist/analysis/analyze_dynamic_weight.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +1 -2
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chain_pool.d.ts +1 -3
- package/dist/analysis/chain_pool.d.ts.map +1 -1
- package/dist/analysis/chain_pool.js +1 -1
- package/dist/analysis/chain_pool.js.map +1 -1
- package/dist/analysis/fills_source.d.ts +1 -51
- package/dist/analysis/fills_source.d.ts.map +1 -1
- package/dist/analysis/fills_source.js +1 -1
- package/dist/analysis/fills_source.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts +2 -3
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +18 -26
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/resolve_source.d.ts +1 -1
- package/dist/analysis/resolve_source.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +2 -7
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.d.ts.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +5 -0
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +2 -2
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +82 -10
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +85 -140
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/core/asymmetric_bounds.d.ts +2 -2
- package/dist/market_adapter/core/asymmetric_bounds.d.ts.map +1 -1
- package/dist/market_adapter/core/asymmetric_bounds.js +15 -12
- package/dist/market_adapter/core/asymmetric_bounds.js.map +1 -1
- package/dist/market_adapter/core/kibana_candles.d.ts +1 -15
- package/dist/market_adapter/core/kibana_candles.d.ts.map +1 -1
- package/dist/market_adapter/core/kibana_candles.js +1 -1
- package/dist/market_adapter/core/kibana_candles.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +1 -35
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +10 -54
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts +1 -8
- package/dist/market_adapter/inputs/kibana_feed_source.d.ts.map +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js +1 -1
- package/dist/market_adapter/inputs/kibana_feed_source.js.map +1 -1
- package/dist/market_adapter/inputs/window_cache.d.ts +2 -63
- package/dist/market_adapter/inputs/window_cache.d.ts.map +1 -1
- package/dist/market_adapter/inputs/window_cache.js +22 -102
- package/dist/market_adapter/inputs/window_cache.js.map +1 -1
- package/dist/market_adapter/interval_utils.d.ts +1 -1
- package/dist/market_adapter/interval_utils.js +1 -1
- package/dist/modules/account_bots.d.ts +70 -6
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +353 -149
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.d.ts.map +1 -1
- package/dist/modules/bitshares-native/crypto/ecc.browser.js +0 -31
- package/dist/modules/bitshares-native/crypto/ecc.browser.js.map +1 -1
- package/dist/modules/bot_defaults.d.ts +98 -0
- package/dist/modules/bot_defaults.d.ts.map +1 -0
- package/dist/modules/bot_defaults.js +169 -0
- package/dist/modules/bot_defaults.js.map +1 -0
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +4 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/config.d.ts +1 -0
- package/dist/modules/config.d.ts.map +1 -1
- package/dist/modules/config.js +1 -0
- package/dist/modules/config.js.map +1 -1
- package/dist/modules/constants.d.ts +11 -2
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +40 -27
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_runtime.d.ts +0 -2
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +1 -10
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +13 -10
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +0 -9
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +13 -0
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +13 -1
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +9 -4
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/launcher/adapter_requirement.js +1 -1
- package/dist/modules/launcher/launch_modes.d.ts +17 -1
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +18 -1
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +8 -1
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/market_adapter_whitelist.d.ts +40 -1
- package/dist/modules/market_adapter_whitelist.d.ts.map +1 -1
- package/dist/modules/market_adapter_whitelist.js +161 -11
- package/dist/modules/market_adapter_whitelist.js.map +1 -1
- package/dist/modules/order/accounting.d.ts +4 -18
- package/dist/modules/order/accounting.d.ts.map +1 -1
- package/dist/modules/order/accounting.js +4 -34
- package/dist/modules/order/accounting.js.map +1 -1
- package/dist/modules/order/export.d.ts +9 -12
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +9 -12
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts +44 -40
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +66 -48
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile.js +111 -20
- package/dist/modules/order/grid_reconcile.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +5 -3
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +18 -3
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +0 -8
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +3 -12
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +22 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +103 -21
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +5 -1
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +5 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/withPoolRef.d.ts +12 -0
- package/dist/modules/order/utils/withPoolRef.d.ts.map +1 -1
- package/dist/modules/order/utils/withPoolRef.js +17 -0
- package/dist/modules/order/utils/withPoolRef.js.map +1 -1
- package/dist/modules/order/working_grid.d.ts +8 -13
- package/dist/modules/order/working_grid.d.ts.map +1 -1
- package/dist/modules/order/working_grid.js +8 -29
- package/dist/modules/order/working_grid.js.map +1 -1
- package/dist/modules/settings_merge.d.ts +10 -1
- package/dist/modules/settings_merge.d.ts.map +1 -1
- package/dist/modules/settings_merge.js +27 -17
- package/dist/modules/settings_merge.js.map +1 -1
- package/dist/modules/validate_profiles.d.ts.map +1 -1
- package/dist/modules/validate_profiles.js +4 -3
- package/dist/modules/validate_profiles.js.map +1 -1
- package/dist/scripts/chart_command.d.ts +48 -0
- package/dist/scripts/chart_command.d.ts.map +1 -0
- package/dist/scripts/chart_command.js +504 -0
- package/dist/scripts/chart_command.js.map +1 -0
- package/dist/scripts/dw.d.ts +3 -0
- package/dist/scripts/dw.d.ts.map +1 -0
- package/dist/scripts/dw.js +30 -0
- package/dist/scripts/dw.js.map +1 -0
- package/dist/scripts/sync-version.js +0 -22
- package/dist/scripts/sync-version.js.map +1 -1
- package/dist/scripts/tv.d.ts +1 -31
- package/dist/scripts/tv.d.ts.map +1 -1
- package/dist/scripts/tv.js +10 -463
- package/dist/scripts/tv.js.map +1 -1
- package/dist/scripts/update.js +99 -62
- package/dist/scripts/update.js.map +1 -1
- package/dist/scripts/update_dist_freshness.d.ts +58 -0
- package/dist/scripts/update_dist_freshness.d.ts.map +1 -0
- package/dist/scripts/update_dist_freshness.js +144 -0
- package/dist/scripts/update_dist_freshness.js.map +1 -0
- package/dist/unlock.js +1 -1
- package/dist/unlock.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +16 -10
- package/docs/COW_INVARIANTS.md +1 -0
- package/docs/DEXBOT_COMPARISON.md +11 -11
- package/docs/EVOLUTION.md +9 -7
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +4 -4
- package/docs/GRID_PRICE_INVARIANT.md +518 -0
- package/docs/GRID_RECALCULATION.md +20 -24
- package/docs/GRID_RECONCILE.md +62 -41
- package/docs/LOGGING.md +1 -0
- package/docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md +556 -0
- package/docs/README.md +18 -18
- package/docs/WORKFLOW.md +13 -8
- package/docs/architecture.md +42 -40
- package/docs/developer_guide.md +5 -8
- package/market_adapter/README.md +45 -65
- package/modules/README.md +3 -2
- package/package.json +7 -4
- package/scripts/README.md +18 -34
- package/scripts/reset-settings.sh +1 -1
- package/analysis/ama_fitting/package.json +0 -12
- package/analysis/analyze_derivatives.ts +0 -320
- package/analysis/derivative_chart_generator.ts +0 -874
- package/analysis/trend_detection/SIGNAL_DOCUMENTATION.md +0 -403
- package/analysis/trend_detection/derivative_analyzer.ts +0 -1059
- package/dist/analysis/analyze_derivatives.d.ts +0 -68
- package/dist/analysis/analyze_derivatives.d.ts.map +0 -1
- package/dist/analysis/analyze_derivatives.js +0 -287
- package/dist/analysis/analyze_derivatives.js.map +0 -1
- package/dist/analysis/derivative_chart_generator.d.ts +0 -12
- package/dist/analysis/derivative_chart_generator.d.ts.map +0 -1
- package/dist/analysis/derivative_chart_generator.js +0 -891
- package/dist/analysis/derivative_chart_generator.js.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts +0 -248
- package/dist/analysis/trend_detection/derivative_analyzer.d.ts.map +0 -1
- package/dist/analysis/trend_detection/derivative_analyzer.js +0 -910
- package/dist/analysis/trend_detection/derivative_analyzer.js.map +0 -1
- package/dist/scripts/generate_market_adapter_whitelist.d.ts +0 -15
- package/dist/scripts/generate_market_adapter_whitelist.d.ts.map +0 -1
- package/dist/scripts/generate_market_adapter_whitelist.js +0 -191
- package/dist/scripts/generate_market_adapter_whitelist.js.map +0 -1
|
@@ -2,9 +2,9 @@
|
|
|
2
2
|
'use strict';
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
|
-
*
|
|
5
|
+
* LAST-FILL-GUARD CHECK
|
|
6
6
|
*
|
|
7
|
-
* Validates LAST-FILL-GUARD discipline
|
|
7
|
+
* Validates LAST-FILL-GUARD discipline: pivot ± halfIncrement
|
|
8
8
|
* Last fill @x with increment i (half=i/2) gates BOTH sides regardless of
|
|
9
9
|
* last side — BUY must be < x*(1-half/100), SELL > x*(1+half/100).
|
|
10
10
|
* e.g. x=1000, i=0.5% => BUY < 997.5 / SELL > 1002.5.
|
|
@@ -21,7 +21,7 @@
|
|
|
21
21
|
* modules/constants.ts:DEFAULT_CONFIG.incrementPercent fallback (0.5).
|
|
22
22
|
*
|
|
23
23
|
* Fetches fill_order operations from Kibana (same pipeline as
|
|
24
|
-
* trade_profitability.ts) and checks for
|
|
24
|
+
* trade_profitability.ts) and checks for last-fill guard violations.
|
|
25
25
|
*
|
|
26
26
|
* Usage:
|
|
27
27
|
* node dist/analysis/grid_correction_check.js --bot-key <bot-key> --hours 168
|
|
@@ -166,7 +166,6 @@ Time range (one of):
|
|
|
166
166
|
|
|
167
167
|
Options:
|
|
168
168
|
--account <id> Override account ID (default: from bot preferredAccount)
|
|
169
|
-
--lookup Legacy: account names always resolve via BitShares node
|
|
170
169
|
--refresh-account Force re-resolution of preferredAccount and update the
|
|
171
170
|
stored accountId when it changed (default: reuse the
|
|
172
171
|
stored accountId with no chain lookup)
|
|
@@ -176,7 +175,7 @@ Options:
|
|
|
176
175
|
--include-cross-pair Check consecutive fills across different pairs (default: same pair only)
|
|
177
176
|
--json <file> Export violations as JSON
|
|
178
177
|
--csv <file> Export violations as CSV
|
|
179
|
-
--verbose
|
|
178
|
+
--verbose Print the fetched trade sequence before checking
|
|
180
179
|
--list-bots List available bot keys and exit
|
|
181
180
|
--help, -h Show this help
|
|
182
181
|
|
|
@@ -208,7 +207,6 @@ function parseArgs() {
|
|
|
208
207
|
start: null,
|
|
209
208
|
end: null,
|
|
210
209
|
account: null,
|
|
211
|
-
lookup: false,
|
|
212
210
|
refreshAccount: false,
|
|
213
211
|
perFill: false,
|
|
214
212
|
includeCrossPair: false,
|
|
@@ -225,7 +223,6 @@ function parseArgs() {
|
|
|
225
223
|
case '--start': opts.start = args[++i]; break;
|
|
226
224
|
case '--end': opts.end = args[++i]; break;
|
|
227
225
|
case '--account': opts.account = args[++i]; break;
|
|
228
|
-
case '--lookup': opts.lookup = true; break;
|
|
229
226
|
case '--refresh-account': opts.refreshAccount = true; break;
|
|
230
227
|
case '--per-fill': opts.perFill = true; break;
|
|
231
228
|
case '--include-cross-pair': opts.includeCrossPair = true; break;
|
|
@@ -472,10 +469,9 @@ function detectViolations(
|
|
|
472
469
|
items: (TradeFill | AggregatedOrder)[],
|
|
473
470
|
includeCrossPair: boolean,
|
|
474
471
|
incrementPercent: number,
|
|
475
|
-
): { violations: Violation[];
|
|
472
|
+
): { violations: Violation[]; checkedTransitions: number } {
|
|
476
473
|
const violations: Violation[] = [];
|
|
477
|
-
let
|
|
478
|
-
let sameDirectionPairs = 0;
|
|
474
|
+
let checkedTransitions = 0;
|
|
479
475
|
|
|
480
476
|
// Helper for a single chronological sequence (already filtered to one pair or global)
|
|
481
477
|
function checkSequence(seq: (TradeFill | AggregatedOrder)[]) {
|
|
@@ -484,8 +480,7 @@ function detectViolations(
|
|
|
484
480
|
const curr = seq[i] as any;
|
|
485
481
|
// Skip same orderId (multi-fill split of one order) — aggregated mode already collapsed, but per-fill may split
|
|
486
482
|
if (prev.orderId && prev.orderId === curr.orderId) continue;
|
|
487
|
-
|
|
488
|
-
if (prev.direction === curr.direction) sameDirectionPairs++;
|
|
483
|
+
checkedTransitions++;
|
|
489
484
|
|
|
490
485
|
const check = isLastFillGuardBlocked(curr.price, curr.direction, prev.price, prev.direction, incrementPercent);
|
|
491
486
|
if (check.blocked) {
|
|
@@ -528,7 +523,7 @@ function detectViolations(
|
|
|
528
523
|
}
|
|
529
524
|
// Sort violations chronologically for reporting
|
|
530
525
|
violations.sort((a, b) => new Date(a.curr.time).getTime() - new Date(b.curr.time).getTime());
|
|
531
|
-
return { violations,
|
|
526
|
+
return { violations, checkedTransitions };
|
|
532
527
|
}
|
|
533
528
|
|
|
534
529
|
// ─── Reporting ────────────────────────────────────────────────────────────────
|
|
@@ -541,8 +536,7 @@ function printReport(
|
|
|
541
536
|
trades: TradeFill[],
|
|
542
537
|
orders: AggregatedOrder[] | null,
|
|
543
538
|
violations: Violation[],
|
|
544
|
-
|
|
545
|
-
sameDirectionPairs: number,
|
|
539
|
+
checkedTransitions: number,
|
|
546
540
|
skipped: number,
|
|
547
541
|
rangeLabel: string,
|
|
548
542
|
botKey: string,
|
|
@@ -565,7 +559,7 @@ function printReport(
|
|
|
565
559
|
|
|
566
560
|
console.log('');
|
|
567
561
|
console.log('═══════════════════════════════════════════════════════════════════');
|
|
568
|
-
console.log('
|
|
562
|
+
console.log(' LAST-FILL-GUARD CHECK (pivot ± halfIncrement)');
|
|
569
563
|
console.log('═══════════════════════════════════════════════════════════════════');
|
|
570
564
|
console.log(` Bot key: ${botKey}${botMeta?.name ? ` (name: ${botMeta.name})` : ''}`);
|
|
571
565
|
if (botMeta) console.log(` Pair: ${botMeta.assetA ?? '?'} / ${botMeta.assetB ?? '?'}`);
|
|
@@ -582,16 +576,16 @@ function printReport(
|
|
|
582
576
|
if (orders) console.log(` Orders (aggregated): ${orders.length} (from ${trades.length} fills)`);
|
|
583
577
|
console.log(` Pairs observed: ${[...pairGroups.keys()].join(', ') || '-'}`);
|
|
584
578
|
if (skipped > 0) console.log(` Skipped (precision): ${skipped}`);
|
|
585
|
-
console.log(`
|
|
586
|
-
console.log(` Violations: ${violations.length}${
|
|
579
|
+
console.log(` Transitions checked: ${checkedTransitions} consecutive fill/order transitions`);
|
|
580
|
+
console.log(` Violations: ${violations.length}${checkedTransitions > 0 ? ` (${((violations.length / checkedTransitions) * 100).toFixed(2)}%)` : ''}`);
|
|
587
581
|
console.log('');
|
|
588
582
|
|
|
589
583
|
if (violations.length === 0) {
|
|
590
|
-
console.log(' ✅ PASS — no
|
|
584
|
+
console.log(' ✅ PASS — no LAST-FILL-GUARD violations detected (all BUY < pivot-half, SELL > pivot+half).');
|
|
591
585
|
console.log('');
|
|
592
|
-
if (
|
|
593
|
-
console.log(' Note: no consecutive
|
|
594
|
-
console.log(' (Need at least two fills/orders
|
|
586
|
+
if (checkedTransitions === 0) {
|
|
587
|
+
console.log(' Note: no consecutive transitions in range to check.');
|
|
588
|
+
console.log(' (Need at least two fills/orders to form a transition.)');
|
|
595
589
|
}
|
|
596
590
|
console.log('');
|
|
597
591
|
return;
|
|
@@ -709,7 +703,7 @@ async function main() {
|
|
|
709
703
|
const opts = parseArgs();
|
|
710
704
|
const { gte, lte, label } = resolveTimeRange(opts);
|
|
711
705
|
|
|
712
|
-
console.log(`\
|
|
706
|
+
console.log(`\nLast-fill guard check — bot-key: ${opts.botKey}`);
|
|
713
707
|
console.log(`Range: ${label}`);
|
|
714
708
|
|
|
715
709
|
const resolvedAccount = await resolveBotAccount(opts.botKey, {
|
|
@@ -770,10 +764,10 @@ async function main() {
|
|
|
770
764
|
items = orders;
|
|
771
765
|
}
|
|
772
766
|
|
|
773
|
-
const { violations,
|
|
767
|
+
const { violations, checkedTransitions } = detectViolations(items, opts.includeCrossPair, incrementPercent);
|
|
774
768
|
|
|
775
769
|
const ordersForReport = opts.perFill ? null : (items as AggregatedOrder[]);
|
|
776
|
-
printReport(trades, ordersForReport, violations,
|
|
770
|
+
printReport(trades, ordersForReport, violations, checkedTransitions, skipped, label, opts.botKey, accountId, botMeta, opts.perFill, opts.includeCrossPair, incrementPercent, gte, lte);
|
|
777
771
|
|
|
778
772
|
if (opts.json) exportJson(opts.json, violations, trades, label, opts.botKey, accountId, incrementPercent);
|
|
779
773
|
if (opts.csv) exportCsv(opts.csv, violations);
|
|
@@ -781,7 +775,7 @@ async function main() {
|
|
|
781
775
|
process.exit(violations.length > 0 ? 2 : 0);
|
|
782
776
|
}
|
|
783
777
|
|
|
784
|
-
export { isLastFillGuardBlocked, classifyFills, aggregateByOrder, detectViolations, TradeFill,
|
|
778
|
+
export { isLastFillGuardBlocked, classifyFills, aggregateByOrder, detectViolations, TradeFill, Violation, AggregatedOrder };
|
|
785
779
|
|
|
786
780
|
if (process.argv[1] && import.meta.url === pathToFileURL(process.argv[1]).href) {
|
|
787
781
|
main().catch(e => {
|
|
@@ -28,7 +28,7 @@ import {
|
|
|
28
28
|
* node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --asset 1.3.113
|
|
29
29
|
* node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --csv trades.csv
|
|
30
30
|
* node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --json results.json
|
|
31
|
-
* node dist/analysis/trade_profitability.js "account-name" --
|
|
31
|
+
* node dist/analysis/trade_profitability.js "account-name" --hours 168
|
|
32
32
|
* node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --trades
|
|
33
33
|
* node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --match-mode fifo
|
|
34
34
|
*/
|
|
@@ -118,7 +118,6 @@ Options:
|
|
|
118
118
|
--end <iso> End time (ISO 8601)
|
|
119
119
|
--hours <n> Lookback hours from now (alternative to --start/--end)
|
|
120
120
|
--asset <assetId> Filter to one base asset (e.g. 1.3.113 for bitUSD)
|
|
121
|
-
--lookup Legacy (no-op): account names always resolve automatically
|
|
122
121
|
--refresh-account Force re-resolution and update the stored accountId
|
|
123
122
|
--csv <file> Export trade list as CSV
|
|
124
123
|
--json <file> Export full analysis as JSON
|
|
@@ -131,7 +130,7 @@ Options:
|
|
|
131
130
|
Examples:
|
|
132
131
|
node dist/analysis/trade_profitability.js 1.2.123456 --hours 720
|
|
133
132
|
node dist/analysis/trade_profitability.js 1.2.123456 --start 2025-01-01 --end 2025-06-01
|
|
134
|
-
node dist/analysis/trade_profitability.js "my-bot-account" --
|
|
133
|
+
node dist/analysis/trade_profitability.js "my-bot-account" --hours 168
|
|
135
134
|
node dist/analysis/trade_profitability.js 1.2.123456 --hours 720 --asset 1.3.113 --csv trades.csv
|
|
136
135
|
node dist/analysis/trade_profitability.js 1.2.123456 --hours 720 --match-mode sequential`);
|
|
137
136
|
}
|
|
@@ -149,7 +148,6 @@ function parseArgs() {
|
|
|
149
148
|
start: null,
|
|
150
149
|
end: null,
|
|
151
150
|
asset: null,
|
|
152
|
-
lookup: false,
|
|
153
151
|
refreshAccount: false,
|
|
154
152
|
csv: null,
|
|
155
153
|
json: null,
|
|
@@ -165,7 +163,6 @@ function parseArgs() {
|
|
|
165
163
|
case '--start': opts.start = args[++i]; break;
|
|
166
164
|
case '--end': opts.end = args[++i]; break;
|
|
167
165
|
case '--asset': opts.asset = args[++i]; break;
|
|
168
|
-
case '--lookup': opts.lookup = true; break;
|
|
169
166
|
case '--refresh-account': opts.refreshAccount = true; break;
|
|
170
167
|
case '--csv': opts.csv = args[++i]; break;
|
|
171
168
|
case '--json': opts.json = args[++i]; break;
|
|
@@ -236,6 +236,11 @@ async function main() {
|
|
|
236
236
|
},
|
|
237
237
|
smaPeriod: config.smaPeriod,
|
|
238
238
|
amaDefaults: {
|
|
239
|
+
// Intentionally the 3-param AMA (er/fast/slow) only: the in-page
|
|
240
|
+
// recomputation has no erSmoothPeriod input by decision — `dexbot tv`
|
|
241
|
+
// stays a plain candle chart. The dw research chart and the live
|
|
242
|
+
// adapter are the mirrors of the full bot AMA config (incl.
|
|
243
|
+
// ama.erSmoothPeriod); the knob is inert by default (global 0).
|
|
239
244
|
erPeriod: config.amaErPeriod ?? amaConfig.erPeriod,
|
|
240
245
|
fastPeriod: config.amaFastPeriod ?? amaConfig.fastPeriod,
|
|
241
246
|
slowPeriod: config.amaSlowPeriod ?? amaConfig.slowPeriod,
|
|
@@ -1469,7 +1469,7 @@ function generateHTML(data: any, title: any = 'TradingView Style Research') {
|
|
|
1469
1469
|
const maxSlopeOffset = Number(slopeCfg.maxSlopeOffset) > 0 ? Number(slopeCfg.maxSlopeOffset) : 0.5;
|
|
1470
1470
|
const maxAsym = gridCfg && Number(gridCfg.maxAsymmetryFactor) > 0
|
|
1471
1471
|
? Number(gridCfg.maxAsymmetryFactor)
|
|
1472
|
-
: (Number(slopeCfg.maxAsymmetryFactor) > 0 ? Number(slopeCfg.maxAsymmetryFactor) : 0.
|
|
1472
|
+
: (Number(slopeCfg.maxAsymmetryFactor) > 0 ? Number(slopeCfg.maxAsymmetryFactor) : 0.333);
|
|
1473
1473
|
const inc = gridCfg && Number(gridCfg.incrementPercent) > 0 ? Number(gridCfg.incrementPercent) : null;
|
|
1474
1474
|
const minSlots = Math.floor(Number(gridCfg && gridCfg.minScaleSlots) > 0 ? Number(gridCfg.minScaleSlots) : (Number(slopeCfg.minScaleSlots) || 0));
|
|
1475
1475
|
// User x-range span (slider 1.2x–2.0x, default 1.55x): the only
|
|
@@ -3066,7 +3066,7 @@ function generateHTML(data: any, title: any = 'TradingView Style Research') {
|
|
|
3066
3066
|
}
|
|
3067
3067
|
makeStepper('sma-period', 1, 1, 0);
|
|
3068
3068
|
makeStepper('vwap-bars', 1, 24, 0);
|
|
3069
|
-
makeStepper('ama-er',
|
|
3069
|
+
makeStepper('ama-er', 10, 1, 0);
|
|
3070
3070
|
makeStepper('ama-fast', 0.1, 0.1, 1);
|
|
3071
3071
|
makeStepper('ama-slow', 1, 0.1, 1);
|
|
3072
3072
|
document.getElementById('ama-init-offset').addEventListener('input', () => {
|
|
@@ -63,6 +63,11 @@ On top of this blend, **Hurst Exponent** and **Permutation Entropy** act as a re
|
|
|
63
63
|
## Quick Start
|
|
64
64
|
|
|
65
65
|
```bash
|
|
66
|
+
# One-step (recommended): same fetch pipeline as `dexbot tv` — bot, pool, or pair
|
|
67
|
+
dexbot dw <bot-key>
|
|
68
|
+
dexbot dw 133 --month 6
|
|
69
|
+
dexbot dw TOKENA/TOKENB --month 1
|
|
70
|
+
|
|
66
71
|
# From JSON candle file
|
|
67
72
|
node dist/analysis/analyze_dynamic_weight.js \
|
|
68
73
|
--file market_adapter/data/lp/<pair>/lp_pool_<id>_<interval>.json
|
|
@@ -77,6 +82,8 @@ node dist/analysis/analyze_dynamic_weight.js \
|
|
|
77
82
|
|
|
78
83
|
Output: `analysis/charts/dynamic_weight_chart.html` (open in browser)
|
|
79
84
|
|
|
85
|
+
`dexbot dw` writes an auto-named chart instead: `analysis/charts/dw_<bot|pool_<id>|<a>_<b>>_1h_<N>m.html`.
|
|
86
|
+
|
|
80
87
|
**Note**: Hurst requires 256 bars and PE requires 58 bars before their regime signals become active. The first portion of the chart will show the full weight without regime gating.
|
|
81
88
|
|
|
82
89
|
## CLI Flags
|
|
@@ -90,6 +97,7 @@ Output: `analysis/charts/dynamic_weight_chart.html` (open in browser)
|
|
|
90
97
|
| `--alpha` | `0.5` | Initial α blend (0 = pure Kalman, 1 = pure AMA) |
|
|
91
98
|
| `--dw` | `0.50` | Initial displacement weight (0 = pure velocity, 1 = full displacement) |
|
|
92
99
|
| `--lb` | `9` | Initial lookback bars (1-32) for AMA slope calculation |
|
|
100
|
+
| `--ema` | `0` | Initial AMA input EMA span in bars (0 = off, 0-32) |
|
|
93
101
|
| `--gain` | `1.0` | Initial gain multiplier |
|
|
94
102
|
| `--clip` | `10` | Initial clip percentile |
|
|
95
103
|
| `--quiet` | `false` | Suppress console output |
|
|
@@ -107,6 +115,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
|
|
|
107
115
|
|
|
108
116
|
### Panel 2 — AMA Slope Input (14%)
|
|
109
117
|
- **Orange line**: AMA3 slope percentage
|
|
118
|
+
- **Gray dashed line**: raw (unfiltered) slope — the reference when the `ema` knob > 0
|
|
110
119
|
- Shows the directional strength of the slow KAMA's trend movement
|
|
111
120
|
- Values outside the clip threshold are flattened before entering the offset formula
|
|
112
121
|
- Background shading mirrors the output signal direction
|
|
@@ -159,6 +168,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
|
|
|
159
168
|
|------|-------|---------|---------|
|
|
160
169
|
| **nz%** | 0–1 | 0.00 | Neutral zone: dead-band below which offset is forced to 0 |
|
|
161
170
|
| **lb** | 1–32 | 9 | Logarithmic. Lookback bars for AMA slope calculation |
|
|
171
|
+
| **ema** | 0–32 | 0 | AMA input EMA span in bars (0 = off). Low-pass filters the AMA before the slope is taken; the gray dashed Raw‰ line shows the unfiltered slope for comparison |
|
|
162
172
|
| **amaS%** | 0.06–0.12 | 0.09 | Logarithmic. Gear ratio for average per-bar AMA slope saturation |
|
|
163
173
|
| **kalS%** | 0.5–1.5 | 1.0 | Logarithmic. Gear ratio for Kalman composite saturation |
|
|
164
174
|
| **clip%** | 0–55 | 10 | Percentile clip: filters extreme inputs (0 = off) |
|
|
@@ -172,7 +182,7 @@ All panels share aligned vertical time grid lines, and the bottom output panel s
|
|
|
172
182
|
|
|
173
183
|
## Copy / Paste Parameters
|
|
174
184
|
|
|
175
|
-
The **copy** button serializes all knob values (α, dw, kf, kfd, dsp, kdt, kfs, cf, lb, amaS%, kalS%, th%, gain, clip%, nz%, regi) to JSON and writes them to both the clipboard and `localStorage`.
|
|
185
|
+
The **copy** button serializes all knob values (α, dw, kf, kfd, dsp, kdt, kfs, cf, lb, ema, amaS%, kalS%, th%, gain, clip%, nz%, regi) to JSON and writes them to both the clipboard and `localStorage`.
|
|
176
186
|
|
|
177
187
|
The **paste** button first checks `localStorage` for parameters from a previous copy in the same browser session. If none found, it prompts for Ctrl+V input. A confirmation popup shows the parsed values before applying them. Click **Apply** to set the knobs, **Cancel** or press **Escape** to dismiss.
|
|
178
188
|
|
|
@@ -182,7 +192,7 @@ The **paste** button first checks `localStorage` for parameters from a previous
|
|
|
182
192
|
|
|
183
193
|
### AMA slope
|
|
184
194
|
|
|
185
|
-
Taking the slope of the AMA produces a signal that is already noise-filtered at the source — a near-zero slope genuinely means sideways, not oscillation that happened to average out. The neutral zone (`nz%` knob) dead-bands any remaining micro-slope residuals
|
|
195
|
+
Taking the slope of the AMA produces a signal that is already noise-filtered at the source — a near-zero slope genuinely means sideways, not oscillation that happened to average out. The neutral zone (`nz%` knob) dead-bands any remaining micro-slope residuals. When that is still too jumpy, the optional `ema` knob low-pass filters the AMA input itself before the slope is taken (0 = off keeps the raw input).
|
|
186
196
|
|
|
187
197
|
### Kalman filter
|
|
188
198
|
|
|
@@ -277,6 +287,7 @@ The lookup table can be customized per-market or per-bot (see [Custom Configurat
|
|
|
277
287
|
## Formulas
|
|
278
288
|
|
|
279
289
|
### AMA Offset
|
|
290
|
+
With `ema` > 0, `AMA_now` / `AMA_lb_bars_ago` below are read from the EMA-filtered AMA input instead of the raw AMA series (everything else identical):
|
|
280
291
|
```
|
|
281
292
|
amaSlope% = ((AMA_now − AMA_lb_bars_ago) / AMA_lb_bars_ago × 100) / lb
|
|
282
293
|
amaClip = clamp(amaSlope%, ±clipThreshold) // percentile-based clip
|
|
@@ -354,6 +365,27 @@ Number of bars to look back when computing AMA slope:
|
|
|
354
365
|
|
|
355
366
|
AMA slope is normalized to an average percent per bar, not the cumulative move across the full lookback. Lower values = more noise, faster reaction. Higher values = smoother signals, more lag without adding gain just because the measurement window is longer.
|
|
356
367
|
|
|
368
|
+
### ema (AMA input EMA span)
|
|
369
|
+
Optional low-pass EMA over the AMA values before the slope is taken (span in bars, α = 2/(span+1), `0` = off). Because differencing and an EMA are both linear filters, they commute — smoothing the AMA input produces exactly the smoothed slope series, modulo the seeding transient.
|
|
370
|
+
|
|
371
|
+
| `ema` | noise reduction* | added lag | verdict |
|
|
372
|
+
|---|---|---|---|
|
|
373
|
+
| 0 | 1× (off) | 0 bars | default, current behavior |
|
|
374
|
+
| 4 | ~2× | ~+1.5 bars | light |
|
|
375
|
+
| 9 (= lb) | ~3× | ~+4 bars | sweet spot |
|
|
376
|
+
| 16 | ~4× | ~+7.5 bars | heavy but usable |
|
|
377
|
+
| 32 | ~5.7× | ~+15.5 bars | upper-bound demo, too laggy to trade |
|
|
378
|
+
|
|
379
|
+
\* standard-deviation reduction for white-ish noise ≈ `sqrt(span)`; lag ≈ `(span-1)/2` bars (EMA group delay) on top of the slope window's `(lb-1)/2`.
|
|
380
|
+
|
|
381
|
+
Measured on ~8.7k hourly bars with the default AMA profile (`lb = 9`): the slow AMA's slope is dominated by low-frequency moves rather than white noise, so the `sqrt(span)` rule overstates the standard-deviation gain — `ema = 9` cut slope std only ~3% but trimmed the largest peaks ~22% and zero-crossings 86 → 74; `ema = 32` cut std ~11%, peaks ~48%, crossings 86 → 44, with a measured lag of ~3 bars at span 9 (theory 4). On this input the knob acts mainly as a peak-trimmer and sign-flip reducer, not as a general noise killer.
|
|
382
|
+
|
|
383
|
+
Effects while tuning:
|
|
384
|
+
- Zero-crossings get rarer, so the `cf` latch matters less and the `nz%` dead-band sits on a smooth curve instead of on noise.
|
|
385
|
+
- |slope| peaks shrink, so the same `amaS%` reaches full offset strength less often — expect to lower `amaS%` (or raise `gain`) to restore output amplitude. The `clip%` percentile pool is rebuilt from the filtered input, which partially self-compensates.
|
|
386
|
+
- Keep total lag in mind: ~`lb/2 + ema/2` bars before the slope reflects a move; at defaults that is ~8 bars (~8 h on the 1 h chart).
|
|
387
|
+
- Panel 2 plots the smoothed slope (orange) over the raw slope (gray dashed) so the noise/lag trade-off is directly visible.
|
|
388
|
+
|
|
357
389
|
### regi (regime sensitivity)
|
|
358
390
|
- 0 = regime multiplier is always 1.0 (Hurst+PE ignored)
|
|
359
391
|
- 1 = default table values used as-is
|
|
@@ -383,6 +415,7 @@ The 3×3 regime multiplier table can be customized per-market or per-bot (defaul
|
|
|
383
415
|
Candle Data
|
|
384
416
|
├── AMA3 (slow KAMA, erPeriod=781)
|
|
385
417
|
│ ├── calculateAMA() → AMA3 values per bar [price panel overlay]
|
|
418
|
+
│ ├── `ema` knob (0 = off) → EMA-filtered AMA input [research chart only]
|
|
386
419
|
│ ├── computeAmaSlopeWeights() → slope%
|
|
387
420
|
│ └── Percentile clip → amaClip → amaOff
|
|
388
421
|
│
|
|
@@ -1,11 +1,10 @@
|
|
|
1
1
|
# Trend Detection
|
|
2
2
|
|
|
3
|
-
This folder contains the chart generators
|
|
3
|
+
This folder contains the chart generators and re-export shims used by the analysis runners. The canonical AMA, Kalman, Hurst, and Permutation Entropy implementations live in `market_adapter/core/` (see below).
|
|
4
4
|
|
|
5
5
|
## Docs
|
|
6
6
|
|
|
7
7
|
- [DYNAMIC_WEIGHT_RESEARCH.md](DYNAMIC_WEIGHT_RESEARCH.md) - dynamic weight research notes for the Kalman/Hurst/PE blend
|
|
8
|
-
- [SIGNAL_DOCUMENTATION.md](SIGNAL_DOCUMENTATION.md) - derivative signal layer documentation
|
|
9
8
|
|
|
10
9
|
## Live Counterpart
|
|
11
10
|
|
|
@@ -13,7 +12,6 @@ This folder contains the chart generators, derivative signal layer, and re-expor
|
|
|
13
12
|
|
|
14
13
|
## Modules
|
|
15
14
|
|
|
16
|
-
- `derivative_analyzer.ts`
|
|
17
15
|
- `dynamic_weight_chart_generator.ts`
|
|
18
16
|
- `kalman_chart_generator.ts`
|
|
19
17
|
- `regime_chart_generator.ts`
|
|
@@ -40,6 +40,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
40
40
|
const AMA_MS_MIN = 0.06, AMA_MS_MAX = 0.12;
|
|
41
41
|
const KAL_MS_MIN = 0.5, KAL_MS_MAX = 1.5;
|
|
42
42
|
const CLIP_PCT_MAX = 20;
|
|
43
|
+
const EMA_MIN = 0, EMA_MAX = 32;
|
|
43
44
|
const TH_MIN = 0, TH_MAX = 0.5;
|
|
44
45
|
const KF_MIN = 0, KF_MAX = 200;
|
|
45
46
|
const KFD_MIN = 1.0, KFD_MAX = 3.0;
|
|
@@ -53,6 +54,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
53
54
|
dispWeight: [0, 1],
|
|
54
55
|
neutralZonePct: [0, 1],
|
|
55
56
|
lookbackBars: [LB_MIN, LB_MAX],
|
|
57
|
+
amaEmaSpan: [EMA_MIN, EMA_MAX],
|
|
56
58
|
amaMaxSlopePct: [AMA_MS_MIN, AMA_MS_MAX],
|
|
57
59
|
kalmanMaxSlopePct: [KAL_MS_MIN, KAL_MS_MAX],
|
|
58
60
|
clipPct: [0, CLIP_PCT_MAX],
|
|
@@ -117,6 +119,10 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
117
119
|
const defaultKalmanDispThresholdMult = data.kalmanDispThresholdMult ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_DISP_THRESHOLD_MULT_DEFAULT;
|
|
118
120
|
const defaultKalmanSmoothSpanPct = data.kalmanSmoothSpanPct ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_KALMAN_SMOOTH_SPAN_PCT_DEFAULT;
|
|
119
121
|
const defaultSignalConfirmBars = data.signalConfirmBars ?? MARKET_ADAPTER.DYNAMIC_WEIGHT_SIGNAL_CONFIRM_BARS_DEFAULT;
|
|
122
|
+
// Research-only `ema` knob: EMA span in bars over the AMA input before the
|
|
123
|
+
// slope is taken. 0 (default) = off, keeps the raw AMA input.
|
|
124
|
+
const defaultAmaEmaSpanRaw = data.amaEmaSpan ?? ma.amaEmaSpan ?? 0;
|
|
125
|
+
const defaultAmaEmaSpan = Math.round(Math.min(EMA_MAX, Math.max(EMA_MIN, Number.isFinite(defaultAmaEmaSpanRaw) ? defaultAmaEmaSpanRaw : EMA_MIN)));
|
|
120
126
|
const regimeInitSlider = Math.round(defaultRegimeSensitivity * 100);
|
|
121
127
|
|
|
122
128
|
const interval = results.length > 1 ?
|
|
@@ -235,7 +241,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
235
241
|
.legend-item { gap: 3px; }
|
|
236
242
|
.legend-val { font-family: monospace; display: inline-block; text-align: right; min-width: 45px; }
|
|
237
243
|
#l-price, #l-ama3 { min-width: 62px; }
|
|
238
|
-
#l-ama-slope, #l-kal-vel, #l-kal-disp { min-width: 54px; }
|
|
244
|
+
#l-ama-slope, #l-ama-slope-raw, #l-kal-vel, #l-kal-disp { min-width: 54px; }
|
|
239
245
|
#l-signal, #l-signal-latched { min-width: 70px; text-align: left; }
|
|
240
246
|
#l-combined-raw, #l-combined-echo { min-width: 42px; }
|
|
241
247
|
#l-mult { min-width: 38px; }
|
|
@@ -280,6 +286,9 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
280
286
|
.ctrl.lb label { min-width: 20px; }
|
|
281
287
|
.ctrl.lb input[type="range"] { accent-color: #39d0d8; width: 80px; }
|
|
282
288
|
.ctrl.lb .val { color: #39d0d8; }
|
|
289
|
+
.ctrl.ema label { min-width: 20px; }
|
|
290
|
+
.ctrl.ema input[type="range"] { accent-color: #f0a000; width: 80px; }
|
|
291
|
+
.ctrl.ema .val { color: #f0a000; }
|
|
283
292
|
.ctrl.off input[type="range"] { accent-color: #3fb950; }
|
|
284
293
|
.ctrl.off .val { color: #3fb950; }
|
|
285
294
|
.ctrl.nz input[type="range"] { accent-color: #8b949e; }
|
|
@@ -325,6 +334,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
325
334
|
<div class="section-label">AMA SLOPE INPUT</div>
|
|
326
335
|
<div class="legend">
|
|
327
336
|
<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>
|
|
337
|
+
<div class="legend-item"><div class="dot" style="background:#8b949e;"></div>Raw‰: <span id="l-ama-slope-raw" class="legend-val">-</span></div>
|
|
328
338
|
</div>
|
|
329
339
|
<div id="ama-chart"></div>
|
|
330
340
|
</div>
|
|
@@ -355,6 +365,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
355
365
|
<div class="group-sep"></div>
|
|
356
366
|
<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>
|
|
357
367
|
<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 (${LB_MIN}-${LB_MAX})"><span class="val" id="lb-value">${lookbackBars}</span></div>
|
|
368
|
+
<div class="ctrl ema"><label for="ema-slider">ema</label><input type="range" id="ema-slider" min="${EMA_MIN}" max="${EMA_MAX}" value="${defaultAmaEmaSpan}" title="AMA input EMA span in bars (${EMA_MIN} = off; noise std ~ 1/sqrt(span), lag ~ (span-1)/2 bars; span ~ lb is the balance point)"><span class="val" id="ema-value">${defaultAmaEmaSpan}</span></div>
|
|
358
369
|
<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 (${AMA_MS_MIN}-${AMA_MS_MAX})"><span class="val" id="ama-ms-value">${defaultAmaMaxSlopePct.toFixed(4)}</span></div>
|
|
359
370
|
<div class="ctrl clip"><label for="clip-slider">clip%</label><input type="range" id="clip-slider" min="0" max="${CLIP_PCT_MAX}" value="${Math.min(defaultClipPct, CLIP_PCT_MAX)}" title="Outlier Clip %"><span class="val" id="clip-value">${Math.min(defaultClipPct, CLIP_PCT_MAX)}%</span></div>
|
|
360
371
|
|
|
@@ -388,7 +399,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
388
399
|
</div>
|
|
389
400
|
</div>
|
|
390
401
|
|
|
391
|
-
<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, sliderRanges: SLIDER_RANGES, 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>
|
|
402
|
+
<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, amaEmaSpan: defaultAmaEmaSpan, gainLogMin: GAIN_LOG_MIN_N, gainLogMax: GAIN_LOG_MAX_N, sliderRanges: SLIDER_RANGES, 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>
|
|
392
403
|
|
|
393
404
|
<script>
|
|
394
405
|
const data = JSON.parse(document.getElementById('payload').textContent);
|
|
@@ -430,6 +441,8 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
430
441
|
const kalMsSliderToVal = (pos) => Math.exp(KAL_MS_LOG_MIN + (pos / 1000) * (KAL_MS_LOG_MAX - KAL_MS_LOG_MIN));
|
|
431
442
|
|
|
432
443
|
let currentLookbackBars = data.lookbackBars ?? ${JSON.stringify(lookbackBars)};
|
|
444
|
+
let currentAmaEmaSpan = Math.max(SR.amaEmaSpan[0], Math.min(SR.amaEmaSpan[1], Math.round(data.amaEmaSpan ?? 0)));
|
|
445
|
+
let currentAmaSource = data.ama3Prices;
|
|
433
446
|
const LB_LOG_MIN = data.lbLogMin;
|
|
434
447
|
const LB_LOG_MAX = data.lbLogMax;
|
|
435
448
|
const lbSliderToVal = (pos) => Math.round(Math.exp(LB_LOG_MIN + (pos / 1000) * (LB_LOG_MAX - LB_LOG_MIN)));
|
|
@@ -468,6 +481,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
468
481
|
const currentLatchedSignals = new Array(data.dates.length).fill(null);
|
|
469
482
|
const dynamicAmaOff = new Array(data.dates.length).fill(null);
|
|
470
483
|
const dynamicAmaSlopePct = new Array(data.dates.length).fill(null);
|
|
484
|
+
const dynamicAmaSlopePctRaw = new Array(data.dates.length).fill(null);
|
|
471
485
|
const dynamicKalOff = new Array(data.dates.length).fill(null);
|
|
472
486
|
const combinedOff = new Array(data.dates.length).fill(null);
|
|
473
487
|
const combinedSell = new Array(data.dates.length).fill(null);
|
|
@@ -582,9 +596,40 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
582
596
|
}
|
|
583
597
|
}
|
|
584
598
|
|
|
585
|
-
|
|
599
|
+
/**
|
|
600
|
+
* Research-only ema knob — EMA low-pass over the AMA input before the
|
|
601
|
+
* slope is taken (EMA-then-difference == difference-then-EMA, both are
|
|
602
|
+
* linear filters). span bars, alpha = 2/(span+1): noise std shrinks ~
|
|
603
|
+
* 1/sqrt(span), lag grows ~ (span-1)/2 bars. Seeded with an SMA over the
|
|
604
|
+
* first 'span' finite values (mirrors the AMA seed SMA) so the transient
|
|
605
|
+
* is gone before any slope bar can go ready. Nulls (warmup / trailing
|
|
606
|
+
* padding) stay null so no fabricated values reach the clip-percentile
|
|
607
|
+
* pool or the offset pipeline. span <= 0 returns the input untouched.
|
|
608
|
+
*/
|
|
609
|
+
function buildEmaSeries(values, span) {
|
|
610
|
+
if (!(span > 0) || !Array.isArray(values)) return values;
|
|
611
|
+
const n = values.length;
|
|
612
|
+
const out = new Array(n);
|
|
613
|
+
const alpha = 2 / (span + 1);
|
|
614
|
+
let seedSum = 0, seedCount = 0;
|
|
615
|
+
for (let i = 0; i < n && seedCount < span; i++) {
|
|
616
|
+
const v = values[i];
|
|
617
|
+
if (v == null || !Number.isFinite(v)) continue;
|
|
618
|
+
seedSum += v; seedCount++;
|
|
619
|
+
}
|
|
620
|
+
let prev = seedCount > 0 ? seedSum / seedCount : null;
|
|
621
|
+
for (let i = 0; i < n; i++) {
|
|
622
|
+
const v = values[i];
|
|
623
|
+
if (v == null || !Number.isFinite(v)) { out[i] = null; continue; }
|
|
624
|
+
prev = prev == null ? v : alpha * v + (1 - alpha) * prev;
|
|
625
|
+
out[i] = prev;
|
|
626
|
+
}
|
|
627
|
+
return out;
|
|
628
|
+
}
|
|
629
|
+
|
|
630
|
+
function computeSlopeAtIndex(idx, lb, values) {
|
|
586
631
|
// Canonical AMA slope % (computeAverageAmaSlopePct injected above).
|
|
587
|
-
const sp = computeAverageAmaSlopePct(
|
|
632
|
+
const sp = computeAverageAmaSlopePct(values[idx], values[idx - lb], lb);
|
|
588
633
|
return sp == null ? 0 : sp;
|
|
589
634
|
}
|
|
590
635
|
|
|
@@ -592,6 +637,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
592
637
|
recalcKalmanVelocity();
|
|
593
638
|
recalcKalmanClipThreshold();
|
|
594
639
|
recalcLatchedSignals();
|
|
640
|
+
currentAmaSource = buildEmaSeries(data.ama3Prices, currentAmaEmaSpan);
|
|
595
641
|
const lb = currentLookbackBars;
|
|
596
642
|
const amaErWarmup = Math.max(0, Number.isFinite(data.amaErPeriod) ? Math.ceil(data.amaErPeriod) : ${JSON.stringify(MARKET_ADAPTER.AMAS[MARKET_ADAPTER.DEFAULT_AMA_KEY as keyof typeof MARKET_ADAPTER.AMAS].erPeriod)});
|
|
597
643
|
const amaReadyBar = Math.max(lb, amaErWarmup + lb);
|
|
@@ -600,14 +646,20 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
600
646
|
// implementation (computeAmaSlopeClipThreshold injected above),
|
|
601
647
|
// shared with the live market adapter service. Trailing padded
|
|
602
648
|
// nulls beyond realBarCount are skipped by its finiteness guards.
|
|
603
|
-
|
|
649
|
+
// Clip pool follows the (possibly EMA-filtered) AMA input so the
|
|
650
|
+
// percentile threshold matches the series the offsets derive from.
|
|
651
|
+
dynamicClipThreshold = computeAmaSlopeClipThreshold(currentAmaSource, amaErWarmup, lb, currentClipPct);
|
|
604
652
|
|
|
605
653
|
for (let i = 0; i < data.realBarCount; i++) {
|
|
606
|
-
// AMA
|
|
607
|
-
|
|
654
|
+
// AMA slope % for the display panel (offsets computed in
|
|
655
|
+
// recalcWeights): the raw reference line from the unfiltered AMA
|
|
656
|
+
// input, the plotted series from the EMA-filtered input.
|
|
657
|
+
dynamicAmaSlopePctRaw[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb, data.ama3Prices);
|
|
658
|
+
dynamicAmaSlopePct[i] = i < amaReadyBar ? null : computeSlopeAtIndex(i, lb, currentAmaSource);
|
|
608
659
|
}
|
|
609
660
|
for (let i = data.realBarCount; i < data.dates.length; i++) {
|
|
610
661
|
dynamicAmaSlopePct[i] = null;
|
|
662
|
+
dynamicAmaSlopePctRaw[i] = null;
|
|
611
663
|
}
|
|
612
664
|
}
|
|
613
665
|
|
|
@@ -633,7 +685,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
633
685
|
// Per-bar offset pipeline — canonical implementation (computeDynamicWeightSeries
|
|
634
686
|
// injected above), shared with the live market adapter service.
|
|
635
687
|
const res = computeDynamicWeightSeries({
|
|
636
|
-
amaValues:
|
|
688
|
+
amaValues: currentAmaSource,
|
|
637
689
|
kalmanVelocityPct: currentKalmanVelocityPct,
|
|
638
690
|
kalmanDisplacementPct: data.kalmanDisplacementPct,
|
|
639
691
|
kalmanIsReady: data.kalmanIsReady,
|
|
@@ -717,7 +769,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
717
769
|
max: Number.isFinite(xs.max) ? xs.max : xMax,
|
|
718
770
|
} : null;
|
|
719
771
|
|
|
720
|
-
amaChart.setData([data.dates, dynamicAmaSlopePct], false);
|
|
772
|
+
amaChart.setData([data.dates, dynamicAmaSlopePctRaw, dynamicAmaSlopePct], false);
|
|
721
773
|
kalmanChart.setData([data.dates, currentKalmanVelocityPct, data.kalmanDisplacementPct], false);
|
|
722
774
|
outputChart.setData([data.dates, combinedOff, echoCombinedOff], false);
|
|
723
775
|
|
|
@@ -752,6 +804,11 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
752
804
|
if (sp == null) { spEl.textContent = '-'; spEl.style.color = '#8b949e'; }
|
|
753
805
|
else { spEl.textContent = (sp >= 0 ? '+' : '') + (sp * 10).toFixed(3) + '‰'; spEl.style.color = sp > 0.01 ? '#2ea043' : sp < -0.01 ? '#f85149' : '#8b949e'; }
|
|
754
806
|
|
|
807
|
+
const spRaw = dynamicAmaSlopePctRaw[idx];
|
|
808
|
+
const spRawEl = document.getElementById('l-ama-slope-raw');
|
|
809
|
+
if (spRaw == null) { spRawEl.textContent = '-'; spRawEl.style.color = '#8b949e'; }
|
|
810
|
+
else { spRawEl.textContent = (spRaw >= 0 ? '+' : '') + (spRaw * 10).toFixed(3) + '‰'; spRawEl.style.color = '#8b949e'; }
|
|
811
|
+
|
|
755
812
|
const vp = currentKalmanVelocityPct[idx];
|
|
756
813
|
const vpEl = document.getElementById('l-kal-vel');
|
|
757
814
|
if (vp == null) { vpEl.textContent = '-'; vpEl.style.color = '#8b949e'; }
|
|
@@ -975,6 +1032,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
975
1032
|
document.getElementById('clip-slider').value = currentClipPct;
|
|
976
1033
|
document.getElementById('nz-slider').value = Math.round(currentNz * 100);
|
|
977
1034
|
document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
|
|
1035
|
+
document.getElementById('ema-slider').value = currentAmaEmaSpan;
|
|
978
1036
|
|
|
979
1037
|
const priceEl = document.getElementById('price-panel');
|
|
980
1038
|
const amaEl = document.getElementById('ama-panel');
|
|
@@ -1010,6 +1068,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
1010
1068
|
scales: { x: { time: true }, p: { auto: true } },
|
|
1011
1069
|
series: [
|
|
1012
1070
|
{ label: 'Time' },
|
|
1071
|
+
{ label: 'Raw‰', stroke: '#8b949e', width: 1, dash: [3, 3], scale: 'p', points: { show: false } },
|
|
1013
1072
|
{ label: 'Slope‰', stroke: '#f0a000', width: 2, scale: 'p', points: { show: false } },
|
|
1014
1073
|
],
|
|
1015
1074
|
axes: [
|
|
@@ -1019,7 +1078,7 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
1019
1078
|
],
|
|
1020
1079
|
cursor: cursorCfg,
|
|
1021
1080
|
hooks: { draw: [makePctFillHook(dynamicAmaSlopePct, 'p', 'rgba(46,160,67,0.20)', 'rgba(248,81,73,0.20)'), makeSignalBgHook('p')] }
|
|
1022
|
-
}, [data.dates, dynamicAmaSlopePct], document.getElementById('ama-chart'));
|
|
1081
|
+
}, [data.dates, dynamicAmaSlopePctRaw, dynamicAmaSlopePct], document.getElementById('ama-chart'));
|
|
1023
1082
|
|
|
1024
1083
|
kalmanChart = new uPlot({
|
|
1025
1084
|
width: kalmanEl.offsetWidth, height: kalmanEl.offsetHeight,
|
|
@@ -1181,6 +1240,12 @@ function generateHTML(data: any, title = 'Dynamic Weight Research') {
|
|
|
1181
1240
|
onSliderChange();
|
|
1182
1241
|
});
|
|
1183
1242
|
|
|
1243
|
+
document.getElementById('ema-slider').addEventListener('input', (e) => {
|
|
1244
|
+
currentAmaEmaSpan = parseInt(e.target.value, 10);
|
|
1245
|
+
document.getElementById('ema-value').textContent = currentAmaEmaSpan;
|
|
1246
|
+
onSliderChange();
|
|
1247
|
+
});
|
|
1248
|
+
|
|
1184
1249
|
document.getElementById('regime-slider').addEventListener('input', (e) => {
|
|
1185
1250
|
currentRegimeSensitivity = parseInt(e.target.value, 10) / 100;
|
|
1186
1251
|
document.getElementById('regime-value').textContent = currentRegimeSensitivity.toFixed(2);
|
|
@@ -1276,6 +1341,11 @@ function applyParams(p, btn) {
|
|
|
1276
1341
|
document.getElementById('lb-slider').value = lbValToSlider(currentLookbackBars);
|
|
1277
1342
|
document.getElementById('lb-value').textContent = currentLookbackBars;
|
|
1278
1343
|
}
|
|
1344
|
+
if (p.amaEmaSpan != null) {
|
|
1345
|
+
currentAmaEmaSpan = Math.max(SR.amaEmaSpan[0], Math.min(SR.amaEmaSpan[1], Math.round(p.amaEmaSpan)));
|
|
1346
|
+
document.getElementById('ema-slider').value = currentAmaEmaSpan;
|
|
1347
|
+
document.getElementById('ema-value').textContent = currentAmaEmaSpan;
|
|
1348
|
+
}
|
|
1279
1349
|
recalcInputs();
|
|
1280
1350
|
recalcWeights();
|
|
1281
1351
|
refreshChartsPreservingZoom();
|
|
@@ -1302,6 +1372,7 @@ function applyParams(p, btn) {
|
|
|
1302
1372
|
regimeSensitivity: 'regime',
|
|
1303
1373
|
dispWeight: 'dw',
|
|
1304
1374
|
lookbackBars: 'lb',
|
|
1375
|
+
amaEmaSpan: 'ema',
|
|
1305
1376
|
};
|
|
1306
1377
|
document.getElementById('paste-confirm-vals').innerHTML = Object.entries(labels)
|
|
1307
1378
|
.filter(([k]) => p[k] != null)
|
|
@@ -1355,6 +1426,7 @@ function applyParams(p, btn) {
|
|
|
1355
1426
|
regimeSensitivity: +currentRegimeSensitivity.toFixed(2),
|
|
1356
1427
|
dispWeight: +currentDw.toFixed(2),
|
|
1357
1428
|
lookbackBars: currentLookbackBars,
|
|
1429
|
+
amaEmaSpan: currentAmaEmaSpan,
|
|
1358
1430
|
};
|
|
1359
1431
|
const json = JSON.stringify(params, null, 2);
|
|
1360
1432
|
localStorage.setItem(LS_KEY, json);
|