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.
Files changed (212) hide show
  1. package/CHANGELOG.md +53 -3
  2. package/README.md +11 -7
  3. package/analysis/README.md +20 -14
  4. package/analysis/account_resolver.ts +171 -0
  5. package/analysis/ama_fitting/analyze_lambda_vs_slow.ts +2 -5
  6. package/analysis/ama_fitting/package.json +1 -1
  7. package/analysis/bot_usage/discover_bot_accounts.ts +20 -27
  8. package/analysis/chain_pool.ts +47 -0
  9. package/analysis/chart_utils.ts +33 -20
  10. package/analysis/derivative_chart_generator.ts +2 -3
  11. package/analysis/fills_source.ts +234 -0
  12. package/analysis/grid_correction_check.ts +104 -244
  13. package/analysis/results/ama_sweep_results_lp_pool_133_1h.json +2455 -0
  14. package/analysis/results/bot_fitting_results_lp_pool_133_1h.json +218 -0
  15. package/analysis/trade_profitability.ts +169 -304
  16. package/analysis/tradingview/README.md +2 -2
  17. package/analysis/tradingview/h-bts_tradingview.html +1570 -0
  18. package/analysis/tradingview/t-bts_tradingview.html +1570 -0
  19. package/analysis/tradingview/tradingview_uplot_chart_generator.ts +3 -4
  20. package/analysis/trend_detection/dynamic_weight_chart_generator.ts +2 -3
  21. package/analysis/trend_detection/kalman_chart_generator.ts +2 -3
  22. package/analysis/trend_detection/package.json +1 -1
  23. package/analysis/trend_detection/regime_chart_generator.ts +2 -3
  24. package/analysis/trend_detection/volatility_chart_generator.ts +2 -3
  25. package/claw/package.json +1 -1
  26. package/claw/runtimes/openclaw-plugin/openclaw.plugin.json +1 -1
  27. package/claw/runtimes/openclaw-plugin/package.json +1 -1
  28. package/claw/tests/test_claw_mcp_transport.ts +2 -2
  29. package/dist/analysis/account_resolver.d.ts +43 -0
  30. package/dist/analysis/account_resolver.d.ts.map +1 -0
  31. package/dist/analysis/account_resolver.js +137 -0
  32. package/dist/analysis/account_resolver.js.map +1 -0
  33. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.d.ts.map +1 -1
  34. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js +2 -4
  35. package/dist/analysis/ama_fitting/analyze_lambda_vs_slow.js.map +1 -1
  36. package/dist/analysis/bot_usage/discover_bot_accounts.js +20 -28
  37. package/dist/analysis/bot_usage/discover_bot_accounts.js.map +1 -1
  38. package/dist/analysis/chain_pool.d.ts +10 -0
  39. package/dist/analysis/chain_pool.d.ts.map +1 -0
  40. package/dist/analysis/chain_pool.js +47 -0
  41. package/dist/analysis/chain_pool.js.map +1 -0
  42. package/dist/analysis/chart_utils.d.ts +14 -1
  43. package/dist/analysis/chart_utils.d.ts.map +1 -1
  44. package/dist/analysis/chart_utils.js +32 -22
  45. package/dist/analysis/chart_utils.js.map +1 -1
  46. package/dist/analysis/derivative_chart_generator.d.ts.map +1 -1
  47. package/dist/analysis/derivative_chart_generator.js +2 -3
  48. package/dist/analysis/derivative_chart_generator.js.map +1 -1
  49. package/dist/analysis/fills_source.d.ts +79 -0
  50. package/dist/analysis/fills_source.d.ts.map +1 -0
  51. package/dist/analysis/fills_source.js +186 -0
  52. package/dist/analysis/fills_source.js.map +1 -0
  53. package/dist/analysis/grid_correction_check.d.ts +4 -20
  54. package/dist/analysis/grid_correction_check.d.ts.map +1 -1
  55. package/dist/analysis/grid_correction_check.js +95 -248
  56. package/dist/analysis/grid_correction_check.js.map +1 -1
  57. package/dist/analysis/trade_profitability.d.ts +18 -19
  58. package/dist/analysis/trade_profitability.d.ts.map +1 -1
  59. package/dist/analysis/trade_profitability.js +136 -280
  60. package/dist/analysis/trade_profitability.js.map +1 -1
  61. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.d.ts.map +1 -1
  62. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js +3 -4
  63. package/dist/analysis/tradingview/tradingview_uplot_chart_generator.js.map +1 -1
  64. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.d.ts.map +1 -1
  65. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js +2 -3
  66. package/dist/analysis/trend_detection/dynamic_weight_chart_generator.js.map +1 -1
  67. package/dist/analysis/trend_detection/kalman_chart_generator.d.ts.map +1 -1
  68. package/dist/analysis/trend_detection/kalman_chart_generator.js +2 -3
  69. package/dist/analysis/trend_detection/kalman_chart_generator.js.map +1 -1
  70. package/dist/analysis/trend_detection/regime_chart_generator.d.ts.map +1 -1
  71. package/dist/analysis/trend_detection/regime_chart_generator.js +2 -3
  72. package/dist/analysis/trend_detection/regime_chart_generator.js.map +1 -1
  73. package/dist/analysis/trend_detection/volatility_chart_generator.d.ts.map +1 -1
  74. package/dist/analysis/trend_detection/volatility_chart_generator.js +2 -3
  75. package/dist/analysis/trend_detection/volatility_chart_generator.js.map +1 -1
  76. package/dist/market_adapter/lp_chart_core.d.ts.map +1 -1
  77. package/dist/market_adapter/lp_chart_core.js +2 -3
  78. package/dist/market_adapter/lp_chart_core.js.map +1 -1
  79. package/dist/modules/account_bots.d.ts +1 -18
  80. package/dist/modules/account_bots.d.ts.map +1 -1
  81. package/dist/modules/account_bots.js +1 -1
  82. package/dist/modules/account_bots.js.map +1 -1
  83. package/dist/modules/bitshares-native/serial/operations.d.ts +1 -84
  84. package/dist/modules/bitshares-native/serial/operations.d.ts.map +1 -1
  85. package/dist/modules/bitshares-native/serial/operations.js +1 -38
  86. package/dist/modules/bitshares-native/serial/operations.js.map +1 -1
  87. package/dist/modules/cli_colors.d.ts +0 -1
  88. package/dist/modules/cli_colors.d.ts.map +1 -1
  89. package/dist/modules/constants.js +5 -5
  90. package/dist/modules/constants.js.map +1 -1
  91. package/dist/modules/credential_policy.d.ts +1 -25
  92. package/dist/modules/credential_policy.d.ts.map +1 -1
  93. package/dist/modules/credential_policy.js +1 -1
  94. package/dist/modules/credential_policy.js.map +1 -1
  95. package/dist/modules/credential_runtime.d.ts +1 -6
  96. package/dist/modules/credential_runtime.d.ts.map +1 -1
  97. package/dist/modules/credential_runtime.js +1 -1
  98. package/dist/modules/credential_runtime.js.map +1 -1
  99. package/dist/modules/credential_session_cache.d.ts +1 -5
  100. package/dist/modules/credential_session_cache.d.ts.map +1 -1
  101. package/dist/modules/credential_session_cache.js +1 -1
  102. package/dist/modules/credential_session_cache.js.map +1 -1
  103. package/dist/modules/crypto/pure_secp256k1.d.ts +1 -2
  104. package/dist/modules/crypto/pure_secp256k1.d.ts.map +1 -1
  105. package/dist/modules/crypto/pure_secp256k1.js +1 -1
  106. package/dist/modules/crypto/pure_secp256k1.js.map +1 -1
  107. package/dist/modules/dexbot_class.d.ts +9 -8
  108. package/dist/modules/dexbot_class.d.ts.map +1 -1
  109. package/dist/modules/dexbot_class.js +9 -8
  110. package/dist/modules/dexbot_class.js.map +1 -1
  111. package/dist/modules/dexbot_cow_runtime.d.ts +87 -7
  112. package/dist/modules/dexbot_cow_runtime.d.ts.map +1 -1
  113. package/dist/modules/dexbot_cow_runtime.js +489 -14
  114. package/dist/modules/dexbot_cow_runtime.js.map +1 -1
  115. package/dist/modules/dexbot_fill_runtime.d.ts +1 -17
  116. package/dist/modules/dexbot_fill_runtime.d.ts.map +1 -1
  117. package/dist/modules/dexbot_fill_runtime.js +1 -1
  118. package/dist/modules/dexbot_fill_runtime.js.map +1 -1
  119. package/dist/modules/dexbot_maintenance_runtime.d.ts +1 -1
  120. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  121. package/dist/modules/dexbot_maintenance_runtime.js +21 -5
  122. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  123. package/dist/modules/dexbot_state_recovery.d.ts +1 -7
  124. package/dist/modules/dexbot_state_recovery.d.ts.map +1 -1
  125. package/dist/modules/dexbot_state_recovery.js +1 -1
  126. package/dist/modules/dexbot_state_recovery.js.map +1 -1
  127. package/dist/modules/launcher/adapter_requirement.d.ts +1 -5
  128. package/dist/modules/launcher/adapter_requirement.d.ts.map +1 -1
  129. package/dist/modules/launcher/adapter_requirement.js +1 -1
  130. package/dist/modules/launcher/adapter_requirement.js.map +1 -1
  131. package/dist/modules/launcher/bot_supervisor.d.ts +1 -6
  132. package/dist/modules/launcher/bot_supervisor.d.ts.map +1 -1
  133. package/dist/modules/launcher/bot_supervisor.js +1 -1
  134. package/dist/modules/launcher/bot_supervisor.js.map +1 -1
  135. package/dist/modules/launcher/foreign_cred_daemon.d.ts +1 -3
  136. package/dist/modules/launcher/foreign_cred_daemon.d.ts.map +1 -1
  137. package/dist/modules/launcher/foreign_cred_daemon.js +1 -1
  138. package/dist/modules/launcher/foreign_cred_daemon.js.map +1 -1
  139. package/dist/modules/launcher/launch_modes.d.ts +1 -2
  140. package/dist/modules/launcher/launch_modes.d.ts.map +1 -1
  141. package/dist/modules/launcher/launch_modes.js +1 -1
  142. package/dist/modules/launcher/launch_modes.js.map +1 -1
  143. package/dist/modules/launcher/market_adapter_runtime.d.ts +1 -3
  144. package/dist/modules/launcher/market_adapter_runtime.d.ts.map +1 -1
  145. package/dist/modules/launcher/market_adapter_runtime.js +1 -1
  146. package/dist/modules/launcher/market_adapter_runtime.js.map +1 -1
  147. package/dist/modules/launcher/monolithic_runtime.d.ts +1 -5
  148. package/dist/modules/launcher/monolithic_runtime.d.ts.map +1 -1
  149. package/dist/modules/launcher/monolithic_runtime.js +1 -1
  150. package/dist/modules/launcher/monolithic_runtime.js.map +1 -1
  151. package/dist/modules/launcher/status_reporting.d.ts +1 -2
  152. package/dist/modules/launcher/status_reporting.d.ts.map +1 -1
  153. package/dist/modules/launcher/status_reporting.js +1 -1
  154. package/dist/modules/launcher/status_reporting.js.map +1 -1
  155. package/dist/modules/order/export.d.ts +1 -8
  156. package/dist/modules/order/export.d.ts.map +1 -1
  157. package/dist/modules/order/export.js +1 -1
  158. package/dist/modules/order/export.js.map +1 -1
  159. package/dist/modules/order/grid.d.ts +7 -0
  160. package/dist/modules/order/grid.d.ts.map +1 -1
  161. package/dist/modules/order/grid.js +52 -98
  162. package/dist/modules/order/grid.js.map +1 -1
  163. package/dist/modules/order/grid_reconcile_internal.d.ts +1 -10
  164. package/dist/modules/order/grid_reconcile_internal.d.ts.map +1 -1
  165. package/dist/modules/order/grid_reconcile_internal.js +2 -2
  166. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  167. package/dist/modules/order/manager.d.ts +10 -2
  168. package/dist/modules/order/manager.d.ts.map +1 -1
  169. package/dist/modules/order/manager.js +20 -7
  170. package/dist/modules/order/manager.js.map +1 -1
  171. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  172. package/dist/modules/order/sync_engine.js +25 -20
  173. package/dist/modules/order/sync_engine.js.map +1 -1
  174. package/dist/modules/order/utils/math.d.ts +1 -10
  175. package/dist/modules/order/utils/math.d.ts.map +1 -1
  176. package/dist/modules/order/utils/math.js +1 -1
  177. package/dist/modules/order/utils/math.js.map +1 -1
  178. package/dist/modules/order/utils/order.d.ts +45 -7
  179. package/dist/modules/order/utils/order.d.ts.map +1 -1
  180. package/dist/modules/order/utils/order.js +160 -15
  181. package/dist/modules/order/utils/order.js.map +1 -1
  182. package/dist/modules/order/utils/system.d.ts +0 -15
  183. package/dist/modules/order/utils/system.d.ts.map +1 -1
  184. package/dist/modules/order/utils/system.js +2 -2
  185. package/dist/modules/order/utils/system.js.map +1 -1
  186. package/dist/modules/process_discovery.d.ts +0 -3
  187. package/dist/modules/process_discovery.d.ts.map +1 -1
  188. package/dist/modules/process_discovery.js +1 -1
  189. package/dist/modules/process_discovery.js.map +1 -1
  190. package/dist/modules/utils/chain_logs.d.ts +1 -2
  191. package/dist/modules/utils/chain_logs.d.ts.map +1 -1
  192. package/dist/modules/utils/chain_logs.js +1 -1
  193. package/dist/modules/utils/chain_logs.js.map +1 -1
  194. package/dist/scripts/analyze-git.js +2 -2
  195. package/dist/scripts/analyze-git.js.map +1 -1
  196. package/dist/scripts/update.js +46 -31
  197. package/dist/scripts/update.js.map +1 -1
  198. package/docs/BITSHARES_ONBOARDING.md +110 -40
  199. package/docs/COPY_ON_WRITE_MASTER_PLAN.md +10 -207
  200. package/docs/COW_INVARIANTS.md +16 -10
  201. package/docs/DEXBOT_COMPARISON.md +3 -3
  202. package/docs/EVOLUTION.md +75 -29
  203. package/docs/FUND_MOVEMENT_AND_ACCOUNTING.md +6 -6
  204. package/docs/GRID_RECALCULATION.md +14 -6
  205. package/docs/GRID_RECONCILE.md +28 -25
  206. package/docs/LIFECYCLE.md +12 -11
  207. package/docs/README.md +48 -42
  208. package/docs/architecture.md +19 -30
  209. package/docs/developer_guide.md +3 -3
  210. package/package.json +1 -1
  211. package/scripts/git-viewer.sh +1 -1
  212. package/tests/README.md +10 -1
@@ -1,20 +1,5 @@
1
1
  #!/usr/bin/env node
2
- interface AssetAmount {
3
- amount: number;
4
- asset_id: string;
5
- }
6
- interface FillRecord {
7
- time: string;
8
- blockNum: number;
9
- opNum: number;
10
- orderId: string;
11
- accountId: string;
12
- pays: AssetAmount;
13
- receives: AssetAmount;
14
- fee: AssetAmount;
15
- isMaker: boolean;
16
- sort: any[];
17
- }
2
+ import { FillRecord } from './fills_source.js';
18
3
  interface TradeFill {
19
4
  time: string;
20
5
  orderId: string;
@@ -70,6 +55,11 @@ declare function classifyFills(fills: FillRecord[], filterAsset: string | null):
70
55
  pairs: Set<string>;
71
56
  };
72
57
  declare function analyzePair(trades: TradeFill[], matchMode?: 'fifo' | 'sequential'): PairAnalysis;
58
+ interface WindowRange {
59
+ /** The analysis window actually queried — drives annualisation. */
60
+ startMs: number;
61
+ endMs: number;
62
+ }
73
63
  interface TradingMetrics {
74
64
  totalLots: number;
75
65
  winRate: number;
@@ -81,8 +71,16 @@ interface TradingMetrics {
81
71
  expectancyPct: number;
82
72
  expectancyR: number;
83
73
  netExpectancyBts: number;
84
- dailyPnlRatio: number;
85
- dailyDownsideRatio: number;
74
+ sharpeAnn: number;
75
+ sortinoAnn: number;
76
+ sharpeAnnSE: number;
77
+ periodLabel: string;
78
+ periodCount: number;
79
+ periodSpanDays: number;
80
+ scoredSpanDays: number;
81
+ annualFactor: number;
82
+ projectedNetPnlPerDay: number;
83
+ projectedNetPnlAnn: number;
86
84
  maxConsecWins: number;
87
85
  maxConsecLosses: number;
88
86
  avgHoldHours: number;
@@ -90,6 +88,7 @@ interface TradingMetrics {
90
88
  bestTradePct: number;
91
89
  worstTradePct: number;
92
90
  mddPct: number;
91
+ mddAbsBts: number;
93
92
  mddHadStablePeak: boolean;
94
93
  isOngoingRecovery: boolean;
95
94
  currentDrawdownDays: number;
@@ -110,6 +109,6 @@ interface TradingMetrics {
110
109
  pctRGreater2: number;
111
110
  pctRLessNeg1: number;
112
111
  }
113
- declare function computeMetrics(pair: PairAnalysis): TradingMetrics;
112
+ declare function computeMetrics(pair: PairAnalysis, window?: WindowRange): TradingMetrics;
114
113
  export { analyzePair, classifyFills, computeMetrics, TradeFill, FillRecord, RealizedPnl, PairAnalysis, TradingMetrics };
115
114
  //# sourceMappingURL=trade_profitability.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"trade_profitability.d.ts","sourceRoot":"","sources":["../../analysis/trade_profitability.ts"],"names":[],"mappings":";AA2GA,UAAU,WAAW;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;CACpB;AAED,UAAU,UAAU;IAChB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,MAAM,CAAC;IAClB,IAAI,EAAE,WAAW,CAAC;IAClB,QAAQ,EAAE,WAAW,CAAC;IACtB,GAAG,EAAE,WAAW,CAAC;IACjB,OAAO,EAAE,OAAO,CAAC;IACjB,IAAI,EAAE,GAAG,EAAE,CAAC;CACf;AAED,UAAU,SAAS;IACf,IAAI,EAAE,MAAM,CAAC;IACb,OAAO,EAAE,MAAM,CAAC;IAChB,SAAS,EAAE,KAAK,GAAG,MAAM,CAAC;IAC1B,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,UAAU,EAAE,MAAM,CAAC;IACnB,WAAW,EAAE,MAAM,CAAC;IACpB,KAAK,EAAE,MAAM,CAAC;IACd,OAAO,EAAE,OAAO,CAAC;IACjB,QAAQ,EAAE,MAAM,CAAC;IACjB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;CAC1B;AAWD,UAAU,WAAW;IACjB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,GAAG,EAAE,MAAM,CAAC;IACZ,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,cAAc,EAAE,MAAM,CAAC;IACvB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,MAAM,EAAE,MAAM,CAAC;IACf,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,QAAQ,EAAE,MAAM,CAAC;IACjB,YAAY,EAAE,MAAM,CAAC;IACrB,WAAW,EAAE,MAAM,CAAC;IACpB,YAAY,EAAE,OAAO,CAAC;IACtB,WAAW,EAAE,OAAO,CAAC;CACxB;AAED,UAAU,YAAY;IAClB,SAAS,EAAE,MAAM,CAAC;IAClB,UAAU,EAAE,MAAM,CAAC;IACnB,IAAI,EAAE,SAAS,EAAE,CAAC;IAClB,KAAK,EAAE,SAAS,EAAE,CAAC;IACnB,YAAY,EAAE,WAAW,EAAE,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,cAAc,EAAE,MAAM,CAAC;IACvB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,gBAAgB,EAAE,MAAM,CAAC;IACzB,eAAe,EAAE,MAAM,CAAC;IACxB,mBAAmB,EAAE,MAAM,CAAC;IAC5B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,WAAW,EAAE,MAAM,CAAC;CACvB;AA8PD,iBAAS,aAAa,CAAC,KAAK,EAAE,UAAU,EAAE,EAAE,WAAW,EAAE,MAAM,GAAG,IAAI,GAAG;IAAE,MAAM,EAAE,SAAS,EAAE,CAAC;IAAC,KAAK,EAAE,GAAG,CAAC,MAAM,CAAC,CAAA;CAAE,CA2FnH;AAID,iBAAS,WAAW,CAAC,MAAM,EAAE,SAAS,EAAE,EAAE,SAAS,GAAE,MAAM,GAAG,YAA2B,GAAG,YAAY,CAyKvG;AAgID,UAAU,cAAc;IACpB,SAAS,EAAE,MAAM,CAAC;IAClB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,MAAM,EAAE,MAAM,CAAC;IACf,OAAO,EAAE,MAAM,CAAC;IAChB,eAAe,EAAE,MAAM,CAAC;IACxB,aAAa,EAAE,MAAM,CAAC;IACtB,aAAa,EAAE,MAAM,CAAC;IACtB,WAAW,EAAE,MAAM,CAAC;IACpB,gBAAgB,EAAE,MAAM,CAAC;IACzB,aAAa,EAAE,MAAM,CAAC;IACtB,kBAAkB,EAAE,MAAM,CAAC;IAC3B,aAAa,EAAE,MAAM,CAAC;IACtB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,eAAe,EAAE,MAAM,CAAC;IACxB,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,CAAC;IACtB,MAAM,EAAE,MAAM,CAAC;IACf,gBAAgB,EAAE,OAAO,CAAC;IAC1B,iBAAiB,EAAE,OAAO,CAAC;IAC3B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,YAAY,EAAE,MAAM,CAAC;IACrB,SAAS,EAAE,MAAM,CAAC;IAClB,SAAS,EAAE,MAAM,CAAC;IAElB,UAAU,EAAE,MAAM,CAAC;IACnB,eAAe,EAAE,MAAM,CAAC;IACxB,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,mBAAmB,EAAE,MAAM,CAAC;IAC5B,gBAAgB,EAAE,MAAM,CAAC;IACzB,iBAAiB,EAAE,MAAM,CAAC;IAC1B,WAAW,EAAE,MAAM,CAAC;IACpB,eAAe,EAAE,MAAM,CAAC;IACxB,OAAO,EAAE,MAAM,CAAC;IAChB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;CACxB;AAaD,iBAAS,cAAc,CAAC,IAAI,EAAE,YAAY,GAAG,cAAc,CA4Q1D;AAwTD,OAAO,EAAE,WAAW,EAAE,aAAa,EAAE,cAAc,EAAE,SAAS,EAAE,UAAU,EAAE,WAAW,EAAE,YAAY,EAAE,cAAc,EAAE,CAAC"}
1
+ {"version":3,"file":"trade_profitability.d.ts","sourceRoot":"","sources":["../../analysis/trade_profitability.ts"],"names":[],"mappings":"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"}
@@ -2,10 +2,8 @@
2
2
  'use strict';
3
3
  import fs from 'node:fs';
4
4
  import { pathToFileURL } from 'node:url';
5
- import * as KC from '../market_adapter/core/kibana_client.js';
6
- import * as C from '../modules/constants.js';
7
- import { findBotKeyByAccountRef, getStoredBotAccountId, persistBotAccountId } from './bot_key_utils.js';
8
- const { kibanaSearch, DEFAULT_CONFIG: BASE_CONFIG } = KC;
5
+ import { resolveAccountRef } from './account_resolver.js';
6
+ import { BTS_ID, assetPrec as getPrec, assetSymbol, fetchAllFills, resolveAssetPrecisions, toReal, } from './fills_source.js';
9
7
  /**
10
8
  * TRADE PROFITABILITY ANALYZER
11
9
  *
@@ -25,71 +23,7 @@ const { kibanaSearch, DEFAULT_CONFIG: BASE_CONFIG } = KC;
25
23
  * node dist/analysis/trade_profitability.js 1.2.3 --hours 168 --match-mode fifo
26
24
  */
27
25
  // ─── Constants ────────────────────────────────────────────────────────────────
28
- const OP_FILL_ORDER = 4;
29
- const BTS_ID = '1.3.0';
30
26
  let BLOCKCHAIN_FEE_PER_FILL = 0.09652; // BTS — flat blockchain operation fee (not market fee); override with --fee-per-order
31
- const ASSETS = {
32
- '1.3.0': { symbol: 'BTS', precision: 5 },
33
- '1.3.118': { symbol: 'GBP', precision: 4 },
34
- '1.3.119': { symbol: 'JPY', precision: 2 },
35
- '1.3.120': { symbol: 'EUR', precision: 4 },
36
- '1.3.1325': { symbol: 'RUBLE', precision: 5 },
37
- '1.3.2512': { symbol: 'EVRAZ', precision: 4 },
38
- '1.3.3291': { symbol: 'TWENTIX', precision: 5 },
39
- '1.3.4099': { symbol: 'XBTSX.STH', precision: 6 },
40
- '1.3.4156': { symbol: 'XBTSX.DOGE', precision: 5 },
41
- '1.3.4157': { symbol: 'XBTSX.BTC', precision: 8 },
42
- '1.3.4159': { symbol: 'XBTSX.LTC', precision: 8 },
43
- '1.3.4176': { symbol: 'XBTSX.DASH', precision: 8 },
44
- '1.3.4274': { symbol: 'XBTSX.BCH', precision: 8 },
45
- '1.3.4760': { symbol: 'XBTSX.ETH', precision: 7 },
46
- '1.3.5537': { symbol: 'IOB.XRP', precision: 4 },
47
- '1.3.5541': { symbol: 'XBTSX.BNB', precision: 7 },
48
- '1.3.5589': { symbol: 'XBTSX.USDT', precision: 6 },
49
- '1.3.5641': { symbol: 'HONEST.CNY', precision: 4 },
50
- '1.3.5649': { symbol: 'HONEST.USD', precision: 4 },
51
- '1.3.5650': { symbol: 'HONEST.BTC', precision: 8 },
52
- '1.3.5659': { symbol: 'HONEST.ETH', precision: 6 },
53
- '1.3.5870': { symbol: 'XBTSX.FIL', precision: 6 },
54
- '1.3.5887': { symbol: 'XBTSX.RUB', precision: 4 },
55
- '1.3.5902': { symbol: 'XBTSX.USDC', precision: 6 },
56
- '1.3.6013': { symbol: 'XBTSX.HIVE', precision: 6 },
57
- '1.3.6124': { symbol: 'XBTSX.AVAX', precision: 6 },
58
- '1.3.6139': { symbol: 'XBTSX.XAUT', precision: 6 },
59
- '1.3.6166': { symbol: 'XBTSX.MATIC', precision: 5 },
60
- '1.3.6241': { symbol: 'XBTSX.ETC', precision: 7 },
61
- '1.3.6268': { symbol: 'BTWTY.EOS', precision: 4 },
62
- '1.3.6301': { symbol: 'HONEST.MONEY', precision: 8 },
63
- '1.3.6304': { symbol: 'HONEST.ADA', precision: 8 },
64
- '1.3.6305': { symbol: 'HONEST.DOT', precision: 8 },
65
- '1.3.6309': { symbol: 'HONEST.ATOM', precision: 8 },
66
- '1.3.6311': { symbol: 'HONEST.ALGO', precision: 8 },
67
- '1.3.6312': { symbol: 'HONEST.FIL', precision: 8 },
68
- '1.3.6313': { symbol: 'HONEST.EOS', precision: 8 },
69
- '1.3.6315': { symbol: 'HONEST.EUR', precision: 4 },
70
- '1.3.6316': { symbol: 'HONEST.GBP', precision: 4 },
71
- '1.3.6317': { symbol: 'HONEST.JPY', precision: 4 },
72
- '1.3.6444': { symbol: 'IOB.XLM', precision: 4 },
73
- '1.3.6573': { symbol: 'XBTSX.DAI', precision: 6 },
74
- '1.3.6620': { symbol: 'XBTSX.A', precision: 6 },
75
- '1.3.6627': { symbol: 'XBTSX.LINK', precision: 6 },
76
- };
77
- const resolvedPrecisions = {};
78
- function assetSymbol(id) {
79
- return ASSETS[id]?.symbol ?? id;
80
- }
81
- function assetPrec(id) {
82
- return ASSETS[id]?.precision ?? resolvedPrecisions[id];
83
- }
84
- function getPrec(id) {
85
- return assetPrec(id);
86
- }
87
- function toReal(amount, assetId) {
88
- const p = getPrec(assetId);
89
- if (p === undefined)
90
- return NaN;
91
- return amount / Math.pow(10, p);
92
- }
93
27
  // ─── CLI ──────────────────────────────────────────────────────────────────────
94
28
  function printHelp() {
95
29
  console.log(`\
@@ -108,7 +42,6 @@ Options:
108
42
  --asset <assetId> Filter to one base asset (e.g. 1.3.113 for bitUSD)
109
43
  --lookup Legacy (no-op): account names always resolve automatically
110
44
  --refresh-account Force re-resolution and update the stored accountId
111
- --node <url> BitShares node URL (default: first healthy from built-in pool)
112
45
  --csv <file> Export trade list as CSV
113
46
  --json <file> Export full analysis as JSON
114
47
  --trades Show per-order PnL detail (hidden by default)
@@ -138,7 +71,6 @@ function parseArgs() {
138
71
  asset: null,
139
72
  lookup: false,
140
73
  refreshAccount: false,
141
- node: C.NODE_MANAGEMENT.DEFAULT_NODES[0],
142
74
  csv: null,
143
75
  json: null,
144
76
  matchMode: 'sequential',
@@ -166,9 +98,6 @@ function parseArgs() {
166
98
  case '--refresh-account':
167
99
  opts.refreshAccount = true;
168
100
  break;
169
- case '--node':
170
- opts.node = args[++i];
171
- break;
172
101
  case '--csv':
173
102
  opts.csv = args[++i];
174
103
  break;
@@ -203,151 +132,6 @@ function parseArgs() {
203
132
  }
204
133
  return opts;
205
134
  }
206
- // ─── Account name resolution ─────────────────────────────────────────────────
207
- async function resolveAccountId(name, nodeUrl) {
208
- const { createReadOnlyClient } = await import('../modules/bitshares-native/index.js');
209
- const client = createReadOnlyClient({ nodes: [nodeUrl] });
210
- // Suppress transport INFO logs during ephemeral connection:
211
- // bitshares-native transport logger (new Logger('Transport')) writes
212
- // "[timestamp] [INFO] [Transport] ..." — silence by raising log level.
213
- const prevLevel = process.env.LOG_LEVEL;
214
- process.env.LOG_LEVEL = 'warn';
215
- try {
216
- await client.connect();
217
- const accounts = await client.db('lookup_account_names', [[name]]);
218
- if (Array.isArray(accounts) && accounts[0]?.id) {
219
- return accounts[0].id;
220
- }
221
- return null;
222
- }
223
- catch (e) {
224
- console.warn(` [warn] Account resolution failed: ${e.message}`);
225
- return null;
226
- }
227
- finally {
228
- try {
229
- client.disconnect();
230
- }
231
- catch (_) { }
232
- process.env.LOG_LEVEL = prevLevel;
233
- }
234
- }
235
- // ─── On-chain asset precision resolution ────────────────────────────────────
236
- /**
237
- * Collects all unique non-BTS asset IDs from fills, resolves unknown
238
- * precisions from the blockchain, and populates the runtime cache.
239
- */
240
- async function resolveAssetPrecisions(fills, nodeUrl) {
241
- const unknownIds = new Set();
242
- for (const f of fills) {
243
- for (const id of [f.pays.asset_id, f.receives.asset_id, f.fee.asset_id]) {
244
- if (id !== BTS_ID && !(id in ASSETS) && !(id in resolvedPrecisions)) {
245
- unknownIds.add(id);
246
- }
247
- }
248
- }
249
- if (unknownIds.size === 0 || !nodeUrl)
250
- return;
251
- const ids = [...unknownIds];
252
- console.log(` Resolving ${ids.length} unknown asset(s) from blockchain...`);
253
- const { createReadOnlyClient } = await import('../modules/bitshares-native/index.js');
254
- const client = createReadOnlyClient({ nodes: [nodeUrl] });
255
- try {
256
- await client.connect();
257
- const assets = await client.db('get_assets', [ids]);
258
- if (Array.isArray(assets)) {
259
- for (const asset of assets) {
260
- if (asset?.id && asset.precision != null) {
261
- resolvedPrecisions[asset.id] = asset.precision;
262
- console.log(` ${asset.id} → ${asset.symbol || '?'} (precision ${asset.precision})`);
263
- }
264
- }
265
- }
266
- const missing = ids.filter(id => !(id in resolvedPrecisions));
267
- if (missing.length > 0) {
268
- console.warn(` [warn] ${missing.length} asset(s) not found on chain: ${missing.join(', ')}. Fills referencing them will be skipped.`);
269
- }
270
- }
271
- catch (e) {
272
- console.warn(` [warn] Asset resolution failed: ${e.message}. Fills with unknown assets will be skipped.`);
273
- }
274
- finally {
275
- try {
276
- client.disconnect();
277
- }
278
- catch (_) { }
279
- }
280
- }
281
- // ─── Kibana Query ────────────────────────────────────────────────────────────
282
- function buildFillQuery(accountId, gte, lte, size) {
283
- return {
284
- size,
285
- track_total_hits: false,
286
- _source: [
287
- 'block_data.block_time',
288
- 'block_data.block_num',
289
- 'operation_id_num',
290
- 'operation_history.op_object.pays',
291
- 'operation_history.op_object.receives',
292
- 'operation_history.op_object.fee',
293
- 'operation_history.op_object.order_id',
294
- 'operation_history.op_object.account_id',
295
- 'operation_history.op_object.is_maker',
296
- ],
297
- query: {
298
- bool: {
299
- filter: [
300
- { term: { operation_type: OP_FILL_ORDER } },
301
- { term: { 'operation_history.op_object.account_id.keyword': accountId } },
302
- { range: { 'block_data.block_time': { gte, lte } } },
303
- ],
304
- },
305
- },
306
- sort: [
307
- { 'block_data.block_time': { order: 'asc' } },
308
- { operation_id_num: { order: 'asc' } },
309
- ],
310
- };
311
- }
312
- async function fetchAllFills(config, accountId, gte, lte) {
313
- const pageSize = 10000;
314
- const fills = [];
315
- let searchAfter = null;
316
- const cfg = { ...BASE_CONFIG, timeout: 60000, ...config };
317
- while (true) {
318
- const query = buildFillQuery(accountId, gte, lte, pageSize);
319
- if (searchAfter)
320
- query.search_after = searchAfter;
321
- const result = await kibanaSearch(cfg, query);
322
- const hits = result?.hits?.hits ?? [];
323
- if (!hits.length)
324
- break;
325
- for (const hit of hits) {
326
- const src = hit?._source;
327
- const op = src?.operation_history?.op_object;
328
- if (!op || !op.pays || !op.receives)
329
- continue;
330
- fills.push({
331
- time: src.block_data?.block_time ?? '',
332
- blockNum: src.block_data?.block_num ?? 0,
333
- opNum: Number(src.operation_id_num ?? 0),
334
- orderId: op.order_id ?? '',
335
- accountId: op.account_id ?? '',
336
- pays: { amount: Number(op.pays.amount ?? 0), asset_id: op.pays.asset_id ?? '' },
337
- receives: { amount: Number(op.receives.amount ?? 0), asset_id: op.receives.asset_id ?? '' },
338
- fee: { amount: Number(op.fee?.amount ?? 0), asset_id: op.fee?.asset_id ?? '' },
339
- isMaker: op.is_maker ?? false,
340
- sort: hit.sort,
341
- });
342
- }
343
- if (hits.length < pageSize)
344
- break;
345
- searchAfter = hits[hits.length - 1].sort;
346
- if (!Array.isArray(searchAfter))
347
- break;
348
- }
349
- return fills;
350
- }
351
135
  // ─── Fill Classification ─────────────────────────────────────────────────────
352
136
  /**
353
137
  * A fill needs strictly positive, finite amounts on both legs. A zero base
@@ -742,7 +526,7 @@ function percentile(sorted, p) {
742
526
  return sorted[f];
743
527
  return sorted[f] * (c - k) + sorted[c] * (k - f);
744
528
  }
745
- function computeMetrics(pair) {
529
+ function computeMetrics(pair, window) {
746
530
  const pnls = pair.realizedPnls;
747
531
  const total = pnls.length;
748
532
  if (total === 0) {
@@ -751,11 +535,15 @@ function computeMetrics(pair) {
751
535
  avgWin: 0, avgLoss: 0, avgWinLossRatio: 0,
752
536
  expectancyBts: 0, expectancyPct: 0, expectancyR: 0,
753
537
  netExpectancyBts: 0,
754
- dailyPnlRatio: 0, dailyDownsideRatio: 0,
538
+ sharpeAnn: NaN, sortinoAnn: NaN, sharpeAnnSE: NaN,
539
+ periodLabel: '—', periodCount: 0, periodSpanDays: 0, scoredSpanDays: 0,
540
+ annualFactor: 0,
541
+ projectedNetPnlPerDay: 0, projectedNetPnlAnn: 0,
755
542
  maxConsecWins: 0, maxConsecLosses: 0,
756
543
  avgHoldHours: 0, limitOrderRatio: 0,
757
544
  bestTradePct: 0, worstTradePct: 0,
758
545
  mddPct: 0,
546
+ mddAbsBts: 0,
759
547
  mddHadStablePeak: false,
760
548
  isOngoingRecovery: false,
761
549
  currentDrawdownDays: 0,
@@ -789,27 +577,87 @@ function computeMetrics(pair) {
789
577
  ? winRate * (avgWin / rMultiple) - (1 - winRate)
790
578
  : Infinity;
791
579
  const netExpectancyBts = pair.totalRealizedPnlNet / total;
792
- // Daily-binned net PnL for mean/std ratio (dimensionful — not a Sharpe ratio)
793
- const dayBuckets = {};
580
+ // ─── Risk-adjusted ratios (window-aware annualisation) ────────────────
581
+ // Bin the window's net PnL into calendar periods and annualise with the
582
+ // matching periods-per-year. Every period in the window is represented
583
+ // (zero-filled): flat periods must count as 0 PnL, otherwise a bot that
584
+ // trades a few days a week is scored as if it traded every day, and the
585
+ // window cannot be scaled honestly to a year.
586
+ const HOUR_MS = 3600_000;
587
+ const DAY_MS = 86_400_000;
588
+ const times = pnls.map(r => Date.parse(r.exitTime)).filter(t => Number.isFinite(t));
589
+ const firstMs = window?.startMs ?? (times.length > 0 ? Math.min(...times) : 0);
590
+ const lastMs = window?.endMs ?? (times.length > 0 ? Math.max(...times) : firstMs + DAY_MS);
591
+ const spanMs = Math.max(lastMs - firstMs, window ? HOUR_MS : DAY_MS);
592
+ const periodSpanDays = spanMs / DAY_MS;
593
+ // Daily buckets need a handful of observations before a daily std means
594
+ // anything; shorter windows fall back to hourly bins so they still
595
+ // annualise instead of dividing by a one-sample std.
596
+ const useHourly = periodSpanDays < 3;
597
+ const periodMs = useHourly ? HOUR_MS : DAY_MS;
598
+ const periodsPerYear = useHourly ? 8760 : 365;
599
+ const periodLabel = useHourly ? '1h' : '1d';
600
+ // Score only whole periods: a trailing partial period (window not an exact
601
+ // multiple of the bin) would otherwise be a deflated observation. It is
602
+ // excluded from ratios, projection AND activity rates alike, so every
603
+ // window-derived metric shares one basis. Bins are aligned to the window
604
+ // start, so an exact-multiple window scores everything.
605
+ const nPeriods = Math.max(1, Math.floor(spanMs / periodMs));
606
+ const scoredSpanDays = nPeriods * periodMs / DAY_MS;
607
+ const scoredEndMs = firstMs + nPeriods * periodMs;
608
+ const exactWindow = spanMs === nPeriods * periodMs;
609
+ // A fill belongs to the scored window if it falls inside the whole
610
+ // periods. On an exact-multiple window the end boundary is inclusive (the
611
+ // ES range query includes `lte`); with a partial tail that boundary fill
612
+ // is trailing and excluded along with the rest of the tail.
613
+ const inScoredWindow = (t) => Number.isFinite(t) && t >= firstMs
614
+ && (t < scoredEndMs || (exactWindow && t === scoredEndMs));
615
+ const periodPnl = new Array(nPeriods).fill(0);
616
+ let scoredFillCount = 0;
794
617
  for (const r of pnls) {
795
- const day = r.exitTime.slice(0, 10);
796
- dayBuckets[day] = (dayBuckets[day] || 0) + r.pnlNet;
618
+ const t = Date.parse(r.exitTime);
619
+ if (!inScoredWindow(t))
620
+ continue;
621
+ let idx = Math.floor((t - firstMs) / periodMs);
622
+ if (idx === nPeriods)
623
+ idx = nPeriods - 1; // inclusive end boundary
624
+ periodPnl[idx] += r.pnlNet;
625
+ scoredFillCount++;
797
626
  }
798
- const dailyRets = Object.values(dayBuckets);
799
- const nDays = dailyRets.length;
800
- const meanDailyRet = nDays > 0 ? dailyRets.reduce((s, v) => s + v, 0) / nDays : 0;
801
- const dailyVar = nDays > 0
802
- ? dailyRets.reduce((s, v) => s + (v - meanDailyRet) ** 2, 0) / nDays
627
+ const meanPeriod = periodPnl.reduce((s, v) => s + v, 0) / nPeriods;
628
+ // Sample variance (n-1): the window is a sample, not the whole population.
629
+ const periodVar = nPeriods > 1
630
+ ? periodPnl.reduce((s, v) => s + (v - meanPeriod) ** 2, 0) / (nPeriods - 1)
803
631
  : 0;
804
- const dailyStd = Math.sqrt(dailyVar);
805
- const annFactor = Math.sqrt(365);
806
- const dailyPnlRatio = dailyStd > 0 ? (meanDailyRet / dailyStd) * annFactor : 0;
807
- // Downside deviation uses only negative returns; same N denominator
808
- const downsideVar = nDays > 0
809
- ? dailyRets.reduce((s, v) => s + (v < 0 ? v * v : 0), 0) / nDays
632
+ const periodStd = Math.sqrt(periodVar);
633
+ const annualFactor = Math.sqrt(periodsPerYear);
634
+ const sharpeAnn = periodStd > 0 ? (meanPeriod / periodStd) * annualFactor : NaN;
635
+ // Estimation uncertainty of the annualised Sharpe (Lo 2002, i.i.d. returns).
636
+ const srPerPeriod = periodStd > 0 ? meanPeriod / periodStd : NaN;
637
+ const sharpeAnnSE = nPeriods > 1 && Number.isFinite(srPerPeriod)
638
+ ? Math.sqrt((1 + 0.5 * srPerPeriod * srPerPeriod) / nPeriods) * annualFactor
639
+ : NaN;
640
+ // Target downside deviation (MAR = 0), sample denominator (n-1) to match
641
+ // the Sharpe convention above — the two ratios stay internally
642
+ // comparable (many textbook Sortinos divide by N; we deliberately
643
+ // don't mix conventions). With no losing periods Sortino is undefined —
644
+ // reporting 0 would read as "terrible", the opposite of truth.
645
+ const downsideVar = nPeriods > 1
646
+ ? periodPnl.reduce((s, v) => s + (v < 0 ? v * v : 0), 0) / (nPeriods - 1)
810
647
  : 0;
811
648
  const downsideStd = Math.sqrt(downsideVar);
812
- const dailyDownsideRatio = downsideStd > 0 ? (meanDailyRet / downsideStd) * annFactor : 0;
649
+ const sortinoAnn = nPeriods < 2
650
+ ? NaN
651
+ : (downsideStd > 0
652
+ ? (meanPeriod / downsideStd) * annualFactor
653
+ : (meanPeriod > 0 ? Infinity : NaN));
654
+ // "This window repeated all year" — linear projection of the scored
655
+ // window's net PnL. Uses the same whole-period basis as the ratios
656
+ // above (not the raw window total), so Sharpe and projection can never
657
+ // disagree about what the window contains.
658
+ const scoredPnl = periodPnl.reduce((s, v) => s + v, 0);
659
+ const projectedNetPnlPerDay = scoredPnl / scoredSpanDays;
660
+ const projectedNetPnlAnn = projectedNetPnlPerDay * 365;
813
661
  // Fills-per-order distribution (grouped by sell order)
814
662
  const fillCounts = [];
815
663
  const orderMap = new Map();
@@ -830,8 +678,13 @@ function computeMetrics(pair) {
830
678
  const oneShotOrderRatio = sellOrdersFilled > 0
831
679
  ? fillCounts.filter(c => c === 1).length / sellOrdersFilled
832
680
  : 0;
833
- const fillsPerDay = nDays > 0 ? total / nDays : 0;
834
- const avgVolumePerDay = nDays > 0 ? (pair.totalBuyQuote + pair.totalSellQuote) / nDays : 0;
681
+ // Activity rates share the scored whole-period basis: numerator and
682
+ // denominator both cover exactly the lots/fills the ratios scored. A fill
683
+ // with an unparseable timestamp has no period, so it is excluded here too.
684
+ const scoredNotional = [...pair.buys, ...pair.sells]
685
+ .reduce((s, f) => s + (inScoredWindow(Date.parse(f.time)) ? f.quoteAmount : 0), 0);
686
+ const fillsPerDay = scoredSpanDays > 0 ? scoredFillCount / scoredSpanDays : 0;
687
+ const avgVolumePerDay = scoredSpanDays > 0 ? scoredNotional / scoredSpanDays : 0;
835
688
  // Avg hold duration
836
689
  let totalHours = 0;
837
690
  let hourCount = 0;
@@ -878,7 +731,7 @@ function computeMetrics(pair) {
878
731
  const STABILITY_TRADES = 3;
879
732
  const MIN_TRADES_FOR_PEAK = 10; // backstop for monotonic equity curves
880
733
  const chronological = [...pnls].sort((a, b) => new Date(a.exitTime).getTime() - new Date(b.exitTime).getTime());
881
- let equity = 0, peak = 0, mddPct = 0;
734
+ let equity = 0, peak = 0, mddPct = 0, mddAbsBts = 0;
882
735
  let maxRecoveryDays = 0;
883
736
  let isOngoingRecovery = false;
884
737
  let currentDrawdownDays = 0;
@@ -924,6 +777,9 @@ function computeMetrics(pair) {
924
777
  const dd = (equity - peak) / peak;
925
778
  if (dd < mddPct)
926
779
  mddPct = dd;
780
+ const ddAbs = peak - equity;
781
+ if (ddAbs > mddAbsBts)
782
+ mddAbsBts = ddAbs;
927
783
  }
928
784
  if (peak > 0 && !hadStablePeak) {
929
785
  if (!hasPrePeakEquity || equity < prePeakMinEquity) {
@@ -945,6 +801,7 @@ function computeMetrics(pair) {
945
801
  }
946
802
  else {
947
803
  mddPct = hasPrePeakEquity ? prePeakMinEquity : 0;
804
+ mddAbsBts = 0;
948
805
  }
949
806
  // Payoff distribution stats
950
807
  const pnlPcts = [...pnls.map(r => r.pnlPct)].sort((a, b) => a - b);
@@ -973,8 +830,16 @@ function computeMetrics(pair) {
973
830
  expectancyPct,
974
831
  expectancyR,
975
832
  netExpectancyBts,
976
- dailyPnlRatio,
977
- dailyDownsideRatio,
833
+ sharpeAnn,
834
+ sortinoAnn,
835
+ sharpeAnnSE,
836
+ periodLabel,
837
+ periodCount: nPeriods,
838
+ periodSpanDays,
839
+ scoredSpanDays,
840
+ annualFactor,
841
+ projectedNetPnlPerDay,
842
+ projectedNetPnlAnn,
978
843
  feeDragPct,
979
844
  maxConsecWins: maxW,
980
845
  maxConsecLosses: maxL,
@@ -983,6 +848,7 @@ function computeMetrics(pair) {
983
848
  bestTradePct,
984
849
  worstTradePct,
985
850
  mddPct,
851
+ mddAbsBts,
986
852
  mddHadStablePeak: hadStablePeak,
987
853
  isOngoingRecovery,
988
854
  currentDrawdownDays,
@@ -1003,11 +869,11 @@ function computeMetrics(pair) {
1003
869
  avgVolumePerDay,
1004
870
  };
1005
871
  }
1006
- function printMetrics(pairs) {
872
+ function printMetrics(pairs, window) {
1007
873
  for (const pair of pairs) {
1008
874
  if (pair.realizedPnls.length === 0)
1009
875
  continue;
1010
- const m = computeMetrics(pair);
876
+ const m = computeMetrics(pair, window);
1011
877
  const pairLabel = `${fmtAsset(pair.baseAsset)}/${fmtAsset(pair.quoteAsset)}`;
1012
878
  console.log('');
1013
879
  console.log(` ── ${pairLabel} — Performance Metrics`);
@@ -1035,13 +901,24 @@ function printMetrics(pairs) {
1035
901
  console.log(` P25 / P75: ${fmtPct(m.p25PnlPct)} / ${fmtPct(m.p75PnlPct)}`);
1036
902
  console.log(` Best / Worst Trade: ${fmtPct(m.bestTradePct)} / ${fmtPct(m.worstTradePct)}`);
1037
903
  console.log('');
1038
- // Risk-adjusted (dimensionful — based on absolute daily PnL, not % returns)
1039
- console.log(` Sharpe (ann): ${m.dailyPnlRatio.toFixed(2)}`);
1040
- console.log(` Sortino (ann): ${m.dailyDownsideRatio.toFixed(2)}`);
904
+ // Risk-adjusted — annualised from the analysis window, zero-filled bins
905
+ const ratioStr = (v) => Number.isFinite(v) ? v.toFixed(2) : (v === Infinity ? '∞' : 'n/a');
906
+ const seStr = Number.isFinite(m.sharpeAnnSE) ? ` ± ${m.sharpeAnnSE.toFixed(2)}` : '';
907
+ const confidence = m.periodSpanDays < 30 ? 'low confidence' : 'ok';
908
+ console.log(` Sharpe (ann): ${ratioStr(m.sharpeAnn)}${seStr} [${m.periodLabel} bins, n=${m.periodCount}, ${confidence}]`);
909
+ const sortinoNote = m.sortinoAnn === Infinity ? ' (no losing periods)' : '';
910
+ console.log(` Sortino (ann): ${ratioStr(m.sortinoAnn)}${sortinoNote}`);
911
+ if (m.periodLabel === '1h') {
912
+ console.log(` ⚠ window < 3 days: hourly bins over an unrepresentative sample — treat the annualised ratios as indicative only and never rank them against 1d-binned runs`);
913
+ }
914
+ const spanStr = m.scoredSpanDays < m.periodSpanDays - 1e-9
915
+ ? `${m.scoredSpanDays.toFixed(1)}d scored of ${m.periodSpanDays.toFixed(1)}d window`
916
+ : `${m.periodSpanDays.toFixed(1)}d`;
917
+ console.log(` Projected net PnL: ${fmt(m.projectedNetPnlAnn, 2)} ${qSymbol}/yr (${fmt(m.projectedNetPnlPerDay, 2)}/day over ${spanStr})`);
1041
918
  console.log('');
1042
919
  // Tail risk
1043
920
  if (m.mddHadStablePeak) {
1044
- console.log(` Max Drawdown: ${fmtPct(m.mddPct)}`);
921
+ console.log(` Max Drawdown: ${fmt(m.mddAbsBts, 4)} ${qSymbol} (${fmtPct(m.mddPct)} of peak cumulative profit)`);
1045
922
  }
1046
923
  else {
1047
924
  console.log(` Min Equity: ${fmt(m.mddPct, 4)} ${fmtAsset(pair.quoteAsset)}`);
@@ -1176,38 +1053,17 @@ async function run() {
1176
1053
  const opts = parseArgs();
1177
1054
  let accountId = opts.accountId;
1178
1055
  if (!/^1\.2\.\d+$/.test(String(accountId))) {
1179
- // Background resolution: the Kibana query below filters on the 1.2.x
1180
- // account_id field, so a name must always resolve first (a raw name
1181
- // would silently return zero fills).
1182
- // Stored ID first: when the name belongs to a bot in profiles/bots.json
1183
- // and its stamped accountId still matches, no chain lookup is needed.
1184
- let matchedKey = null;
1185
- let stored = null;
1186
- try {
1187
- const match = findBotKeyByAccountRef(accountId);
1188
- if (match) {
1189
- matchedKey = match.botKey;
1190
- stored = getStoredBotAccountId(match.botKey, accountId);
1191
- }
1192
- }
1193
- catch (_) {
1194
- // bots.json issues must never break resolution; fall through to chain.
1195
- }
1196
- if (stored && !opts.refreshAccount) {
1197
- console.log(` Using stored accountId ${stored} from profiles/bots.json (no lookup needed; pass --refresh-account to re-verify)`);
1198
- accountId = stored;
1199
- }
1200
- else {
1201
- const resolved = await resolveAccountId(accountId, opts.node);
1202
- if (!resolved) {
1203
- console.error(` Could not resolve "${accountId}" to an account ID`);
1204
- process.exit(1);
1205
- }
1206
- accountId = resolved;
1207
- if (matchedKey && persistBotAccountId(matchedKey, resolved)) {
1208
- console.log(` Stored accountId ${resolved} in profiles/bots.json`);
1209
- }
1056
+ // The Kibana query below filters on the 1.2.x account_id field, so a
1057
+ // name must always resolve first (a raw name would silently return
1058
+ // zero fills). Shared resolver: reuses a stored accountId from
1059
+ // profiles/bots.json when one matches the name and stamps the result
1060
+ // back onto the bot entry on a fresh lookup.
1061
+ const resolved = await resolveAccountRef(accountId, { refresh: opts.refreshAccount });
1062
+ if (!resolved.accountId) {
1063
+ console.error(` Could not resolve "${accountId}" to an account ID`);
1064
+ process.exit(1);
1210
1065
  }
1066
+ accountId = resolved.accountId;
1211
1067
  }
1212
1068
  // Build time range
1213
1069
  const now = new Date();
@@ -1233,7 +1089,7 @@ async function run() {
1233
1089
  process.exit(0);
1234
1090
  }
1235
1091
  // Resolve unknown asset precisions from blockchain
1236
- await resolveAssetPrecisions(fills, opts.node);
1092
+ await resolveAssetPrecisions(fills);
1237
1093
  // Classify fills
1238
1094
  const { trades, pairs } = classifyFills(fills, opts.asset);
1239
1095
  if (trades.length === 0) {
@@ -1281,7 +1137,7 @@ async function run() {
1281
1137
  if (opts.showPnlDetail) {
1282
1138
  printPnlDetail(analyses);
1283
1139
  }
1284
- printMetrics(analyses);
1140
+ printMetrics(analyses, { startMs: Date.parse(gte), endMs: Date.parse(lte) });
1285
1141
  if (opts.csv) {
1286
1142
  exportCsv(analyses, opts.csv);
1287
1143
  }