forked from froooze/DEXBot2
-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathconstants.ts
More file actions
2001 lines (1776 loc) · 101 KB
/
Copy pathconstants.ts
File metadata and controls
2001 lines (1776 loc) · 101 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
'use strict';
/**
* modules/constants.ts - Configuration and Constants
*
* Global configuration, constants, and defaults for DEXBot2.
* Most exported objects are frozen at module load to prevent accidental runtime modifications.
* Local overrides can be loaded from profiles/general.settings.json
*
* ===============================================================================
* EXPORTED CONSTANTS
* ===============================================================================
*
* ENUM DEFINITIONS:
* 1. ORDER_TYPES - Grid entry categories
* { SELL: 'sell', BUY: 'buy', SPREAD: 'spread' }
* - SELL: Orders above market price, size in base asset (assetA)
* - BUY: Orders below market price, size in quote asset (assetB)
* - SPREAD: Placeholder orders in spread zone around market price
*
* 2. ORDER_STATES - Order lifecycle states (affects fund tracking)
* { VIRTUAL: 'virtual', ACTIVE: 'active', PARTIAL: 'partial' }
* - VIRTUAL: Not yet on-chain, size in funds.virtual (reserved)
* Also used for filled orders converted to SPREAD placeholders
* - ACTIVE: Placed on-chain, size in funds.committed
* - PARTIAL: Partially filled on-chain, mixed state
*
* DEFAULT CONFIGURATION (applied when not explicitly set):
* 3. DEFAULT_CONFIG - Bot configuration defaults
* Price: startPrice, minPrice, maxPrice, incrementPercent, targetSpreadPercent
* Control: active, dryRun
* Trading pair: assetA, assetB
* Allocation: weightDistribution, botFunds, activeOrders
*
* TIMING PARAMETERS:
* 4. TIMING - Operational timing constants
* SYNC_DELAY_MS, ACCOUNT_TOTALS_TIMEOUT_MS, MILLISECONDS_PER_SECOND
* BLOCKCHAIN_FETCH_INTERVAL_MIN, FILL_DEDUPE_WINDOW_MS
* FILL_RECORD_RETENTION_MS
* LOCK_TIMEOUT_MS, SYNC_LOCK_TIMEOUT_MS
* CONNECTION_TIMEOUT_MS, DAEMON_STARTUP_TIMEOUT_MS
* RUN_LOOP_DEFAULT_MS, OPEN_ORDERS_SYNC_LOOP_ENABLED, CHECK_INTERVAL_MS
* CREDENTIAL_BROADCAST_TIMEOUT_MS, CREDENTIAL_DAEMON_INNER_DEADLINE_MS
*
* GRID & ORDER LIMITS:
* 5. GRID_LIMITS - Grid sizing and scaling constraints
* MIN_SPREAD_FACTOR, MIN_ORDER_SIZE_FACTOR, MIN_SPREAD_ORDERS
* FUND_INVARIANT_PERCENT_TOLERANCE, GRID_REGENERATION_PERCENTAGE
* PARTIAL_DUST_THRESHOLD_PERCENTAGE, PRICE_TOLERANCE_MAX_PERCENT
* Includes GRID_COMPARISON sub-object for grid divergence metrics
*
* 6. INCREMENT_BOUNDS - Price increment percentage validation
* MIN_PERCENT, MAX_PERCENT, MIN_FACTOR, MAX_FACTOR
*
* FEE CONFIGURATION:
* 7. FEE_PARAMETERS - Fee calculation and reservation parameters
* BTS_RESERVATION_MULTIPLIER, BTS_FALLBACK_FEE
* MAKER_FEE_PERCENT, MAKER_REFUND_PERCENT, TAKER_FEE_PERCENT
* GRAPHENE_FEE_RATE_DENOM, GRAPHENE_COLLATERAL_RATIO_DENOM
* BTS_ACQUIRE_THRESHOLD, BTS_ACQUIRE_TARGET_MULTIPLIER, POOL_SLIPPAGE_TOLERANCE
*
* API & BLOCKCHAIN:
* 8. API_LIMITS - Blockchain API call constraints
* POOL_BATCH_SIZE, MAX_POOL_SCAN_BATCHES, ORDERBOOK_DEPTH, LP_API_MAX_PAGE
*
* FILL PROCESSING:
* 9. FILL_PROCESSING - Fill event handling configuration
* MODE, OPERATION_TYPE,
* MAX_CONSECUTIVE_CONSUMER_FAILURES, CONSUMER_BACKOFF_INITIAL_MS, CONSUMER_BACKOFF_MAX_MS
* (Fill/broadcast batch sizing is derived from the grid gap-slot count;
* see DEXBot._getGapSlotBatchSize.)
*
* MARKET ADAPTER CONFIGURATION:
* 10. MARKET_ADAPTER - Price tracking and grid recalculation trigger settings
* AMA_DELTA_THRESHOLD_PERCENT: % change in AMA center price that triggers grid reset
* DEFAULT_AMA_KEY: Default AMA profile used for `gridPrice: "ama"`
* AMAS: Built-in AMA1..AMA4 presets for market adapter defaults
* Related to bot AMA configuration (profiles/bots.json: ama.enabled, erPeriod, etc.)
* Stored in: profiles/general.settings.json
*
* MAINTENANCE & MONITORING:
* 11. MAINTENANCE - Background maintenance task configuration
* CLEANUP_PROBABILITY: Probability of running cleanup on each fill cycle
* Note: HEALTH_CHECK_INTERVAL_MS lives under NODE_MANAGEMENT (12)
*
* 12. NODE_MANAGEMENT - Multi-node health checking and failover configuration
* DEFAULT_NODES: List of BitShares nodes for redundancy
* HEALTH_CHECK_INTERVAL_MS, HEALTH_CHECK_TIMEOUT_MS, MAX_PING_MS
* BLACKLIST_THRESHOLD: Failures before node is blacklisted
* EXPECTED_CHAIN_ID: BitShares mainnet chain ID validation
* SELECTION_STRATEGY: Node selection algorithm (latency-based)
*
* 13. UPDATER - Version checking and update notification
* ACTIVE, REPOSITORY_URL, BRANCH, SCHEDULE
*
* LOGGING CONFIGURATION:
* 14. LOGGING_CONFIG - Structured logging configuration
* changeTracking: Smart change detection
* display.colors: TTY color support
* display.fundStatus, display.statusSummary
* Categories for enabling/disabling log types
*
* 15. LOG_LEVEL - Current logging verbosity level
* Affects which messages are displayed: 'debug', 'info', 'warn', 'error'
*
* 16. PIPELINE_TIMING - Pipeline execution timing thresholds
* 17. COW_PERFORMANCE - Copy-on-write grid performance settings
* 18. REBALANCE_STATES - Rebalance lifecycle state enum
* 19. COW_ACTIONS - Copy-on-write action type enum
* 20. CR_ZONES - Credit ratio zone constants
* 21. DEFAULT_TARGET_CR - Default target collateral ratio
* 22. NATIVE_CLIENT - Native blockchain client configuration
* 23. LAUNCHER - Launcher configuration (PM2, Docker)
* 24. BUILD_DIR - Build directory path
* 25. BTS_PRECISION - BTS asset precision
* 26. DAEMON_ERRORS - Credential daemon error codes
* 27. DAEMON_CODES - Credential daemon codes
* 28. CREDENTIAL_PROMPTS - Credential prompt configuration
*
* ===============================================================================
*
* LOCAL SETTINGS OVERRIDE:
* Read from profiles/general.settings.json if it exists.
* Supports overriding any exported constant with custom values.
* Useful for development, testing, and performance tuning.
*
* FREEZING:
* Most exported objects are frozen at module load to prevent accidental runtime modifications.
* This ensures constants remain truly constant throughout bot lifetime.
*
* ===============================================================================
*/
// Order categories used by the OrderManager when classifying grid entries.
import { BUILD_DIR } from './utils/build_dir.js';
import { readGeneralSettings } from './general_settings.js';
import { mergeSettings } from './settings_merge.js';
import { getErrorMessage } from './utils/errors.js';
const ORDER_TYPES = Object.freeze({
SELL: 'sell',
BUY: 'buy',
SPREAD: 'spread'
});
// Life-cycle states assigned to generated or active orders.
// State transitions affect fund calculations in manager.recalculateFunds()
const ORDER_STATES = Object.freeze({
VIRTUAL: 'virtual', // Not on-chain, size in funds.virtual; also used for fully filled orders converted to SPREAD
ACTIVE: 'active', // On-chain, size in funds.committed.grid (and .chain if has orderId)
PARTIAL: 'partial' // On-chain, partially filled order, size in funds.committed.grid (and .chain if has orderId)
});
// Rebalance lifecycle states used by COW planning/broadcast/commit pipeline.
const REBALANCE_STATES = Object.freeze({
NORMAL: 'NORMAL',
REBALANCING: 'REBALANCING',
BROADCASTING: 'BROADCASTING'
});
// Canonical action labels used by grid reconciliation and batch broadcasting.
const COW_ACTIONS = Object.freeze({
CREATE: 'create',
CANCEL: 'cancel',
UPDATE: 'update'
});
// Defaults applied when instantiating an OrderManager with minimal configuration.
// These values are used when a parameter is not explicitly provided in the bot config.
let DEFAULT_CONFIG = {
// Price configuration
startPrice: "pool", // Market price source: "pool" (liquidity pool), "book" (order book), or numeric value
minPrice: "2x", // Lower price bound: "Nx" = N times below startPrice, or numeric value
maxPrice: "2x", // Upper price bound: "Nx" = N times above startPrice, or numeric value
gridPrice: null, // Optional reference price for x-factor bounds calculation.
// "pool" = use the live pool price for the pair
// "book" = use the live order book price for the pair
// "ama"/"ama1".."ama4" = use the effective center snapshot from profiles/orders/<botKey>.dynamicgrid.json
// numeric = fixed numeric value
// null = use startPrice
incrementPercent: 0.5, // Price step between grid levels (0.5 = 0.5% geometric spacing)
targetSpreadPercent: 2, // Target spread width between best buy and best sell (2 = 2%)
// Bot control
active: true, // Whether bot should actively place/manage orders
dryRun: false, // If true, simulate operations without blockchain transactions
creditOnly: false, // If true, skip grid trading and only run credit operations
// Trading pair
assetA: null, // Base asset symbol (e.g., "BTS")
assetB: null, // Quote asset symbol (e.g., "USD")
// Fund allocation
weightDistribution: { sell: 1, buy: 1 }, // Geometric weight for order sizing (1 = ~1:2 center/outer split, 0.5 = linear)
botFunds: { sell: "100%", buy: "100%" }, // Percentage of wallet balance to allocate ("100%" or numeric value)
activeOrders: { sell: 20, buy: 20 }, // Number of orders to maintain closest to market on each side
reserveOrders: { buy: 0, sell: 0 }, // Edge-pinned insurance orders resting live outside the window (buy: grid floor, sell: grid ceiling; 0 disables)
// BTS fee management for non-BTS pairs
min_BTS_value: null, // Minimum BTS balance to maintain (null = auto from activeOrders × fees × multiplier)
};
// Range quality zones for price bounds (minPrice/maxPrice multipliers).
// Used for pre-entry legend in the bot editor (mountain-style).
// Thresholds per user spec: green >=2x, yellow >=1.55x, orange 1.40x–1.55x, red <1.40x.
let RANGE_QUALITY = {
GREEN_MIN: 2.0, // >=2.0x → green (wide)
YELLOW_MIN: 1.55, // >=1.55x → yellow (effeciant)
ORANGE_MIN: 1.40, // >=1.40x → orange (tight)
RED_MAX: 1.40, // <1.40x → red (suizidal) — exclusive upper bound for red
};
// Timing constants used by OrderManager and helpers
let TIMING = {
SYNC_DELAY_MS: 500,
ACCOUNT_TOTALS_TIMEOUT_MS: 10000,
// Conversion factor: milliseconds per second
MILLISECONDS_PER_SECOND: 1000,
// Blockchain fetch interval: how often to refresh blockchain account values (in minutes)
// Default: 240 minutes (4 hours). Set to 0 or non-number to disable periodic fetches.
BLOCKCHAIN_FETCH_INTERVAL_MIN: 240,
// Override interval for shared accounts (multiple bots on same account).
// When the fund registry detects multiple bots on the same account, the fetch
// interval drops to this value to reduce stale balance window.
SHARED_ACCOUNT_FETCH_INTERVAL_MIN: 5,
// BTS acquisition for non-BTS pairs: min cooldown between acquisition attempts (minutes)
BTS_ACQUIRE_COOLDOWN_MIN: 60,
// Fill processing timing
FILL_DEDUPE_WINDOW_MS: 5000, // 5 seconds - window for deduplicating same fill events
FILL_RECORD_RETENTION_MS: 7 * 24 * 60 * 60 * 1000, // 7 days - how long to keep persisted fill records (was 1h, increased to close double-credit window on restarts >1h apart)
PROCESSED_FILL_PERSIST_BATCH_MS: 250, // 250ms - coalesce processed-fill persistence writes under burst load
PROCESSED_FILL_PERSIST_BATCH_SIZE: 25, // Flush immediately once this many processed fills are queued
AUDIT_LOG_MAX_SIZE: 100 * 1024 * 1024, // 100 MB total disk budget for audit logs (current + rotated)
AUDIT_LOG_MAX_FILES: 5, // number of rotated audit log files to retain
// Order locking timing
// Reduced from 30s to 10s to prevent lock-based starvation under high fill rates.
// Locks that exceed this timeout are auto-expired by _cleanExpiredLocks() to ensure
// orders are never permanently blocked if a process crashes while holding the lock.
// This self-healing mechanism prevents deadlocks while still protecting against races.
LOCK_TIMEOUT_MS: 10000, // 10 seconds - balances transaction latency with lock starvation prevention
// Sync lock acquisition timeout - prevents indefinite lock hangs
// Uses Promise.race() to enforce timeout on lock acquisition attempts
SYNC_LOCK_TIMEOUT_MS: 20000, // 20 seconds - prevents deadlocks while allowing slow operations
// Suspect-empty-read guard (Fix C): an empty open-orders read while the
// grid still holds placed orders is usually a lagging/partial node, not a
// genuinely emptied account. Reconciliation is refused for this many
// consecutive empty reads; only after the limit is confirmed does the
// sync accept the empty account (virtualize everything). Any non-empty
// read resets the counter.
SYNC_SUSPECT_EMPTY_READ_LIMIT: 3,
// Delay before the confirming re-read when a grid resync observes an EMPTY
// open-order read. A trigger reset that wipes a live grid on a single
// 0-order read is the phantom-reset failure mode; the resync demands one
// confirming re-read (after this delay) before treating the account as
// genuinely empty. A contradicted re-read (non-empty) aborts acceptance
// and feeds the fresh snapshot to the resync instead.
SYNC_EMPTY_READ_CONFIRM_DELAY_MS: 2000,
// Connection and initialization timeouts
CONNECTION_TIMEOUT_MS: 30000, // 30 seconds - BitShares client connection establishment timeout
DAEMON_STARTUP_TIMEOUT_MS: 60000, // 60 seconds - Private key daemon startup timeout
RETRY_BACKOFF_CAP_MS: 30000, // 30 seconds - Max exponential backoff delay for connection retries
DAEMON_PING_TIMEOUT_MS: 5000, // 5 seconds - Private key daemon ping/healthcheck timeout
CREDENTIAL_DAEMON_WATCHDOG_MS: 60000, // 60 seconds - Credential daemon watchdog polling interval
// Main loop and polling defaults
RUN_LOOP_DEFAULT_MS: 300000, // 5 minutes - default open-orders sync cycle delay (env override: OPEN_ORDERS_SYNC_LOOP_MS)
OPEN_ORDERS_SYNC_LOOP_ENABLED: false, // Preferred flag: continuous open-order watchdog sync loop (default false — react to fills only)
CHECK_INTERVAL_MS: 100, // 100 milliseconds - polling interval for connection/daemon readiness checks
// Dust health check interval: periodic detection of partials below the dust
// threshold that were missed by the post-fill pipeline (e.g. from prior
// bot lifetime after crash/restart).
DUST_HEALTH_CHECK_INTERVAL_MS: 5 * 60 * 1000, // 5 minutes
// DUST_CANCEL_TIMEOUT_MS: Max time to wait for the fill-processing lock
// before skipping a dust-cancel cycle. Must be short enough to not starve
// the timer (5 min / 5 s = 60 slots per interval), but long enough to
// survive moderate fill-processing bursts.
DUST_CANCEL_TIMEOUT_MS: 5 * 1000, // 5 seconds
// FILL_BROADCAST_DEFER_MAX_MS: Bound on fill-consumer deferral while a
// broadcast region is active. The consumer defers (instead of acquiring
// the fill lock and sleeping up to 30s inside it) so concurrent consumers
// never queue as lock waiters and time out. Past this bound a stuck flag
// falls through to the legacy in-lock wait, which still caps at 30s and
// proceeds — a leaked flag can delay fills, never starve them.
FILL_BROADCAST_DEFER_MAX_MS: 60 * 1000, // 60 seconds
// FILL_TOTALS_RETRY_BASE_MS / MAX_MS: Backoff for re-processing fills
// parked when the accountTotals refresh failed (stale snapshot). The
// deferred fills are held OUTSIDE the live queue and re-queued on a
// timer — never dropped, never hot-spun. Delay doubles per consecutive
// parked cycle (10s, 20s, 40s) capped at 60s; the attempt counter resets
// on the first cycle with no deferrals.
FILL_TOTALS_RETRY_BASE_MS: 10 * 1000, // 10 seconds
FILL_TOTALS_RETRY_MAX_MS: 60 * 1000, // 60 seconds
// SPREAD_STALE_WARN_MS / ESCALATE_MS: Out-of-spread persistence watchdog.
// The spread check retries every pipeline-empty tick (level-triggered),
// but a correction can keep producing zero candidates (no funds side, no
// correctable slots) while the grid sits stale. Past the warn threshold
// each tick is surfaced; past the escalate threshold maintenance requests
// a structural resync that re-centers the grid (existing resync guards
// dedupe concurrent requests; re-center clears any held-plan signature
// by moving the boundary). Time-based so it holds for any tick cadence.
SPREAD_STALE_WARN_MS: 10 * 60 * 1000, // 10 minutes
SPREAD_STALE_ESCALATE_MS: 30 * 60 * 1000, // 30 minutes
// Dedicated escalation cooldown for the spread-stale watchdog. Deliberately
// separate from BOUNDARY_HOLD_RESYNC_COOLDOWN_MS so tuning boundary-hold
// behavior never silently changes the spread watchdog cadence.
SPREAD_STALE_RESYNC_COOLDOWN_MS: 5 * 60 * 1000, // 5 minutes
// BOUNDARY_HOLD_RESYNC_THRESHOLD / COOLDOWN: consecutive boundary-hold
// batches (each carrying fresh fills) after which the COW executor asks
// for a guard-aware structural re-center. A hold is correct maker
// discipline when a guard vetoes stale-priced refills, but a growing run
// means the grid is trailing the market and fill-less replans cannot heal
// it (they re-derive the identical veto). At the threshold the executor
// requests a structural resync that re-derives centers on the live pivot;
// the cooldown prevents resync storms.
BOUNDARY_HOLD_RESYNC_THRESHOLD: 4,
BOUNDARY_HOLD_RESYNC_COOLDOWN_MS: 5 * 60 * 1000, // 5 minutes
// GRID_PRICE_INVARIANT_RESYNC_THRESHOLD / COOLDOWN: consecutive COW batches
// that reject the SAME slot's emission as off-grid, after which the guard
// asks for a structural resync. Without this an in-process corrupted
// slot.price is rejected forever: every cycle re-plans from that same slot
// object (the spread-correction planner carries candidate.price straight
// from manager.orders), is rejected again, and warns. Nothing heals it
// short of a restart, so the slot is dead while the bot is alive and the
// repeated warns train operators to ignore them.
//
// The structural resync is the right healer because loadGrid now repairs
// slot prices from the genesis ladder on reload (both 'log' and 'enforce'
// modes), and the full-reset fallback rebuilds clean geometry. Auto-healing
// in place at rejection time is deliberately NOT done: silently overwriting
// slot.price would erase the diagnostic signal that distinguishes the four
// corruption sources (legacy persisted state, migration fallback,
// genesis-identity mismatch, unknown live writer). Count first, escalate on
// persistence.
GRID_PRICE_INVARIANT_RESYNC_THRESHOLD: 3,
GRID_PRICE_INVARIANT_RESYNC_COOLDOWN_MS: 15 * 60 * 1000, // 15 minutes
// DEFERRED_HOLD_ESCALATE_MS: how long an unchanged deferred-hold signature
// (same chain order ids/prices/sizes/reasons) may persist before the hold
// is escalated to a structural resync. Out-of-rail orphans hold locked
// funds and are never auto-cancelled per cycle (by design -- cancelling on
// ambiguous evidence is irreversible), so "held indefinitely" had no exit.
// The resync is that exit: the full reset's reconcile is update-first
// (unmatched chain orders are price-updated onto rail slots, cancelling
// only true surplus), so funds are released without inventing a new
// cancellation policy.
DEFERRED_HOLD_ESCALATE_MS: 24 * 60 * 60 * 1000, // 24 hours
DEFERRED_HOLD_RESYNC_COOLDOWN_MS: 6 * 60 * 60 * 1000, // 6 hours
// Blockchain settle delay before follow-up structural work after a scheduled maintenance action.
// Gives maintenance-triggered cancels/rebalances time to acquire locks, broadcast, and settle
// before a deferred grid resync attempts more on-chain changes.
BLOCKCHAIN_SETTLE_DELAY_MS: 6000,
// Credit deal proactive renewal timing
CREDIT_DEAL_CHECK_INTERVAL_MIN: 60, // How often to check credit deal expiry (minutes)
CREDIT_DEAL_EXPIRY_THRESHOLD_HOURS: 12, // Proactively renew deals expiring within this window
CREDIT_DEAL_SPLIT_MAX_PIECES: 48, // Max pieces per _splitOversizedCreditDeals cycle (~4.8min at 6s/piece)
// LOCK_REFRESH_MIN_MS: Minimum interval for refreshing order lock leases during long operations.
// Prevents lock expiration during extended reconciliations or batch operations.
// Default: 250ms (4 refreshes per second minimum during long operations).
LOCK_REFRESH_MIN_MS: 250,
// LOG_THROTTLE_INTERVAL_MS: Default throttle interval for _logThrottled
// in accounting.ts et al. Prevents repeated identical log messages from
// flooding the log during sustained error conditions.
LOG_THROTTLE_INTERVAL_MS: 30000,
// BOTS_CONFIG_POLL_INTERVAL_MS: How often to poll bots.json for changes
// (semantic fingerprint check via adapter_requirement). Guarantees that
// new/updated active bot entries are detected within this window.
// Decoupled from the heavy BLOCKCHAIN_FETCH_INTERVAL_MIN (240min) so
// config changes are visible quickly even on single-bot accounts. This is
// the single shared interval for both the unlock-wrapper market-adapter
// watchdog and the per-bot fallback poll in wrapper-less modes.
BOTS_CONFIG_POLL_INTERVAL_MS: 60 * 1000,
// CREDENTIAL_BROADCAST_TIMEOUT_MS: Outer timeout for a credential-daemon broadcast
// request, enforced by the bot socket client (modules/dexbot_credential_client.ts).
// Rationale: Broadcasts can take much longer than read-only daemon calls because the
// daemon must sign locally, then push the signed transaction to a BitShares node and
// wait for chain inclusion. 30s gives slow mainnet nodes enough headroom on cold
// start while still bounding bot-side wait time.
// If this outer timer fires before the daemon responds, the bot raises a typed
// BroadcastUncertainError and enters the recovery path (chain may or may not have
// accepted the operations).
CREDENTIAL_BROADCAST_TIMEOUT_MS: 30000,
// CREDENTIAL_DAEMON_INNER_DEADLINE_MS: Inner deadline enforced inside the credential
// daemon's broadcastWithDeadline (credential-daemon.ts). Must be strictly less than
// CREDENTIAL_BROADCAST_TIMEOUT_MS so the daemon can report a typed
// { success:false, code:'BROADCAST_DEADLINE' } failure before the bot-side
// socket timer fires. 5s of slack is enough for the bot to receive and
// process the typed reply on a slow connection. 25s gives slow mainnet
// nodes most of the outer window for a successful broadcast; the recovery
// path handles whatever takes longer. The deadline caps the TOTAL wall
// time across all daemon broadcast attempts, so retries can never hang
// the bot.
CREDENTIAL_DAEMON_INNER_DEADLINE_MS: 25000,
// CREDENTIAL_DAEMON_BROADCAST_RETRIES: Number of broadcast attempts pinned
// to a SINGLE node in the credential daemon's broadcastWithDeadline
// (attempts total, not retries after the first). All attempts use the
// same node — only when they ALL fail with failures that provably never
// reached the chain (pre-transmit: connection setup, WebSocket not open,
// frame send errors) does the daemon report the node failure to the node
// health ledger and rotate to the next best node. Uncertain failures (RPC
// timeout, connection dropped with a response pending) are NEVER retried —
// the transaction may have landed and a re-sign would duplicate it — they
// are reported to the bot as BROADCAST_DEADLINE for verify-before-retry.
// Total wall time across all nodes/attempts stays capped by
// CREDENTIAL_DAEMON_INNER_DEADLINE_MS.
CREDENTIAL_DAEMON_BROADCAST_RETRIES: 3,
// CREDENTIAL_DAEMON_BROADCAST_BACKOFF_MS: Delay between pinned-node
// attempts in the credential daemon. Pre-transmit failures are fast (no
// RPC wait), so a short backoff keeps the retry burst well inside the
// inner deadline.
CREDENTIAL_DAEMON_BROADCAST_BACKOFF_MS: 1000,
// CREDENTIAL_DAEMON_SOCKET_TIMEOUT_MS: Idle timeout for credential daemon
// Unix socket client connections. If a client connects but sends no complete
// request (no newline-delimited JSON) within this window, the connection is
// closed. Prevents idle connections from accumulating in the daemon.
CREDENTIAL_DAEMON_SOCKET_TIMEOUT_MS: 30000,
// CREDENTIAL_DAEMON_MAX_BUFFER_SIZE: Maximum per-connection buffer size for
// partial (non-newline-terminated) data. If a client sends more data than
// this without a newline, the connection is terminated with an error.
// Prevents OOM from a single malicious or broken client connection.
CREDENTIAL_DAEMON_MAX_BUFFER_SIZE: 1024 * 1024,
// SAFETY_NET_SYNC_TIMEOUT_MS: Cap on the post-reconnect safety-net sync
// in dexbot_class.ts. Must stay below the 20s shutdown lock timeout so
// it never holds _fillProcessingLock longer than the shutdown deadline.
SAFETY_NET_SYNC_TIMEOUT_MS: 25000,
// TARGETED_DRIFT_SYNC_COOLDOWN_MS: Minimum interval between targeted drift
// reconciliation cycles. Prevents rapid re-triggering when the grid is
// oscillating around the drift threshold.
TARGETED_DRIFT_SYNC_COOLDOWN_MS: 60000,
// LIGHTWEIGHT_SYNC_CHECK_INTERVAL_MS: Interval for the lightweight open-orders consistency
// check in the maintenance loop. Fetches on-chain order count and compares to grid active
// order count without a full sync. Catches silent divergence early (e.g., orders that were
// cancelled externally or never placed).
// Default: 900000 (15 min) — balances detection speed with blockchain query load.
LIGHTWEIGHT_SYNC_CHECK_INTERVAL_MS: 900000,
// SYNC_LOCK_FORCE_RELEASE_AGE_MS: Maximum age for a sync lock before it is force-released.
// If a sync operation holds the lock longer than this, the lock is released so subsequent
// syncs are not permanently blocked. The sync that timed out continues running but the lock
// is detached from it.
// Derived as SYNC_LOCK_TIMEOUT_MS * 2 after override merge (see end of this module).
SYNC_LOCK_FORCE_RELEASE_AGE_MS: 40000, // overridden by derivation after merge
// GRID_BLOAT_RESYNC_GRACE_MS: Grace period before the maintenance runtime
// re-triggers a structural resync for a previously detected grid bloat.
// Prevents immediate re-triggering when Grid.loadGrid already scheduled one
// but requestStructuralGridResync was not yet wired (startup path).
// Default: 300000 (5min) — gives the initial resync time to resolve.
GRID_BLOAT_RESYNC_GRACE_MS: 300000,
// MAX_ACCOUNT_TOTALS_AGE_MS: Maximum age of cached accountTotals before
// optimistic deductions are refused and a fresh chain fetch is required.
// Prevents optimistic balance drift from diverging too far from chain reality
// between periodic blockchain fetches.
// Default: 120000 (2 min) — 2x the shared-account fetch interval so single-bot
// setups always have a recent-enough baseline.
MAX_ACCOUNT_TOTALS_AGE_MS: 120000,
// STALE_TOTALS_WARN_RATE_LIMIT_MS: How often an accountTotals-staleness
// warning may be repeated for a given manager. Repeated identical warnings
// per fill batch (when the chain is unreachable) flood the logs; one per
// interval keeps the signal alive without drowning out other messages.
STALE_TOTALS_WARN_RATE_LIMIT_MS: 60000,
// SAFETY_PAUSE_TIMEOUT_MS: Maximum duration a fund recalculation pause may
// remain active. If pauseFundRecalc is not resumed within this window, a
// safety watchdog force-resets the counter to prevent permanent fund recalc
// suppression from a missed finally block.
// Default: 30000 (30s) — ample for any bulk operation that pauses recalc.
SAFETY_PAUSE_TIMEOUT_MS: 30000,
// LOGGER_DRAIN_TIMEOUT_MS: Per-cycle timeout for logger _drainQueue.
// If a single drain cycle exceeds this, remaining lines are discarded and
// the flush promise is force-resolved to prevent hanging shutdown.
// Default: 10000 (10s) — generous for file I/O.
LOGGER_DRAIN_TIMEOUT_MS: 10000,
// SLOW_RECONNECT_INTERVAL_MS: Interval for perpetual reconnect polling
// after all standard exponential-backoff attempts are exhausted.
// Default: 60000 (60s) — slow but ensures eventual reconnection.
SLOW_RECONNECT_INTERVAL_MS: 60000,
// FETCH_HISTORY_PAGE_TIMEOUT_MS: Per-page timeout inside
// fetchFillHistoryEntries. Prevents a single slow page from blocking
// the entire history scan for RPC_TIMEOUT_MS.
// Default: 10000 (10s).
FETCH_HISTORY_PAGE_TIMEOUT_MS: 10000,
// FETCH_HISTORY_TOTAL_DEADLINE_MS: Outer deadline for the entire
// fetchFillHistoryEntries loop. If the scan takes longer than this,
// it returns partial results rather than blocking notice processing.
// Default: 60000 (60s).
FETCH_HISTORY_TOTAL_DEADLINE_MS: 60000,
// OFFER_CACHE_TTL_MS: TTL for the credit runtime's _objectCache entry
// for credit offers. After this age, _getOfferById re-fetches from chain.
// Default: 600000 (10 min) — offers rarely change, so a moderate cache is safe.
OFFER_CACHE_TTL_MS: 600000,
// MPA_FEED_MAX_AGE_MS: Maximum age for a cached MPA feed price fallback.
// If the last known feed price is older than this, it is considered stale
// and unavailable rather than silently using an outdated value.
// Default: 1800000 (30 min).
MPA_FEED_MAX_AGE_MS: 1800000,
// CREDIT_RATE_MAX_AGE_MS: Maximum age for a cached credit conversion rate
// fallback. Same rationale as MPA_FEED_MAX_AGE_MS.
// Default: 900000 (15 min).
CREDIT_RATE_MAX_AGE_MS: 900000,
};
// Grid limits and scaling constants
let GRID_LIMITS = {
// MIN_SPREAD_FACTOR: Ensures spread is at least (incrementPercent × MIN_SPREAD_FACTOR) slots wide.
// Rationale: Spread must be sufficiently wide to:
// 1. Avoid order collision (orders too close get rejected by blockchain)
// 2. Allow bid-ask arbitrage room (market makers profit from the spread)
// 3. Scale proportionally to grid spacing (tighter increments need tighter spread buffer)
//
// Example Calculation (incrementPercent = 0.5%, targetSpread = 2%):
// - MIN_SPREAD_FACTOR = 2.1 → minSpread = 0.5% × 2.1 = 1.05%
// - But target is 2%, so final spread = max(1.05%, 2%) = 2% (target wins)
// - This ensures spread is at least (0.5% × 2.1) but respects user's targetSpread
//
// Default: 2.1 ensures 3-slot minimum gap even with tight increment (see modules/order/utils/math.ts::calculateGapSlots)
MIN_SPREAD_FACTOR: 2.1,
// MIN_ORDER_SIZE_FACTOR: Minimum order size = blockchain_minimum × this factor.
// Rationale: Orders smaller than blockchain minimum are rejected.
// - Blockchain minimum ≈ 1 satoshi (10^-8 per unit)
// - Float arithmetic and fee deductions can round amounts down
// - Safety factor = 50× ensures orders survive rounding
// Example: If blockchainMin = 1 BTS, then minSize = 50 BTS (very conservative for mainnet)
// This trades off efficiency (larger minimum) for reliability (never hits rounding floor)
MIN_ORDER_SIZE_FACTOR: 50,
// PRICE_TOLERANCE_MAX_PERCENT: Maximum price tolerance as fraction of grid price.
// Clamps calculatePriceTolerance so tiny dust-sized orders never produce tolerances
// larger than this fraction. 0.01 = 1% of price.
PRICE_TOLERANCE_MAX_PERCENT: 0.01,
// PRICE_TOLERANCE_MIN_ABSOLUTE: Floor for the price tolerance cap in price units.
// Ensures the cap is non-zero even for extremely cheap assets.
PRICE_TOLERANCE_MIN_ABSOLUTE: 0.0001,
// ORPHAN_ADOPTION_TOLERANCE_MULTIPLIER: Legacy fallback multiplier for calculatePriceTolerance
// (sync_engine pass-2 before genesis). With genesis-frozen nearest-slot (slotIndexForPrice)
// this multiplier is deprecated — deterministic slotId equality replaces widening. Kept for
// migration fallback when genesis missing; otherwise unused.
ORPHAN_ADOPTION_TOLERANCE_MULTIPLIER: 4,
// GRID_REGENERATION_PERCENTAGE: Trigger threshold for automatic grid size recalculation.
// Works in BOTH directions (bidirectional), sharing one threshold:
// GROW: IF (availableFunds / allocatedCapital) x 100 >= threshold -> regenerate
// After fills, free balance rises relative to allocated grid capital.
// SHRINK: IF (gridTracked - allocatedCapital) / allocatedCapital x 100 >= threshold
// -> regenerate. After external fund removal the grid-tracked size
// (ACTIVE + PARTIAL + VIRTUAL) stays put while the allocation sinks, so
// affected orders are resized down (limit_order_update to a smaller size,
// which releases funds back on chain). Deliberately NOT based on per-side
// chain-total drops: a normal fill moves value across sides (pays one
// asset, receives the other), so fill handling owns that resize.
// Rationale: After fills, free balance rises relative to allocated grid capital.
// - 3% = regen triggered when available funds represent ≥3% of side allocation
// - This allows gradual accumulation while preventing lag during high-fill periods
// - If threshold too low: constant regeneration (churn, fees)
// - If threshold too high: capital remains underutilized, grid undersized
// Example: 20 active orders × 100 BTS each = 2000 BTS grid
// - availableFunds ≥ 60 BTS → (60/2000 = 3%) triggers regeneration
// - Allows ~3 fill-proceeds before resize (reduces churn)
// Checked independently per side, allowing asymmetric fill patterns.
GRID_REGENERATION_PERCENTAGE: 3,
// PARTIAL_DUST_THRESHOLD_PERCENTAGE: Threshold for treating partially-filled orders as "dust".
// Formula: IF (actualSize / idealSize) × 100 < threshold → dust
// Rationale: Partially-filled orders become progressively smaller.
// - 5% = orders below 5% of ideal size are rotated (to restore grid symmetry)
// - Dust orders waste grid slots (they should be closed or brought back to ideal size)
// - Rotation replaces dust order with a fresh one at proper size
// - Detects both accidental low fills and normal fill-chain truncation
// Example: idealSize = 100 BTS, but actual = 3 BTS → (3/100 = 3%) < 5% → dust
// - This order would be rotated to free the slot
PARTIAL_DUST_THRESHOLD_PERCENTAGE: 5,
// Allowed drift fraction before triggering fund-invariant recovery (0.1% = 0.001).
FUND_INVARIANT_PERCENT_TOLERANCE: 0.1,
// FUND_INVARIANT_HEAL_ON_RECOVERY_FAIL: guarded "trust-chain" free-balance
// seeding on recovery failure. When recovery keeps failing on the SAME
// one-sided drift (e.g. SELL tracked-free is consistently short of the
// chain total after a fill/broadcast chaos window), re-seed the tracked
// FREE balance for that side from the freshly fetched chain total minus
// the (already chain-reconciled) committed grid sizes, instead of looping
// recovery attempts and eventually blocking new orders. Default off —
// free-balance derivation can absorb third-party locked funds on SHARED
// accounts, so this is opt-in and (unless the allow-shared switch below is
// also set) refused when more than one bot is registered on the account.
FUND_INVARIANT_HEAL_ON_RECOVERY_FAIL: false,
// FUND_INVARIANT_HEAL_ALLOW_SHARED: permit the trust-chain heal even when
// multiple bots are registered on the same account. total = free + grid is
// then only an approximation of this bot's share (other bots' committed
// orders are part of the chain total); only enable when the account is
// exclusively managed by one bot at a time.
FUND_INVARIANT_HEAL_ALLOW_SHARED: false,
// FUND_INVARIANT_HEAL_MIN_PERSISTENT_CHECKS: consecutive fund-invariant
// checks that must report the same one-sided drift (same side, same
// direction) before the trust-chain heal may seed the free balance. Keeps
// a single transient mismatch from triggering a heal.
FUND_INVARIANT_HEAL_MIN_PERSISTENT_CHECKS: 2,
// FUND_INVARIANT_HEAL_MIN_PERSIST_MS: minimum wall-clock span between the
// first and last recorded drift check before the trust-chain heal may
// apply. A single busy fill cycle can run two quick recalculateFunds
// calls back-to-back; the duration gate keeps a sub-second double-recalc
// from satisfying the persistence requirement. 0 disables the gate.
FUND_INVARIANT_HEAL_MIN_PERSIST_MS: 30 * 1000,
// MIN_SPREAD_ORDERS: Minimum number of empty slots in spread zone (between best buy and best sell).
// Rationale: Spread must be sufficiently wide to:
// 1. Prevent order collision (blockchain rejects orders with identical price)
// 2. Ensure market makers profit from the bid-ask difference
// 3. Allow price movement without orders crossing each other
// Default: 2 (at least 2 empty slots between buy and sell sides)
// Example: Buy @ 99.9, empty, empty, Sell @ 100.1 → 2-slot spread (acceptable)
// Buy @ 99.9, empty, Sell @ 100.0 → 1-slot spread (too tight, rebalance triggered)
MIN_SPREAD_ORDERS: 2,
// GAP_EVACUATION_STREAK_THRESHOLD: Consecutive rebalance cycles a live
// on-chain order may sit inside the gap band (by slot-index geometry)
// before it is treated as a stuck gap-evacuation candidate. The streak
// is in-memory on the manager and resets on restart (a restart re-plans
// evacuation from scratch anyway).
GAP_EVACUATION_STREAK_THRESHOLD: 2,
// GAP_EVACUATION_CANCEL_THRESHOLD: Consecutive cycles after which the
// manager queues a cancel-only evacuation correction for a stuck in-band
// order (detected by GAP_EVACUATION_STREAK_THRESHOLD, which acts as the
// warn threshold one cycle earlier). Cancel settles the slot back to a
// spread placeholder — no re-placement, fee-light.
GAP_EVACUATION_CANCEL_THRESHOLD: 3,
// Grid comparison metrics
// Detects significant divergence between calculated (in-memory) and persisted grid state
// after order fills and rotations
// NOTE: Independent from MARKET_ADAPTER.AMA_DELTA_THRESHOLD_PERCENT
// - RMS_PERCENTAGE: Triggers grid reset when calculated grid diverges from blockchain state
// - AMA_DELTA_THRESHOLD_PERCENT: Triggers grid reset when AMA center price moves significantly
// Both can be configured independently in profiles/general.settings.json
GRID_COMPARISON: {
// Metric calculation: RMS (Root Mean Square) of relative order size differences
// Formula: RMS = √(mean of ((calculated - persisted) / persisted)²)
// Represents the quadratic mean of relative size errors
// Divergence threshold for automatic grid regeneration (RMS as percentage)
// When compareGrids() metric exceeds this threshold, updateGridOrderSizes will be triggered
// Set to 0 to completely disable RMS divergence checks (Issue #5: RMS Divergence Check Disabling)
//
// RMS Threshold Reference Table (for 5% distribution: 5% outliers, 95% perfect):
// ┌────────────────────────────────────────────────────────┐
// │ RMS % │ Avg Error │ Description │
// ├────────────────────────────────────────────────────────┤
// │ 0 │ N/A │ Disabled (no checks) │
// │ 4.5% │ ~1.0% │ Very strict │
// │ 9.8% │ ~2.2% │ Strict │
// │ 14.3% │ ~3.2% │ Default (balanced) │
// │ 20.1% │ ~4.5% │ Lenient │
// │ 31.7% │ ~7.1% │ Very lenient │
// │ 44.7% │ ~10% │ Extremely lenient │
// └────────────────────────────────────────────────────────┘
RMS_PERCENTAGE: 14.3
},
// RELATIVE_ORDER_UPDATE_THRESHOLD_PERCENT: Relative threshold for in-memory
// order equality checks in COW delta planning.
// Example: 0.1 means two values are considered equal when diff < 0.1% of magnitude.
// Note: Final blockchain update filtering still happens with integer precision checks.
RELATIVE_ORDER_UPDATE_THRESHOLD_PERCENT: 0.1,
// PRICE_DRIFT_TOLERANCE_MULTIPLIER: Legacy for price-drift-orphan tagging when genesis missing.
// With genesis-frozen nearest-slot, drift is deterministic no-available-nearest-slot (gap/occupied)
// not a tolerance band. Kept for diagnostics fallback only.
PRICE_DRIFT_TOLERANCE_MULTIPLIER: 4,
};
// Increment percentage bounds for grid configuration
let INCREMENT_BOUNDS = {
// Minimum increment percentage allowed (0.01%)
MIN_PERCENT: 0.01,
// Maximum increment percentage allowed (10%)
MAX_PERCENT: 10,
// Minimum increment as decimal factor (0.01% = 0.0001)
MIN_FACTOR: 0.0001,
// Maximum increment as decimal factor (10% = 0.10)
MAX_FACTOR: 0.10
};
// Fee-related parameters for order operations
let FEE_PARAMETERS = {
// BTS_RESERVATION_MULTIPLIER: Factor applied to totalTargetOrders to reserve BTS fee budget.
// Formula: BTS reserved = totalTargetOrders × BTS_RESERVATION_MULTIPLIER
// Rationale: Each order can be updated/cancelled during rebalancing, and each operation incurs a fee.
// - Conservative estimate: each order may be touched ~5 times during its lifetime
// - Orders get: created (1 fee), rotated (2 fees: cancel + place), updated (1 fee), cancelled (1 fee)
// - So ~5 fees per order is safe buffer to prevent fee starvation
// Example: 20 active orders → 20 × 5 = 100 BTS reserved for fee operations
// Set to 0 to disable fee reservation (not recommended for production).
BTS_RESERVATION_MULTIPLIER: 5,
// Fallback BTS fee (satoshis) when dynamic fee calculation fails.
// Rationale: During startup or when fee API is unavailable, use this conservative estimate.
// - 100 satoshis = 0.001 BTS (BTS_PRECISION = 5)
// - Using satoshi precision prevents integer division errors
// - Actual fees are calculated and deducted once fee API responds
BTS_FALLBACK_FEE: 100,
// MAKER_FEE_PERCENT: Percentage of base fee charged for maker orders (orders that rest in book).
// Rationale: BitShares incentivizes providing liquidity (making orders) with lower fees.
// - 0.1 = 10% of the base order creation fee
// - Typical: 2 BTS base fee × 0.1 = 0.2 BTS charged (maker)
// - vs. 2 BTS for taker orders (taker: 100% of base fee)
// - This 10× discount encourages grid bots (primarily makers) to place orders
MAKER_FEE_PERCENT: 0.1,
// MAKER_REFUND_PERCENT: Percentage of maker fee refunded after order execution/cancellation.
// Rationale: BitShares refunds unused maker fees when order is cancelled or fills.
// - 0.9 = 90% refund of the maker fee paid
// - Typical: Paid 0.2 BTS, get 0.18 BTS refund, net cost 0.02 BTS
// - Refund arrives in a separate transaction after cancellation
// - This incentive structure encourages taker participation (market efficiency)
MAKER_REFUND_PERCENT: 0.9,
// TAKER_FEE_PERCENT: Percentage of base fee charged for taker orders (orders that cross spread).
// Rationale: Takers (who immediately fill) pay full fee; they consume liquidity.
// - 1.0 = 100% of the base order creation fee
// - Typical: 2 BTS base fee × 1.0 = 2 BTS charged (no refund)
// - Full fee covers order broadcast and execution costs
TAKER_FEE_PERCENT: 1.0,
// GRAPHENE_FEE_RATE_DENOM: BitShares credit-offer fee-rate denominator.
// The on-chain fee_rate is an integer; fee percent = fee_rate / DENOM.
// Example: fee_rate 30000 → 30000 / 1000000 = 3% flat fee at repayment.
GRAPHENE_FEE_RATE_DENOM: 1000000,
// DEFAULT_MAX_FEE_RATE_PER_DAY: Default maximum daily fee rate for credit offers.
// 1/2900 ≈ 0.0003448 = 0.03448% per day = ~1.034% per month.
// This provides a reasonable default cap so short-duration high-flat-fee offers
// are rejected while long-duration low-flat-fee offers are accepted.
DEFAULT_MAX_FEE_RATE_PER_DAY: 1 / 2900,
// GRAPHENE_COLLATERAL_RATIO_DENOM: Denominator for target_collateral_ratio in call_order_update operations.
// Matches the protocol constant in bitshares-core (libraries/protocol/include/graphene/protocol/config.hpp).
// On-chain value = human_CR * DENOM. Example: 2.0 CR → 2000 on chain.
GRAPHENE_COLLATERAL_RATIO_DENOM: 1000,
// BTS_ACQUIRE_THRESHOLD: Trigger acquisition when BTS free drops below min_BTS_value × this factor.
// At 1.0, acquisition fires exactly when BTS hits min_BTS_value.
BTS_ACQUIRE_THRESHOLD: 1,
// BTS_ACQUIRE_TARGET_MULTIPLIER: Target BTS after acquisition = min_BTS_value × this factor.
// At 3.0, fills to 3× min_BTS_value, creating a hysteresis band.
// The bot won't re-acquire until it burns through 2× min_BTS_value in fees.
BTS_ACQUIRE_TARGET_MULTIPLIER: 3,
// POOL_SLIPPAGE_TOLERANCE: Max slippage for pool swaps (decimal fraction).
// min_to_receive = expectedAmount × (1 - tolerance).
POOL_SLIPPAGE_TOLERANCE: 0.02,
// FEE_CACHE_RETRY_ATTEMPTS: Number of retry attempts for fee cache initialization per asset.
// Each failed asset is retried with linear backoff (delay × attempt number).
// The sleep fires only between attempts, not after the final one, so 3 attempts
// produce 2 delays (1s + 2s = 3s worst-case wait per asset).
FEE_CACHE_RETRY_ATTEMPTS: 3,
// FEE_CACHE_RETRY_DELAY_MS: Base delay in ms between fee cache retry attempts.
// Actual delay = FEE_CACHE_RETRY_DELAY_MS × attempt_number (linear backoff).
FEE_CACHE_RETRY_DELAY_MS: 1000,
};
// Collateral ratio health zones for MPA position management.
// Only the red boundaries are stored; everything between is green (acceptable).
let CR_ZONES = Object.freeze({
RED_HIGH: 3.0,
RED_LOW: 1.7,
});
// Default target collateral ratio when returning to green zone.
// Computed as the midpoint of the green band.
const DEFAULT_TARGET_CR = (CR_ZONES.RED_LOW + CR_ZONES.RED_HIGH) / 2;
// Build output directory name (relative to project root).
// Centralized in modules/utils/build_dir.ts to avoid circular dependency with
// general_settings.ts (both constants.ts and general_settings.ts import it).
const DAEMON_ERRORS = Object.freeze({
SESSION_EXPIRED: 'invalid or expired session',
SOURCE_AUTH_DENIED: 'invalid source authentication',
});
// DAEMON_CODES: Canonical error-code constants shared between the credential
// daemon, the bot client, and tests. Every code value is identical to its
// property name so that a receiver comparing against e.g.
// err.code === DAEMON_CODES.BROADCAST_DEADLINE stays in sync with
// senders that assign the same symbol.
const DAEMON_CODES = Object.freeze({
BROADCAST_DEADLINE: 'BROADCAST_DEADLINE',
CREDENTIAL_DAEMON_UNAVAILABLE: 'CREDENTIAL_DAEMON_UNAVAILABLE',
});
// Interactive credential-prompt limits
// (modules/chain_keys.ts master-password authentication, etc.).
// Capped to prevent infinite stdin loops on a corrupted vault or a forgotten
// password without requiring Ctrl+C.
let CREDENTIAL_PROMPTS = {
// MAX_MASTER_PASSWORD_ATTEMPTS: Hard upper bound on master-password
// retries. The vault unlock throws MasterPasswordError after this many
// failed attempts. The check is enforced both in _promptPassword and at
// the top of the authenticate() loop as defense-in-depth.
// 3 attempts: scrypt N=2^17 makes each guess expensive (~1s), so 3
// attempts absorbs legitimate typos without meaningfully weakening
// brute-force resistance.
MAX_MASTER_PASSWORD_ATTEMPTS: 3,
};
// BTS blockchain precision constant.
// Number of decimal places for BTS on BitShares (5 decimals → 1 satoshi = 0.00001 BTS).
// Used for converting raw chain deferred_fee to float BTS in sync/reconcile paths.
let BTS_PRECISION = 5;
// API request limits and batch sizes for blockchain operations
let API_LIMITS = {
// Maximum number of liquidity pools per batch request during pool scanning
POOL_BATCH_SIZE: 100,
// Maximum number of batch iterations for pool scanning (~10k total pools)
MAX_POOL_SCAN_BATCHES: 100,
// Depth of order book to fetch for market price derivation
ORDERBOOK_DEPTH: 5,
// Maximum page for LP history API queries (market adapter)
LP_API_MAX_PAGE: 101,
};
// Fill processing configuration
let FILL_PROCESSING = {
// Mode for fill processing: 'history' reads from historical fills
MODE: 'history',
// Operation type for fill_order blockchain operations
OPERATION_TYPE: 4,
// NOTE: fill/broadcast batch sizing is derived from the grid gap-slot
// count (DEXBot._getGapSlotBatchSize). There is deliberately no fixed
// fill-batch or ops-per-broadcast constant here.
// MAX_CONSECUTIVE_CONSUMER_FAILURES: Threshold for the _consumeFillQueue
// watchdog. Below this count, the consumer re-schedules on every failure
// via setImmediate. At or above this count, the consumer switches to
// exponential backoff (see CONSUMER_BACKOFF_* below) instead of stopping
// permanently. The counter is reset on the consumer's success path and on
// shutdown, so transient failure bursts (e.g., a credential daemon outage
// that self-resolves) recover automatically once a cycle succeeds.
MAX_CONSECUTIVE_CONSUMER_FAILURES: 5,
// CONSUMER_BACKOFF_INITIAL_MS: First backoff delay after the consumer
// has hit MAX_CONSECUTIVE_CONSUMER_FAILURES. Each subsequent failure
// doubles the delay up to CONSUMER_BACKOFF_MAX_MS.
CONSUMER_BACKOFF_INITIAL_MS: 15000,
// CONSUMER_BACKOFF_MAX_MS: Upper bound on the consumer's backoff delay
// between retries once the failure budget is exhausted. With defaults
// (5 failures, 15s initial, 60s cap) the worst-case retry interval
// after a sustained outage is 60 seconds. The consumer NEVER stops
// re-scheduling entirely — the original infinite setImmediate loop was
// changed to slow-but-persistent retries.
// Reduced from 300s to 60s per tuning review: time-sensitive fills (e.g.,
// credential daemon recovery) should not wait 5 minutes between retries.
CONSUMER_BACKOFF_MAX_MS: 60000,
};
// Cleanup and maintenance parameters
let MAINTENANCE = {
// Probability of running cleanup operation on any cycle (0.1 = 10%)
CLEANUP_PROBABILITY: 0.1
};
// Node management and health checking configuration
let NODE_MANAGEMENT = {
// Whether node failover is enabled when no explicit setting is present.
DEFAULT_ENABLED: true,
// Startup retry/backoff for transient BitShares connection failures.
STARTUP_RETRY_INITIAL_DELAY_MS: 500,
STARTUP_RETRY_MAX_DELAY_MS: 5000,
STARTUP_REFRESH_INTERVAL_MS: 30000,
// Cooldown window between successive failover assessments (ms).
// Kept above the native transport close-coalesce window so cascading
// close events normally collapse into one node-health assessment.
FAILOVER_ASSESSMENT_COOLDOWN_MS: 500,
// Default node list (used if no config file)
DEFAULT_NODES: [
'wss://btsws.roelandp.nl/ws',
'wss://cloud.xbts.io/ws',
'wss://node.xbts.io/ws',
'wss://public.xbts.io/ws',
'wss://dex.iobanker.com/ws',
'wss://api.dex.trading/',
'wss://api.bts.mobi/ws',
'wss://api.btslebin.com/ws',
'wss://api.bitshares.dev/ws',
'wss://bitsharesapi.loclx.io'
],
// Health check defaults
HEALTH_CHECK_INTERVAL_MS: 4 * 60 * 60 * 1000, // 4 hours
CREDENTIAL_DAEMON_NODE_REFRESH_INTERVAL_MS: 60 * 60 * 1000, // 1 hour - lightweight health-cache reread by credential daemon
HEALTH_CHECK_TIMEOUT_MS: 5000, // 5 seconds per check
MAX_PING_MS: 3000, // Max acceptable latency
BLACKLIST_THRESHOLD: 3, // Failures before blacklist
BLACKLIST_COOLDOWN_MS: 24 * 60 * 60 * 1000, // 24 hours before retrying blacklisted nodes
FAILURE_REPORT_COOLDOWN_MS: 1000, // Min ms between failure count increments (prevents rapid-fire blacklisting)
// Consecutive successful health probes required before a node whose last
// failure came from the live transport (not a health check) gets its
// failure ledger reset. Prevents a single "is it up?" probe from erasing
// live-transport strikes, so flapping nodes still reach BLACKLIST_THRESHOLD.
LIVE_FAILURE_HEALTH_SUCCESS_STREAK: 2,
// Expected chain ID (BitShares mainnet)
EXPECTED_CHAIN_ID: '4018d7844c78f6a6c41c6a552b898022310fc5dec06da467ee7905a8dad512c8',
// Selection strategy
SELECTION_STRATEGY: 'latency' // latency-based selection
};
// Pipeline timeout configuration
let PIPELINE_TIMING = {
// TIMEOUT_MS: Maximum duration for pipeline operations before forcing maintenance.
// Rationale: If pipeline hangs (stuck in lock, slow blockchain, etc), force a maintenance cycle.
// - 300000 ms = 5 minutes
// - Prevents infinite hangs; ensures bot recovers or logs the problem
// - Typical pipeline cycle completes in <100ms (unless blockchain is slow)
// - 5 minute timeout allows for slow network periods while still being responsive
// When timeout is exceeded, pipeline triggers maintenance (logs, cleanup, state check).
TIMEOUT_MS: 300000,
// RECOVERY_RETRY_INTERVAL_MS: Minimum cooldown time between fund invariant recovery attempts.
// Rationale: When a fund tracking invariant violation is detected:
// - First attempt: immediate (no wait) — try to fix quickly
// - Subsequent attempts: wait at least this duration — prevent tight retry loops
// - Prevents blockchain query spam while allowing eventual convergence
//
// Backoff Timeline Example (defaults):
// - T=0s: Violation detected → immediate recovery attempt #1
// - T=45s: Next violation check → still in cooldown, skip retry
// - T=65s: Next violation check → cooldown expired, attempt #2
// - T=125s: Attempt #3 (another 60s)
// - T=180s: Attempt #4
// - T=240s: Attempt #5 (max reached)
// - T=300s: Fill arrives → counter resets, ready for new recovery episode
//
// Default: 60000 ms (60 seconds) balances responsiveness with resource efficiency.
// - Too low (e.g., 5s): constant recovery attempts, high blockchain load
// - Too high (e.g., 600s): slow detection of new invariant drift, bad UX
RECOVERY_RETRY_INTERVAL_MS: 60000,
// MAX_RECOVERY_ATTEMPTS: Maximum recovery retry attempts before giving up.
// Rationale: Limit total recovery effort to prevent runaway loop.
// - After N failed attempts, bot stops recovery until next fill/sync