dexbot 1.6.2 → 1.6.4
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 +53 -3
- package/README.md +11 -7
- package/analysis/README.md +20 -14
- package/analysis/account_resolver.ts +171 -0
- package/analysis/ama_fitting/analyze_lambda_vs_slow.ts +2 -5
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/bot_usage/discover_bot_accounts.ts +20 -27
- package/analysis/chain_pool.ts +47 -0
- package/analysis/chart_utils.ts +33 -20
- package/analysis/derivative_chart_generator.ts +2 -3
- package/analysis/fills_source.ts +234 -0
- package/analysis/grid_correction_check.ts +104 -244
- package/analysis/results/ama_sweep_results_lp_pool_133_1h.json +2455 -0
- package/analysis/results/bot_fitting_results_lp_pool_133_1h.json +218 -0
- package/analysis/trade_profitability.ts +169 -304
- package/analysis/tradingview/README.md +2 -2
- package/analysis/tradingview/h-bts_tradingview.html +1570 -0
- package/analysis/tradingview/t-bts_tradingview.html +1570 -0
- package/analysis/tradingview/tradingview_uplot_chart_generator.ts +3 -4
- package/analysis/trend_detection/dynamic_weight_chart_generator.ts +2 -3
- package/analysis/trend_detection/kalman_chart_generator.ts +2 -3
- package/analysis/trend_detection/package.json +1 -1
- package/analysis/trend_detection/regime_chart_generator.ts +2 -3
- package/analysis/trend_detection/volatility_chart_generator.ts +2 -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/dist/analysis/account_resolver.d.ts +43 -0
- package/dist/analysis/account_resolver.d.ts.map +1 -0
- package/dist/analysis/account_resolver.js +137 -0
- package/dist/analysis/account_resolver.js.map +1 -0
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts.map +1 -1
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +2 -4
- package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -1
- package/dist/analysis/bot_usage/discover_bot_accounts.js +20 -28
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
- package/dist/analysis/chain_pool.d.ts +10 -0
- package/dist/analysis/chain_pool.d.ts.map +1 -0
- package/dist/analysis/chain_pool.js +47 -0
- package/dist/analysis/chain_pool.js.map +1 -0
- package/dist/analysis/chart_utils.d.ts +14 -1
- package/dist/analysis/chart_utils.d.ts.map +1 -1
- package/dist/analysis/chart_utils.js +32 -22
- package/dist/analysis/chart_utils.js.map +1 -1
- package/dist/analysis/derivative_chart_generator.d.ts.map +1 -1
- package/dist/analysis/derivative_chart_generator.js +2 -3
- package/dist/analysis/derivative_chart_generator.js.map +1 -1
- package/dist/analysis/fills_source.d.ts +79 -0
- package/dist/analysis/fills_source.d.ts.map +1 -0
- package/dist/analysis/fills_source.js +186 -0
- package/dist/analysis/fills_source.js.map +1 -0
- package/dist/analysis/grid_correction_check.d.ts +4 -20
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +95 -248
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/trade_profitability.d.ts +18 -19
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +136 -280
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +3 -4
- package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +2 -3
- package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
- package/dist/analysis/trend_detection/kalman_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/kalman_chart_generator.js +2 -3
- package/dist/analysis/trend_detection/kalman_chart_generator.js.map +1 -1
- package/dist/analysis/trend_detection/regime_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/regime_chart_generator.js +2 -3
- package/dist/analysis/trend_detection/regime_chart_generator.js.map +1 -1
- package/dist/analysis/trend_detection/volatility_chart_generator.d.ts.map +1 -1
- package/dist/analysis/trend_detection/volatility_chart_generator.js +2 -3
- package/dist/analysis/trend_detection/volatility_chart_generator.js.map +1 -1
- package/dist/market_adapter/lp_chart_core.d.ts.map +1 -1
- package/dist/market_adapter/lp_chart_core.js +2 -3
- package/dist/market_adapter/lp_chart_core.js.map +1 -1
- package/dist/modules/account_bots.d.ts +1 -18
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +1 -1
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares-native/serial/operations.d.ts +1 -84
- package/dist/modules/bitshares-native/serial/operations.d.ts.map +1 -1
- package/dist/modules/bitshares-native/serial/operations.js +1 -38
- package/dist/modules/bitshares-native/serial/operations.js.map +1 -1
- package/dist/modules/cli_colors.d.ts +0 -1
- package/dist/modules/cli_colors.d.ts.map +1 -1
- package/dist/modules/constants.js +5 -5
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credential_policy.d.ts +1 -25
- package/dist/modules/credential_policy.d.ts.map +1 -1
- package/dist/modules/credential_policy.js +1 -1
- package/dist/modules/credential_policy.js.map +1 -1
- package/dist/modules/credential_runtime.d.ts +1 -6
- package/dist/modules/credential_runtime.d.ts.map +1 -1
- package/dist/modules/credential_runtime.js +1 -1
- package/dist/modules/credential_runtime.js.map +1 -1
- package/dist/modules/credential_session_cache.d.ts +1 -5
- package/dist/modules/credential_session_cache.d.ts.map +1 -1
- package/dist/modules/credential_session_cache.js +1 -1
- package/dist/modules/credential_session_cache.js.map +1 -1
- package/dist/modules/crypto/pure_secp256k1.d.ts +1 -2
- package/dist/modules/crypto/pure_secp256k1.d.ts.map +1 -1
- package/dist/modules/crypto/pure_secp256k1.js +1 -1
- package/dist/modules/crypto/pure_secp256k1.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +9 -8
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +9 -8
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +87 -7
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +489 -14
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_fill_runtime.d.ts +1 -17
- package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_fill_runtime.js +1 -1
- package/dist/modules/dexbot_fill_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 +21 -5
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/dexbot_state_recovery.d.ts +1 -7
- package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
- package/dist/modules/dexbot_state_recovery.js +1 -1
- package/dist/modules/dexbot_state_recovery.js.map +1 -1
- package/dist/modules/launcher/adapter_requirement.d.ts +1 -5
- package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -1
- package/dist/modules/launcher/adapter_requirement.js +1 -1
- package/dist/modules/launcher/adapter_requirement.js.map +1 -1
- package/dist/modules/launcher/bot_supervisor.d.ts +1 -6
- package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
- package/dist/modules/launcher/bot_supervisor.js +1 -1
- package/dist/modules/launcher/bot_supervisor.js.map +1 -1
- package/dist/modules/launcher/foreign_cred_daemon.d.ts +1 -3
- package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -1
- package/dist/modules/launcher/foreign_cred_daemon.js +1 -1
- package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -1
- package/dist/modules/launcher/launch_modes.d.ts +1 -2
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +1 -1
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.d.ts +1 -3
- package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/market_adapter_runtime.js +1 -1
- package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts +1 -5
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +1 -1
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/launcher/status_reporting.d.ts +1 -2
- package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
- package/dist/modules/launcher/status_reporting.js +1 -1
- package/dist/modules/launcher/status_reporting.js.map +1 -1
- package/dist/modules/order/export.d.ts +1 -8
- package/dist/modules/order/export.d.ts.map +1 -1
- package/dist/modules/order/export.js +1 -1
- package/dist/modules/order/export.js.map +1 -1
- package/dist/modules/order/grid.d.ts +7 -0
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +52 -98
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.d.ts +1 -10
- package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
- package/dist/modules/order/grid_reconcile_internal.js +2 -2
- package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
- package/dist/modules/order/manager.d.ts +10 -2
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +20 -7
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/sync_engine.d.ts.map +1 -1
- package/dist/modules/order/sync_engine.js +25 -20
- package/dist/modules/order/sync_engine.js.map +1 -1
- package/dist/modules/order/utils/math.d.ts +1 -10
- package/dist/modules/order/utils/math.d.ts.map +1 -1
- package/dist/modules/order/utils/math.js +1 -1
- package/dist/modules/order/utils/math.js.map +1 -1
- package/dist/modules/order/utils/order.d.ts +45 -7
- package/dist/modules/order/utils/order.d.ts.map +1 -1
- package/dist/modules/order/utils/order.js +160 -15
- package/dist/modules/order/utils/order.js.map +1 -1
- package/dist/modules/order/utils/system.d.ts +0 -15
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +2 -2
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/process_discovery.d.ts +0 -3
- package/dist/modules/process_discovery.d.ts.map +1 -1
- package/dist/modules/process_discovery.js +1 -1
- package/dist/modules/process_discovery.js.map +1 -1
- package/dist/modules/utils/chain_logs.d.ts +1 -2
- package/dist/modules/utils/chain_logs.d.ts.map +1 -1
- package/dist/modules/utils/chain_logs.js +1 -1
- package/dist/modules/utils/chain_logs.js.map +1 -1
- package/dist/scripts/analyze-git.js +2 -2
- package/dist/scripts/analyze-git.js.map +1 -1
- package/dist/scripts/update.js +46 -31
- package/dist/scripts/update.js.map +1 -1
- package/docs/BITSHARES_ONBOARDING.md +110 -40
- package/docs/COPY_ON_WRITE_MASTER_PLAN.md +10 -207
- package/docs/COW_INVARIANTS.md +16 -10
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +75 -29
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +6 -6
- package/docs/GRID_RECALCULATION.md +14 -6
- package/docs/GRID_RECONCILE.md +28 -25
- package/docs/LIFECYCLE.md +12 -11
- package/docs/README.md +48 -42
- package/docs/architecture.md +19 -30
- package/docs/developer_guide.md +3 -3
- package/package.json +1 -1
- package/scripts/git-viewer.sh +1 -1
- package/tests/README.md +10 -1
package/CHANGELOG.md
CHANGED
|
@@ -2,11 +2,59 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.6.4] - 2026-09-22 - Fund-Driven Spread Correction, GapSlots+1 Batch Cap, Analysis Shared Modules
|
|
6
|
+
|
|
7
|
+
### 2026-09-21
|
|
8
|
+
|
|
9
|
+
- **Fix(grid)**: include VIRTUAL orders in RMS structural divergence — `compareGrids()` filtered each side's RMS metric to ACTIVE orders only, so persisted-vs-ideal drift in VIRTUAL slots was invisible. VIRTUAL slots carry the planned reservation for unplaced rail slots (`funds.virtual`), and Available = ChainFree − Virtual − fees, so an over-reserved or stale virtual pins spending power without ever tripping a structural check. `filterForRms` now covers ACTIVE + VIRTUAL (PARTIAL/SPREAD stay excluded); ideal sizing already covers every slot of the side, so no other logic changes. A side whose only divergence is in VIRTUAL reservations now yields an RMS metric and can trigger the `rms_structural_grid_resync` full resync; the 14.3% threshold is unchanged. Docs: divergence-filter wording corrected in `docs/architecture.md` and `docs/GRID_RECALCULATION.md` (`modules/order/grid.ts`, `tests/test_grid_comparison.ts`; full suite 291 pass / 0 fail).
|
|
10
|
+
|
|
11
|
+
- **Feat(spread)**: make spread correction purely fund-driven with a balance refresh fallback — `prepareSpreadCorrectionOrders` / `determineOrderSideByFunds` shrank resting orders to manufacture budget (self-funded tail recycling plus a generic redistribution donor loop). Those paths moved inventory within a rail and were gamed by stale-size snapshots: a donor's "current" size could lag a fill, so the recovered budget was phantom and the grid churned instead of healing. All shrink paths removed: corrections are funded only by free available/chainFree; when no side has funds the check returns `fundsExhausted` and the maintenance runtime refreshes account totals + open orders on the next tick via `getTargetedSyncReason` (a stale zero balance is re-read, not met with inventory recycling). New `tests/test_spread_pure_fund_driven.ts` (zero free → no side/create/shrink; funded → spend ≤ free and top-up only); SGP-6 now expects a skip (`modules/order/grid.ts`, `modules/dexbot_maintenance_runtime.ts`, `tests/test_targeted_drift_reconcile.ts`).
|
|
12
|
+
|
|
13
|
+
- **Fix(sync)**: stop false sync-lock "disappeared" logs and correct shadow-lock metric — `_doSyncFromOpenOrders` collected slot ids and chain order ids into `orderIdsToLock`, then re-verified each with `mgr.orders.has(id)`. `mgr.orders` is keyed by slot id only, so every placed order's chain id failed the check and was logged as "disappeared between collection and locking" (~40 lines per sync, 3,461 in one production log) — phantom lines once mistaken for a COW race during a fill-storm investigation, and chain ids were silently dropped from the lock set. Collection and locking are synchronous, so the re-verification guarded an impossible window; the full collected set is now locked. `shadowOrderIds` aliases one order under two keys, so `size` double-counted live orders; new `OrderManager.getActiveShadowLockCount()` feeds the `shadowLocks` / `shadowLocksActive` metrics. New `tests/test_sync_lock_id_verification.ts` (`modules/order/sync_engine.ts`, `modules/order/manager.ts`, `modules/dexbot_maintenance_runtime.ts`).
|
|
14
|
+
|
|
15
|
+
- **Feat(batch)**: raise fill/broadcast batch cap to gapSlots + 1 — `_getGapSlotBatchSize()`, the single source of truth for the fill-batch chunk size and the per-broadcast op cap, now returns the resolved gap-slot count + 1 instead of the count itself, giving one slot of headroom so a queue or operation set that previously spilled into an extra sequential broadcast chunk completes in a single cycle/transaction (1..gapSlots+1 fills → one unified batch; the non-finite/non-positive fallback still returns 1). Docs, constants/cow-runtime comments, and the batch-sizing tests updated to the new contract (`modules/dexbot_class.ts`, `modules/constants.ts`, `modules/dexbot_cow_runtime.ts`, `tests/test_cow_ops_per_broadcast.ts`, `tests/test_fill_batch_chunking.ts`, `tests/sim_batching.ts`).
|
|
16
|
+
|
|
17
|
+
### 2026-09-20
|
|
18
|
+
|
|
19
|
+
- **Docs(invariant)**: make grid-price invariant doc a fully present-tense contract — drops the mixed-state "What is still open" section (the blocking check has seen live traffic: 75 judgeable checks, 0 violations across four live bot logs), folds each item into its owning section, documents `runFinalPivotGate` as the seventh gate, adds a key-constants table, and cross-links the doc from `LIFECYCLE.md`, `COW_INVARIANTS.md` (INV-GRID-004), `GRID_RECONCILE.md`, `tests/README.md`, and `docs/README.md`; repoints dead `CONSOLIDATED_ORPHAN_FIX_SUMMARY.md` references in `CHANGELOG.md` to the retrospective. Docs-only, no code or test behavior (`docs/GRID_PRICE_INVARIANT.md`, `docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md`).
|
|
20
|
+
|
|
21
|
+
- **Refactor(code)**: purge dead code and internal-only exports across modules — drops the unreachable `processed_transaction` serializer chain in `bitshares-native/serial/operations.ts` (export list 98 → 17 names), the unused `CliColors` type, the never-read `_recentlyRotatedOrderIds` field, plus 56 internal-only exports across account/launcher/order/credential/crypto modules. Symbol visibility only, no behavioral impact (`npx tsc --noEmit` clean; `npm test` 290 pass, 0 fail).
|
|
22
|
+
|
|
23
|
+
- **Docs(engine)**: consolidate order-engine docs and move legacy narrative to the history hubs — replaces `docs/CONSOLIDATED_ORPHAN_FIX_SUMMARY.md` with the synthesis hub `docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md` (§0–§8 numbering preserved: code comments and tests cite it), folds the grid-price-invariant history into retrospective Appendix A, and refreshes drifted refs/symbols across `COW_INVARIANTS.md`, `GRID_RECONCILE.md`, `LIFECYCLE.md`, `FUND_MOVEMENT_AND_ACCOUNTING.md`, and `architecture.md`. History (COW three-era/build-step narrative, memory-only tracking) moves into `docs/EVOLUTION.md`; docs/comment-only change, no runtime behavior.
|
|
24
|
+
|
|
25
|
+
### 2026-09-18
|
|
26
|
+
|
|
27
|
+
- **Fix(range)**: restore suizidal range threshold to 1.45x — reverts the 1.40 widening so sub-1.45x ranges flag red (suizidal) again and the tight orange zone is 1.45x–1.55x; single-sourced via `RANGE_QUALITY`, bot-editor legend and live range coloring only (`modules/constants.ts`). Trivial: TradingView toolbar tag shortened to `Offset` (tooltip keeps full name).
|
|
28
|
+
|
|
29
|
+
### 2026-09-16
|
|
30
|
+
|
|
31
|
+
- **Docs(onboarding)**: first-run troubleshooting — npm 12 install-script blocking + manual-build fallback, `dexbot` unknown-command diagnosis (failed link vs stale shell cache), `npm link` EACCES recovery, and pacman `-Syu` for Arch-derivative installs (`docs/BITSHARES_ONBOARDING.md`, `README.md`, `scripts/git-viewer.sh`). Docs-only.
|
|
32
|
+
|
|
33
|
+
- **Refactor(analysis)**: centralize account/node handling, drop `--node` flags — new `analysis/chain_pool.ts` (single read-only chain entry point), `analysis/account_resolver.ts` (single preferredAccount/override decision tree), and `analysis/fills_source.ts` (shared asset-precision table + paginated fill fetch); `trade_profitability.ts` and `grid_correction_check.ts` use the shared modules. Name → accountId resolution always goes through the full built-in pool; breaks anyone passing the undocumented `--node` flag; no production runtime path touched. New `tests/test_analysis_account_resolver.ts`; full suite 290 pass, 0 fail, 15 live skipped.
|
|
34
|
+
|
|
35
|
+
- **Feat(analysis)**: window-aware annualisation of profitability metrics — Sharpe/Sortino now bin the queried window into whole periods (daily for ≥3-day windows, else hourly), zero-filled so flat periods count as 0 PnL, with sample (n−1) variance and √periods-per-year scaling; Sharpe prints with Lo-2002 estimation error, Sortino returns Infinity with no losing periods; new "Projected net PnL" (scored net / scored days × 365); trailing partial periods excluded from ratios, projection, and activity rates via one shared predicate (`analysis/trade_profitability.ts`, `tests/test_trade_profitability_fees.ts`, `analysis/README.md`).
|
|
36
|
+
|
|
37
|
+
- **Feat(charts)**: inline vendored uPlot into generated chart HTML — every generated chart embeds the vendored uPlot JS+CSS via `uplotInlineTags()`, so each export is a single self-contained document rendering anywhere with no CDN, install, or sibling-dir dependency (~53KB per chart; tests assert the banner is present and no `../uplot/` reference remains).
|
|
38
|
+
|
|
39
|
+
- **Fix(update)**: don't fake pm2 process list or claim restarts that failed — when `pm2 jlist` fails the selective restart step no longer substitutes config-active bots for the process list, and `restarted` is set only when at least one `pm2 restart` succeeds, so the monolithic auto-start fallback and the manual-start notice fire correctly on installs without pm2 (`scripts/update.ts`).
|
|
40
|
+
|
|
41
|
+
## [1.6.3] - 2026-09-14 - Correction-Queue Staleness Guard, Grid-Checker Price Epochs, Final Pivot Gate
|
|
42
|
+
|
|
43
|
+
### 2026-09-15
|
|
44
|
+
|
|
45
|
+
- **Fix(guard)**: final pre-broadcast pivot gate — a fill queued AFTER the batch-start pivot freeze but BEFORE broadcast passed every per-action LAST-FILL-GUARD check on a stale pivot (live incident on a market-pair bot: freeze at .745, sell fill queued at .765, batch of 4 broadcast at .910 against a buy pivot; the violating sell rotation filled 6s later, 0.6% below the true sell threshold, and tripped the fund invariant on drain). New `runFinalPivotGate()` re-checks BUILT ops against a re-refreshed pivot after the op-building loop and before the batch summary, fund validation, and single-flight claim: unchanged pivot is a pure no-op (arrays untouched, no extra probes); a moved/armed pivot re-runs the guard on each op's final price with the build loop's bypass rules (spread-correction CREATEs incl. batch-origin fallback, stamped gap-evacuations; unstamped evacuations guarded normally, same-slot UPDATEs resolve via source-id fallback) and drops violators into the existing skipped-slot restore paths (rotations restore source+dest from master, creates feed the refill-hold intersect, dropped CREATE pending entries removed by slot). Fail-open throughout: unresolvable price/type, cold pivot, and refresh throws all KEEP the op; cancels and size-updates are never gated. Pair-mode/chunk grouping is computed lazily at broadcast from the filtered opContexts (no stored indexes go stale); `cancelOpIndexByOrderId` (no live readers past op-building) is rebuilt from kept contexts as defence-in-depth. Pending-index hygiene: the gate's compaction REMAPS this batch's kept pending-broadcast entries' stored opIndex/ctxIndex to the compacted positions (lockstep keeps the two arrays aligned with each other but does not rewrite indexes stored inside pending entries — an unremapped ctxIndex would resolve to a shifted context after the first drop, letting the uncertain-broadcast reconcile adopt a matched chain order into the wrong slot); entries are identified by the fingerprints collected at record time (`recordPendingBroadcast` now returns its fingerprint; `entry.batchId` is always null in production and cannot discriminate), and entries whose referenced op was dropped are removed. Gate stats are kept separate from the build loop's counters (own `finalGateStats` object; reported as `gateChecked/gatePassed/gateSkipped/gateBypassed` fields on the batch summary, omitted when the gate did not re-check) so the summary's checked/passed/skipped totals stay the build loop's verdicts. The frozen pivot is captured AFTER the batch-start refresh (the refresh is part of the freeze) so batches whose freeze picked up a pre-freeze queued fill no longer trigger a spurious "pivot moved" warn + redundant re-check. Observability: always-on `Final gate: queue a->b pivot x->y` at debug, pivot-moved line at warn with freeze-queue depth + drop count, per-drop lines at warn. New `tests/test_final_pivot_gate.ts` (FG-1..FG-11, incl. incident replay dropping both violating rotations, the same-slot fallback, and pending-index remap identity checks); neighbor suites green (`modules/dexbot_cow_runtime.ts`).
|
|
46
|
+
|
|
47
|
+
### 2026-09-14
|
|
48
|
+
|
|
49
|
+
- **Fix(correction-queue)**: validate queued price corrections against the live slot before broadcast — corrections are queued as snapshots (expected price/size at detect time), but a geometry-changing resync re-slots/re-prices orders without invalidating the queue, so the maintenance pre-gate drain replayed stale snapshots and broadcast UPDATEs that reverted resync placements back onto occupied levels. Entries are now validated pre-broadcast against the live slot (ownership, price via slot predicates, size via integer quantum); stale entries are dropped and self-heal via the next sync re-queue. Zero-delta `updateOrder` returns `{success:true, skipped:true}` so routine no-ops no longer count as permanent failures; queue removal/dedupe is keyed on `(chainOrderId, isSurplus)` so sibling entries sharing a chain id survive; `queuedAt`/`queuedBy` provenance is stamped at queue time (sync detectors + gap-evacuation); drain summaries report `staleDropped` to keep failure WARN a true signal. New `tests/test_correction_queue_staleness.ts` (11 cases); 10 neighbor suites green (`modules/order/utils/order.ts`, `modules/order/sync_engine.ts`, `modules/dexbot_cow_runtime.ts`, `modules/dexbot_maintenance_runtime.ts`, `modules/order/manager.ts`).
|
|
50
|
+
|
|
51
|
+
- **Fix(checker)**: split grid-correction checker fills into price epochs across repriced order lifetimes — the checker combined fills from different lifetimes of one order id after native repricing (limit-order update) operations and reported a false monotonicity violation. It now queries limit-order update operations and splits fills into chronological price epochs, preserving weighted aggregation for partial fills within one unchanged epoch (`analysis/grid_correction_check.ts`, `analysis/README.md`; TypeScript build passes, historical checker run clean).
|
|
52
|
+
|
|
5
53
|
## [1.6.2] - 2026-09-14 - Grid-Price Invariant, Gap-Slot Batch Sizing, Candle Shard Cache, Recovery Tolerance, Fill Counter Hygiene, Node List Cleanup
|
|
6
54
|
|
|
7
55
|
### 2026-09-14
|
|
8
56
|
|
|
9
|
-
- **Fix(grid)**: enforce grid-price invariant at emission sites + escalate persistent corruption and stranded holds — the engine had two sources of truth for a slot's price — the genesis ladder (`priceForSlot(idx, genesis)`) and the mutable `slot.price` field — and every violation was the second winning over the first. Five mechanisms fed it: orphan adoption overwrote the slot's identity price (S1), a pre-broadcast "freshness" step re-substituted that corrupted price at debug level (S2), guards checked range membership but never grid membership (S3), the fill-guard pivot was written from unvalidated fill prices (S4), and guard bypasses skipped even that (S5). New `checkGridPriceInvariant` / `reportGridPriceInvariant` (`modules/order/utils/order.ts`) require the emitted price for a slot to equal its genesis level (fail-open on everything unjudgeable: no genesis, unparseable id, out-of-ladder index, non-finite price, checker error), enforced blocking at all six emission sites: CREATE, UPDATE (rotation), CREATE-FALLBACK (`modules/dexbot_cow_runtime.ts`); RECONCILE-CREATE, RECONCILE-UPDATE, STARTUP-CREATE (`modules/order/grid_reconcile_internal.ts`). Rotation UPDATE derives the emitted price from the destination slot's genesis level (`deriveRotationPrice`) instead of trusting the planner's carried `newPrice`; both pre-broadcast price substitutions removed (drift reported at warn, never adopted); legacy orphan adoption no longer writes `chainOrder.price` into `slot.price` and gains a rail guard; materialize path derives slot price (and side) from the ladder; `loadGrid` repairs a mismatched slot price from the genesis ladder in both validation modes; fill-guard pivot validated onto the ladder (`resolveOnGridPivot`: near-ladder pivots snap, far off-ladder fail open and are counted as `pivotOffGrid`). Persistent invariant rejection escalates after `GRID_PRICE_INVARIANT_RESYNC_THRESHOLD` consecutive rejecting batches per slot (bot-scoped streak, reset on clean check, 15m cooldown) to `requestStructuralGridResync('grid-price-invariant-violation')`. Out-of-bounds policy: hold and surface — grid geometry is not invalidated by the market leaving it (the removed 5% placement gate is documented as a design deadlock, not a tuning problem). New `docs/GRID_PRICE_INVARIANT.md` plus `docs/README.md` index entry and `CONSOLIDATED_ORPHAN_FIX_SUMMARY.md` status refresh. Tests: new `tests/test_grid_price_invariant_guard.ts` (GPI-001..015) and `tests/test_grid_price_invariant_wiring.ts` (GPI-WIRE-001..008) plus `tests/test_startup_and_guard_log_hygiene.ts` (GPI-LOG-001); every behavioral fix mutation-tested (11 escalation/guard mutations verified); audited over 1,153 slots across five geometries with zero rejections; full suite 282/282 pass (`modules/order/utils/order.ts`, `modules/dexbot_cow_runtime.ts`, `modules/order/grid_reconcile_internal.ts`, `modules/order/grid.ts`).
|
|
57
|
+
- **Fix(grid)**: enforce grid-price invariant at emission sites + escalate persistent corruption and stranded holds — the engine had two sources of truth for a slot's price — the genesis ladder (`priceForSlot(idx, genesis)`) and the mutable `slot.price` field — and every violation was the second winning over the first. Five mechanisms fed it: orphan adoption overwrote the slot's identity price (S1), a pre-broadcast "freshness" step re-substituted that corrupted price at debug level (S2), guards checked range membership but never grid membership (S3), the fill-guard pivot was written from unvalidated fill prices (S4), and guard bypasses skipped even that (S5). New `checkGridPriceInvariant` / `reportGridPriceInvariant` (`modules/order/utils/order.ts`) require the emitted price for a slot to equal its genesis level (fail-open on everything unjudgeable: no genesis, unparseable id, out-of-ladder index, non-finite price, checker error), enforced blocking at all six emission sites: CREATE, UPDATE (rotation), CREATE-FALLBACK (`modules/dexbot_cow_runtime.ts`); RECONCILE-CREATE, RECONCILE-UPDATE, STARTUP-CREATE (`modules/order/grid_reconcile_internal.ts`). Rotation UPDATE derives the emitted price from the destination slot's genesis level (`deriveRotationPrice`) instead of trusting the planner's carried `newPrice`; both pre-broadcast price substitutions removed (drift reported at warn, never adopted); legacy orphan adoption no longer writes `chainOrder.price` into `slot.price` and gains a rail guard; materialize path derives slot price (and side) from the ladder; `loadGrid` repairs a mismatched slot price from the genesis ladder in both validation modes; fill-guard pivot validated onto the ladder (`resolveOnGridPivot`: near-ladder pivots snap, far off-ladder fail open and are counted as `pivotOffGrid`). Persistent invariant rejection escalates after `GRID_PRICE_INVARIANT_RESYNC_THRESHOLD` consecutive rejecting batches per slot (bot-scoped streak, reset on clean check, 15m cooldown) to `requestStructuralGridResync('grid-price-invariant-violation')`. Out-of-bounds policy: hold and surface — grid geometry is not invalidated by the market leaving it (the removed 5% placement gate is documented as a design deadlock, not a tuning problem). New `docs/GRID_PRICE_INVARIANT.md` plus `docs/README.md` index entry and a `CONSOLIDATED_ORPHAN_FIX_SUMMARY.md` status refresh (now absorbed into `docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md`). Tests: new `tests/test_grid_price_invariant_guard.ts` (GPI-001..015) and `tests/test_grid_price_invariant_wiring.ts` (GPI-WIRE-001..008) plus `tests/test_startup_and_guard_log_hygiene.ts` (GPI-LOG-001); every behavioral fix mutation-tested (11 escalation/guard mutations verified); audited over 1,153 slots across five geometries with zero rejections; full suite 282/282 pass (`modules/order/utils/order.ts`, `modules/dexbot_cow_runtime.ts`, `modules/order/grid_reconcile_internal.ts`, `modules/order/grid.ts`).
|
|
10
58
|
|
|
11
59
|
- **Fix(fill)**: retire deferred-retry counter on queue settle + pin COW finally drain — the fill-consumer deferral counter (`_deferredFillRetryWaits`) only retired when a pending retry timer fired against an already-empty queue, so a queue drained by any other path left the counter elevated and the deferral log cadence stuck at warn (every 12th defer) after the backlog cleared. The counter now retires in `consumeFillQueue` at both settle points (top-of-run empty early-return and tail-after-drain); `_deferredFillRetryTimer` declared/initialized on the bot class and cleared on shutdown so the unref'd timer cannot leak across lifecycle restarts. New RETRY-007 pins the counter reset on a clean drain (7 → 0 on settle); RETRY-006 pins the COW batch finally (injected `_ensureCredentialDaemonWritable` throw after `_batchInFlight++` / `_cowBroadcastInFlight` asserts the finally releases both and reschedules the fill consumer exactly once; the COW layer converts the throw into a handled `{executed:false}` abort via `_handleBatchHardAbort`) (`modules/dexbot_fill_runtime.ts`, `modules/dexbot_class.ts`, `tests/test_fill_defer_retry.ts`; neighbors green, `tsc --noEmit` clean).
|
|
12
60
|
|
|
@@ -14,17 +62,19 @@ All notable changes to this project will be documented in this file.
|
|
|
14
62
|
|
|
15
63
|
- **Fix(nodes)**: prune dead default nodes and single-source the connection-trace node list — `NODE_MANAGEMENT.DEFAULT_NODES` drops three dead endpoints, leaving seven live defaults; `tests/test_connection_trace.ts` no longer carries its own hardcoded copy — it reads `NODE_MANAGEMENT.DEFAULT_NODES` with an optional `BITSHARES_TRACE_NODE` override for single-node tracing, and the transport login probe uses `NODES[0]` instead of a hardcoded URL, so future node-list changes propagate automatically (`modules/constants.ts`, `tests/test_connection_trace.ts`).
|
|
16
64
|
|
|
65
|
+
- **Test(seams)**: eliminate wall-clock sleeps and live-chain hangs from offline tests — offline unit tests slept on production pacing delays and opened real WebSocket connections (suite past 125s, several files over 5s each). New production test seams with defaults unchanged: `createSubscriptionManager` accepts `overrides.noticeCoalesceMs` (page-timeout watchdog unref'd), COW `options.pollIntervalMs` for the 1.5s missing-create poll (set/restored via try/finally across every batch exit path incl. re-plan recursion), `scheduleDeferredFillRetry`/`parkFillsForTotalsRetry` `retryDelayMs`, credit-split `settleDelayMs` plus overridable collateral-balance fetch, sync-engine targeted-refetch fns plus `_skipEmptyReadConfirmDelay`, and `disconnectClient` stopping node health monitoring on teardown. New `resolveSeamMs()`/`resolveSeamMsOrNull()` (`modules/utils/errors.ts`) consolidate seam resolution (explicit 0 disables the delay; null/undefined falls through to the default — fixes `retryDelayMs` `> 0` silently dropping explicit 0 and `Number(null) === 0` selecting 0); each caller records its resolved delay so tests assert resolution instead of wall-clock timing (write-only in production). New `tests/test_seam_resolve.ts` plus COW-COMMIT-012 (seam restore on the guard-refusal path, precedence explicit > bot > default, explicit 0 honored) and RETRY-003b / TOTALS-004 (0 resolves to 0 — the only discriminating input for `||`-vs-`??` regressions, verified by fault injection); two stale pre-eager-gap-recovery mock expectations corrected, dead CJS override removed. Suite 287 passed, 0 failed, 15 live skipped, ~85s (was ~126s); `tsc` main + tests clean (`modules/bitshares-native/subscriptions.ts`, `modules/bitshares_client.ts`, `modules/credit_runtime.ts`, `modules/dexbot_cow_runtime.ts`, `modules/dexbot_fill_runtime.ts`, `modules/dexbot_startup_runtime.ts`, `modules/order/sync_engine.ts`, `modules/utils/errors.ts`).
|
|
66
|
+
|
|
17
67
|
### 2026-09-13
|
|
18
68
|
|
|
19
69
|
- **Feat(batch)**: derive fill/broadcast batch sizing from gap-slot count — two independent fixed caps (`FILL_PROCESSING.MAX_FILL_BATCH_SIZE` and `COW_PERFORMANCE.MAX_OPS_PER_BROADCAST`, both 4) bounded fill batching and per-broadcast order operations with no relationship to actual grid geometry. New `_getGapSlotBatchSize()` resolves via `resolveGapSlots()` (`manager._gapSlots`, else `calculateGapSlots(config)`, ≥1 floor, falls back to 1 on failure); legacy `_getMaxFillBatchSize()` / `_getMaxOpsPerBroadcast()` kept as thin aliases; dead `config._gapSlots` fallback operand removed; both constants removed from `modules/constants.ts` (remaining COW_PERFORMANCE keys unchanged). `executeChunkedWithRetryOnUncertain` prefers the gap-slot size with its own ≥1 guard. Docs across `architecture.md`, `LIFECYCLE.md`, `FUND_MOVEMENT_AND_ACCOUNTING.md`, `developer_guide.md` use gap-slot batch sizing terminology (stale FILL_PROCESSING snippet removed); `COPY_ON_WRITE_MASTER_PLAN.md` marks `MAX_OPS_PER_BROADCAST` removed with a pointer. Completed/obsolete legacy docs removed (not describing anything still in the code): `BROWSER_COMPAT_PLAN.md` (superseded by the package.json browser field + AGENTS.md), `GRID_PRICE_SLOT_DETERMINISM_PLAN.md` (implemented), `PLAN_FILL_STALE_RESTORE_MAIN.md` (implemented), `PLAN_MIN_BTS_VALUE.md` (completed plan), `crash_report_jan_mar_2026.md` (historical incident). Batch size now scales with grid size by construction (larger-gap grids produce larger fill batches and larger single broadcast transactions than the old fixed cap of 4 — intended); removed keys simply drop out of the runtime-settings override merge, no remaining consumers (`modules/dexbot_class.ts`, `modules/constants.ts`, `modules/dexbot_cow_runtime.ts`; `tests/test_cow_ops_per_broadcast.ts` mirrors via `manager._gapSlots` with strict `calculateGapSlots` equality, chunking/death-spiral expectations updated, `tests/sim_batching.ts` uses the GAP_SLOTS constant; full `npm test` exit 0).
|
|
20
70
|
|
|
21
|
-
- **Fix(cache)**: store Kibana candle cache in stable calendar-month shards — run-relative chunk files (`chunk_<index>_<dates>`) shifted names on every run because windows anchor at floored-now, forcing a full rewrite plus an orphan-deletion pass each invocation; the deletion destroyed cached history (fully-disjoint files wiped by narrow runs, boundary-straddling files losing out-of-window buckets). Storage is now fixed UTC calendar-month shards (`<base>.shard_YYYY-MM.json`) decoupled from querying: runs load only overlapping shards, fetch only genuinely missing buckets, and rewrite solely shards that gained buckets (higher-volume-wins) or query coverage (monotonically unioned `queriedRanges`); pure-reuse runs do zero writes/deletes. Legacy `*.chunk_*` files are absorbed (buckets + clipped coverage folded into shards) and retired only when every bucket provably lives in a shard; disjoint legacy is never loaded or deleted. Removed `cleanupOrphanCacheChunks`, `chunkPathFor`, `siblingChunkFiles`, `priorQueriedInWindow`; preserved immutable-gap pruning, 48h tail refresh, partial-window withholding, fetch retry budget. Windows are now fetch-planning splits only (`{index, gte, lte}`); the LP orphan-cleanup wrapper/export is dropped. Preceded by the narrower prune-scope fix (run passes its active coverage; orphans whose `meta.timeRange` does not overlap it are kept) plus the chart fixes it was validated against: update-marker div reuse (`um-wrap` class) and log-scale vertical-pan/wheel-zoom bbox offset correction (`market_adapter/inputs/window_cache.ts`, `market_adapter/inputs/fetch_lp_data.ts`, `market_adapter/README.md`, `analysis/tradingview/tradingview_uplot_chart_generator.ts`; `tests/test_window_cache.ts` rewritten with shard mapping, coverage set ops, scoped-load, and end-to-end integrations; LP/book assertions target `.shard_` files; full suite exit 0; live-verified: repeated feed chart runs show pure reuse, a wider run refetched one genuinely-lost span and left 7 stable shards).
|
|
71
|
+
- **Fix(cache)**: store Kibana candle cache in stable calendar-month shards — run-relative chunk files (`chunk_<index>_<dates>`) shifted names on every run because windows anchor at floored-now, forcing a full rewrite plus an orphan-deletion pass each invocation; the deletion destroyed cached history (fully-disjoint files wiped by narrow runs, boundary-straddling files losing out-of-window buckets). Storage is now fixed UTC calendar-month shards (`<base>.shard_YYYY-MM.json`) decoupled from querying: runs load only overlapping shards, fetch only genuinely missing buckets, and rewrite solely shards that gained buckets (higher-volume-wins) or query coverage (monotonically unioned `queriedRanges`); pure-reuse runs do zero writes/deletes. Legacy `*.chunk_*` files are absorbed (buckets + clipped coverage folded into shards) and retired only when every bucket provably lives in a shard; disjoint legacy is never loaded or deleted. Removed `cleanupOrphanCacheChunks`, `chunkPathFor`, `siblingChunkFiles`, `priorQueriedInWindow`; preserved immutable-gap pruning, 48h tail refresh, partial-window withholding, fetch retry budget. Windows are now fetch-planning splits only (`{index, gte, lte}`); the LP orphan-cleanup wrapper/export is dropped. Preceded by the narrower prune-scope fix (run passes its active coverage; orphans whose `meta.timeRange` does not overlap it are kept) plus the chart fixes it was validated against (issues #29/#30): update-marker div reuse (`um-wrap` class) and log-scale vertical-pan/wheel-zoom bbox offset correction (`market_adapter/inputs/window_cache.ts`, `market_adapter/inputs/fetch_lp_data.ts`, `market_adapter/README.md`, `analysis/tradingview/tradingview_uplot_chart_generator.ts`; `tests/test_window_cache.ts` rewritten with shard mapping, coverage set ops, scoped-load, and end-to-end integrations; LP/book assertions target `.shard_` files; full suite exit 0; live-verified: repeated feed chart runs show pure reuse, a wider run refetched one genuinely-lost span and left 7 stable shards).
|
|
22
72
|
|
|
23
73
|
## [1.6.1] - 2026-09-13 - Never-Run-Stale Hardening, Whitelist Range-Scaling Opt-In, Live-Save Docs, Shelf-Count Hardening
|
|
24
74
|
|
|
25
75
|
### 2026-09-13
|
|
26
76
|
|
|
27
|
-
- **Fix(recovery)**: give the GRID-PRICE-INVARIANT guard and the deferred-hold policy a self-healing exit — both had the same shape (state the guard refuses to emit, but nothing repairs it) and both now escalate to the existing structural resync rather than running forever. A slot rejected as off-grid is skipped and warned, which is correct for a one-off, but the recurring planner carries the slot's price straight from `manager.orders`, so an in-process corruption is re-planned, re-rejected and re-warned every cycle with nothing able to heal it short of a restart — the slot is dead while the bot looks healthy, and the repeated warns train operators to ignore them. The guard now counts **consecutive** rejecting batches per slot (bot-scoped — the monolithic runtime (`dexbot.ts`, the `dexbot` bin) builds EVERY active bot in one process, so a module-level streak would let one bot's rejections push another to the threshold on its FIRST rejection and fire a spurious resync on a healthy bot, and the count also must not outlive a repair; GPI-WIRE-009 pins the cross-bot case) and at `TIMING.GRID_PRICE_INVARIANT_RESYNC_THRESHOLD` (3) fires `requestStructuralGridResync('grid-price-invariant-violation', {slotId, expected, actual, site, streak})`; a clean check clears the streak so escalation means "rejected N consecutive batches", not "N times ever", and `GRID_PRICE_INVARIANT_RESYNC_COOLDOWN_MS` (15m) bounds repeats. Healing in place at rejection time is deliberately NOT done — silently rewriting `slot.price` would erase the diagnostic signal distinguishing the four corruption sources. Separately, out-of-rail orphans hold locked funds and are never auto-cancelled per cycle (correct: cancelling on ambiguous evidence is irreversible), but "held indefinitely" had no exit; a hold whose signature is unchanged for `DEFERRED_HOLD_ESCALATE_MS` (24h) now escalates at `error` to `requestStructuralGridResync('deferred-hold-stale')`, which is safe because the full reset's reconcile is update-first (unmatched orders are price-updated onto rail slots, only true surplus cancelled), so funds are released without inventing a new cancellation policy. Hold age is tracked **per stranded order** (bot-scoped map keyed `id@price/size:reason`), because two simpler clocks were wrong: `manager._lastUnmatchedChainOrdersAt` is unconditionally refreshed on every sync observing any unmatched order, so it records "when we last looked" and an age gate on it could never fire (HOLD-007 pins this); and the whole-held-set signature clock looked correct but was **reset by unrelated churn** — the signature includes every entry's reason, so an unrelated hold flapping in and out restarted the clock every cycle and starved a genuinely stranded order of escalation forever (HOLD-010 pins this; a 6-hourly flap was simulated reporting a clock reset on every tick). A third defect sat in the same path: the signature-change branch `return`ed before the escalation call, so any churn skipped escalation entirely — escalation now runs on both branches, since a signature change is a reason to re-log, not to stop evaluating age. Escalation triggers only on genuinely **stranded** reasons via a narrow allow-list (`isStrandedHoldOrder`: `out-of-rail-deferred`, `out-of-grid-deferred`) rather than the broad `-deferred` non-blocking filter, because a resync cannot end a broadcast region or re-evaluate an uncommitted boundary; `broadcast-active-deferred`, `boundary-hold-trailing-market`, `boundary-unknown-deferred` and `held-plan-unchanged-deferred` are excluded (HOLD-011 pins this), and the allow-list fails closed so a future transient reason is excluded by default. Both escalations reuse the existing debounced, batch-in-flight-aware resync path; a second repair mechanism would duplicate tested machinery. New GPI-WIRE-006..009 (`tests/test_grid_price_invariant_wiring.ts`) and HOLD-006..011 (`tests/test_hold_and_center_guards.ts`); mutation-verified that removing the escalation call, removing the streak reset, disabling the cooldown, removing the hold escalation, re-gating escalation behind the signature early return, reading the hold age from the whole-set clock, and replacing the stranded allow-list with the broad `-deferred` filter are each caught (`modules/constants.ts`, `modules/dexbot_cow_runtime.ts`, `modules/dexbot_maintenance_runtime.ts`, `docs/GRID_PRICE_INVARIANT.md`, `docs/CONSOLIDATED_ORPHAN_FIX_SUMMARY.md`).
|
|
77
|
+
- **Fix(recovery)**: give the GRID-PRICE-INVARIANT guard and the deferred-hold policy a self-healing exit — both had the same shape (state the guard refuses to emit, but nothing repairs it) and both now escalate to the existing structural resync rather than running forever. A slot rejected as off-grid is skipped and warned, which is correct for a one-off, but the recurring planner carries the slot's price straight from `manager.orders`, so an in-process corruption is re-planned, re-rejected and re-warned every cycle with nothing able to heal it short of a restart — the slot is dead while the bot looks healthy, and the repeated warns train operators to ignore them. The guard now counts **consecutive** rejecting batches per slot (bot-scoped — the monolithic runtime (`dexbot.ts`, the `dexbot` bin) builds EVERY active bot in one process, so a module-level streak would let one bot's rejections push another to the threshold on its FIRST rejection and fire a spurious resync on a healthy bot, and the count also must not outlive a repair; GPI-WIRE-009 pins the cross-bot case) and at `TIMING.GRID_PRICE_INVARIANT_RESYNC_THRESHOLD` (3) fires `requestStructuralGridResync('grid-price-invariant-violation', {slotId, expected, actual, site, streak})`; a clean check clears the streak so escalation means "rejected N consecutive batches", not "N times ever", and `GRID_PRICE_INVARIANT_RESYNC_COOLDOWN_MS` (15m) bounds repeats. Healing in place at rejection time is deliberately NOT done — silently rewriting `slot.price` would erase the diagnostic signal distinguishing the four corruption sources. Separately, out-of-rail orphans hold locked funds and are never auto-cancelled per cycle (correct: cancelling on ambiguous evidence is irreversible), but "held indefinitely" had no exit; a hold whose signature is unchanged for `DEFERRED_HOLD_ESCALATE_MS` (24h) now escalates at `error` to `requestStructuralGridResync('deferred-hold-stale')`, which is safe because the full reset's reconcile is update-first (unmatched orders are price-updated onto rail slots, only true surplus cancelled), so funds are released without inventing a new cancellation policy. Hold age is tracked **per stranded order** (bot-scoped map keyed `id@price/size:reason`), because two simpler clocks were wrong: `manager._lastUnmatchedChainOrdersAt` is unconditionally refreshed on every sync observing any unmatched order, so it records "when we last looked" and an age gate on it could never fire (HOLD-007 pins this); and the whole-held-set signature clock looked correct but was **reset by unrelated churn** — the signature includes every entry's reason, so an unrelated hold flapping in and out restarted the clock every cycle and starved a genuinely stranded order of escalation forever (HOLD-010 pins this; a 6-hourly flap was simulated reporting a clock reset on every tick). A third defect sat in the same path: the signature-change branch `return`ed before the escalation call, so any churn skipped escalation entirely — escalation now runs on both branches, since a signature change is a reason to re-log, not to stop evaluating age. Escalation triggers only on genuinely **stranded** reasons via a narrow allow-list (`isStrandedHoldOrder`: `out-of-rail-deferred`, `out-of-grid-deferred`) rather than the broad `-deferred` non-blocking filter, because a resync cannot end a broadcast region or re-evaluate an uncommitted boundary; `broadcast-active-deferred`, `boundary-hold-trailing-market`, `boundary-unknown-deferred` and `held-plan-unchanged-deferred` are excluded (HOLD-011 pins this), and the allow-list fails closed so a future transient reason is excluded by default. Both escalations reuse the existing debounced, batch-in-flight-aware resync path; a second repair mechanism would duplicate tested machinery. New GPI-WIRE-006..009 (`tests/test_grid_price_invariant_wiring.ts`) and HOLD-006..011 (`tests/test_hold_and_center_guards.ts`); mutation-verified that removing the escalation call, removing the streak reset, disabling the cooldown, removing the hold escalation, re-gating escalation behind the signature early return, reading the hold age from the whole-set clock, and replacing the stranded allow-list with the broad `-deferred` filter are each caught (`modules/constants.ts`, `modules/dexbot_cow_runtime.ts`, `modules/dexbot_maintenance_runtime.ts`, `docs/GRID_PRICE_INVARIANT.md`, `docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md` — successor of the then-cited `docs/CONSOLIDATED_ORPHAN_FIX_SUMMARY.md`).
|
|
28
78
|
|
|
29
79
|
- **Fix(shelf)**: exclude shelf orders from every grid count (issue #27 follow-up) — the four shelf follow-ups gated `reserveEdgeIdSet`, `matchedExcess`, and the geometric size recalc to `parseSlotIndex(id) !== null`, but three counters feeding the same decisions never got the gate, so a live fork-kept shelf (non-slot-N id, `ACTIVE` + `orderId`) still poisoned them via the fail-open `isSlotInRail` geometry. `_countActiveOnGrid` inflated `matchedOnGrid` (suppressing `neededSlots`/creates), `chainCount` in `_reconcileStartupSide` counted shelf-bound chain orders (fabricating a `chain - target` surplus that cancelled real window orders), `countLiveGridOrders` masked window shortfalls in targeted sync, and `_getOnChainOrders` plus the spread `buyCount`/`sellCount` masked `oneSideEmpty`. All now apply the same slot-N gate (no-op on grids that only mint slot-N ids); shelf stays in the geometric-recalc denominator by design (conservative sizing direction). `_pickVirtualSlotsToActivate` and the `getInitialOrdersToActivate` window picks are gated too so a `VIRTUAL` shelf can never consume window activation budget (`modules/order/grid_reconcile_internal.ts`, `modules/dexbot_maintenance_runtime.ts`, `modules/order/grid.ts`, `modules/order/manager.ts`; `tests/test_reserve_orders.ts` shelf expectations corrected to the fixed behavior — chain 12 grid vs target 8 plans 4 rail cancels, grid count converges to 8 with 3 shelf surviving alongside — plus a new block pinning `_countActiveOnGrid`, startup create non-suppression, targeted-sync `buy 5/8` firing through shelf, and slot-N-only activation picks; `tests/test_startup_decision.ts`, `tests/test_resync_balance_fix.ts`, `tests/test_resync_duplicate_race.ts` fixtures migrated from ad-hoc ids to `slot-N` ids per the production invariant; full suite exit 0, zero failures).
|
|
30
80
|
|
package/README.md
CHANGED
|
@@ -65,8 +65,8 @@ Install Git:
|
|
|
65
65
|
sudo apt-get update
|
|
66
66
|
sudo apt-get install git
|
|
67
67
|
|
|
68
|
-
# Arch/Manjaro
|
|
69
|
-
sudo pacman -
|
|
68
|
+
# Arch/Manjaro/CachyOS
|
|
69
|
+
sudo pacman -Syu git
|
|
70
70
|
|
|
71
71
|
# Fedora/RHEL
|
|
72
72
|
sudo dnf install git
|
|
@@ -122,6 +122,10 @@ npm install
|
|
|
122
122
|
npm link
|
|
123
123
|
```
|
|
124
124
|
|
|
125
|
+
If `npm link` fails with `EACCES` or `dexbot` isn't found afterwards, see the
|
|
126
|
+
[first-run troubleshooting](docs/BITSHARES_ONBOARDING.md#troubleshooting-first-run-mistakes)
|
|
127
|
+
section in the onboarding guide.
|
|
128
|
+
|
|
125
129
|
### Where your data lives
|
|
126
130
|
|
|
127
131
|
Both installs use the same CLI and store all user state — keys, `bots.json`, logs — in `~/.config/dexbot2/profiles` (Windows: `%USERPROFILE%\.config\dexbot2\profiles`). State lives outside the repo/package tree, so it survives reinstalls and `npm update -g`. A source checkout that already contains a populated `profiles/` directory keeps using it. Override the location with `DEXBOT_PROFILE_ROOT` (see the [developer guide](docs/developer_guide.md)).
|
|
@@ -271,7 +275,7 @@ dexbot clear # Clear log files
|
|
|
271
275
|
dexbot default # Reset settings to defaults
|
|
272
276
|
```
|
|
273
277
|
|
|
274
|
-
##
|
|
278
|
+
## ⚙️ PM2 Process Management
|
|
275
279
|
|
|
276
280
|
PM2 is optional — `dexbot start` is the native solution.
|
|
277
281
|
|
|
@@ -313,12 +317,12 @@ Logs are written to `logs/` in the profiles directory in all modes: the monolith
|
|
|
313
317
|
- **[Docs Index](docs/README.md)** - Main documentation hub
|
|
314
318
|
- **[Architecture](docs/architecture.md)** - System design, fill processing pipeline, and testing strategy
|
|
315
319
|
- **[Lifecycle](docs/LIFECYCLE.md)** - End-to-end walkthrough: startup, fill-driven, and AMA-driven flows with diagrams
|
|
316
|
-
- **[Evolution Report](docs/EVOLUTION.md)** - Project timeline, architecture phases, and release history
|
|
317
|
-
- **[Developer Guide](docs/developer_guide.md)** - Development guide, environment variables, examples, and glossary
|
|
318
|
-
- **[Workflow](docs/WORKFLOW.md)** - Project workflow and contribution guide
|
|
319
|
-
- **[Fund Movement & Accounting](docs/FUND_MOVEMENT_AND_ACCOUNTING.md)** - Fund accounting, grid topology, and rotation mechanics
|
|
320
320
|
- **[Copy-on-Write Plan](docs/COPY_ON_WRITE_MASTER_PLAN.md)** - Copy-on-Write grid architecture
|
|
321
|
+
- **[Fund Movement & Accounting](docs/FUND_MOVEMENT_AND_ACCOUNTING.md)** - Fund accounting, grid topology, and rotation mechanics
|
|
321
322
|
- **[Claw API Boundary](claw/docs/AI_BOT_LIBRARY_API.md)** - Responsibility split between the AI layer and the DEXBot2 execution layer
|
|
323
|
+
- **[Developer Guide](docs/developer_guide.md)** - Development guide, environment variables, examples, and glossary
|
|
324
|
+
- **[Workflow](docs/WORKFLOW.md)** - Project workflow and contribution guide
|
|
325
|
+
- **[Evolution Report](docs/EVOLUTION.md)** - Project timeline, architecture phases, and release history
|
|
322
326
|
|
|
323
327
|
## 🤝 Contributing
|
|
324
328
|
|
package/analysis/README.md
CHANGED
|
@@ -134,6 +134,8 @@ Fetches `fill_order` operations for a BitShares account from Kibana within a spe
|
|
|
134
134
|
|
|
135
135
|
**Pipeline:** Kibana fill query → on-chain asset precision resolution → buy/sell classification → chronological matching (sequential LIFO by default) → per-pair summary + optional per-match detail.
|
|
136
136
|
|
|
137
|
+
**Account resolution:** an account name (or a bare `1.2.x` ID) is resolved through the shared `account_resolver.ts` helper. When the name matches a bot in `profiles/bots.json`, the resolved ID is stamped onto that entry as `accountId`, so later runs resolve offline; `--refresh-account` forces a fresh lookup.
|
|
138
|
+
|
|
137
139
|
```bash
|
|
138
140
|
# Account by ID, last 7 days (default)
|
|
139
141
|
node dist/analysis/trade_profitability.js 1.2.123456
|
|
@@ -164,7 +166,6 @@ node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
|
164
166
|
| `--asset <id>` | all | Filter to one base asset ID |
|
|
165
167
|
| `--lookup` | off | Legacy (no-op): account names always resolve automatically |
|
|
166
168
|
| `--refresh-account` | off | Force re-resolution and update the stored `accountId` |
|
|
167
|
-
| `--node <url>` | first healthy from built-in pool (10 nodes) | BitShares node for account + asset resolution |
|
|
168
169
|
| `--csv <file>` | — | Export chronologically sorted trade list |
|
|
169
170
|
| `--json <file>` | — | Export full analysis with per-pair PnL data |
|
|
170
171
|
| `--match-mode <mode>` | `sequential` | Matching mode: `sequential` (LIFO, default) or `fifo` |
|
|
@@ -177,8 +178,8 @@ node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
|
177
178
|
**Asset precision handling:**
|
|
178
179
|
|
|
179
180
|
1. Assets listed in the static `ASSETS` table (BTS, TWENTIX, XBTSX.*, HONEST.*, IOB.*, etc.) resolve instantly.
|
|
180
|
-
2. Unknown assets are resolved on-chain via `get_assets`
|
|
181
|
-
3. If
|
|
181
|
+
2. Unknown assets are resolved on-chain via `get_assets` against the built-in node pool, with results cached at runtime.
|
|
182
|
+
3. If resolution fails or an asset is not found on chain, the affected fills are **skipped** with a warning (no abort).
|
|
182
183
|
|
|
183
184
|
**PnL methodology:**
|
|
184
185
|
|
|
@@ -206,9 +207,10 @@ node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
|
206
207
|
| `Expectancy (gross)` | How much one trade is expected to earn before fees. Positive = edge exists. The `R` version normalises this by the average loss size (reports in R-multiples instead of BTS). The `net` version subtracts fees. |
|
|
207
208
|
| `Median R` | The middle R-multiple value (half of trades are above, half below). `>1R` / `>2R` = % of trades that earned more than 1× or 2× the average loss. `<-1R` = % that lost more than 1× the average loss. |
|
|
208
209
|
| `PnL distribution` | Median, P25, P75, Best, Worst — the centre, spread, and extremes of per-trade return %. Not annualised, just per cycle. |
|
|
209
|
-
| `Sharpe (ann)` |
|
|
210
|
-
| `Sortino (ann)` |
|
|
211
|
-
| `
|
|
210
|
+
| `Sharpe (ann)` | The window's net PnL per unit of volatility, annualised (`mean/std × √periods-per-year`). Binned daily for ≥ 3-day windows, hourly below; every period counts, flat ones as 0 PnL. Shown as `value ± estimation error [bin, n, confidence]` — short windows are low confidence, and only same-bin runs are comparable. Dimensionful (absolute PnL, not % returns). |
|
|
211
|
+
| `Sortino (ann)` | As Sharpe, but only losing periods feed the downside deviation. `∞` means the window had no losing periods. |
|
|
212
|
+
| `Projected net PnL` | Scored-window net PnL scaled linearly to a year (`÷ scored days × 365`) — same whole-period basis as Sharpe/Sortino, so a trailing partial period is excluded from both. A projection, not a forecast. |
|
|
213
|
+
| `Max Drawdown` | Largest peak-to-trough decline of the realised-PnL curve, in quote units (with the same decline as a % of peak cumulative profit). Realised only — open inventory isn't marked. |
|
|
212
214
|
| `Max Recovery Time` | Longest time (in days) from the deepest point of a drawdown back to a new equity high. |
|
|
213
215
|
| `Max Consecutive W/L` | Longest streak of winning or losing round-trips. Grouped by sell order, so one order covering multiple buy lots counts as one result. Grid bots naturally cluster wins during trends — streaks of 100-200 are not alarming. |
|
|
214
216
|
| `Avg hold time` | Average time (hours) between buying an asset and selling it. |
|
|
@@ -216,16 +218,16 @@ node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
|
216
218
|
| `Sell orders filled` | Number of distinct sell orders that were filled in the period. |
|
|
217
219
|
| `Partial fills/order` | How many buy lots each sell order consumed (mean, median, max). For a grid bot: 2.0 median means half the orders clear 2 grid levels; 18 max means one big sweep. |
|
|
218
220
|
| `One-shot orders` | % of orders that matched exactly 1 buy lot. Low % = your grid is thick enough that orders routinely cover multiple levels. |
|
|
219
|
-
| `Fills/day` | Average matched lots per
|
|
220
|
-
| `Avg vol/day` | Average daily trading volume in the quote asset. |
|
|
221
|
+
| `Fills/day` | Average matched lots per scored day, on the same whole-period basis as the ratios above. Raw activity speed. |
|
|
222
|
+
| `Avg vol/day` | Average daily trading volume in the quote asset over that same scored window. |
|
|
221
223
|
|
|
222
224
|
</details>
|
|
223
225
|
|
|
224
226
|
### Grid Correction Check (`grid_correction_check.ts`)
|
|
225
227
|
|
|
226
|
-
Validates grid discipline from the same Kibana fill pipeline as `trade_profitability.ts`: two consecutive same-direction fills on a pair must be monotonic — sell prices rising, buy prices falling (equal is OK). An inversion means the bot placed an order below its own previous sell (or above its own previous buy), e.g. an orphaned order filling outside grid accounting. Used as the external regression gate for the orphan-fix plans in `docs/
|
|
228
|
+
Validates grid discipline from the same Kibana fill pipeline as `trade_profitability.ts`: two consecutive same-direction fills on a pair must be monotonic — sell prices rising, buy prices falling (equal is OK). An inversion means the bot placed an order below its own previous sell (or above its own previous buy), e.g. an orphaned order filling outside grid accounting. Used as the external regression gate for the orphan-fix plans in `docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md`.
|
|
227
229
|
|
|
228
|
-
**Pipeline:** Kibana `fill_order` query (paginated `search_after`) → on-chain asset precision resolution → buy/sell classification → chronological sort → per-order aggregation (
|
|
230
|
+
**Pipeline:** Kibana `fill_order` query (paginated `search_after`) → on-chain asset precision resolution → buy/sell classification → chronological sort → per-order/price-epoch aggregation (partial fills at one price collapsed to weighted-average; repriced order lifetimes kept separate) → consecutive same-direction pair comparison → violation report with daily histogram.
|
|
229
231
|
|
|
230
232
|
```bash
|
|
231
233
|
# Per-order aggregated check (default), last 7 days
|
|
@@ -253,7 +255,6 @@ Exit code `0` = pass, `2` = violations found, `1` = fatal error. Bot keys resolv
|
|
|
253
255
|
| `--account <id>` | bot `preferredAccount` | Override account ID or name |
|
|
254
256
|
| `--lookup` | off | Legacy (no-op): account names always resolve via BitShares node when no stored ID exists |
|
|
255
257
|
| `--refresh-account` | off | Force re-resolution of `preferredAccount` and update the stored `accountId` when it changed |
|
|
256
|
-
| `--node <url>` | first built-in node | Node for account/asset resolution |
|
|
257
258
|
| `--per-fill` | off | Check at fill granularity instead of per-order aggregated |
|
|
258
259
|
| `--include-cross-pair` | off | Also check consecutive fills across different pairs |
|
|
259
260
|
| `--tolerance <pct>` | `0` | Adverse price move (%) forgiven before flagging |
|
|
@@ -263,7 +264,7 @@ Exit code `0` = pass, `2` = violations found, `1` = fatal error. Bot keys resolv
|
|
|
263
264
|
|
|
264
265
|
</details>
|
|
265
266
|
|
|
266
|
-
**Notes:** strict sat-level comparison is the ground truth (`--tolerance` only forgives small inversions);
|
|
267
|
+
**Notes:** strict sat-level comparison is the ground truth (`--tolerance` only forgives small inversions); partial fills at one price are collapsed to a weighted-average price in the default mode, but fills after a native order repricing are kept in separate price epochs so updated orders are not mixed together.
|
|
267
268
|
|
|
268
269
|
## Charts & Visualization
|
|
269
270
|
|
|
@@ -485,7 +486,12 @@ Details: [bot_fitting/README.md](bot_fitting/README.md)
|
|
|
485
486
|
| `price_sources.ts` | Unified candle source abstraction (`json`, `market_adapter`) |
|
|
486
487
|
| `chart_utils.ts` | Shared chart rendering utilities |
|
|
487
488
|
| `math_utils.ts` | Shared math utilities |
|
|
488
|
-
| `bot_key_utils.ts` | Bot-key resolution
|
|
489
|
+
| `bot_key_utils.ts` | Bot-key resolution, candle file lookup, `accountId` persistence (`persistBotAccountId`) |
|
|
490
|
+
| `account_resolver.ts` | Account resolution for all tools: `preferredAccount` / `--account` → `1.2.x`, stamping the result into `profiles/bots.json` |
|
|
491
|
+
| `chain_pool.ts` | Ephemeral read-only chain client over the built-in node pool (account + asset lookups) |
|
|
492
|
+
| `fills_source.ts` | Shared `fill_order` Kibana fetch/query and the static asset-precision table + on-chain cache |
|
|
493
|
+
|
|
494
|
+
On-chain account and asset lookups in the fill-based tools go through `account_resolver.ts` / `fills_source.ts` (both built on `chain_pool.ts`): tool scripts must not open their own read-only clients or carry their own node list. The batch backfill `resolve_bot_accounts.ts` is the exception — it reuses the production chain client over one connection.
|
|
489
495
|
|
|
490
496
|
## npm Script Shortcuts
|
|
491
497
|
|
|
@@ -519,5 +525,5 @@ npm run ama:chart:lp-local -- --data market_adapter/data/lp/<pair>/lp_pool_<id>_
|
|
|
519
525
|
## Related Docs
|
|
520
526
|
|
|
521
527
|
- [Market Adapter](../market_adapter/README.md) — live AMA pricing, grid triggers, dynamic weights, and recalc triggers
|
|
522
|
-
- [
|
|
528
|
+
- [Order Engine Retrospective](../docs/ORDER_ENGINE_POST_1.0_RETROSPECTIVE.md) — orphan/gap-band root-cause plans; `grid_correction_check` is their regression gate
|
|
523
529
|
- [DEXBot2 Tuning Cheat Sheet](../claw/docs/DEXBOT2_TUNING_CHEAT_SHEET.md) — grid tuning reference for live bots
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
'use strict';
|
|
2
|
+
|
|
3
|
+
/**
|
|
4
|
+
* SHARED ACCOUNT RESOLUTION (analysis)
|
|
5
|
+
*
|
|
6
|
+
* Central implementation of "preferredAccount / bare account reference →
|
|
7
|
+
* 1.2.x accountId" for every analysis tool, including stamping the resolved ID
|
|
8
|
+
* back into profiles/bots.json so later runs skip the chain lookup.
|
|
9
|
+
*
|
|
10
|
+
* trade_profitability.ts and grid_correction_check.ts used to each carry a
|
|
11
|
+
* private copy of this decision tree, of the chain lookup and of the node
|
|
12
|
+
* handling — all of it lives here now.
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import { findBotKeyByAccountRef, loadBotMeta, persistBotAccountId } from './bot_key_utils.js';
|
|
16
|
+
import { withReadOnlyClient } from './chain_pool.js';
|
|
17
|
+
|
|
18
|
+
const ACCOUNT_ID_RE = /^1\.2\.\d+$/;
|
|
19
|
+
|
|
20
|
+
/** Chain lookup seam: batch tools can reuse one connection across lookups. */
|
|
21
|
+
type NameLookup = (name: string) => Promise<string | null>;
|
|
22
|
+
|
|
23
|
+
/** Default lookup: ephemeral read-only client over the built-in node pool. */
|
|
24
|
+
async function lookupNameOnChain(name: string): Promise<string | null> {
|
|
25
|
+
try {
|
|
26
|
+
return await withReadOnlyClient(async (client) => {
|
|
27
|
+
const accounts = await client.db('lookup_account_names', [[name]]);
|
|
28
|
+
return Array.isArray(accounts) && accounts[0]?.id ? String(accounts[0].id) : null;
|
|
29
|
+
});
|
|
30
|
+
} catch (e: any) {
|
|
31
|
+
console.warn(` [warn] Account resolution failed: ${e.message}`);
|
|
32
|
+
return null;
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type ResolveSource = 'typed-id' | 'override-id' | 'override-resolved' | 'preferred-id' | 'stored' | 'resolved';
|
|
37
|
+
type ResolveFailure = 'bot-not-found' | 'no-preferred-account' | 'override-unresolved' | 'name-unresolved';
|
|
38
|
+
|
|
39
|
+
interface ResolveOptions {
|
|
40
|
+
/** Explicit override (--account). Wins over the bot entry and is never persisted. */
|
|
41
|
+
overrideAccount?: string | null;
|
|
42
|
+
/** Ignore a stored accountId and re-resolve on chain (--refresh-account). */
|
|
43
|
+
refresh?: boolean;
|
|
44
|
+
/** Suppress informational output; failures are reported via `reason`. */
|
|
45
|
+
quiet?: boolean;
|
|
46
|
+
/** Chain lookup override (batch tools reuse one connection, tests stub it). */
|
|
47
|
+
lookup?: NameLookup;
|
|
48
|
+
/** Override profiles/bots.json (tests use a temp file). */
|
|
49
|
+
botsFile?: string;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
interface ResolvedAccount {
|
|
53
|
+
accountId: string | null;
|
|
54
|
+
botKey: string | null;
|
|
55
|
+
botMeta: any | null;
|
|
56
|
+
source: ResolveSource | null;
|
|
57
|
+
reason: ResolveFailure | null;
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/**
|
|
61
|
+
* Persist a resolved ID onto the bot entry (no-op when it already matches) and
|
|
62
|
+
* report the outcome. `previous` is the stored value the write replaces, if any.
|
|
63
|
+
*/
|
|
64
|
+
function persistResolvedAccountId(botKey: string, accountId: string, previous: string | null, quiet: boolean, botsFile?: string): boolean {
|
|
65
|
+
const wrote = persistBotAccountId(botKey, accountId, botsFile);
|
|
66
|
+
if (quiet) return wrote;
|
|
67
|
+
if (wrote) {
|
|
68
|
+
console.log(previous && previous !== accountId
|
|
69
|
+
? ` Updated stored accountId ${previous} → ${accountId} in profiles/bots.json for '${botKey}'`
|
|
70
|
+
: ` Stored accountId ${accountId} in profiles/bots.json for '${botKey}'`);
|
|
71
|
+
} else if (previous === accountId) {
|
|
72
|
+
console.log(` Stored accountId ${accountId} confirmed up to date`);
|
|
73
|
+
} else {
|
|
74
|
+
console.warn(` [warn] Could not store accountId ${accountId} in profiles/bots.json for '${botKey}'`);
|
|
75
|
+
}
|
|
76
|
+
return wrote;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Resolve the chain account for a bot entry:
|
|
81
|
+
* explicit override > typed 1.2.x preferredAccount > stored accountId >
|
|
82
|
+
* fresh chain lookup of the name.
|
|
83
|
+
* The resolved ID is persisted onto the entry whenever it differs from the
|
|
84
|
+
* stored one (an explicit override is never persisted — it is not the bot's
|
|
85
|
+
* preferredAccount).
|
|
86
|
+
*/
|
|
87
|
+
async function resolveBotAccount(botKey: string, options: ResolveOptions = {}): Promise<ResolvedAccount> {
|
|
88
|
+
const { overrideAccount = null, refresh = false, quiet = false, lookup = lookupNameOnChain, botsFile } = options;
|
|
89
|
+
const botMeta = botKey ? loadBotMeta(botKey, botsFile) : null;
|
|
90
|
+
|
|
91
|
+
// An explicit override always wins and is never persisted.
|
|
92
|
+
const override = overrideAccount != null ? String(overrideAccount).trim() : '';
|
|
93
|
+
if (override) {
|
|
94
|
+
if (ACCOUNT_ID_RE.test(override)) {
|
|
95
|
+
return { accountId: override, botKey, botMeta, source: 'override-id', reason: null };
|
|
96
|
+
}
|
|
97
|
+
if (!quiet) console.log(` Resolving account name '${override}'...`);
|
|
98
|
+
const id = await lookup(override);
|
|
99
|
+
if (!id) return { accountId: null, botKey, botMeta, source: null, reason: 'override-unresolved' };
|
|
100
|
+
if (!quiet) console.log(` → ${id}`);
|
|
101
|
+
return { accountId: String(id), botKey, botMeta, source: 'override-resolved', reason: null };
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
if (!botMeta) return { accountId: null, botKey, botMeta: null, source: null, reason: 'bot-not-found' };
|
|
105
|
+
|
|
106
|
+
const prefRaw = botMeta.preferredAccount != null ? String(botMeta.preferredAccount).trim() : '';
|
|
107
|
+
if (!prefRaw) return { accountId: null, botKey, botMeta, source: null, reason: 'no-preferred-account' };
|
|
108
|
+
|
|
109
|
+
const stored = ACCOUNT_ID_RE.test(String(botMeta.accountId ?? '')) ? String(botMeta.accountId) : null;
|
|
110
|
+
|
|
111
|
+
// A typed 1.2.x preferredAccount is authoritative — the stored accountId is
|
|
112
|
+
// a derived cache and must never override it. Self-heal the cache.
|
|
113
|
+
if (ACCOUNT_ID_RE.test(prefRaw)) {
|
|
114
|
+
if (stored !== prefRaw) persistResolvedAccountId(botKey, prefRaw, stored, quiet, botsFile);
|
|
115
|
+
return { accountId: prefRaw, botKey, botMeta, source: 'preferred-id', reason: null };
|
|
116
|
+
}
|
|
117
|
+
|
|
118
|
+
// Name + fresh-enough cache + no refresh requested: offline-friendly fast path.
|
|
119
|
+
if (stored && !refresh) {
|
|
120
|
+
if (!quiet) console.log(` Using stored accountId ${stored} from profiles/bots.json (no lookup needed; pass --refresh-account to re-verify)`);
|
|
121
|
+
return { accountId: stored, botKey, botMeta, source: 'stored', reason: null };
|
|
122
|
+
}
|
|
123
|
+
|
|
124
|
+
if (!quiet) console.log(` Resolving account name '${prefRaw}'...`);
|
|
125
|
+
const id = await lookup(prefRaw);
|
|
126
|
+
if (!id) return { accountId: null, botKey, botMeta, source: null, reason: 'name-unresolved' };
|
|
127
|
+
if (!quiet) console.log(` → ${id}`);
|
|
128
|
+
persistResolvedAccountId(botKey, String(id), stored, quiet, botsFile);
|
|
129
|
+
return { accountId: String(id), botKey, botMeta, source: 'resolved', reason: null };
|
|
130
|
+
}
|
|
131
|
+
|
|
132
|
+
/**
|
|
133
|
+
* Resolve a bare account reference (1.2.x ID or name) to a chain account ID.
|
|
134
|
+
* When a bot in profiles/bots.json claims the name, the resolved ID is
|
|
135
|
+
* persisted onto that entry via resolveBotAccount.
|
|
136
|
+
*/
|
|
137
|
+
async function resolveAccountRef(accountRef: string, options: ResolveOptions = {}): Promise<ResolvedAccount> {
|
|
138
|
+
const ref = String(accountRef ?? '').trim();
|
|
139
|
+
if (ACCOUNT_ID_RE.test(ref)) {
|
|
140
|
+
return { accountId: ref, botKey: null, botMeta: null, source: 'typed-id', reason: null };
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
// A bot claiming this name is the persist target; its preferredAccount is
|
|
144
|
+
// the same name, so resolveBotAccount owns the whole decision tree.
|
|
145
|
+
let match: { botKey: string; meta: any } | null = null;
|
|
146
|
+
try {
|
|
147
|
+
match = findBotKeyByAccountRef(ref, options.botsFile);
|
|
148
|
+
} catch (_) {
|
|
149
|
+
// bots.json issues must never break resolution; fall through to chain.
|
|
150
|
+
}
|
|
151
|
+
if (match) return resolveBotAccount(match.botKey, options);
|
|
152
|
+
|
|
153
|
+
const lookup = options.lookup ?? lookupNameOnChain;
|
|
154
|
+
if (!options.quiet) console.log(` Resolving account name '${ref}'...`);
|
|
155
|
+
const id = await lookup(ref);
|
|
156
|
+
if (!id) return { accountId: null, botKey: null, botMeta: null, source: null, reason: 'name-unresolved' };
|
|
157
|
+
if (!options.quiet) console.log(` → ${id}`);
|
|
158
|
+
return { accountId: String(id), botKey: null, botMeta: null, source: 'resolved', reason: null };
|
|
159
|
+
}
|
|
160
|
+
|
|
161
|
+
export {
|
|
162
|
+
ACCOUNT_ID_RE,
|
|
163
|
+
lookupNameOnChain,
|
|
164
|
+
resolveBotAccount,
|
|
165
|
+
resolveAccountRef,
|
|
166
|
+
ResolveOptions,
|
|
167
|
+
ResolvedAccount,
|
|
168
|
+
ResolveSource,
|
|
169
|
+
ResolveFailure,
|
|
170
|
+
NameLookup,
|
|
171
|
+
};
|
|
@@ -10,6 +10,7 @@ import { ensureDir } from '../../modules/order/utils/system.js';
|
|
|
10
10
|
import { PATHS } from '../../modules/paths.js';
|
|
11
11
|
import { MARKET_ADAPTER } from '../../modules/constants.js';
|
|
12
12
|
import { getErrorMessage } from '../../modules/utils/errors.js';
|
|
13
|
+
import { uplotInlineTags } from '../chart_utils.js';
|
|
13
14
|
|
|
14
15
|
/**
|
|
15
16
|
* LAMBDA vs SLOW ANALYSIS
|
|
@@ -186,9 +187,6 @@ function generateChartHtml(results: any, metricCache: any, fixEr: any, fixFast:
|
|
|
186
187
|
const cacheSlow = metricCache.map((m: any) => m.slow);
|
|
187
188
|
const cacheMove = metricCache.map((m: any) => m.movement);
|
|
188
189
|
|
|
189
|
-
const uplotCode = fs.readFileSync(path.join(PATHS.ANALYSIS.ASSETS_DIR, 'uPlot.iife.min.js'), 'utf8');
|
|
190
|
-
const uplotCSS = fs.readFileSync(path.join(PATHS.ANALYSIS.ASSETS_DIR, 'uPlot.min.css'), 'utf8');
|
|
191
|
-
|
|
192
190
|
const amaAnnotations = [
|
|
193
191
|
{ label: 'AMA1', lambda: 0.0031, slow: 62.1, color: '#ef5350' },
|
|
194
192
|
{ label: 'AMA2', lambda: 0.0025, slow: 72.0, color: '#fb8c00' },
|
|
@@ -203,7 +201,6 @@ function generateChartHtml(results: any, metricCache: any, fixEr: any, fixFast:
|
|
|
203
201
|
<meta name="viewport" content="width=device-width, initial-scale=1.0">
|
|
204
202
|
<title>λ → Slow — ER=${fixEr} Fast=${fixFast}</title>
|
|
205
203
|
<style>
|
|
206
|
-
${uplotCSS}
|
|
207
204
|
body { font-family: system-ui, sans-serif; margin: 20px; background: #1e1e2e; color: #cdd6f4; }
|
|
208
205
|
h1 { font-size: 16px; margin: 0 0 2px 0; }
|
|
209
206
|
.subtitle { color: #a6adc8; font-size: 12px; margin-bottom: 12px; }
|
|
@@ -231,7 +228,7 @@ function generateChartHtml(results: any, metricCache: any, fixEr: any, fixFast:
|
|
|
231
228
|
${amaAnnotations.map(a => `<span class="legend-item"><span class="legend-dot" style="background:${a.color}"></span> ${a.label}</span>`).join('\n')}
|
|
232
229
|
</div>
|
|
233
230
|
</div>
|
|
234
|
-
|
|
231
|
+
${uplotInlineTags()}
|
|
235
232
|
<script>
|
|
236
233
|
(function() {
|
|
237
234
|
const xs = ${JSON.stringify(xs)};
|
|
@@ -1,10 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
'use strict';
|
|
3
|
-
import { createRequire } from 'node:module';
|
|
4
|
-
const require = createRequire(import.meta.url);
|
|
5
|
-
|
|
6
|
-
|
|
7
3
|
import { getStorage } from '../../modules/storage/index.js';
|
|
4
|
+
import { withReadOnlyClient } from '../chain_pool.js';
|
|
8
5
|
const { writeJSON } = getStorage();
|
|
9
6
|
|
|
10
7
|
/**
|
|
@@ -58,7 +55,6 @@ import {
|
|
|
58
55
|
buildTopUpdaterAccountsQuery,
|
|
59
56
|
DEFAULT_CONFIG,
|
|
60
57
|
} from './kibana_bot_queries.js';
|
|
61
|
-
import { NODE_MANAGEMENT } from '../../modules/constants.js';
|
|
62
58
|
|
|
63
59
|
// ─── Types ────────────────────────────────────────────────────────────────────
|
|
64
60
|
|
|
@@ -283,33 +279,30 @@ function dexScore(creates: number, fills: number, _cancels: number, gridScore: n
|
|
|
283
279
|
// ─── Account resolution ───────────────────────────────────────────────────────
|
|
284
280
|
|
|
285
281
|
async function resolveNames(ids: string[]): Promise<Record<string, string>> {
|
|
286
|
-
const { createReadOnlyClient } = require('../../modules/bitshares-native');
|
|
287
|
-
const client = createReadOnlyClient({ nodes: NODE_MANAGEMENT.DEFAULT_NODES });
|
|
288
|
-
|
|
289
282
|
const map: Record<string, any> = {};
|
|
290
283
|
try {
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
284
|
+
// Ephemeral read-only client over the built-in node pool (shared helper).
|
|
285
|
+
await withReadOnlyClient(async (client) => {
|
|
286
|
+
// BitShares db.get_objects accepts an array of IDs
|
|
287
|
+
const objects = await client.db('get_objects', [ids]);
|
|
288
|
+
for (const obj of (objects ?? [])) {
|
|
289
|
+
if (obj?.id && obj?.name) map[obj.id] = obj.name;
|
|
290
|
+
}
|
|
297
291
|
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
292
|
+
// Resolve extra asset precisions while connected
|
|
293
|
+
const toCheck = ['IOB.XRP', 'HONEST.MONEY', 'XBTSX.XRP', 'XBTSX.USDT', 'USD', 'CNY'];
|
|
294
|
+
for (const sym of toCheck) {
|
|
295
|
+
try {
|
|
296
|
+
const assets = await client.db('lookup_asset_symbols', [[sym]]);
|
|
297
|
+
const a = Array.isArray(assets) ? assets[0] : null;
|
|
298
|
+
if (a?.id && !(a.id in ASSET_PRECISION)) {
|
|
299
|
+
(ASSET_PRECISION as Record<string, any>)[a.id] = a.precision;
|
|
300
|
+
}
|
|
301
|
+
} catch (_) {}
|
|
302
|
+
}
|
|
303
|
+
});
|
|
309
304
|
} catch (e: any) {
|
|
310
305
|
console.warn(` [warn] Name resolution failed: ${e.message}`);
|
|
311
|
-
} finally {
|
|
312
|
-
try { client.disconnect(); } catch (_) {}
|
|
313
306
|
}
|
|
314
307
|
return map;
|
|
315
308
|
}
|