dexbot 1.4.25 → 1.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +28 -0
- package/README.md +6 -2
- package/analysis/README.md +14 -7
- package/analysis/ama_fitting/package.json +1 -1
- package/analysis/analyze_derivatives.ts +1 -1
- package/analysis/bot_key_utils.ts +272 -3
- package/analysis/bot_usage/discover_bot_accounts.ts +64 -9
- package/analysis/bot_usage/kibana_bot_queries.ts +40 -1
- package/analysis/chart_utils.ts +6 -3
- package/analysis/grid_correction_check.ts +56 -20
- package/analysis/resolve_bot_accounts.ts +199 -0
- package/analysis/trade_profitability.ts +36 -8
- package/analysis/tradingview/README.md +25 -0
- package/analysis/tradingview/analyze_tradingview.ts +2 -2
- package/analysis/trend_detection/package.json +1 -1
- package/claw/modules/claw_launcher.ts +5 -4
- package/claw/modules/credit_runtime_adapter.ts +2 -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/dist/analysis/analyze_derivatives.js +1 -1
- package/dist/analysis/bot_key_utils.d.ts +28 -1
- package/dist/analysis/bot_key_utils.d.ts.map +1 -1
- package/dist/analysis/bot_key_utils.js +385 -3
- package/dist/analysis/bot_key_utils.js.map +1 -1
- package/dist/analysis/bot_usage/discover_bot_accounts.js +60 -10
- package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts +45 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.d.ts.map +1 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js +39 -1
- package/dist/analysis/bot_usage/kibana_bot_queries.js.map +1 -1
- package/dist/analysis/chart_utils.d.ts +2 -1
- package/dist/analysis/chart_utils.d.ts.map +1 -1
- package/dist/analysis/chart_utils.js +4 -1
- package/dist/analysis/chart_utils.js.map +1 -1
- package/dist/analysis/grid_correction_check.d.ts.map +1 -1
- package/dist/analysis/grid_correction_check.js +60 -20
- package/dist/analysis/grid_correction_check.js.map +1 -1
- package/dist/analysis/resolve_bot_accounts.d.ts +3 -0
- package/dist/analysis/resolve_bot_accounts.d.ts.map +1 -0
- package/dist/analysis/resolve_bot_accounts.js +216 -0
- package/dist/analysis/resolve_bot_accounts.js.map +1 -0
- package/dist/analysis/trade_profitability.d.ts.map +1 -1
- package/dist/analysis/trade_profitability.js +40 -8
- package/dist/analysis/trade_profitability.js.map +1 -1
- package/dist/analysis/tradingview/analyze_tradingview.js +2 -2
- package/dist/analysis/tradingview/analyze_tradingview.js.map +1 -1
- package/dist/bot.d.ts.map +1 -1
- package/dist/bot.js +2 -1
- package/dist/bot.js.map +1 -1
- package/dist/dexbot.d.ts.map +1 -1
- package/dist/dexbot.js +73 -17
- package/dist/dexbot.js.map +1 -1
- package/dist/market_adapter/ama_signal_runner.js +2 -2
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js +2 -1
- package/dist/market_adapter/inputs/fetch_cex_synthetic_data.js.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts +4 -1
- package/dist/market_adapter/inputs/fetch_lp_data.d.ts.map +1 -1
- package/dist/market_adapter/inputs/fetch_lp_data.js +3 -2
- package/dist/market_adapter/inputs/fetch_lp_data.js.map +1 -1
- package/dist/modules/account_bots.d.ts +42 -1
- package/dist/modules/account_bots.d.ts.map +1 -1
- package/dist/modules/account_bots.js +168 -17
- package/dist/modules/account_bots.js.map +1 -1
- package/dist/modules/bitshares_client.d.ts +4 -1
- package/dist/modules/bitshares_client.d.ts.map +1 -1
- package/dist/modules/bitshares_client.js +12 -4
- package/dist/modules/bitshares_client.js.map +1 -1
- package/dist/modules/bot_settings.d.ts.map +1 -1
- package/dist/modules/bot_settings.js +2 -1
- package/dist/modules/bot_settings.js.map +1 -1
- package/dist/modules/cli_colors.d.ts +39 -0
- package/dist/modules/cli_colors.d.ts.map +1 -0
- package/dist/modules/cli_colors.js +42 -0
- package/dist/modules/cli_colors.js.map +1 -0
- package/dist/modules/constants.d.ts.map +1 -1
- package/dist/modules/constants.js +15 -11
- package/dist/modules/constants.js.map +1 -1
- package/dist/modules/credit_pricing.d.ts +61 -0
- package/dist/modules/credit_pricing.d.ts.map +1 -0
- package/dist/modules/credit_pricing.js +237 -0
- package/dist/modules/credit_pricing.js.map +1 -0
- package/dist/modules/credit_runtime.d.ts +1 -0
- package/dist/modules/credit_runtime.d.ts.map +1 -1
- package/dist/modules/credit_runtime.js +49 -107
- package/dist/modules/credit_runtime.js.map +1 -1
- package/dist/modules/dexbot_class.d.ts +2 -1
- package/dist/modules/dexbot_class.d.ts.map +1 -1
- package/dist/modules/dexbot_class.js +22 -6
- package/dist/modules/dexbot_class.js.map +1 -1
- package/dist/modules/dexbot_cow_runtime.d.ts +19 -2
- package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_cow_runtime.js +249 -26
- package/dist/modules/dexbot_cow_runtime.js.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.d.ts +15 -16
- package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
- package/dist/modules/dexbot_maintenance_runtime.js +69 -42
- package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
- package/dist/modules/fund_registry.d.ts +1 -1
- package/dist/modules/fund_registry.js +1 -1
- package/dist/modules/launcher/adapter_requirement.d.ts +38 -0
- package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -0
- package/dist/modules/launcher/adapter_requirement.js +141 -0
- package/dist/modules/launcher/adapter_requirement.js.map +1 -0
- package/dist/modules/launcher/bot_supervisor.d.ts +2 -2
- package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
- package/dist/modules/launcher/bot_supervisor.js +32 -3
- package/dist/modules/launcher/bot_supervisor.js.map +1 -1
- package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
- package/dist/modules/launcher/launch_modes.js +5 -4
- package/dist/modules/launcher/launch_modes.js.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts +1 -1
- package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
- package/dist/modules/launcher/monolithic_runtime.js +12 -8
- package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
- package/dist/modules/launcher/status_reporting.d.ts +6 -6
- package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
- package/dist/modules/launcher/status_reporting.js +9 -6
- package/dist/modules/launcher/status_reporting.js.map +1 -1
- package/dist/modules/order/export.d.ts +1 -1
- package/dist/modules/order/export.js +1 -1
- package/dist/modules/order/grid.d.ts.map +1 -1
- package/dist/modules/order/grid.js +47 -12
- package/dist/modules/order/grid.js.map +1 -1
- package/dist/modules/order/logger.d.ts +23 -1
- package/dist/modules/order/logger.d.ts.map +1 -1
- package/dist/modules/order/logger.js +60 -17
- package/dist/modules/order/logger.js.map +1 -1
- package/dist/modules/order/manager.d.ts.map +1 -1
- package/dist/modules/order/manager.js +43 -3
- package/dist/modules/order/manager.js.map +1 -1
- package/dist/modules/order/sync_engine.js +1 -1
- package/dist/modules/order/utils/system.d.ts +52 -4
- package/dist/modules/order/utils/system.d.ts.map +1 -1
- package/dist/modules/order/utils/system.js +106 -14
- package/dist/modules/order/utils/system.js.map +1 -1
- package/dist/modules/order/utils/validate.d.ts +1 -1
- package/dist/modules/order/utils/validate.d.ts.map +1 -1
- package/dist/modules/order/utils/validate.js +43 -3
- package/dist/modules/order/utils/validate.js.map +1 -1
- package/dist/modules/utils/chain_logs.d.ts +11 -0
- package/dist/modules/utils/chain_logs.d.ts.map +1 -0
- package/dist/modules/utils/chain_logs.js +23 -0
- package/dist/modules/utils/chain_logs.js.map +1 -0
- package/dist/modules/utils/sanitize_key.d.ts +7 -0
- package/dist/modules/utils/sanitize_key.d.ts.map +1 -1
- package/dist/modules/utils/sanitize_key.js +15 -0
- package/dist/modules/utils/sanitize_key.js.map +1 -1
- package/dist/pm2.d.ts +11 -1
- package/dist/pm2.d.ts.map +1 -1
- package/dist/pm2.js +66 -15
- package/dist/pm2.js.map +1 -1
- package/dist/scripts/analyze-credit.d.ts +4 -0
- package/dist/scripts/analyze-credit.d.ts.map +1 -0
- package/dist/scripts/analyze-credit.js +760 -0
- package/dist/scripts/analyze-credit.js.map +1 -0
- package/dist/scripts/analyze-orders.d.ts +1 -10
- package/dist/scripts/analyze-orders.d.ts.map +1 -1
- package/dist/scripts/analyze-orders.js +11 -34
- package/dist/scripts/analyze-orders.js.map +1 -1
- package/dist/scripts/test-credit-renewal.d.ts.map +1 -1
- package/dist/scripts/test-credit-renewal.js +37 -5
- package/dist/scripts/test-credit-renewal.js.map +1 -1
- package/dist/scripts/tv.d.ts +11 -0
- package/dist/scripts/tv.d.ts.map +1 -0
- package/dist/scripts/tv.js +357 -0
- package/dist/scripts/tv.js.map +1 -0
- package/dist/scripts/update.js +77 -10
- package/dist/scripts/update.js.map +1 -1
- package/dist/unlock.d.ts.map +1 -1
- package/dist/unlock.js +20 -8
- package/dist/unlock.js.map +1 -1
- package/docs/DEXBOT_COMPARISON.md +3 -3
- package/docs/EVOLUTION.md +7 -6
- package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +1 -1
- package/docs/MPA_CREDIT_USAGE.md +3 -1
- package/docs/README.md +2 -2
- package/docs/WORKFLOW.md +3 -1
- package/package.json +2 -1
- package/scripts/README.md +12 -0
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,34 @@
|
|
|
2
2
|
|
|
3
3
|
All notable changes to this project will be documented in this file.
|
|
4
4
|
|
|
5
|
+
## [1.5.0] - 2026-09-05 - Credit Overview & Whitelist-Scoped CR, TradingView Shortcut, Reload Command, Guard & Rotation Hardening
|
|
6
|
+
|
|
7
|
+
### 2026-09-02
|
|
8
|
+
|
|
9
|
+
- **Fix(editor)**: bold Mountain weight in selector display (`modules/account_bots.ts`).
|
|
10
|
+
|
|
11
|
+
### 2026-09-03
|
|
12
|
+
|
|
13
|
+
- **Feat(cli)**: add `dexbot credit` live per-bot debt overview and centralize CLI color palette — new `scripts/analyze-credit.ts` (wired as `dexbot credit [<bot>]`) queries live `get_margin_positions` + paginated `get_credit_deals_by_borrower` per preferredAccount and prints summed debt/collateral per asset per bot (lending-asset filtered, shared accounts tagged, chain chatter muted, inclusive-pagination overlap dropped to avoid double-counting); new `modules/cli_colors.ts` (`CLI_COLORS`) is the single source of truth for every ANSI shade repo-wide with no visual change (`scripts/analyze-credit.ts`, `dexbot.ts`, `modules/cli_colors.ts`, `README.md`, `docs/WORKFLOW.md`, `tests/test_analyze_credit.ts`).
|
|
14
|
+
|
|
15
|
+
### 2026-09-04
|
|
16
|
+
|
|
17
|
+
- **Feat(credit)**: whitelist-scoped CR reporting and shared credit-pricing math — new browser-safe `modules/credit_pricing.ts` holds the canonical math (collateral-map normalization, core/legacy-reversed orientation, offer conversion rates, per-deal and value-weighted average CR, fee helpers) with `credit_runtime.ts` delegating to it (conversion-rate fallback extended offer → pool-derived → universal market price incl. BTS-bridge hops); analyzer per-pair `Curr. CR` and per-bot `Avar. CR` are now scoped to whitelisted `debtPolicy.lending` pairs priced on the current offer, color-coded vs `maxCollateralRatio`, with `Excluded` split by reason and uncapped offer pagination; new `derivePriceViaBridges`/`derivePriceWithBridges` in `modules/order/utils/system.ts` (LP legs bridge only on opt-in) (`modules/credit_pricing.ts`, `modules/credit_runtime.ts`, `modules/order/utils/system.ts`, new `tests/test_credit_pricing.ts`, `tests/test_price_bridges.ts`, `tests/test_credit_conversion_fallback.ts`).
|
|
18
|
+
- **Feat(cli)**: add `dexbot reload` that leaves the credential daemon untouched — mirrors `restart` on every runtime surface (unlock `reload-all` recycles bots + market adapter via SIGUSR2 skipping the daemon re-unlock block, supervisor `reload`/`reload-all`, `pm2 restart` with no `ensureCredentialDaemon`, `reload dexbot-cred` rejected) so bot/adapter recycling no longer costs a password prompt (`unlock.ts`, `modules/launcher/bot_supervisor.ts`, `modules/launcher/launch_modes.ts`, `modules/launcher/monolithic_runtime.ts`, `pm2.ts`, `dexbot.ts`, `README.md`, `docs/WORKFLOW.md`).
|
|
19
|
+
- **Fix(identity)**: enforce case-insensitive bot-name identity across lookups — new `isSameBotName` in `modules/utils/sanitize_key.ts` (sanitized compare with blank-input guard) used by bot_settings, dexbot, unlock, bot, pm2, claw launcher/profiles/credit adapter, maintenance runtime, market-adapter inputs and helper scripts; PM2 stop/delete/restart/reload resolve to the canonical stored name, example pair-derived bot names in comments/help/fixtures replaced with a generic placeholder (published changelog history untouched).
|
|
20
|
+
- **Fix(update)**: run `npm install` only when dependencies actually changed — new `needsDepsInstall(preUpdateHead)` gate in `scripts/update.ts` (install only when node_modules missing/incomplete, manifests changed since pre-update commit, or manifests worktree-dirty; fail-open on git errors) plus a `lockRegenerated` double-install guard (`scripts/update.ts`).
|
|
21
|
+
- **Fix(adapter)**: centralize market-adapter ownership in unlock wrapper on one 1min interval — new `modules/launcher/adapter_requirement.ts` (semantic name:gridPrice fingerprint, corrupt/unreadable bots.json reported not thrown); wrapper exports `DEXBOT_ADAPTER_OWNER=wrapper` so supervised bots skip adapter sync and create no poll timer (wrapper-less modes keep the in-bot fallback); `TIMING.BOTS_CONFIG_POLL_INTERVAL_MS` now 60s shared with the watchdog interval; fixes `''`-fingerprint false-change and corrupt-skip regressions (`modules/launcher/adapter_requirement.ts`, `unlock.ts`, `modules/constants.ts`, `modules/launcher/*`, `modules/dexbot_class.ts`).
|
|
22
|
+
- **Feat(accounts)**: cache bot account IDs in bots.json for offline resolution — byte-preserving in-place `accountId` patch in `analysis/bot_key_utils.ts` (comment/string-aware scanner, re-parse assert, atomic write, full-rewrite fallback) with `persistBotAccountId`/`getStoredBotAccountId`/`findBotKeyByAccountRef`; new `analysis/resolve_bot_accounts.ts` batch backfill CLI (`npm run analysis:resolve-accounts`, `--dry-run`/`--refresh`/`--bot-key`/`--json`); editor stamps IDs via `ensureBotAccountId`, analysis tools and `test-credit-renewal` prefer the cache with `--refresh-account` re-verify; process-wide console floor (`setGlobalConsoleLevel`) silences node-init chatter during supervised lookups (`analysis/bot_key_utils.ts`, `analysis/resolve_bot_accounts.ts`, `modules/account_bots.ts`, `modules/order/logger.ts`, `modules/bitshares_client.ts`, `tests/test_bot_account_id.js`, `tests/test_logger_console_floor.js`).
|
|
23
|
+
- **Fix(guard)**: close stale-pivot race in last-fill guard and harden spread correction — `refreshLastFillPivotFromQueue()` peeks the incoming fill queue before every COW guard check (CREATE, rotation UPDATE, fallback CREATE) and `recordLastFilledPrices()` now runs per chunk so later broadcast chunks gate on the true latest fill; per-action plan-origin stamping keeps spread-correction CREATE bypass while rotation UPDATEs never bypass; `determineOrderSideByFunds()` returns null instead of cross-asset raw-unit compare when priceless with both sides funded; spread check defers on post-gate fills with funds recalc before side decision; batch-level LAST-FILL-GUARD summary + per-batch pivot log lines added (`modules/dexbot_cow_runtime.ts`, `modules/dexbot_class.ts`, `modules/order/grid.ts`, `modules/dexbot_maintenance_runtime.ts`, `modules/order/manager.ts`, new `tests/test_stale_guard_pivot_fixes.js`).
|
|
24
|
+
|
|
25
|
+
### 2026-09-05
|
|
26
|
+
|
|
27
|
+
- **Feat(cli)**: add `dexbot tv` one-step TradingView chart command — `dexbot tv <bot|pool-id|AssetA/AssetB> [--month N]` pulls 1h candles in chunked 1-month windows and delegates rendering to the TradingView exporter, replacing the manual fetch-then-export two-step; fetcher chunking reused via exported `fetchCandlesSequentially`/`outputPath`/`buildFetchWindowsFromRange`, chain-log mute centralized in `modules/utils/chain_logs.ts`, saved-chart paths print as clickable `file://` links via `toFileUrl` (`scripts/tv.ts`, `dexbot.ts`, `market_adapter/inputs/fetch_lp_data.ts`, `modules/utils/chain_logs.ts`, `analysis/chart_utils.ts`, docs).
|
|
28
|
+
- **Fix(cow)**: clamp COW rotation size to booked remainder for partial surplus — new `clampRotationSizeForPartial` in `modules/order/utils/validate.ts` applied at both rotation-pairing sites (`pairRotations`, `optimizeRebalanceActions`) so rotations sourced from PARTIAL surplus target the booked remainder instead of the hole size, ending the plan→skip→restore loop that stranded holes and left non-contiguous rails (`modules/order/utils/validate.ts`, `modules/order/manager.ts`, `modules/order/utils/system.ts`, `tests/test_cow_master_plan.ts` COW-019).
|
|
29
|
+
- **Fix(credit)**: distinguish Avar/Curr CR lines in `dexbot credit` output — display-only: Avar. CR label orange+bold printed first, Curr. CR label grey+bold with trailing comma, values keep health colors (`scripts/analyze-credit.ts`).
|
|
30
|
+
- **Feat(discovery)**: split HIGH discovery tier into DEXBot2 vs DEXBot1-style by op-77 usage — DEXBot2 broadcasts native `limit_order_update` ops (type 77) for COW re-prices while DEXBot1-style only cancels+recreates, so presence of op 77 in-window assigns flavor; display-only split (score formula unchanged) with Updates column in HIGH tier, summary and `--output-json` carrying flavor/updates (`analysis/bot_usage/kibana_bot_queries.ts`, `analysis/bot_usage/discover_bot_accounts.ts`).
|
|
31
|
+
- **Fix(cli)**: forward `--help`/`-h` to the `credit` and `tv` sub-scripts — the CLI intercepted help flags anywhere in argv and always printed generic help, so `dexbot credit --help` / `dexbot tv --help` never reached the scripts that own their usage text; the dispatcher now resolves the requested command first and lets only scripts with offline help handling (`credit`, `tv`) keep the flag, everything else keeps the generic help (`dexbot.ts`).
|
|
32
|
+
|
|
5
33
|
## [1.4.25] - 2026-09-02 - Genesis-Frozen Price-Slot Determinism, Self-Trade & Fill-Guard Hardening, Grid Orphan & Recovery Hardening
|
|
6
34
|
|
|
7
35
|
### 2026-08-29
|
package/README.md
CHANGED
|
@@ -235,12 +235,13 @@ Defaults in [`modules/constants.ts`](modules/constants.ts) are overridable at gl
|
|
|
235
235
|
|
|
236
236
|
## 🎯 Zero-Dependency Process Management
|
|
237
237
|
|
|
238
|
-
`dexbot start` is the recommended production runtime (global install). Repo-root users can run `./unlock` instead. It runs the selected bot set as one monolithic bot process, with the credential daemon and market adapter in separate helper processes. Monolithic start/stop/restart controls apply to the whole runtime, not to individual bots.
|
|
238
|
+
`dexbot start` is the recommended production runtime (global install). Repo-root users can run `./unlock` instead. It runs the selected bot set as one monolithic bot process, with the credential daemon and market adapter in separate helper processes. Monolithic start/stop/reload/restart controls apply to the whole runtime, not to individual bots.
|
|
239
239
|
|
|
240
240
|
```bash
|
|
241
241
|
dexbot start/stop # Stop/start the monolithic runtime
|
|
242
242
|
dexbot start --dryrun # Dry-run (no transactions broadcast)
|
|
243
|
-
dexbot
|
|
243
|
+
dexbot reload # Reload the runtime (leaves credential daemon untouched)
|
|
244
|
+
dexbot restart # Restart the runtime (re-unlocks credential daemon)
|
|
244
245
|
dexbot delete # Shut down and clean up
|
|
245
246
|
```
|
|
246
247
|
|
|
@@ -260,6 +261,8 @@ dexbot enable {all|<bot>} # Enable bot in config
|
|
|
260
261
|
dexbot stat # Runtime status (unlock or PM2)
|
|
261
262
|
dexbot order [<bot>] # Analyze order grids
|
|
262
263
|
dexbot order --export # Export as HTML to analysis/charts/
|
|
264
|
+
dexbot tv <bot|pool|A/B> # TradingView 1h chart with AMA overlay (default: 3 months)
|
|
265
|
+
dexbot credit [<bot>] # Live summed MPA + borrowed-credit positions per asset per bot
|
|
263
266
|
|
|
264
267
|
dexbot update # Update DEXBot2
|
|
265
268
|
dexbot clear # Clear log files
|
|
@@ -272,6 +275,7 @@ PM2 is optional — `dexbot start` is the native solution.
|
|
|
272
275
|
|
|
273
276
|
```bash
|
|
274
277
|
dexbot pm2 [<bot>] # Start with PM2
|
|
278
|
+
dexbot pm2 reload {all|<bot>} # Reload managed apps, dexbot-cred untouched
|
|
275
279
|
dexbot pm2 restart {all|<bot>|dexbot-cred} # Safe restart
|
|
276
280
|
dexbot pm2 stop {all|<bot>} # Stop (via wrapper)
|
|
277
281
|
dexbot pm2 delete {all|<bot>} # Delete (via wrapper)
|
package/analysis/README.md
CHANGED
|
@@ -24,7 +24,7 @@ Tools that inspect DEXBot trading behavior and the market data it operates on. O
|
|
|
24
24
|
| [`grid_correction_check.ts`](#grid-correction-check-grid_correction_checkts) | "Is my grid placing orders monotonically?" — sell/buy price inversion detector | `npm run analysis:grid-check -- --bot-key <bot-key>` |
|
|
25
25
|
| [`analyze_risk_profile.ts`](#risk-profile-analyzer-analyze_risk_profilets) | "How wide should my Safe Range clamps be?" | `node dist/analysis/analyze_risk_profile.js --bot-key <bot-key>` |
|
|
26
26
|
| [`analyze_trade_heatmap.ts`](#trade-heatmap-analyze_trade_heatmapts) | "Where did trade volume cluster vs the AMA?" | `node dist/analysis/analyze_trade_heatmap.js --bot-key <bot-key>` |
|
|
27
|
-
| [`tradingview/analyze_tradingview.ts`](#tradingview-chart-tradingviewanalyze_tradingviewts) | "Just give me a candle chart" | `
|
|
27
|
+
| [`tradingview/analyze_tradingview.ts`](#tradingview-chart-tradingviewanalyze_tradingviewts) | "Just give me a candle chart" | `dexbot tv <bot-key>` |
|
|
28
28
|
| [`analyze_dynamic_weight.ts`](#dynamic-weight-research-analyze_dynamic_weightts) | "Are buy/sell weights tuned for this regime?" | `node dist/analysis/analyze_dynamic_weight.js --bot-key <bot-key>` |
|
|
29
29
|
| [`analyze_volatility.ts`](#volatility-analyze_volatilityts) | "Both weights clipped too hard / not enough?" | `node dist/analysis/analyze_volatility.js --bot-key <bot-key>` |
|
|
30
30
|
| [`analyze_regime.ts`](#supporting-sub-signals) | "Is the trend/chaos gate too aggressive?" | `node dist/analysis/analyze_regime.js --bot-key <bot-key>` |
|
|
@@ -138,8 +138,8 @@ Fetches `fill_order` operations for a BitShares account from Kibana within a spe
|
|
|
138
138
|
# Account by ID, last 7 days (default)
|
|
139
139
|
node dist/analysis/trade_profitability.js 1.2.123456
|
|
140
140
|
|
|
141
|
-
# Account by name
|
|
142
|
-
node dist/analysis/trade_profitability.js "my-account-name" --
|
|
141
|
+
# Account by name (auto-resolved in the background)
|
|
142
|
+
node dist/analysis/trade_profitability.js "my-account-name" --hours 720
|
|
143
143
|
|
|
144
144
|
# Absolute window with asset filter
|
|
145
145
|
node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
@@ -162,7 +162,8 @@ node dist/analysis/trade_profitability.js 1.2.123456 \
|
|
|
162
162
|
| `--end <iso>` | — | End time |
|
|
163
163
|
| `--hours <n>` | `168` (7d) | Lookback hours (alternative to start/end) |
|
|
164
164
|
| `--asset <id>` | all | Filter to one base asset ID |
|
|
165
|
-
| `--lookup` | off |
|
|
165
|
+
| `--lookup` | off | Legacy (no-op): account names always resolve automatically |
|
|
166
|
+
| `--refresh-account` | off | Force re-resolution and update the stored `accountId` |
|
|
166
167
|
| `--node <url>` | first healthy from built-in pool (10 nodes) | BitShares node for account + asset resolution |
|
|
167
168
|
| `--csv <file>` | — | Export chronologically sorted trade list |
|
|
168
169
|
| `--json <file>` | — | Export full analysis with per-pair PnL data |
|
|
@@ -240,7 +241,7 @@ npm run analysis:grid-check -- --bot-key <bot-key> --per-fill --hours 168
|
|
|
240
241
|
npm run analysis:grid-check -- --bot-key <bot-key> --hours 168 --tolerance 0.1
|
|
241
242
|
```
|
|
242
243
|
|
|
243
|
-
Exit code `0` = pass, `2` = violations found, `1` = fatal error. Bot keys resolve via `profiles/bots.json` (`--list-bots` to enumerate); the account defaults to the bot's `preferredAccount` and can be overridden with `--account <1.2.x|name>`.
|
|
244
|
+
Exit code `0` = pass, `2` = violations found, `1` = fatal error. Bot keys resolve via `profiles/bots.json` (`--list-bots` to enumerate); the account defaults to the bot's stored `accountId` when present (no chain lookup — the ID is auto-saved next to `preferredAccount` after the first successful name resolution, re-verified with `--refresh-account`), otherwise `preferredAccount` is resolved on-chain, and can be overridden with `--account <1.2.x|name>`.
|
|
244
245
|
|
|
245
246
|
<details><summary>Options (click to expand)</summary>
|
|
246
247
|
|
|
@@ -250,7 +251,8 @@ Exit code `0` = pass, `2` = violations found, `1` = fatal error. Bot keys resolv
|
|
|
250
251
|
| `--hours <n>` | `168` | Lookback hours from now |
|
|
251
252
|
| `--start <iso>` / `--end <iso>` | — | Absolute time window |
|
|
252
253
|
| `--account <id>` | bot `preferredAccount` | Override account ID or name |
|
|
253
|
-
| `--lookup` | off |
|
|
254
|
+
| `--lookup` | off | Legacy (no-op): account names always resolve via BitShares node when no stored ID exists |
|
|
255
|
+
| `--refresh-account` | off | Force re-resolution of `preferredAccount` and update the stored `accountId` when it changed |
|
|
254
256
|
| `--node <url>` | first built-in node | Node for account/asset resolution |
|
|
255
257
|
| `--per-fill` | off | Check at fill granularity instead of per-order aggregated |
|
|
256
258
|
| `--include-cross-pair` | off | Also check consecutive fills across different pairs |
|
|
@@ -309,7 +311,12 @@ node dist/analysis/analyze_trade_heatmap.js \
|
|
|
309
311
|
Generates a standalone TradingView-style HTML chart with candle OHLC, SMA, AMA, VWMA, and volume panel. See [tradingview/README.md](tradingview/README.md) for full documentation.
|
|
310
312
|
|
|
311
313
|
```bash
|
|
312
|
-
#
|
|
314
|
+
# Recommended one-step: bot, pool, or pair (fetches candles + renders, default 3 months)
|
|
315
|
+
dexbot tv <bot-key>
|
|
316
|
+
dexbot tv 133
|
|
317
|
+
dexbot tv TOKENA/TOKENB
|
|
318
|
+
|
|
319
|
+
# Manual: bot-key (auto-resolves candle file and AMA settings)
|
|
313
320
|
npm run analysis:tradingview -- --source market_adapter --bot-key <bot-key>
|
|
314
321
|
|
|
315
322
|
# From an explicit candle file
|
|
@@ -4,7 +4,8 @@ import fs from 'node:fs';
|
|
|
4
4
|
import path from 'node:path';
|
|
5
5
|
import { PATHS } from '../modules/paths.js';
|
|
6
6
|
import { getStorage } from '../modules/storage/index.js';
|
|
7
|
-
const { readJSON } = getStorage();
|
|
7
|
+
const { readJSON, writeJSON } = getStorage();
|
|
8
|
+
import { parseJsonWithComments } from '../modules/order/utils/system.js';
|
|
8
9
|
import { MARKET_ADAPTER } from '../modules/constants.js';
|
|
9
10
|
import { loadMarketProfiles } from './tradingview/tradingview_uplot_chart_generator.js';
|
|
10
11
|
import { sanitizeKey } from '../modules/utils/sanitize_key.js';
|
|
@@ -22,7 +23,13 @@ function loadBotSettings(filePath = PATHS.PROFILES.BOTS_JSON) {
|
|
|
22
23
|
try {
|
|
23
24
|
return readJSON(filePath);
|
|
24
25
|
} catch (_) {
|
|
25
|
-
|
|
26
|
+
// The bot editor reads/writes through a comment-tolerant parser, so
|
|
27
|
+
// operator comments may legally exist — fall back before giving up.
|
|
28
|
+
try {
|
|
29
|
+
return parseJsonWithComments(fs.readFileSync(filePath, 'utf8'));
|
|
30
|
+
} catch (_) {
|
|
31
|
+
return null;
|
|
32
|
+
}
|
|
26
33
|
}
|
|
27
34
|
}
|
|
28
35
|
|
|
@@ -70,6 +77,268 @@ function loadBotMeta(botKey: any, filePath = PATHS.PROFILES.BOTS_JSON) {
|
|
|
70
77
|
return loose || null;
|
|
71
78
|
}
|
|
72
79
|
|
|
80
|
+
/**
|
|
81
|
+
* Locate the source span of the entryIndex-th object literal inside the
|
|
82
|
+
* top-level "bots" array of a (possibly comment-bearing) JSON document.
|
|
83
|
+
* Strings and // + block comments are skipped while scanning, so braces
|
|
84
|
+
* inside them never disturb the balance count. Returns null when the
|
|
85
|
+
* document shape is unexpected — callers fall back to a full rewrite.
|
|
86
|
+
*/
|
|
87
|
+
function findBotsEntrySpan(raw: string, entryIndex: number): { start: number; end: number } | null {
|
|
88
|
+
const m = /"bots"\s*:\s*\[/.exec(raw);
|
|
89
|
+
if (!m) return null;
|
|
90
|
+
let i = m.index + m[0].length; // just past '['
|
|
91
|
+
const n = raw.length;
|
|
92
|
+
let count = 0;
|
|
93
|
+
const skipWsComments = () => {
|
|
94
|
+
while (i < n) {
|
|
95
|
+
const c = raw[i];
|
|
96
|
+
if (c === ' ' || c === '\t' || c === '\n' || c === '\r') { i++; continue; }
|
|
97
|
+
if (c === '/' && raw[i + 1] === '/') { while (i < n && raw[i] !== '\n') i++; continue; }
|
|
98
|
+
if (c === '/' && raw[i + 1] === '*') { i += 2; while (i < n && !(raw[i] === '*' && raw[i + 1] === '/')) i++; i += 2; continue; }
|
|
99
|
+
break;
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
const skipString = () => {
|
|
103
|
+
const q = raw[i];
|
|
104
|
+
i++;
|
|
105
|
+
while (i < n) {
|
|
106
|
+
const c = raw[i];
|
|
107
|
+
if (c === '\\') { i += 2; continue; }
|
|
108
|
+
if (c === q) { i++; return; }
|
|
109
|
+
i++;
|
|
110
|
+
}
|
|
111
|
+
};
|
|
112
|
+
// Skip one JSON value starting at raw[i]; false on malformed input.
|
|
113
|
+
const skipValue = (): boolean => {
|
|
114
|
+
skipWsComments();
|
|
115
|
+
if (i >= n) return false;
|
|
116
|
+
const c = raw[i];
|
|
117
|
+
if (c === '"' || c === "'") { skipString(); return true; }
|
|
118
|
+
if (c === '{' || c === '[') {
|
|
119
|
+
const open = c;
|
|
120
|
+
const close = c === '{' ? '}' : ']';
|
|
121
|
+
i++;
|
|
122
|
+
let depth = 1;
|
|
123
|
+
while (i < n && depth > 0) {
|
|
124
|
+
const d = raw[i];
|
|
125
|
+
if (d === '"' || d === "'") { skipString(); continue; }
|
|
126
|
+
if (d === '/' && raw[i + 1] === '/') { while (i < n && raw[i] !== '\n') i++; continue; }
|
|
127
|
+
if (d === '/' && raw[i + 1] === '*') { i += 2; while (i < n && !(raw[i] === '*' && raw[i + 1] === '/')) i++; i += 2; continue; }
|
|
128
|
+
if (d === open) depth++;
|
|
129
|
+
else if (d === close) depth--;
|
|
130
|
+
i++;
|
|
131
|
+
}
|
|
132
|
+
return depth === 0;
|
|
133
|
+
}
|
|
134
|
+
while (i < n && raw[i] !== ',' && raw[i] !== ']' && raw[i] !== '}') i++;
|
|
135
|
+
return true;
|
|
136
|
+
};
|
|
137
|
+
for (;;) {
|
|
138
|
+
skipWsComments();
|
|
139
|
+
if (i >= n) return null;
|
|
140
|
+
if (raw[i] === ']') return null; // array ended before entryIndex
|
|
141
|
+
if (raw[i] === '{') {
|
|
142
|
+
const start = i;
|
|
143
|
+
if (!skipValue()) return null;
|
|
144
|
+
if (count === entryIndex) return { start, end: i };
|
|
145
|
+
count++;
|
|
146
|
+
} else if (!skipValue()) {
|
|
147
|
+
return null;
|
|
148
|
+
}
|
|
149
|
+
skipWsComments();
|
|
150
|
+
if (i < n && raw[i] === ',') { i++; continue; }
|
|
151
|
+
if (i < n && raw[i] === ']') return null;
|
|
152
|
+
return null; // malformed separator
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
/**
|
|
157
|
+
* Locate the string value of a top-level key inside one bot-entry source
|
|
158
|
+
* span. Unlike a regex, this scan skips over // and block comments as well
|
|
159
|
+
* as string literals, so a key-looking fragment inside a comment (e.g.
|
|
160
|
+
* `// "accountId": "1.2.9999" (old)`) can never be mistaken for the real
|
|
161
|
+
* key. Returns the span of the quoted value (including quotes), or null
|
|
162
|
+
* when the key is absent or its value is not a string.
|
|
163
|
+
*/
|
|
164
|
+
function findEntryKeyValueSpan(entrySrc: string, key: string): { valueStart: number; valueEnd: number } | null {
|
|
165
|
+
const n = entrySrc.length;
|
|
166
|
+
const isWs = (c: string) => c === ' ' || c === '\t' || c === '\n' || c === '\r';
|
|
167
|
+
let i = 0;
|
|
168
|
+
let depth = 0;
|
|
169
|
+
while (i < n) {
|
|
170
|
+
const c = entrySrc[i];
|
|
171
|
+
if (isWs(c)) { i++; continue; }
|
|
172
|
+
if (c === '/' && entrySrc[i + 1] === '/') { while (i < n && entrySrc[i] !== '\n') i++; continue; }
|
|
173
|
+
if (c === '/' && entrySrc[i + 1] === '*') { i += 2; while (i < n && !(entrySrc[i] === '*' && entrySrc[i + 1] === '/')) i++; i += 2; continue; }
|
|
174
|
+
if (c === '{' || c === '[') { depth++; i++; continue; }
|
|
175
|
+
if (c === '}' || c === ']') { depth--; i++; continue; }
|
|
176
|
+
if (c !== '"') { i++; continue; }
|
|
177
|
+
// String literal: read it (honoring backslash escapes).
|
|
178
|
+
let j = i + 1;
|
|
179
|
+
let str = '';
|
|
180
|
+
while (j < n) {
|
|
181
|
+
const d = entrySrc[j];
|
|
182
|
+
if (d === '\\') { str += entrySrc.slice(j, j + 2); j += 2; continue; }
|
|
183
|
+
if (d === '"') break;
|
|
184
|
+
str += d; j++;
|
|
185
|
+
}
|
|
186
|
+
if (j >= n) return null;
|
|
187
|
+
const closingQuote = j;
|
|
188
|
+
if (depth !== 1 || str !== key) { i = closingQuote + 1; continue; }
|
|
189
|
+
// Candidate key at top level: expect `:` then a quoted value.
|
|
190
|
+
let k = closingQuote + 1;
|
|
191
|
+
while (k < n && isWs(entrySrc[k])) k++;
|
|
192
|
+
if (entrySrc[k] !== ':') { i = closingQuote + 1; continue; }
|
|
193
|
+
k++;
|
|
194
|
+
while (k < n && isWs(entrySrc[k])) k++;
|
|
195
|
+
if (entrySrc[k] !== '"') { i = closingQuote + 1; continue; }
|
|
196
|
+
let m = k + 1;
|
|
197
|
+
while (m < n) {
|
|
198
|
+
const e = entrySrc[m];
|
|
199
|
+
if (e === '\\') { m += 2; continue; }
|
|
200
|
+
if (e === '"') return { valueStart: k, valueEnd: m + 1 };
|
|
201
|
+
m++;
|
|
202
|
+
}
|
|
203
|
+
return null;
|
|
204
|
+
}
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
|
|
208
|
+
/**
|
|
209
|
+
* Byte-preserving accountId update: replaces the value in place, or inserts
|
|
210
|
+
* `"accountId"` right after `"preferredAccount"` keeping the line indent, so
|
|
211
|
+
* comments, key order and formatting elsewhere in the file survive. The
|
|
212
|
+
* patched document is re-parsed (comments allowed) and the value asserted
|
|
213
|
+
* before the atomic tmp+rename write. Returns false on any uncertainty —
|
|
214
|
+
* callers fall back to a full rewrite.
|
|
215
|
+
*/
|
|
216
|
+
function patchAccountIdInRaw(filePath: string, entryIndex: number, accountId: string): boolean {
|
|
217
|
+
let raw: string;
|
|
218
|
+
try {
|
|
219
|
+
raw = fs.readFileSync(filePath, 'utf8');
|
|
220
|
+
} catch (_) {
|
|
221
|
+
return false;
|
|
222
|
+
}
|
|
223
|
+
const span = findBotsEntrySpan(raw, entryIndex);
|
|
224
|
+
if (!span) return false;
|
|
225
|
+
const entrySrc = raw.slice(span.start, span.end);
|
|
226
|
+
let patchedEntry: string | null = null;
|
|
227
|
+
const idSpan = findEntryKeyValueSpan(entrySrc, 'accountId');
|
|
228
|
+
if (idSpan) {
|
|
229
|
+
patchedEntry = entrySrc.slice(0, idSpan.valueStart)
|
|
230
|
+
+ `"${accountId}"`
|
|
231
|
+
+ entrySrc.slice(idSpan.valueEnd);
|
|
232
|
+
} else {
|
|
233
|
+
const prefSpan = findEntryKeyValueSpan(entrySrc, 'preferredAccount');
|
|
234
|
+
if (!prefSpan) return false;
|
|
235
|
+
const lineStart = entrySrc.lastIndexOf('\n', prefSpan.valueEnd) + 1;
|
|
236
|
+
const indentMatch = /^[ \t]*/.exec(entrySrc.slice(lineStart));
|
|
237
|
+
const indent = indentMatch ? indentMatch[0] : ' ';
|
|
238
|
+
patchedEntry = entrySrc.slice(0, prefSpan.valueEnd)
|
|
239
|
+
+ `,\n${indent}"accountId": "${accountId}"`
|
|
240
|
+
+ entrySrc.slice(prefSpan.valueEnd);
|
|
241
|
+
}
|
|
242
|
+
if (!patchedEntry || patchedEntry === entrySrc) return false;
|
|
243
|
+
const patched = raw.slice(0, span.start) + patchedEntry + raw.slice(span.end);
|
|
244
|
+
try {
|
|
245
|
+
const reparsed = parseJsonWithComments(patched);
|
|
246
|
+
const checkEntries = Array.isArray(reparsed?.bots) ? reparsed.bots : [];
|
|
247
|
+
if (checkEntries[entryIndex]?.accountId !== accountId) return false;
|
|
248
|
+
} catch (_) {
|
|
249
|
+
return false;
|
|
250
|
+
}
|
|
251
|
+
const tmpPath = `${filePath}.tmp-persist-${process.pid}-${Date.now()}`;
|
|
252
|
+
try {
|
|
253
|
+
fs.writeFileSync(tmpPath, patched, 'utf8');
|
|
254
|
+
fs.renameSync(tmpPath, filePath);
|
|
255
|
+
return true;
|
|
256
|
+
} catch (_) {
|
|
257
|
+
try { fs.unlinkSync(tmpPath); } catch (_) { /* best-effort cleanup */ }
|
|
258
|
+
return false;
|
|
259
|
+
}
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
/**
|
|
263
|
+
* Persist a resolved account name → ID mapping onto the bot entry in
|
|
264
|
+
* profiles/bots.json (`accountId` next to `preferredAccount`), so analysis
|
|
265
|
+
* tools can skip the chain lookup on subsequent runs. No-op when the stored
|
|
266
|
+
* value already matches. Prefers a byte-preserving textual patch (comments,
|
|
267
|
+
* key order and formatting survive); falls back to a full rewrite using the
|
|
268
|
+
* same atomic writeJSON serialization the bot editor uses. Returns true when
|
|
269
|
+
* the file was updated.
|
|
270
|
+
*/
|
|
271
|
+
function persistBotAccountId(botKey: any, accountId: string, filePath = PATHS.PROFILES.BOTS_JSON): boolean {
|
|
272
|
+
if (!botKey || !/^1\.2\.\d+$/.test(String(accountId))) return false;
|
|
273
|
+
let settings: any = null;
|
|
274
|
+
try {
|
|
275
|
+
settings = loadBotSettings(filePath);
|
|
276
|
+
} catch (_) {
|
|
277
|
+
return false;
|
|
278
|
+
}
|
|
279
|
+
const entries = Array.isArray(settings?.bots) ? settings.bots : [];
|
|
280
|
+
const normalizedKey = String(botKey).toLowerCase();
|
|
281
|
+
const index = entries.findIndex((bot: any, i: number) => computeBotKey(bot, i) === normalizedKey);
|
|
282
|
+
if (index < 0 || entries[index]?.accountId === accountId) return false;
|
|
283
|
+
try {
|
|
284
|
+
if (patchAccountIdInRaw(filePath, index, String(accountId))) return true;
|
|
285
|
+
} catch (_) {
|
|
286
|
+
// Fall through to the full rewrite below.
|
|
287
|
+
}
|
|
288
|
+
entries[index].accountId = String(accountId);
|
|
289
|
+
try {
|
|
290
|
+
writeJSON(filePath, settings);
|
|
291
|
+
return true;
|
|
292
|
+
} catch (_) {
|
|
293
|
+
return false;
|
|
294
|
+
}
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
/**
|
|
298
|
+
* Return the stored chain account ID for a bot key, or null when none is
|
|
299
|
+
* stamped yet. When expectedRef is given, the stored ID is only returned if
|
|
300
|
+
* the entry's current preferredAccount still matches it — a rename must
|
|
301
|
+
* never silently reuse a stale ID.
|
|
302
|
+
*/
|
|
303
|
+
function getStoredBotAccountId(botKey: any, expectedRef?: string | null, filePath = PATHS.PROFILES.BOTS_JSON): string | null {
|
|
304
|
+
if (!botKey) return null;
|
|
305
|
+
let meta: any = null;
|
|
306
|
+
try {
|
|
307
|
+
meta = loadBotMeta(botKey, filePath);
|
|
308
|
+
} catch (_) {
|
|
309
|
+
return null;
|
|
310
|
+
}
|
|
311
|
+
const stored = meta && /^1\.2\.\d+$/.test(String(meta.accountId ?? '')) ? String(meta.accountId) : null;
|
|
312
|
+
if (!stored) return null;
|
|
313
|
+
if (expectedRef != null && String(meta.preferredAccount ?? '').toLowerCase() !== String(expectedRef).toLowerCase()) return null;
|
|
314
|
+
return stored;
|
|
315
|
+
}
|
|
316
|
+
|
|
317
|
+
/**
|
|
318
|
+
* Find the bot entry whose preferredAccount matches a raw account reference
|
|
319
|
+
* (case-insensitive). Used by tools that accept a bare account name to locate
|
|
320
|
+
* the stored accountId and the persist target. Returns null for 1.2.x input
|
|
321
|
+
* or when no bot claims the name.
|
|
322
|
+
*/
|
|
323
|
+
function findBotKeyByAccountRef(ref: any, filePath = PATHS.PROFILES.BOTS_JSON): { botKey: string; meta: any } | null {
|
|
324
|
+
if (!ref || /^1\.2\.\d+$/.test(String(ref))) return null;
|
|
325
|
+
let settings: any = null;
|
|
326
|
+
try {
|
|
327
|
+
settings = loadBotSettings(filePath);
|
|
328
|
+
} catch (_) {
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
const entries = Array.isArray(settings?.bots) ? settings.bots : [];
|
|
332
|
+
const want = String(ref).toLowerCase();
|
|
333
|
+
for (let i = 0; i < entries.length; i++) {
|
|
334
|
+
const entry = entries[i];
|
|
335
|
+
if (entry && String(entry.preferredAccount ?? '').toLowerCase() === want) {
|
|
336
|
+
return { botKey: computeBotKey(entry, i), meta: entry };
|
|
337
|
+
}
|
|
338
|
+
}
|
|
339
|
+
return null;
|
|
340
|
+
}
|
|
341
|
+
|
|
73
342
|
function resolveAmaConfig(botKey: any) {
|
|
74
343
|
const botMeta = loadBotMeta(botKey);
|
|
75
344
|
// Unknown bot key: fall back to the global erSmoothPeriod default (same
|
|
@@ -153,5 +422,5 @@ function resolveAmaKey(botKey: any) {
|
|
|
153
422
|
return 'AMA3';
|
|
154
423
|
}
|
|
155
424
|
|
|
156
|
-
export { loadBotSettings, sanitizeKey, computeBotKey, resolveBotKey, candleFileForBot, resolveCandleFile, loadBotMeta, resolveAmaConfig, resolveAmaKey }
|
|
425
|
+
export { loadBotSettings, sanitizeKey, computeBotKey, resolveBotKey, candleFileForBot, resolveCandleFile, loadBotMeta, getStoredBotAccountId, findBotKeyByAccountRef, persistBotAccountId, resolveAmaConfig, resolveAmaKey }
|
|
157
426
|
|
|
@@ -18,12 +18,16 @@ const { writeJSON } = getStorage();
|
|
|
18
18
|
* Q1 — top 100 accounts by limit_order_create count
|
|
19
19
|
* Q2 — top 100 accounts by limit_order_cancel count
|
|
20
20
|
* Q3 — top 100 accounts by fill_order count
|
|
21
|
+
* Q4 — top 200 accounts by limit_order_update count (op 77, DEXBot2 fingerprint)
|
|
21
22
|
* 2. Merge & pre-filter:
|
|
22
23
|
* creates ≥ MIN_CREATES (grid analysis determines DEXBot candidacy)
|
|
23
24
|
* 3. Grid analysis (parallel, batches of 5):
|
|
24
25
|
* Fetch 200 raw orders per candidate → per-session geometric spacing test
|
|
25
26
|
* 4. BitShares: resolve account IDs → names (batch db.get_objects call)
|
|
26
|
-
* 5. Rank by DEX score and print table
|
|
27
|
+
* 5. Rank by DEX score and print table.
|
|
28
|
+
* HIGH tier (80+) is split by op-77 usage:
|
|
29
|
+
* updates ≥ 1 → DEXBot2 (native limit_order_update in-place re-price)
|
|
30
|
+
* updates = 0 → DEXBot1-style (cancel-only, cancel + recreate)
|
|
27
31
|
*
|
|
28
32
|
* Usage:
|
|
29
33
|
* node dist/analysis/bot_usage/discover_bot_accounts.js
|
|
@@ -51,6 +55,7 @@ import {
|
|
|
51
55
|
buildTopSellerAccountsQuery,
|
|
52
56
|
buildTopCancellerAccountsQuery,
|
|
53
57
|
buildTopFilledAccountsQuery,
|
|
58
|
+
buildTopUpdaterAccountsQuery,
|
|
54
59
|
DEFAULT_CONFIG,
|
|
55
60
|
} from './kibana_bot_queries.js';
|
|
56
61
|
import { NODE_MANAGEMENT } from '../../modules/constants.js';
|
|
@@ -61,6 +66,7 @@ interface AccountCounts {
|
|
|
61
66
|
creates: number;
|
|
62
67
|
cancels: number;
|
|
63
68
|
fills: number;
|
|
69
|
+
updates: number;
|
|
64
70
|
}
|
|
65
71
|
|
|
66
72
|
interface CandidateInfo {
|
|
@@ -68,6 +74,8 @@ interface CandidateInfo {
|
|
|
68
74
|
creates: number;
|
|
69
75
|
cancels: number;
|
|
70
76
|
fills: number;
|
|
77
|
+
updates: number;
|
|
78
|
+
flavor: 'DEXBot2' | 'DEXBot1' | '';
|
|
71
79
|
cancelRatio: number;
|
|
72
80
|
fillRate: number;
|
|
73
81
|
gridScore: number;
|
|
@@ -323,18 +331,21 @@ async function run() {
|
|
|
323
331
|
|
|
324
332
|
console.log('Phase 1: Querying Kibana for top active accounts...');
|
|
325
333
|
|
|
326
|
-
const [createRes, cancelRes, fillRes]: any[] = await Promise.all([
|
|
334
|
+
const [createRes, cancelRes, fillRes, updateRes]: any[] = await Promise.all([
|
|
327
335
|
withRetry(() => kibanaSearch(KIBANA_CFG, buildTopSellerAccountsQuery(lookbackH, 200, opts.minCreates)),
|
|
328
336
|
'top-seller query'),
|
|
329
337
|
withRetry(() => kibanaSearch(KIBANA_CFG, buildTopCancellerAccountsQuery(lookbackH, 200, 5)),
|
|
330
338
|
'top-canceller query'),
|
|
331
339
|
withRetry(() => kibanaSearch(KIBANA_CFG, buildTopFilledAccountsQuery(lookbackH, 200, 3)),
|
|
332
340
|
'top-fills query'),
|
|
341
|
+
withRetry(() => kibanaSearch(KIBANA_CFG, buildTopUpdaterAccountsQuery(lookbackH, 200, 1)),
|
|
342
|
+
'top-updater query'),
|
|
333
343
|
]);
|
|
334
344
|
|
|
335
345
|
const createBuckets = createRes?.aggregations?.by_account?.buckets ?? [];
|
|
336
346
|
const cancelBuckets = cancelRes?.aggregations?.by_account?.buckets ?? [];
|
|
337
347
|
const fillBuckets = fillRes?.aggregations?.by_account?.buckets ?? [];
|
|
348
|
+
const updateBuckets = updateRes?.aggregations?.by_account?.buckets ?? [];
|
|
338
349
|
|
|
339
350
|
if (opts.verbose) {
|
|
340
351
|
console.log(` [verbose] createRes keys: ${Object.keys(createRes ?? {}).join(', ')}`);
|
|
@@ -344,6 +355,7 @@ async function run() {
|
|
|
344
355
|
console.log(` Creates: ${createBuckets.length} accounts with ≥${opts.minCreates} creates`);
|
|
345
356
|
console.log(` Cancels: ${cancelBuckets.length} accounts with ≥5 cancels`);
|
|
346
357
|
console.log(` Fills: ${fillBuckets.length} accounts with ≥3 fills`);
|
|
358
|
+
console.log(` Updates: ${updateBuckets.length} accounts with ≥1 limit_order_update (op 77)`);
|
|
347
359
|
|
|
348
360
|
if (createBuckets.length === 0) {
|
|
349
361
|
console.log('');
|
|
@@ -360,17 +372,23 @@ async function run() {
|
|
|
360
372
|
console.log('\nPhase 2: Merging and filtering candidates...');
|
|
361
373
|
|
|
362
374
|
const accounts: Record<string, AccountCounts> = {};
|
|
363
|
-
for (const b of createBuckets) accounts[b.key] = { creates: b.doc_count, cancels: 0, fills: 0 };
|
|
375
|
+
for (const b of createBuckets) accounts[b.key] = { creates: b.doc_count, cancels: 0, fills: 0, updates: 0 };
|
|
364
376
|
for (const b of cancelBuckets) {
|
|
365
|
-
if (!accounts[b.key]) accounts[b.key] = { creates: 0, cancels: 0, fills: 0 };
|
|
377
|
+
if (!accounts[b.key]) accounts[b.key] = { creates: 0, cancels: 0, fills: 0, updates: 0 };
|
|
366
378
|
accounts[b.key].cancels = b.doc_count;
|
|
367
379
|
}
|
|
368
380
|
for (const b of fillBuckets) {
|
|
369
|
-
if (!accounts[b.key]) accounts[b.key] = { creates: 0, cancels: 0, fills: 0 };
|
|
381
|
+
if (!accounts[b.key]) accounts[b.key] = { creates: 0, cancels: 0, fills: 0, updates: 0 };
|
|
370
382
|
accounts[b.key].fills = b.doc_count;
|
|
371
383
|
}
|
|
384
|
+
for (const b of updateBuckets) {
|
|
385
|
+
if (!accounts[b.key]) accounts[b.key] = { creates: 0, cancels: 0, fills: 0, updates: 0 };
|
|
386
|
+
accounts[b.key].updates = b.doc_count;
|
|
387
|
+
}
|
|
372
388
|
|
|
373
389
|
// Pre-filter: must have creates ≥ minCreates (grid analysis determines DEXBot candidacy)
|
|
390
|
+
// Flavor: any limit_order_update (op 77) in-window → DEXBot2 (native in-place
|
|
391
|
+
// re-price); zero updates → DEXBot1-style (cancel-only: cancel + recreate).
|
|
374
392
|
const candidates: CandidateInfo[] = (Object.entries(accounts) as [string, AccountCounts][])
|
|
375
393
|
.filter(([, s]) => s.creates >= opts.minCreates)
|
|
376
394
|
.map(([id, s]) => ({
|
|
@@ -378,6 +396,8 @@ async function run() {
|
|
|
378
396
|
creates: s.creates,
|
|
379
397
|
cancels: s.cancels,
|
|
380
398
|
fills: s.fills,
|
|
399
|
+
updates: s.updates,
|
|
400
|
+
flavor: (s.updates >= 1 ? 'DEXBot2' : 'DEXBot1') as 'DEXBot2' | 'DEXBot1',
|
|
381
401
|
cancelRatio: s.cancels / Math.max(s.creates, 1),
|
|
382
402
|
fillRate: s.creates > 0 ? (s.fills / s.creates * 100) : 0,
|
|
383
403
|
} as CandidateInfo))
|
|
@@ -492,9 +512,41 @@ async function run() {
|
|
|
492
512
|
console.log('════════════════════════════════════════════════════════════════════════════════════');
|
|
493
513
|
console.log('');
|
|
494
514
|
|
|
495
|
-
// Print by DEX score tier
|
|
515
|
+
// Print by DEX score tier. HIGH tier (80+) splits by update-order usage:
|
|
516
|
+
// DEXBot2 — broadcast limit_order_update (op 77) in-window (in-place re-price)
|
|
517
|
+
// DEXBot1-style — cancel-only, no op 77 (cancel + recreate)
|
|
518
|
+
const highV2 = results.filter(r => r.dexScore >= 80 && r.updates >= 1);
|
|
519
|
+
const highV1 = results.filter(r => r.dexScore >= 80 && r.updates < 1);
|
|
520
|
+
|
|
521
|
+
const hdrHigh = ' # Name ID Creates Fills Cancel Fill% C/C MaxBatch Incr% Grid DEX Updates';
|
|
522
|
+
function printHighGroup(label: string, rows: CandidateInfo[]) {
|
|
523
|
+
if (!rows.length) return;
|
|
524
|
+
console.log(` ── ${label}`);
|
|
525
|
+
console.log('');
|
|
526
|
+
console.log(hdrHigh);
|
|
527
|
+
console.log(' ' + '─'.repeat(hdrHigh.length - 1));
|
|
528
|
+
for (const r of rows) {
|
|
529
|
+
const rank = String(results.indexOf(r) + 1).padStart(2);
|
|
530
|
+
const name = r.name.padEnd(22).slice(0, 22);
|
|
531
|
+
const id = r.id.padEnd(14);
|
|
532
|
+
const creates = String(r.creates).padStart(7);
|
|
533
|
+
const fills = String(r.fills).padStart(6);
|
|
534
|
+
const cancels = String(r.cancels).padStart(7);
|
|
535
|
+
const fr = (r.fillRate.toFixed(1) + '%').padStart(5);
|
|
536
|
+
const cr = r.cancelRatio.toFixed(2).padStart(4);
|
|
537
|
+
const batch = String(r.maxBatch || '-').padStart(9);
|
|
538
|
+
const inc = (r.impliedInc != null ? r.impliedInc.toFixed(2) + '%' : 'n/a').padStart(6);
|
|
539
|
+
const grid = String(r.gridScore).padStart(5);
|
|
540
|
+
const dex = String(r.dexScore).padStart(4);
|
|
541
|
+
const upd = String(r.updates).padStart(7);
|
|
542
|
+
console.log(` ${rank} ${name} ${id} ${creates} ${fills} ${cancels} ${fr} ${cr} ${batch} ${inc} ${grid} ${dex} ${upd}`);
|
|
543
|
+
}
|
|
544
|
+
console.log('');
|
|
545
|
+
}
|
|
546
|
+
printHighGroup('HIGH confidence — DEXBot2 (80+, uses limit_order_update op 77)', highV2);
|
|
547
|
+
printHighGroup('HIGH confidence — DEXBot1-style (80+, cancel-only, no op 77)', highV1);
|
|
548
|
+
|
|
496
549
|
const tiers = [
|
|
497
|
-
{ label: 'HIGH confidence (80+) — almost certainly DEXBot/DEXBot2', min: 80, max: 101 },
|
|
498
550
|
{ label: 'MEDIUM confidence (50–79) — likely a grid bot', min: 50, max: 80 },
|
|
499
551
|
{ label: 'LOW confidence (25–49) — some automation detected', min: 25, max: 50 },
|
|
500
552
|
{ label: 'WEAK signal (<25) — create/cancel pattern, no grid', min: 0, max: 25 },
|
|
@@ -531,13 +583,13 @@ async function run() {
|
|
|
531
583
|
|
|
532
584
|
// ── Summary ───────────────────────────────────────────────────────────────
|
|
533
585
|
|
|
534
|
-
const high =
|
|
586
|
+
const high = highV2.length + highV1.length;
|
|
535
587
|
const medium = results.filter(r => r.dexScore >= 50 && r.dexScore < 80).length;
|
|
536
588
|
const low = results.filter(r => r.dexScore >= 25 && r.dexScore < 50).length;
|
|
537
589
|
|
|
538
590
|
console.log('════════════════════════════════════════════════════════════════════════════════════');
|
|
539
591
|
console.log(` Total candidates scanned: ${results.length}`);
|
|
540
|
-
console.log(` HIGH (80+): ${high} accounts —
|
|
592
|
+
console.log(` HIGH (80+): ${high} accounts — DEXBot2: ${highV2.length} (op 77 updates) / DEXBot1-style: ${highV1.length} (cancel-only)`);
|
|
541
593
|
console.log(` MEDIUM (50+): ${medium} accounts — likely grid bots`);
|
|
542
594
|
console.log(` LOW (25+): ${low} accounts — weak signal`);
|
|
543
595
|
console.log('');
|
|
@@ -545,6 +597,7 @@ async function run() {
|
|
|
545
597
|
console.log(' C/C = cancel/create ratio (grid bots: ~1.0)');
|
|
546
598
|
console.log(' Incr% = implied grid increment from price spacing');
|
|
547
599
|
console.log(' Grid = grid quality 0-100 | DEX = DEXBot confidence 0-100');
|
|
600
|
+
console.log(' Updates = limit_order_update (op 77) count — DEXBot2 only; 0 = cancel-only (DEXBot1-style)');
|
|
548
601
|
console.log('');
|
|
549
602
|
|
|
550
603
|
// ── Export JSON ────────────────────────────────────────────────────────────
|
|
@@ -553,9 +606,11 @@ async function run() {
|
|
|
553
606
|
const exportData = results.map(r => ({
|
|
554
607
|
id: r.id,
|
|
555
608
|
name: r.name,
|
|
609
|
+
flavor: r.flavor,
|
|
556
610
|
creates: r.creates,
|
|
557
611
|
fills: r.fills,
|
|
558
612
|
cancels: r.cancels,
|
|
613
|
+
updates: r.updates,
|
|
559
614
|
fillPct: r.fillRate,
|
|
560
615
|
cancelRatio: r.cancelRatio,
|
|
561
616
|
maxBatch: r.maxBatch,
|