dexbot 1.2.0 → 1.2.1

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 (34) hide show
  1. package/dist/credential-daemon.d.ts.map +1 -1
  2. package/dist/credential-daemon.js +6 -3
  3. package/dist/credential-daemon.js.map +1 -1
  4. package/dist/modules/credential_policy.d.ts.map +1 -1
  5. package/dist/modules/credential_policy.js +7 -5
  6. package/dist/modules/credential_policy.js.map +1 -1
  7. package/dist/modules/dexbot_class.d.ts.map +1 -1
  8. package/dist/modules/dexbot_class.js +52 -20
  9. package/dist/modules/dexbot_class.js.map +1 -1
  10. package/dist/modules/dexbot_maintenance_runtime.d.ts.map +1 -1
  11. package/dist/modules/dexbot_maintenance_runtime.js +4 -0
  12. package/dist/modules/dexbot_maintenance_runtime.js.map +1 -1
  13. package/dist/modules/order/accounting.d.ts +8 -2
  14. package/dist/modules/order/accounting.d.ts.map +1 -1
  15. package/dist/modules/order/accounting.js +25 -15
  16. package/dist/modules/order/accounting.js.map +1 -1
  17. package/dist/modules/order/grid.js +1 -1
  18. package/dist/modules/order/grid.js.map +1 -1
  19. package/dist/modules/order/grid_reconcile_internal.js +13 -2
  20. package/dist/modules/order/grid_reconcile_internal.js.map +1 -1
  21. package/dist/modules/order/manager.d.ts +82 -16
  22. package/dist/modules/order/manager.d.ts.map +1 -1
  23. package/dist/modules/order/manager.js +126 -235
  24. package/dist/modules/order/manager.js.map +1 -1
  25. package/dist/modules/order/sync_engine.d.ts +2 -3
  26. package/dist/modules/order/sync_engine.d.ts.map +1 -1
  27. package/dist/modules/order/sync_engine.js +13 -3
  28. package/dist/modules/order/sync_engine.js.map +1 -1
  29. package/dist/modules/order/utils/order.d.ts.map +1 -1
  30. package/dist/modules/order/utils/order.js +42 -6
  31. package/dist/modules/order/utils/order.js.map +1 -1
  32. package/dist/modules/types.d.ts +7 -12
  33. package/dist/modules/types.d.ts.map +1 -1
  34. package/package.json +1 -1
@@ -11,8 +11,7 @@
11
11
  *
12
12
  * SECTION 1: EXTERNAL DEPENDENCIES
13
13
  * SECTION 2: COW REBALANCE ENGINE
14
- * SECTION 3: STATE MANAGER
15
- * SECTION 4: ORDER MANAGER CLASS
14
+ * SECTION 3: ORDER MANAGER CLASS
16
15
  * ===============================================================================
17
16
  */
18
17
  // ===============================================================================
@@ -57,7 +56,7 @@ const { toFiniteNumber, isValidNumber } = Format;
57
56
  // 1. Master grid NEVER modified during planning (immutable during rebalance)
58
57
  // 2. Fills that arrive during rebalance are QUEUED, not lost
59
58
  // 3. Working grid is DISPOSABLE - if rebalance fails, discard it
60
- // 4. Only ONE rebalance plan active at a time (StateManager.rebalance)
59
+ // 4. Only ONE rebalance plan active at a time (_rebalanceState)
61
60
  // 5. Staleness detection aborts if master changes (fills, manual commands)
62
61
  //
63
62
  // FILL HANDLING DURING REBALANCE:
@@ -138,189 +137,7 @@ class COWRebalanceEngine {
138
137
  }
139
138
  }
140
139
  // ===============================================================================
141
- // SECTION 3: STATE MANAGER
142
- // ===============================================================================
143
- class StateManager {
144
- logger;
145
- rebalance;
146
- recovery;
147
- gridRegen;
148
- bootstrap;
149
- broadcast;
150
- signals;
151
- pipeline;
152
- constructor(options = {}) {
153
- this.logger = options.logger || null;
154
- this.reset();
155
- }
156
- reset() {
157
- this.rebalance = {
158
- state: REBALANCE_STATES.NORMAL,
159
- currentWorkingGrid: null
160
- };
161
- this.recovery = {
162
- phase: 'idle',
163
- attemptCount: 0,
164
- lastAttemptAt: 0,
165
- inFlight: false,
166
- lastFailureAt: 0
167
- };
168
- this.gridRegen = {
169
- buy: { armed: true, lastTriggeredAt: 0 },
170
- sell: { armed: true, lastTriggeredAt: 0 }
171
- };
172
- this.bootstrap = {
173
- isBootstrapping: false
174
- };
175
- this.broadcast = {
176
- isBroadcasting: false,
177
- startedAt: 0,
178
- };
179
- this.signals = {
180
- lastIllegalState: null,
181
- lastAccountingFailure: null
182
- };
183
- this.pipeline = {
184
- blockedSince: null,
185
- recoveryAttempted: false
186
- };
187
- }
188
- getRebalanceState() {
189
- return this.rebalance.state;
190
- }
191
- setRebalanceState(state) {
192
- this.rebalance.state = state;
193
- this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
194
- }
195
- isRebalancing() {
196
- return this.rebalance.state === REBALANCE_STATES.REBALANCING;
197
- }
198
- isBroadcasting() {
199
- return this.rebalance.state === REBALANCE_STATES.BROADCASTING;
200
- }
201
- setWorkingGrid(workingGrid) {
202
- this.rebalance.currentWorkingGrid = workingGrid;
203
- }
204
- getWorkingGrid() {
205
- return this.rebalance.currentWorkingGrid;
206
- }
207
- clearWorkingGrid() {
208
- this.rebalance.currentWorkingGrid = null;
209
- }
210
- recordRecoveryAttempt() {
211
- this.recovery.phase = 'scanning';
212
- this.recovery.attemptCount++;
213
- this.recovery.lastAttemptAt = Date.now();
214
- this.recovery.inFlight = true;
215
- }
216
- completeRecovery(success) {
217
- this.recovery.phase = success ? 'idle' : 'executing';
218
- this.recovery.inFlight = false;
219
- if (!success) {
220
- this.recovery.lastFailureAt = Date.now();
221
- }
222
- }
223
- isRecoveryInFlight() {
224
- return this.recovery.inFlight;
225
- }
226
- getRecoveryStats() {
227
- return { ...this.recovery };
228
- }
229
- isSideArmed(side) {
230
- return this.gridRegen[side]?.armed ?? false;
231
- }
232
- disarmSide(side) {
233
- if (this.gridRegen[side]) {
234
- this.gridRegen[side].armed = false;
235
- this.gridRegen[side].lastTriggeredAt = Date.now();
236
- }
237
- }
238
- armSide(side) {
239
- if (this.gridRegen[side]) {
240
- this.gridRegen[side].armed = true;
241
- }
242
- }
243
- startBootstrap() {
244
- this.bootstrap.isBootstrapping = true;
245
- this.logger?.log('[BOOTSTRAP] Started', 'debug');
246
- }
247
- finishBootstrap() {
248
- this.bootstrap.isBootstrapping = false;
249
- this.logger?.log('[BOOTSTRAP] Finished', 'debug');
250
- }
251
- isBootstrapping() {
252
- return this.bootstrap.isBootstrapping;
253
- }
254
- startBroadcasting() {
255
- this.broadcast.isBroadcasting = true;
256
- this.broadcast.startedAt = Date.now();
257
- this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
258
- }
259
- stopBroadcasting() {
260
- this.broadcast.isBroadcasting = false;
261
- this.broadcast.startedAt = 0;
262
- }
263
- isBroadcastingActive() {
264
- if (this.broadcast.isBroadcasting && this.broadcast.startedAt > 0) {
265
- const elapsed = Date.now() - this.broadcast.startedAt;
266
- if (elapsed > 120000) {
267
- this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
268
- this.broadcast.isBroadcasting = false;
269
- this.broadcast.startedAt = 0;
270
- }
271
- }
272
- return this.broadcast.isBroadcasting;
273
- }
274
- setIllegalStateSignal(signal) {
275
- this.signals.lastIllegalState = {
276
- ...signal,
277
- at: Date.now()
278
- };
279
- }
280
- consumeIllegalStateSignal() {
281
- const signal = this.signals.lastIllegalState;
282
- this.signals.lastIllegalState = null;
283
- return signal;
284
- }
285
- setAccountingFailureSignal(signal) {
286
- this.signals.lastAccountingFailure = {
287
- ...signal,
288
- at: Date.now()
289
- };
290
- }
291
- consumeAccountingFailureSignal() {
292
- const signal = this.signals.lastAccountingFailure;
293
- this.signals.lastAccountingFailure = null;
294
- return signal;
295
- }
296
- markPipelineBlocked() {
297
- if (!this.pipeline.blockedSince) {
298
- this.pipeline.blockedSince = Date.now();
299
- }
300
- }
301
- markPipelineClear() {
302
- this.pipeline.blockedSince = null;
303
- this.pipeline.recoveryAttempted = false;
304
- }
305
- getPipelineBlockedDuration() {
306
- return this.pipeline.blockedSince ? Date.now() - this.pipeline.blockedSince : 0;
307
- }
308
- isPipelineBlocked() {
309
- return this.pipeline.blockedSince !== null;
310
- }
311
- getState() {
312
- return {
313
- rebalance: { ...this.rebalance, currentWorkingGrid: null },
314
- recovery: { ...this.recovery },
315
- gridRegen: { ...this.gridRegen },
316
- bootstrap: { ...this.bootstrap },
317
- broadcast: { ...this.broadcast },
318
- pipeline: { ...this.pipeline }
319
- };
320
- }
321
- }
322
- // ===============================================================================
323
- // SECTION 4: ORDER MANAGER CLASS
140
+ // SECTION 3: ORDER MANAGER CLASS
324
141
  // ===============================================================================
325
142
  class OrderManager {
326
143
  config;
@@ -332,7 +149,14 @@ class OrderManager {
332
149
  accountant;
333
150
  strategy;
334
151
  sync;
335
- _state;
152
+ _rebalanceState;
153
+ _bootstrapping;
154
+ _broadcastingFlag;
155
+ _broadcastingStartedAt;
156
+ _illegalStateSignal;
157
+ _accountingFailureSignal;
158
+ _recoveryStateValue;
159
+ _gridRegenStateValue;
336
160
  _ordersByState;
337
161
  _ordersByType;
338
162
  targetSpreadCount;
@@ -395,7 +219,23 @@ class OrderManager {
395
219
  this.accountant = new Accountant(this);
396
220
  this.strategy = new StrategyEngine(this);
397
221
  this.sync = new SyncEngine(this);
398
- this._state = new StateManager({ logger: this.logger });
222
+ this._rebalanceState = REBALANCE_STATES.NORMAL;
223
+ this._bootstrapping = false;
224
+ this._broadcastingFlag = false;
225
+ this._broadcastingStartedAt = 0;
226
+ this._illegalStateSignal = null;
227
+ this._accountingFailureSignal = null;
228
+ this._recoveryStateValue = {
229
+ phase: 'idle',
230
+ attemptCount: 0,
231
+ lastAttemptAt: 0,
232
+ inFlight: false,
233
+ lastFailureAt: 0
234
+ };
235
+ this._gridRegenStateValue = {
236
+ buy: { armed: true, lastTriggeredAt: 0 },
237
+ sell: { armed: true, lastTriggeredAt: 0 }
238
+ };
399
239
  // Index Sets use mutable mutation patterns controlled via _applyOrderUpdate
400
240
  // These are private implementation details and must NOT be mutated directly
401
241
  // All external code must go through the COW pipeline
@@ -430,7 +270,7 @@ class OrderManager {
430
270
  // Level 2: _gridLock (grid mutations)
431
271
  // Level 3: _syncLock (sync operations)
432
272
  // Level 4: _fundLock (fund operations — innermost)
433
- // WARNING: AsyncLock is NOT reentrant.
273
+ // Note: AsyncLock IS re-entrant (acquire detects nested calls via _holding).
434
274
  this._divergenceLock = new AsyncLock({ level: 0 });
435
275
  this._fillProcessingLock = new AsyncLock({ level: 1 });
436
276
  this._gridLock = new AsyncLock({ level: 2 });
@@ -461,7 +301,8 @@ class OrderManager {
461
301
  lastSyncDurationMs: 0,
462
302
  metricsStartTime: Date.now()
463
303
  };
464
- this._state.startBootstrap();
304
+ this._bootstrapping = true;
305
+ this.logger?.log('[BOOTSTRAP] Started', 'debug');
465
306
  this._currentWorkingGrid = null;
466
307
  this._cowEngine = null;
467
308
  this._cleanExpiredLocks();
@@ -479,48 +320,77 @@ class OrderManager {
479
320
  }
480
321
  _clearWorkingGridRef() {
481
322
  this._currentWorkingGrid = null;
482
- this._state.clearWorkingGrid();
483
- this._state.setRebalanceState(REBALANCE_STATES.NORMAL);
323
+ this._rebalanceState = REBALANCE_STATES.NORMAL;
484
324
  }
485
325
  _setRebalanceState(state) {
486
- this._state.setRebalanceState(state);
326
+ this._rebalanceState = state;
327
+ this.logger?.log(`[COW] Rebalance state: ${state}`, 'debug');
487
328
  }
488
329
  /**
489
330
  * @returns {boolean}
490
331
  */
491
332
  isRebalancing() {
492
- return this._state.isRebalancing();
333
+ return this._rebalanceState === REBALANCE_STATES.REBALANCING;
493
334
  }
494
335
  /**
495
336
  * @returns {boolean}
496
337
  */
497
338
  isBroadcasting() {
498
- return this._state.isBroadcasting();
339
+ return this._rebalanceState === REBALANCE_STATES.BROADCASTING;
340
+ }
341
+ /**
342
+ * @returns {boolean}
343
+ */
344
+ isBootstrapping() {
345
+ return this._bootstrapping;
346
+ }
347
+ /**
348
+ * Whether a broadcast is currently active (no side-effects).
349
+ * @returns {boolean}
350
+ */
351
+ isBroadcastingActive() {
352
+ return this._broadcastingFlag;
353
+ }
354
+ /**
355
+ * Auto-clears a stale broadcast flag after 120s so a hung
356
+ * broadcast cannot permanently block rebalancing.
357
+ * Separated from isBroadcastingActive() so the side-effect only
358
+ * runs from one well-defined location (maintenance logic) instead
359
+ * of from every read call-site.
360
+ * @returns {void}
361
+ */
362
+ _clearStaleBroadcastFlag() {
363
+ if (this._broadcastingFlag && this._broadcastingStartedAt > 0) {
364
+ const elapsed = Date.now() - this._broadcastingStartedAt;
365
+ if (elapsed > 120000) {
366
+ this.logger?.log?.('[BROADCAST] Auto-clearing stale broadcast flag after 120s', 'warn');
367
+ this._broadcastingFlag = false;
368
+ this._broadcastingStartedAt = 0;
369
+ }
370
+ }
499
371
  }
500
372
  /**
501
373
  * Whether the manager is in the middle of a rebalance plan or broadcast.
502
- * NOT a pure predicate: delegates to _state.isBroadcastingActive()
503
- * (see StateManager.isBroadcastingActive, manager.ts:362) which
504
- * auto-clears a stale broadcast flag after 120s (producing a warn log)
505
- * so that a hung broadcast cannot permanently block rebalancing.
506
374
  * @returns {boolean}
507
375
  */
508
376
  isPlanningActive() {
509
- return this.isRebalancing() || this._state.isBroadcastingActive();
377
+ return this.isRebalancing() || this.isBroadcastingActive();
510
378
  }
511
379
  /**
512
380
  * @returns {void}
513
381
  */
514
382
  startBootstrap() {
515
- this._state.startBootstrap();
383
+ this._bootstrapping = true;
384
+ this.logger?.log('[BOOTSTRAP] Started', 'debug');
516
385
  }
517
386
  /**
518
387
  * @returns {import('./types').BootstrapResult}
519
388
  */
520
389
  finishBootstrap() {
521
390
  const result = { hadDrift: false, driftInfo: null };
522
- if (this._state.isBootstrapping()) {
523
- this._state.finishBootstrap();
391
+ if (this._bootstrapping) {
392
+ this._bootstrapping = false;
393
+ this.logger?.log('[BOOTSTRAP] Finished', 'debug');
524
394
  // Validate fund state at bootstrap completion - if drift exists here,
525
395
  // it's not transient (grid is now stable) and indicates a potential bug
526
396
  const driftCheck = this.checkFundDriftAfterFills();
@@ -538,13 +408,16 @@ class OrderManager {
538
408
  * @returns {void}
539
409
  */
540
410
  startBroadcasting() {
541
- this._state.startBroadcasting();
411
+ this._broadcastingFlag = true;
412
+ this._broadcastingStartedAt = Date.now();
413
+ this.logger?.log?.('[BROADCAST] Flag set — fill processing will be deferred until stopBroadcasting()', 'debug');
542
414
  }
543
415
  /**
544
416
  * @returns {void}
545
417
  */
546
418
  stopBroadcasting() {
547
- this._state.stopBroadcasting();
419
+ this._broadcastingFlag = false;
420
+ this._broadcastingStartedAt = 0;
548
421
  }
549
422
  /**
550
423
  * @returns {void}
@@ -765,6 +638,9 @@ class OrderManager {
765
638
  */
766
639
  pauseRecalcLogging() {
767
640
  this._pauseRecalcLogging = true;
641
+ if (this._pauseRecalcLoggingWatchdog) {
642
+ clearTimeout(this._pauseRecalcLoggingWatchdog);
643
+ }
768
644
  this._pauseRecalcLoggingWatchdog = setTimeout(() => {
769
645
  this.logger?.log?.(`[MANAGER] pauseRecalcLogging safety watchdog: forcing resume after ${TIMING.SAFETY_PAUSE_TIMEOUT_MS}ms`, 'warn');
770
646
  this._pauseRecalcLogging = false;
@@ -895,11 +771,12 @@ class OrderManager {
895
771
  const fatalError = validation.errors.find(e => e.isFatal || e.code === 'ILLEGAL_SPREAD_STATE');
896
772
  if (fatalError) {
897
773
  this.logger.log(fatalError.message, 'error');
898
- this._state.setIllegalStateSignal({
774
+ this._illegalStateSignal = {
899
775
  id: order.id,
900
776
  context,
901
- message: fatalError.message
902
- });
777
+ message: fatalError.message,
778
+ at: Date.now()
779
+ };
903
780
  if (this._throwOnIllegalState) {
904
781
  const err = new Error(fatalError.message);
905
782
  err.code = fatalError.code;
@@ -1217,13 +1094,17 @@ class OrderManager {
1217
1094
  * @returns {Object|null} The consumed signal or null
1218
1095
  */
1219
1096
  consumeIllegalStateSignal() {
1220
- return this._state.consumeIllegalStateSignal();
1097
+ const signal = this._illegalStateSignal;
1098
+ this._illegalStateSignal = null;
1099
+ return signal;
1221
1100
  }
1222
1101
  /**
1223
1102
  * @returns {Object|null} The consumed signal or null
1224
1103
  */
1225
1104
  consumeAccountingFailureSignal() {
1226
- return this._state.consumeAccountingFailureSignal();
1105
+ const signal = this._accountingFailureSignal;
1106
+ this._accountingFailureSignal = null;
1107
+ return signal;
1227
1108
  }
1228
1109
  /**
1229
1110
  * @param {Object} BitShares - BitShares API instance
@@ -1289,16 +1170,14 @@ class OrderManager {
1289
1170
  if (recoveryInFlight) {
1290
1171
  reasons.push('recovery sync in-flight');
1291
1172
  }
1292
- if (broadcasting || this._state.isBroadcastingActive()) {
1173
+ if (broadcasting || this.isBroadcastingActive()) {
1293
1174
  reasons.push('broadcasting active orders');
1294
1175
  }
1295
1176
  if (reasons.length > 0 && !this._pipelineBlockedSince) {
1296
1177
  this._pipelineBlockedSince = Date.now();
1297
- this._state.markPipelineBlocked();
1298
1178
  }
1299
1179
  else if (reasons.length === 0) {
1300
1180
  this._pipelineBlockedSince = null;
1301
- this._state.markPipelineClear();
1302
1181
  }
1303
1182
  return {
1304
1183
  isEmpty: reasons.length === 0,
@@ -1316,7 +1195,6 @@ class OrderManager {
1316
1195
  return false;
1317
1196
  this.ordersNeedingPriceCorrection = [];
1318
1197
  this._pipelineBlockedSince = null;
1319
- this._state.markPipelineClear();
1320
1198
  return true;
1321
1199
  }
1322
1200
  /**
@@ -1510,6 +1388,13 @@ class OrderManager {
1510
1388
  }
1511
1389
  try {
1512
1390
  if (!skipRecalc) {
1391
+ // If the last accounting failure was due to stale accountTotals,
1392
+ // the commit proceeded without locking funds. Refresh accountTotals
1393
+ // before recalculateFunds() to avoid a false-positive drift recovery.
1394
+ if (this._lastAccountingFailure?.reason === 'stale') {
1395
+ this.logger.log('[COW] Stale accountTotals during commit; refreshing before recalculateFunds to avoid false recovery', 'warn');
1396
+ await this.fetchAccountTotals(this.accountId);
1397
+ }
1513
1398
  await this.recalculateFunds();
1514
1399
  }
1515
1400
  const duration = Date.now() - startTime;
@@ -1520,8 +1405,10 @@ class OrderManager {
1520
1405
  }
1521
1406
  catch (recalcErr) {
1522
1407
  this.logger.log(`[COW] Fund recalculation failed post-commit: ${recalcErr.message}`, 'error');
1523
- this._recoveryState = this._recoveryState || {};
1524
- this._recoveryState.lastFailureAt = Date.now();
1408
+ if (!this._recoveryStateValue) {
1409
+ this._recoveryStateValue = { phase: 'idle', attemptCount: 0, lastAttemptAt: 0, inFlight: false, lastFailureAt: 0 };
1410
+ }
1411
+ this._recoveryStateValue.lastFailureAt = Date.now();
1525
1412
  }
1526
1413
  finally {
1527
1414
  this._clearWorkingGridRef();
@@ -1536,7 +1423,7 @@ class OrderManager {
1536
1423
  validateGridStateForPersistence(options = {}) {
1537
1424
  const result = validateGridForPersistence(this.orders, this.accountTotals);
1538
1425
  const allowBootstrapTransient = options.allowBootstrapTransient !== false;
1539
- if (!result.isValid && allowBootstrapTransient && this._state.isBootstrapping()) {
1426
+ if (!result.isValid && allowBootstrapTransient && this._bootstrapping) {
1540
1427
  this.logger.log(`[BOOTSTRAP] Transient state (expected): ${result.reason}`, 'debug');
1541
1428
  return { isValid: true, reason: null };
1542
1429
  }
@@ -1596,7 +1483,14 @@ class OrderManager {
1596
1483
  getMetrics() {
1597
1484
  return {
1598
1485
  ...this._metrics,
1599
- state: this._state.getState(),
1486
+ state: {
1487
+ rebalance: { state: this._rebalanceState, currentWorkingGrid: null },
1488
+ recovery: { ...this._recoveryStateValue },
1489
+ gridRegen: { ...this._gridRegenStateValue },
1490
+ bootstrap: { isBootstrapping: this._bootstrapping },
1491
+ broadcast: { isBroadcasting: this._broadcastingFlag, startedAt: this._broadcastingStartedAt },
1492
+ pipeline: { blockedSince: this._pipelineBlockedSince, recoveryAttempted: this._recoveryAttempted }
1493
+ },
1600
1494
  currentTime: Date.now()
1601
1495
  };
1602
1496
  }
@@ -1625,33 +1519,30 @@ class OrderManager {
1625
1519
  comparePrecisions
1626
1520
  });
1627
1521
  }
1628
- // Delegates to _state.rebalance (StateManager's internal field).
1629
- // Single source of truth — the getter/setter exists only for backward
1630
- // compat with code that reads _rebalanceState directly.
1631
- get _rebalanceState() {
1632
- return this._state.getRebalanceState();
1633
- }
1634
- set _rebalanceState(value) {
1635
- this._state.setRebalanceState(value);
1636
- }
1637
1522
  get _lastIllegalState() {
1638
- return this._state.signals?.lastIllegalState || null;
1523
+ return this._illegalStateSignal || null;
1639
1524
  }
1640
1525
  set _lastIllegalState(value) {
1641
1526
  if (value) {
1642
- this._state.setIllegalStateSignal(value);
1527
+ this._illegalStateSignal = {
1528
+ ...value,
1529
+ at: Date.now()
1530
+ };
1643
1531
  }
1644
1532
  }
1645
1533
  get _lastAccountingFailure() {
1646
- return this._state.signals?.lastAccountingFailure || null;
1534
+ return this._accountingFailureSignal || null;
1647
1535
  }
1648
1536
  set _lastAccountingFailure(value) {
1649
1537
  if (value) {
1650
- this._state.setAccountingFailureSignal(value);
1538
+ this._accountingFailureSignal = {
1539
+ ...value,
1540
+ at: Date.now()
1541
+ };
1651
1542
  }
1652
1543
  }
1653
1544
  get _recoveryState() {
1654
- return this._state.recovery;
1545
+ return this._recoveryStateValue;
1655
1546
  }
1656
1547
  set _recoveryState(value) {
1657
1548
  const fallback = {
@@ -1662,19 +1553,19 @@ class OrderManager {
1662
1553
  lastFailureAt: 0,
1663
1554
  structuralResyncRequested: false
1664
1555
  };
1665
- this._state.recovery = {
1556
+ this._recoveryStateValue = {
1666
1557
  ...fallback,
1667
1558
  ...(value && typeof value === 'object' ? value : {})
1668
1559
  };
1669
1560
  }
1670
1561
  get _gridRegenState() {
1671
- return this._state.gridRegen;
1562
+ return this._gridRegenStateValue;
1672
1563
  }
1673
1564
  set _gridRegenState(value) {
1674
1565
  if (!value || typeof value !== 'object')
1675
1566
  return;
1676
1567
  const defaultSide = { armed: true, lastTriggeredAt: 0 };
1677
- this._state.gridRegen = {
1568
+ this._gridRegenStateValue = {
1678
1569
  buy: { ...defaultSide, ...(value.buy || {}) },
1679
1570
  sell: { ...defaultSide, ...(value.sell || {}) }
1680
1571
  };