2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
//| UQ_State.mqh |
//| Universal Quant EA - identity and persistent risk state |
//| |
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//| State is stored as an ASCII key=value block in the terminal |
//| common folder. The file name and the embedded identity hash are |
//| both derived from broker server + account login + portfolio id, |
//| so one account can never silently inherit another's baselines. |
//| |
//| Lifecycle policy - fail closed: |
//| * a state file that is absent, empty, truncated, corrupt, |
//| version-unsupported, identity-mismatched or schema-invalid |
//| is NEVER treated as a fresh installation; |
//| * only an explicit owner-authorized first initialization with |
//| a deliberately declared positive baseline may create a new |
//| risk cycle; |
//| * a redundant marker global variable records that the scope |
//| has already been initialized, so deleting the state file |
//| cannot silently restart a risk cycle; |
//| * every serialized state carries a checksum over a canonical |
//| fixed-order field block, verified on load and again after |
//| every write; |
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//| * every state carries a strictly increasing generation that is |
//| written ahead of the primary, read back and reconciled, so a |
//| stale copy can never overwrite a newer verified state; |
//| * the previous verified state is retained as a backup and is |
//| only ever used under an explicit owner authorization, and any |
//| recovered state always starts behind a recovery hold. |
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//| |
//| No software can prove that a previous installation never |
//| existed if every evidence source was deliberately erased. In |
//| that case the owner must authorize a new risk cycle explicitly. |
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
# ifndef UQ_STATE_MQH
# define UQ_STATE_MQH
# include "UQ_Defs.mqh"
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
# define UQ_STATE_TEMP_EXT " .tmp "
# define UQ_STATE_BACKUP_EXT " .bak "
# define UQ_STATE_CHECKSUM_KEY " checksum "
# define UQ_STATE_CHECKSUM_LENGTH 16
# define UQ_STATE_RETRY_SECONDS 5
# define UQ_STATE_MAX_CLOCK_SKEW 86400
# define UQ_STATE_MAX_RESET_SIG 64
2026-10-31 13:28:11 +07:00
//--- A terminal global variable whose name is longer than this is silently
//--- dropped by the terminal while GlobalVariableSet() still reports success.
//--- The loss is only observable through a read-back verification, so every
//--- derived name must stay inside the limit instead of relying on the write
//--- to fail.
# define UQ_GV_NAME_MAX_CHARS 63
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
//| Identity of the persistence scope |
//+------------------------------------------------------------------+
struct UQIdentity
{
string server ; // broker server name (never written to disk)
long login ; // account login (never written to disk)
string portfolio ; // EA portfolio identifier
string key ; // file-system safe scope key
2026-10-31 13:28:11 +07:00
string hash ; // integrity hash of the scope triple (LEGACY)
string fingerprintV2 ; // SHA-256 of the canonical, unsanitised identity tuple
2026-10-31 13:47:47 +07:00
//--- true when the LEGACY serialisation of this tuple cannot be decoded back
//--- to its components: 'server + "|" + login + "|" + portfolio' is then
//--- shared with a different tuple, so a legacy state found under the legacy
//--- locators can never be proven to belong to this identity and is never
//--- migrated
bool legacyAmbiguous ;
2026-10-30 07:19:59 +07:00
} ;
//+------------------------------------------------------------------+
//| Persisted risk state |
//+------------------------------------------------------------------+
struct UQRiskState
{
int format ;
2026-10-31 13:28:11 +07:00
string identity ; // integrity hash of the owning scope (LEGACY)
string identityV2 ; // full V2 fingerprint of the owning scope (schema 8)
2026-10-30 07:19:59 +07:00
double baselineBalance ; // B0 - fixed initial balance baseline
bool baselineValid ;
datetime baselineTime ;
double dailyStartEquity ; // D0 - starting equity of the trading day
bool dailyValid ;
datetime dailyDate ; // broker-server calendar day of D0
datetime dailyStartTime ;
bool dailyLock ;
datetime dailyLockTime ;
bool staticLock ;
datetime staticLockTime ;
bool targetLock ;
datetime targetLockTime ;
2026-10-30 08:37:46 +07:00
string resetSignature ; // legacy pre-cycle reset signature, migrated and unused
2026-10-30 07:19:59 +07:00
datetime createdAt ;
datetime updatedAt ;
2026-10-30 08:37:46 +07:00
int cycleSequence ; // risk-cycle counter, 1 for the first cycle
string cycleId ; // unique identifier of the current risk cycle
string resetNonceHistory ; // consumed reset nonces, '|' separated
double priorBaselineBalance ; // B0 of the cycle closed by the most recent reset
string priorCycleId ; // identifier of that previous cycle
2026-10-30 10:20:54 +07:00
int priorLockKind ; // 0 none, 1 static-drawdown, 2 equity-target, 3 both
2026-10-30 08:37:46 +07:00
datetime priorLockTime ; // lock event that authorized the most recent reset
2026-10-30 08:52:18 +07:00
//--- outstanding entry risk. A submitted entry is reserved before the
//--- request leaves the terminal and released only after the broker state
//--- proves where the order went, so a restart cannot forget it.
ENUM_UQ_RESERVE_STATE reserveState ;
double reserveMoney ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
ulong reserveTicket ; // order ticket returned by the server
2026-10-30 08:52:18 +07:00
string reserveSymbol ;
datetime reserveTime ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
string reserveId ; // identity of this request, unique per scope
ulong reserveMagic ; // Magic Number of the request
string reserveScope ; // portfolio scope the request belongs to
int reserveDirection ; // +1 buy, -1 sell, 0 when it cannot be proven
double reserveVolume ; // requested volume
double reserveEntry ; // expected entry price of the request
double reserveStop ; // submitted protective stop
ulong reserveDeal ; // deal ticket reported by the server
ulong reservePosition ; // position identifier reported for the deal
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- persistence generation. Every authoritative state carries a strictly
//--- increasing revision, so an interrupted commit is always detectable and
//--- a stale copy can never overwrite a newer verified state.
long stateRevision ;
bool recoveryHold ; // entries blocked until the owner authorizes recovery
datetime recoveryHoldTime ; // when the recovery hold was recorded
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
//--- monitoring continuity. The heartbeat records the last verified
//--- execution moment, so a restart can distinguish a trading day that was
//--- observed from its boundary from one whose equity path was never seen.
datetime monitorTime ; // last verified monitoring heartbeat (server time)
int monitorGapSeconds ; // most recent unobserved interval, 0 = none
bool gapHold ; // entries blocked until an observed day boundary
datetime gapHoldTime ; // when the monitoring-gap hold was recorded
2026-10-30 07:19:59 +07:00
} ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//+------------------------------------------------------------------+
//| Classification of one load attempt |
//+------------------------------------------------------------------+
enum ENUM_UQ_STATE_LOAD
{
UQ_LOAD_ERROR = -1 , // file exists but is unusable
UQ_LOAD_ABSENT = 0 , // no file at this location
UQ_LOAD_VALID = 1 , // current format, checksum verified
UQ_LOAD_LEGACY = 2 // previous published format, schema verified
} ;
//+------------------------------------------------------------------+
//| Reconciliation state of the redundant initialization marker |
//+------------------------------------------------------------------+
enum ENUM_UQ_MARKER_STATE
{
2026-10-31 13:47:47 +07:00
UQ_MARKER_ABSENT = 0 , // marker missing: no evidence either way
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
UQ_MARKER_MATCH = 1 , // marker agrees with the persisted state
2026-10-31 13:47:47 +07:00
UQ_MARKER_MISMATCH = 2 , // marker disagrees with the persisted state
UQ_MARKER_INVALID = 3 // marker exists but holds no usable record
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
} ;
//+------------------------------------------------------------------+
//| Canonical persisted field table. The order of this enumeration |
//| defines the order of the canonical block that is hashed, so the |
//| checksum is independent of the physical line order in the file. |
//+------------------------------------------------------------------+
enum ENUM_UQ_STATE_FIELD
{
UQ_F_FORMAT = 0 ,
UQ_F_IDENTITY ,
UQ_F_BASELINE_BALANCE ,
UQ_F_BASELINE_VALID ,
UQ_F_BASELINE_TIME ,
UQ_F_DAILY_EQUITY ,
UQ_F_DAILY_VALID ,
UQ_F_DAILY_DATE ,
UQ_F_DAILY_START_TIME ,
UQ_F_DAILY_LOCK ,
UQ_F_DAILY_LOCK_TIME ,
UQ_F_STATIC_LOCK ,
UQ_F_STATIC_LOCK_TIME ,
UQ_F_TARGET_LOCK ,
UQ_F_TARGET_LOCK_TIME ,
UQ_F_RESET_SIGNATURE ,
UQ_F_CREATED_AT ,
UQ_F_UPDATED_AT ,
2026-10-30 08:37:46 +07:00
UQ_F_CYCLE_SEQ ,
UQ_F_CYCLE_ID ,
UQ_F_RESET_NONCE_HISTORY ,
UQ_F_PRIOR_B0 ,
UQ_F_PRIOR_CYCLE_ID ,
UQ_F_PRIOR_LOCK_KIND ,
UQ_F_PRIOR_LOCK_TIME ,
2026-10-30 08:52:18 +07:00
UQ_F_RESERVE_STATE ,
UQ_F_RESERVE_MONEY ,
UQ_F_RESERVE_TICKET ,
UQ_F_RESERVE_SYMBOL ,
UQ_F_RESERVE_TIME ,
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
UQ_F_RESERVE_ID ,
UQ_F_RESERVE_MAGIC ,
UQ_F_RESERVE_SCOPE ,
UQ_F_RESERVE_DIRECTION ,
UQ_F_RESERVE_VOLUME ,
UQ_F_RESERVE_ENTRY ,
UQ_F_RESERVE_STOP ,
UQ_F_RESERVE_DEAL ,
UQ_F_RESERVE_POSITION ,
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
UQ_F_STATE_REVISION ,
UQ_F_RECOVERY_HOLD ,
UQ_F_RECOVERY_TIME ,
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
UQ_F_MONITOR_TIME ,
UQ_F_MONITOR_GAP ,
UQ_F_GAP_HOLD ,
UQ_F_GAP_HOLD_TIME ,
2026-10-31 13:28:11 +07:00
//--- schema 8. The V2 identity is appended LAST on purpose: every earlier
//--- schema hashes only the leading N fields of this table, so inserting a
//--- field anywhere else would silently change what an old checksum covers
//--- and no previously persisted state could be verified any more.
UQ_F_IDENTITY_V2 ,
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
UQ_F_COUNT
} ;
2026-10-30 08:37:46 +07:00
//+------------------------------------------------------------------+
//+------------------------------------------------------------------+
//| Number of fields written by the schemas that predate the risk- |
//| cycle schema. They lead the canonical table, so a legacy checksum |
//| is recomputed over exactly the block that produced it. |
//+------------------------------------------------------------------+
2026-10-30 08:52:18 +07:00
# define UQ_STATE_BASE_FIELDS 18
# define UQ_STATE_CYCLE_FIELDS 25
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
# define UQ_STATE_RESERVE_FIELDS 30
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
# define UQ_STATE_IDENTITY_FIELDS 39
# define UQ_STATE_GENERATION_FIELDS 42
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
# define UQ_STATE_MONITOR_FIELDS 46
2026-10-31 13:28:11 +07:00
# define UQ_STATE_V2_FIELDS 47
2026-10-30 08:52:18 +07:00
//+------------------------------------------------------------------+
//| Number of fields that a given schema serialises. A checksum is |
//| only valid over the exact block that produced it, so the field |
//| count is derived from the schema version and never from the |
//| current build. |
//+------------------------------------------------------------------+
int UQFieldCountForFormat ( const int fmt )
{
if ( fmt > = UQ_STATE_FORMAT )
return ( ( int ) UQ_F_COUNT ) ;
2026-10-31 13:28:11 +07:00
if ( fmt > = UQ_STATE_MONITOR_FORMAT )
return ( ( int ) UQ_STATE_MONITOR_FIELDS ) ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
if ( fmt > = UQ_STATE_GENERATION_FORMAT )
return ( ( int ) UQ_STATE_GENERATION_FIELDS ) ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
if ( fmt > = UQ_STATE_IDENTITY_FORMAT )
return ( ( int ) UQ_STATE_IDENTITY_FIELDS ) ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
if ( fmt = = UQ_STATE_RESERVE_FORMAT )
return ( ( int ) UQ_STATE_RESERVE_FIELDS ) ;
2026-10-30 08:52:18 +07:00
if ( fmt = = UQ_STATE_CYCLE_FORMAT )
return ( ( int ) UQ_STATE_CYCLE_FIELDS ) ;
return ( ( int ) UQ_STATE_BASE_FIELDS ) ;
}
2026-10-30 08:37:46 +07:00
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
//| Reset a state structure to defaults |
//+------------------------------------------------------------------+
void UQStateReset ( UQRiskState & st )
{
st . format = UQ_STATE_FORMAT ;
st . identity = " " ;
2026-10-31 13:28:11 +07:00
st . identityV2 = " " ;
2026-10-30 07:19:59 +07:00
st . baselineBalance = 0.0 ;
st . baselineValid = false ;
st . baselineTime = 0 ;
st . dailyStartEquity = 0.0 ;
st . dailyValid = false ;
st . dailyDate = 0 ;
st . dailyStartTime = 0 ;
st . dailyLock = false ;
st . dailyLockTime = 0 ;
st . staticLock = false ;
st . staticLockTime = 0 ;
st . targetLock = false ;
st . targetLockTime = 0 ;
st . resetSignature = " " ;
st . createdAt = 0 ;
st . updatedAt = 0 ;
2026-10-30 08:37:46 +07:00
st . cycleSequence = 0 ;
st . cycleId = " " ;
st . resetNonceHistory = " " ;
st . priorBaselineBalance = 0.0 ;
st . priorCycleId = " " ;
st . priorLockKind = 0 ;
st . priorLockTime = 0 ;
2026-10-30 08:52:18 +07:00
st . reserveState = UQ_RESERVE_NONE ;
st . reserveMoney = 0.0 ;
st . reserveTicket = 0 ;
st . reserveSymbol = " " ;
st . reserveTime = 0 ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
st . reserveId = " " ;
st . reserveMagic = 0 ;
st . reserveScope = " " ;
st . reserveDirection = 0 ;
st . reserveVolume = 0.0 ;
st . reserveEntry = 0.0 ;
st . reserveStop = 0.0 ;
st . reserveDeal = 0 ;
st . reservePosition = 0 ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
st . stateRevision = 0 ;
st . recoveryHold = false ;
st . recoveryHoldTime = 0 ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
st . monitorTime = 0 ;
st . monitorGapSeconds = 0 ;
st . gapHold = false ;
st . gapHoldTime = 0 ;
2026-10-30 07:19:59 +07:00
}
//+------------------------------------------------------------------+
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//| Canonical key of a persisted field |
//+------------------------------------------------------------------+
string UQFieldKey ( const int idx )
{
switch ( idx )
{
case UQ_F_FORMAT : return ( " format " ) ;
case UQ_F_IDENTITY : return ( " identity " ) ;
case UQ_F_BASELINE_BALANCE : return ( " baselineBalance " ) ;
case UQ_F_BASELINE_VALID : return ( " baselineValid " ) ;
case UQ_F_BASELINE_TIME : return ( " baselineTime " ) ;
case UQ_F_DAILY_EQUITY : return ( " dailyStartEquity " ) ;
case UQ_F_DAILY_VALID : return ( " dailyValid " ) ;
case UQ_F_DAILY_DATE : return ( " dailyDate " ) ;
case UQ_F_DAILY_START_TIME : return ( " dailyStartTime " ) ;
case UQ_F_DAILY_LOCK : return ( " dailyLock " ) ;
case UQ_F_DAILY_LOCK_TIME : return ( " dailyLockTime " ) ;
case UQ_F_STATIC_LOCK : return ( " staticLock " ) ;
case UQ_F_STATIC_LOCK_TIME : return ( " staticLockTime " ) ;
case UQ_F_TARGET_LOCK : return ( " targetLock " ) ;
case UQ_F_TARGET_LOCK_TIME : return ( " targetLockTime " ) ;
case UQ_F_RESET_SIGNATURE : return ( " resetSignature " ) ;
case UQ_F_CREATED_AT : return ( " createdAt " ) ;
case UQ_F_UPDATED_AT : return ( " updatedAt " ) ;
2026-10-30 08:37:46 +07:00
case UQ_F_CYCLE_SEQ : return ( " cycleSequence " ) ;
case UQ_F_CYCLE_ID : return ( " cycleId " ) ;
case UQ_F_RESET_NONCE_HISTORY : return ( " resetNonceHistory " ) ;
case UQ_F_PRIOR_B0 : return ( " priorBaselineBalance " ) ;
case UQ_F_PRIOR_CYCLE_ID : return ( " priorCycleId " ) ;
case UQ_F_PRIOR_LOCK_KIND : return ( " priorLockKind " ) ;
case UQ_F_PRIOR_LOCK_TIME : return ( " priorLockTime " ) ;
2026-10-30 08:52:18 +07:00
case UQ_F_RESERVE_STATE : return ( " reserveState " ) ;
case UQ_F_RESERVE_MONEY : return ( " reserveMoney " ) ;
case UQ_F_RESERVE_TICKET : return ( " reserveTicket " ) ;
case UQ_F_RESERVE_SYMBOL : return ( " reserveSymbol " ) ;
case UQ_F_RESERVE_TIME : return ( " reserveTime " ) ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
case UQ_F_RESERVE_ID : return ( " reserveId " ) ;
case UQ_F_RESERVE_MAGIC : return ( " reserveMagic " ) ;
case UQ_F_RESERVE_SCOPE : return ( " reserveScope " ) ;
case UQ_F_RESERVE_DIRECTION : return ( " reserveDirection " ) ;
case UQ_F_RESERVE_VOLUME : return ( " reserveVolume " ) ;
case UQ_F_RESERVE_ENTRY : return ( " reserveEntry " ) ;
case UQ_F_RESERVE_STOP : return ( " reserveStop " ) ;
case UQ_F_RESERVE_DEAL : return ( " reserveDeal " ) ;
case UQ_F_RESERVE_POSITION : return ( " reservePosition " ) ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
case UQ_F_STATE_REVISION : return ( " stateRevision " ) ;
case UQ_F_RECOVERY_HOLD : return ( " recoveryHold " ) ;
case UQ_F_RECOVERY_TIME : return ( " recoveryHoldTime " ) ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
case UQ_F_MONITOR_TIME : return ( " monitorTime " ) ;
case UQ_F_MONITOR_GAP : return ( " monitorGapSeconds " ) ;
case UQ_F_GAP_HOLD : return ( " gapHold " ) ;
case UQ_F_GAP_HOLD_TIME : return ( " gapHoldTime " ) ;
2026-10-31 13:28:11 +07:00
case UQ_F_IDENTITY_V2 : return ( " identityV2 " ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
}
return ( " " ) ;
}
//+------------------------------------------------------------------+
//| Index of a persisted field, or -1 when unknown |
//+------------------------------------------------------------------+
int UQFieldIndex ( const string key )
{
for ( int i = 0 ; i < ( int ) UQ_F_COUNT ; i + + )
if ( UQFieldKey ( i ) = = key )
return ( i ) ;
return ( -1 ) ;
}
//+------------------------------------------------------------------+
//| Canonical serialised value of one persisted field |
//+------------------------------------------------------------------+
string UQStateFieldValue ( UQRiskState & st , const int idx )
{
switch ( idx )
{
case UQ_F_FORMAT : return ( IntegerToString ( st . format ) ) ;
case UQ_F_IDENTITY : return ( st . identity ) ;
case UQ_F_BASELINE_BALANCE : return ( DoubleToString ( st . baselineBalance , 8 ) ) ;
case UQ_F_BASELINE_VALID : return ( st . baselineValid ? " 1 " : " 0 " ) ;
case UQ_F_BASELINE_TIME : return ( IntegerToString ( ( long ) st . baselineTime ) ) ;
case UQ_F_DAILY_EQUITY : return ( DoubleToString ( st . dailyStartEquity , 8 ) ) ;
case UQ_F_DAILY_VALID : return ( st . dailyValid ? " 1 " : " 0 " ) ;
case UQ_F_DAILY_DATE : return ( IntegerToString ( ( long ) st . dailyDate ) ) ;
case UQ_F_DAILY_START_TIME : return ( IntegerToString ( ( long ) st . dailyStartTime ) ) ;
case UQ_F_DAILY_LOCK : return ( st . dailyLock ? " 1 " : " 0 " ) ;
case UQ_F_DAILY_LOCK_TIME : return ( IntegerToString ( ( long ) st . dailyLockTime ) ) ;
case UQ_F_STATIC_LOCK : return ( st . staticLock ? " 1 " : " 0 " ) ;
case UQ_F_STATIC_LOCK_TIME : return ( IntegerToString ( ( long ) st . staticLockTime ) ) ;
case UQ_F_TARGET_LOCK : return ( st . targetLock ? " 1 " : " 0 " ) ;
case UQ_F_TARGET_LOCK_TIME : return ( IntegerToString ( ( long ) st . targetLockTime ) ) ;
case UQ_F_RESET_SIGNATURE : return ( st . resetSignature ) ;
case UQ_F_CREATED_AT : return ( IntegerToString ( ( long ) st . createdAt ) ) ;
case UQ_F_UPDATED_AT : return ( IntegerToString ( ( long ) st . updatedAt ) ) ;
2026-10-30 08:37:46 +07:00
case UQ_F_CYCLE_SEQ : return ( IntegerToString ( ( long ) st . cycleSequence ) ) ;
case UQ_F_CYCLE_ID : return ( st . cycleId ) ;
case UQ_F_RESET_NONCE_HISTORY : return ( st . resetNonceHistory ) ;
case UQ_F_PRIOR_B0 : return ( DoubleToString ( st . priorBaselineBalance , 8 ) ) ;
case UQ_F_PRIOR_CYCLE_ID : return ( st . priorCycleId ) ;
case UQ_F_PRIOR_LOCK_KIND : return ( IntegerToString ( ( long ) st . priorLockKind ) ) ;
case UQ_F_PRIOR_LOCK_TIME : return ( IntegerToString ( ( long ) st . priorLockTime ) ) ;
2026-10-30 08:52:18 +07:00
case UQ_F_RESERVE_STATE : return ( IntegerToString ( ( long ) st . reserveState ) ) ;
case UQ_F_RESERVE_MONEY : return ( DoubleToString ( st . reserveMoney , 8 ) ) ;
case UQ_F_RESERVE_TICKET : return ( IntegerToString ( ( long ) st . reserveTicket ) ) ;
case UQ_F_RESERVE_SYMBOL : return ( st . reserveSymbol ) ;
case UQ_F_RESERVE_TIME : return ( IntegerToString ( ( long ) st . reserveTime ) ) ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
case UQ_F_RESERVE_ID : return ( st . reserveId ) ;
case UQ_F_RESERVE_MAGIC : return ( IntegerToString ( ( long ) st . reserveMagic ) ) ;
case UQ_F_RESERVE_SCOPE : return ( st . reserveScope ) ;
case UQ_F_RESERVE_DIRECTION : return ( IntegerToString ( ( long ) st . reserveDirection ) ) ;
case UQ_F_RESERVE_VOLUME : return ( DoubleToString ( st . reserveVolume , 8 ) ) ;
case UQ_F_RESERVE_ENTRY : return ( DoubleToString ( st . reserveEntry , 8 ) ) ;
case UQ_F_RESERVE_STOP : return ( DoubleToString ( st . reserveStop , 8 ) ) ;
case UQ_F_RESERVE_DEAL : return ( IntegerToString ( ( long ) st . reserveDeal ) ) ;
case UQ_F_RESERVE_POSITION : return ( IntegerToString ( ( long ) st . reservePosition ) ) ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
case UQ_F_STATE_REVISION : return ( IntegerToString ( st . stateRevision ) ) ;
case UQ_F_RECOVERY_HOLD : return ( st . recoveryHold ? " 1 " : " 0 " ) ;
case UQ_F_RECOVERY_TIME : return ( IntegerToString ( ( long ) st . recoveryHoldTime ) ) ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
case UQ_F_MONITOR_TIME : return ( IntegerToString ( ( long ) st . monitorTime ) ) ;
case UQ_F_MONITOR_GAP : return ( IntegerToString ( ( long ) st . monitorGapSeconds ) ) ;
case UQ_F_GAP_HOLD : return ( st . gapHold ? " 1 " : " 0 " ) ;
case UQ_F_GAP_HOLD_TIME : return ( IntegerToString ( ( long ) st . gapHoldTime ) ) ;
2026-10-31 13:28:11 +07:00
case UQ_F_IDENTITY_V2 : return ( st . identityV2 ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
}
return ( " " ) ;
}
//+------------------------------------------------------------------+
//| Canonical block that is covered by the integrity checksum |
//+------------------------------------------------------------------+
string UQStateCanonicalBody ( UQRiskState & st )
{
2026-10-30 08:52:18 +07:00
//--- every published schema is hashed over its own field set only, so an
//--- older checksum still verifies and can be migrated deterministically
const int fields = UQFieldCountForFormat ( st . format ) ;
2026-10-30 08:37:46 +07:00
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
string body = " " ;
2026-10-30 08:37:46 +07:00
for ( int i = 0 ; i < fields ; i + + )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
body + = UQFieldKey ( i ) + " = " + UQStateFieldValue ( st , i ) + " \r \n " ;
return ( body ) ;
}
//+------------------------------------------------------------------+
//| Strict integer parser: rejects empty, blank and non-numeric text |
//+------------------------------------------------------------------+
bool UQParseLong ( const string v , long & out )
{
out = 0 ;
const int n = StringLen ( v ) ;
if ( n < = 0 | | n > 20 )
return ( false ) ;
int start = 0 ;
const ushort first = StringGetCharacter ( v , 0 ) ;
if ( first = = ' - ' | | first = = ' + ' )
start = 1 ;
if ( start > = n )
return ( false ) ;
int digits = 0 ;
for ( int i = start ; i < n ; i + + )
{
const ushort c = StringGetCharacter ( v , i ) ;
if ( c < ' 0 ' | | c > ' 9 ' )
return ( false ) ;
digits + + ;
}
if ( digits = = 0 )
return ( false ) ;
out = StringToInteger ( v ) ;
return ( true ) ;
}
//+------------------------------------------------------------------+
//| Strict double parser: one optional leading sign, one decimal |
//| point, at least one digit and no trailing garbage |
//+------------------------------------------------------------------+
bool UQParseDoubleStrict ( const string v , double & out )
{
out = 0.0 ;
const int n = StringLen ( v ) ;
if ( n < = 0 | | n > 40 )
return ( false ) ;
int start = 0 ;
const ushort first = StringGetCharacter ( v , 0 ) ;
if ( first = = ' - ' | | first = = ' + ' )
start = 1 ;
if ( start > = n )
return ( false ) ;
int digits = 0 ;
int points = 0 ;
for ( int i = start ; i < n ; i + + )
{
const ushort c = StringGetCharacter ( v , i ) ;
if ( c = = ' . ' )
{
points + + ;
if ( points > 1 )
return ( false ) ;
continue ;
}
if ( c < ' 0 ' | | c > ' 9 ' )
return ( false ) ;
digits + + ;
}
if ( digits = = 0 )
return ( false ) ;
out = StringToDouble ( v ) ;
return ( MathIsValidNumber ( out ) ) ;
}
//+------------------------------------------------------------------+
//| Strict boolean parser: only the literals 0 and 1 are accepted |
//+------------------------------------------------------------------+
bool UQParseBoolStrict ( const string v , bool & out )
{
out = false ;
if ( v = = " 0 " )
return ( true ) ;
if ( v = = " 1 " )
{
out = true ;
return ( true ) ;
}
return ( false ) ;
}
//+------------------------------------------------------------------+
//| Parse a state block into a structure. Every required field must |
//| be present exactly once; unknown fields are rejected. |
//+------------------------------------------------------------------+
bool UQStateParseBody ( const string body , UQRiskState & st ,
bool & hasChecksum , string & checksumStored , string & err )
{
hasChecksum = false ;
checksumStored = " " ;
err = " " ;
UQStateReset ( st ) ;
char seen [ ] ;
ArrayResize ( seen , ( int ) UQ_F_COUNT ) ;
ArrayInitialize ( seen , ( char ) 0 ) ;
string lines [ ] ;
const int n = StringSplit ( body , ' \n ' , lines ) ;
for ( int i = 0 ; i < n ; i + + )
{
string line = lines [ i ] ;
StringTrimRight ( line ) ;
StringTrimLeft ( line ) ;
if ( StringLen ( line ) = = 0 )
continue ;
const int eq = StringFind ( line , " = " ) ;
if ( eq < = 0 )
{
err = StringFormat ( " malformed line %d " , i + 1 ) ;
return ( false ) ;
}
const string key = StringSubstr ( line , 0 , eq ) ;
const string val = StringSubstr ( line , eq + 1 ) ;
if ( key = = UQ_STATE_CHECKSUM_KEY )
{
if ( hasChecksum )
{
err = " duplicate checksum field " ;
return ( false ) ;
}
if ( StringLen ( val ) ! = UQ_STATE_CHECKSUM_LENGTH )
{
err = " malformed checksum field " ;
return ( false ) ;
}
hasChecksum = true ;
checksumStored = val ;
continue ;
}
const int idx = UQFieldIndex ( key ) ;
if ( idx < 0 )
{
err = StringFormat ( " unknown field '%s' " , key ) ;
return ( false ) ;
}
if ( seen [ idx ] ! = 0 )
{
err = StringFormat ( " duplicate field '%s' " , key ) ;
return ( false ) ;
}
seen [ idx ] = ( char ) 1 ;
long iv = 0 ;
double dv = 0.0 ;
bool bv = false ;
switch ( idx )
{
case UQ_F_FORMAT :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'format' is not an integer " ; return ( false ) ; }
st . format = ( int ) iv ;
break ;
case UQ_F_IDENTITY :
st . identity = val ;
break ;
case UQ_F_BASELINE_BALANCE :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'baselineBalance' is not numeric " ; return ( false ) ; }
st . baselineBalance = dv ;
break ;
case UQ_F_BASELINE_VALID :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'baselineValid' is not a 0/1 flag " ; return ( false ) ; }
st . baselineValid = bv ;
break ;
case UQ_F_BASELINE_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'baselineTime' is not an integer " ; return ( false ) ; }
st . baselineTime = ( datetime ) iv ;
break ;
case UQ_F_DAILY_EQUITY :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'dailyStartEquity' is not numeric " ; return ( false ) ; }
st . dailyStartEquity = dv ;
break ;
case UQ_F_DAILY_VALID :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'dailyValid' is not a 0/1 flag " ; return ( false ) ; }
st . dailyValid = bv ;
break ;
case UQ_F_DAILY_DATE :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'dailyDate' is not an integer " ; return ( false ) ; }
st . dailyDate = ( datetime ) iv ;
break ;
case UQ_F_DAILY_START_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'dailyStartTime' is not an integer " ; return ( false ) ; }
st . dailyStartTime = ( datetime ) iv ;
break ;
case UQ_F_DAILY_LOCK :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'dailyLock' is not a 0/1 flag " ; return ( false ) ; }
st . dailyLock = bv ;
break ;
case UQ_F_DAILY_LOCK_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'dailyLockTime' is not an integer " ; return ( false ) ; }
st . dailyLockTime = ( datetime ) iv ;
break ;
case UQ_F_STATIC_LOCK :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'staticLock' is not a 0/1 flag " ; return ( false ) ; }
st . staticLock = bv ;
break ;
case UQ_F_STATIC_LOCK_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'staticLockTime' is not an integer " ; return ( false ) ; }
st . staticLockTime = ( datetime ) iv ;
break ;
case UQ_F_TARGET_LOCK :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'targetLock' is not a 0/1 flag " ; return ( false ) ; }
st . targetLock = bv ;
break ;
case UQ_F_TARGET_LOCK_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'targetLockTime' is not an integer " ; return ( false ) ; }
st . targetLockTime = ( datetime ) iv ;
break ;
case UQ_F_RESET_SIGNATURE :
st . resetSignature = val ;
break ;
case UQ_F_CREATED_AT :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'createdAt' is not an integer " ; return ( false ) ; }
st . createdAt = ( datetime ) iv ;
break ;
case UQ_F_UPDATED_AT :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'updatedAt' is not an integer " ; return ( false ) ; }
st . updatedAt = ( datetime ) iv ;
break ;
2026-10-30 08:37:46 +07:00
case UQ_F_CYCLE_SEQ :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'cycleSequence' is not an integer " ; return ( false ) ; }
st . cycleSequence = ( int ) iv ;
break ;
case UQ_F_CYCLE_ID :
st . cycleId = val ;
break ;
case UQ_F_RESET_NONCE_HISTORY :
st . resetNonceHistory = val ;
break ;
case UQ_F_PRIOR_B0 :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'priorBaselineBalance' is not numeric " ; return ( false ) ; }
st . priorBaselineBalance = dv ;
break ;
case UQ_F_PRIOR_CYCLE_ID :
st . priorCycleId = val ;
break ;
case UQ_F_PRIOR_LOCK_KIND :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'priorLockKind' is not an integer " ; return ( false ) ; }
st . priorLockKind = ( int ) iv ;
break ;
case UQ_F_PRIOR_LOCK_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'priorLockTime' is not an integer " ; return ( false ) ; }
st . priorLockTime = ( datetime ) iv ;
break ;
2026-10-30 08:52:18 +07:00
case UQ_F_RESERVE_STATE :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'reserveState' is not an integer " ; return ( false ) ; }
st . reserveState = ( ENUM_UQ_RESERVE_STATE ) iv ;
break ;
case UQ_F_RESERVE_MONEY :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'reserveMoney' is not numeric " ; return ( false ) ; }
st . reserveMoney = dv ;
break ;
case UQ_F_RESERVE_TICKET :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'reserveTicket' is not an integer " ; return ( false ) ; }
st . reserveTicket = ( ulong ) iv ;
break ;
case UQ_F_RESERVE_SYMBOL :
st . reserveSymbol = val ;
break ;
case UQ_F_RESERVE_TIME :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'reserveTime' is not an integer " ; return ( false ) ; }
st . reserveTime = ( datetime ) iv ;
break ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
case UQ_F_RESERVE_ID :
st . reserveId = val ;
break ;
case UQ_F_RESERVE_MAGIC :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'reserveMagic' is not an integer " ; return ( false ) ; }
st . reserveMagic = ( ulong ) iv ;
break ;
case UQ_F_RESERVE_SCOPE :
st . reserveScope = val ;
break ;
case UQ_F_RESERVE_DIRECTION :
if ( ! UQParseLong ( val , iv ) )
{ err = " field 'reserveDirection' is not an integer " ; return ( false ) ; }
st . reserveDirection = ( int ) iv ;
break ;
case UQ_F_RESERVE_VOLUME :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'reserveVolume' is not numeric " ; return ( false ) ; }
st . reserveVolume = dv ;
break ;
case UQ_F_RESERVE_ENTRY :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'reserveEntry' is not numeric " ; return ( false ) ; }
st . reserveEntry = dv ;
break ;
case UQ_F_RESERVE_STOP :
if ( ! UQParseDoubleStrict ( val , dv ) )
{ err = " field 'reserveStop' is not numeric " ; return ( false ) ; }
st . reserveStop = dv ;
break ;
case UQ_F_RESERVE_DEAL :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'reserveDeal' is not an integer " ; return ( false ) ; }
st . reserveDeal = ( ulong ) iv ;
break ;
case UQ_F_RESERVE_POSITION :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'reservePosition' is not an integer " ; return ( false ) ; }
st . reservePosition = ( ulong ) iv ;
break ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
case UQ_F_STATE_REVISION :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'stateRevision' is not an integer " ; return ( false ) ; }
st . stateRevision = ( long ) iv ;
break ;
case UQ_F_RECOVERY_HOLD :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'recoveryHold' is not a 0/1 flag " ; return ( false ) ; }
st . recoveryHold = bv ;
break ;
case UQ_F_RECOVERY_TIME :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'recoveryHoldTime' is not an integer " ; return ( false ) ; }
st . recoveryHoldTime = ( datetime ) iv ;
break ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
case UQ_F_MONITOR_TIME :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'monitorTime' is not an integer " ; return ( false ) ; }
st . monitorTime = ( datetime ) iv ;
break ;
case UQ_F_MONITOR_GAP :
if ( ! UQParseLong ( val , iv ) | | iv < 0 | | iv > ( long ) UQ_MONITOR_GAP_MAX_SECONDS )
{ err = " field 'monitorGapSeconds' is not a bounded integer " ; return ( false ) ; }
st . monitorGapSeconds = ( int ) iv ;
break ;
case UQ_F_GAP_HOLD :
if ( ! UQParseBoolStrict ( val , bv ) )
{ err = " field 'gapHold' is not a 0/1 flag " ; return ( false ) ; }
st . gapHold = bv ;
break ;
case UQ_F_GAP_HOLD_TIME :
if ( ! UQParseLong ( val , iv ) | | iv < 0 )
{ err = " field 'gapHoldTime' is not an integer " ; return ( false ) ; }
st . gapHoldTime = ( datetime ) iv ;
break ;
2026-10-31 13:28:11 +07:00
case UQ_F_IDENTITY_V2 :
//--- the complete 64 character fingerprint is required, so a
//--- truncated or empty value can never pass as an identity
if ( ! UQIdentityFingerprintValid ( val ) )
{ err = " field 'identityV2' is not a 64 character fingerprint " ; return ( false ) ; }
st . identityV2 = val ;
break ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
default :
err = " unexpected field index " ;
return ( false ) ;
}
}
2026-10-30 08:37:46 +07:00
//--- a schema published before the risk-cycle fields existed carries no
//--- cycle block: only its own field set is mandatory
2026-10-30 08:52:18 +07:00
const int required = UQFieldCountForFormat ( st . format ) ;
2026-10-30 08:37:46 +07:00
for ( int f = 0 ; f < required ; f + + )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( seen [ f ] = = 0 )
{
err = StringFormat ( " required field '%s' is missing " , UQFieldKey ( f ) ) ;
return ( false ) ;
}
return ( true ) ;
}
//+------------------------------------------------------------------+
//| Schema and semantics validation of a parsed state |
//+------------------------------------------------------------------+
bool UQStateValidate ( UQIdentity & id , UQRiskState & st , string & err )
{
err = " " ;
2026-10-30 08:52:18 +07:00
if ( st . format < UQ_STATE_LEGACY_FORMAT | | st . format > UQ_STATE_FORMAT )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
err = StringFormat ( " unsupported state format %d " , st . format ) ;
return ( false ) ;
}
2026-10-31 13:28:11 +07:00
//--- the legacy identity field keeps its exact previous meaning and is
//--- validated for every schema, including schema 8
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( st . identity ! = id . hash )
{
err = " state belongs to a different identity scope " ;
return ( false ) ;
}
2026-10-31 13:28:11 +07:00
if ( st . format < UQ_STATE_FORMAT )
{
//--- a state written before schema 8 has no V2 identity, and its checksum
//--- does not cover one: a value appearing there was never proven
if ( StringLen ( st . identityV2 ) > 0 )
{
err = " legacy state unexpectedly carries a V2 identity " ;
return ( false ) ;
}
}
else
{
//--- schema 8 must carry the complete V2 fingerprint of exactly this
//--- logical identity. An empty or missing value is never accepted.
if ( ! UQIdentityFingerprintValid ( st . identityV2 ) )
{
err = " V2 identity fingerprint is missing or malformed " ;
return ( false ) ;
}
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
{
err = " the live V2 identity fingerprint is unavailable " ;
return ( false ) ;
}
if ( st . identityV2 ! = id . fingerprintV2 )
{
err = " state belongs to a different V2 identity scope " ;
return ( false ) ;
}
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( st . createdAt < = 0 )
{
err = " state creation timestamp is missing " ;
return ( false ) ;
}
if ( st . updatedAt < st . createdAt )
{
err = " state update timestamp precedes the creation timestamp " ;
return ( false ) ;
}
const datetime now = UQServerNow ( ) ;
if ( now > 0 & & st . updatedAt > now + ( datetime ) UQ_STATE_MAX_CLOCK_SKEW )
{
err = " state update timestamp lies implausibly far in the future " ;
return ( false ) ;
}
//--- fixed baseline
if ( ! st . baselineValid )
{
err = " baseline validity flag is not set " ;
return ( false ) ;
}
if ( ! ( st . baselineBalance > 0.0 ) )
{
err = " baseline B0 is not positive " ;
return ( false ) ;
}
if ( st . baselineTime < = 0 )
{
err = " baseline timestamp is missing " ;
return ( false ) ;
}
//--- daily baseline
if ( ! st . dailyValid )
{
err = " daily validity flag is not set " ;
return ( false ) ;
}
if ( ! ( st . dailyStartEquity > 0.0 ) )
{
err = " daily baseline D0 is not positive " ;
return ( false ) ;
}
if ( st . dailyDate < = 0 )
{
err = " broker-server trading date is missing " ;
return ( false ) ;
}
if ( UQDayStart ( st . dailyDate ) ! = st . dailyDate )
{
err = " broker-server trading date is not day-aligned " ;
return ( false ) ;
}
if ( st . dailyStartTime < = 0 )
{
err = " daily baseline timestamp is missing " ;
return ( false ) ;
}
//--- lock consistency
if ( st . dailyLock & & st . dailyLockTime < = 0 )
{
err = " daily lock is active without a consistent lock event " ;
return ( false ) ;
}
if ( ! st . dailyLock & & st . dailyLockTime ! = 0 )
{
err = " daily lock time is set while the daily lock is inactive " ;
return ( false ) ;
}
if ( st . staticLock & & st . staticLockTime < = 0 )
{
err = " static-drawdown lock is active without a consistent lock event " ;
return ( false ) ;
}
if ( ! st . staticLock & & st . staticLockTime ! = 0 )
{
err = " static-drawdown lock time is set while the lock is inactive " ;
return ( false ) ;
}
if ( st . targetLock & & st . targetLockTime < = 0 )
{
err = " equity-target lock is active without a consistent lock event " ;
return ( false ) ;
}
if ( ! st . targetLock & & st . targetLockTime ! = 0 )
{
err = " equity-target lock time is set while the lock is inactive " ;
return ( false ) ;
}
if ( st . staticLock & & st . targetLock )
{
2026-10-30 08:37:46 +07:00
//--- the two permanent locks are independent: an equity target can be
//--- reached before a drawdown (or the reverse), and neither may be lost
//--- just because the other is also armed. The entry gate reports the
//--- static-drawdown lock first, and liquidation is required either way.
if ( ! ( st . staticLockTime > 0 & & st . targetLockTime > 0 ) )
{
err = " both permanent locks are active without consistent lock events " ;
return ( false ) ;
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
}
//--- reset identifier must stay a single serialisable token
if ( StringLen ( st . resetSignature ) > UQ_STATE_MAX_RESET_SIG )
{
err = " reset-operation identifier is too long " ;
return ( false ) ;
}
if ( StringFind ( st . resetSignature , " = " ) > = 0 | |
StringFind ( st . resetSignature , " \r " ) > = 0 | |
StringFind ( st . resetSignature , " \n " ) > = 0 )
{
err = " reset-operation identifier contains a forbidden character " ;
return ( false ) ;
}
2026-10-30 08:37:46 +07:00
//--- risk-cycle identity, only present from the cycle schema onwards
2026-10-30 08:52:18 +07:00
if ( st . format > = UQ_STATE_CYCLE_FORMAT )
2026-10-30 08:37:46 +07:00
{
if ( st . cycleSequence < 1 )
{
err = " risk-cycle sequence is not positive " ;
return ( false ) ;
}
if ( StringLen ( st . cycleId ) < = 0 | | StringLen ( st . cycleId ) > UQ_RESET_CYCLE_ID_MAX_LEN )
{
err = " risk-cycle identifier is missing or too long " ;
return ( false ) ;
}
if ( StringFind ( st . cycleId , " = " ) > = 0 | |
StringFind ( st . cycleId , " | " ) > = 0 | |
StringFind ( st . cycleId , " \r " ) > = 0 | |
StringFind ( st . cycleId , " \n " ) > = 0 )
{
err = " risk-cycle identifier contains a forbidden character " ;
return ( false ) ;
}
//--- consumed reset nonces
if ( StringLen ( st . resetNonceHistory ) > UQ_RESET_HISTORY_MAX_CHARS )
{
err = " consumed reset-nonce history is too long " ;
return ( false ) ;
}
if ( StringLen ( st . resetNonceHistory ) > 0 )
{
if ( UQResetNonceHistoryCount ( st . resetNonceHistory ) > UQ_RESET_HISTORY_MAX )
{
err = " consumed reset-nonce history holds too many entries " ;
return ( false ) ;
}
string nonces [ ] ;
const int nn = StringSplit ( st . resetNonceHistory , ( ushort ) ' | ' , nonces ) ;
for ( int k = 0 ; k < nn ; k + + )
{
if ( StringLen ( nonces [ k ] ) = = 0 )
continue ;
if ( ! UQResetNonceValid ( nonces [ k ] ) )
{
err = " consumed reset-nonce history holds an invalid nonce " ;
return ( false ) ;
}
}
}
2026-10-30 10:20:54 +07:00
//--- reference to the cycle that the last reset closed.
//--- The recorded kind is a bit field: 1 = static-drawdown, 2 =
//--- equity-target, 3 = both. A cycle can legitimately end with both
//--- permanent locks armed, so value 3 is accepted and preserved instead
//--- of rejecting the whole state and losing the surviving lock.
if ( ! UQPriorLockKindValid ( st . priorLockKind ) )
2026-10-30 08:37:46 +07:00
{
err = " recorded prior lock kind is not recognized " ;
return ( false ) ;
}
2026-10-30 10:20:54 +07:00
if ( st . priorLockKind = = UQ_PRIOR_LOCK_NONE )
2026-10-30 08:37:46 +07:00
{
if ( st . priorLockTime ! = 0 | | StringLen ( st . priorCycleId ) > 0 | |
st . priorBaselineBalance ! = 0.0 )
{
err = " prior-cycle reference is recorded without a prior lock event " ;
return ( false ) ;
}
}
else
{
if ( st . priorLockTime < = 0 )
{
err = " prior lock event timestamp is missing " ;
return ( false ) ;
}
if ( StringLen ( st . priorCycleId ) < = 0 | |
StringLen ( st . priorCycleId ) > UQ_RESET_CYCLE_ID_MAX_LEN )
{
err = " prior cycle identifier is missing or too long " ;
return ( false ) ;
}
if ( ! ( st . priorBaselineBalance > 0.0 ) )
{
err = " prior cycle baseline is not positive " ;
return ( false ) ;
}
2026-10-30 10:20:54 +07:00
//--- a permanent lock of a closed cycle can only exist because an
//--- owner reset closed that cycle, and every applied reset consumes
//--- exactly one nonce: the recorded history must prove it
if ( UQResetNonceHistoryCount ( st . resetNonceHistory ) < 1 )
{
err = " recorded prior lock kind has no consumed reset nonce " ;
return ( false ) ;
}
2026-10-30 08:37:46 +07:00
if ( st . priorCycleId = = st . cycleId | | st . priorLockTime > st . createdAt )
{
err = " prior-cycle reference is inconsistent with the current cycle " ;
return ( false ) ;
}
}
2026-10-30 10:20:54 +07:00
if ( st . cycleSequence = = 1 & & st . priorLockKind ! = UQ_PRIOR_LOCK_NONE )
2026-10-30 08:37:46 +07:00
{
err = " the first risk cycle cannot reference a previous cycle " ;
return ( false ) ;
}
}
2026-10-30 08:52:18 +07:00
//--- entry-risk reservation, only present from the reservation schema onwards
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
if ( st . format > = UQ_STATE_RESERVE_FORMAT )
2026-10-30 08:52:18 +07:00
{
if ( st . reserveState ! = UQ_RESERVE_NONE & &
st . reserveState ! = UQ_RESERVE_SENT & &
st . reserveState ! = UQ_RESERVE_UNRESOLVED )
{
err = " entry-risk reservation state is not recognized " ;
return ( false ) ;
}
if ( st . reserveState = = UQ_RESERVE_NONE )
{
if ( st . reserveMoney ! = 0.0 | | st . reserveTicket ! = 0 | |
StringLen ( st . reserveSymbol ) > 0 | | st . reserveTime ! = 0 )
{
err = " entry-risk reservation is recorded while its state is empty " ;
return ( false ) ;
}
}
else
{
if ( ! ( st . reserveMoney > 0.0 ) )
{
err = " reserved entry risk is not positive " ;
return ( false ) ;
}
if ( st . reserveTime < = 0 )
{
err = " reserved entry risk has no timestamp " ;
return ( false ) ;
}
if ( StringLen ( st . reserveSymbol ) < = 0 | |
StringLen ( st . reserveSymbol ) > UQ_RESERVE_SYMBOL_MAX_LEN )
{
err = " reserved entry symbol is missing or too long " ;
return ( false ) ;
}
if ( StringFind ( st . reserveSymbol , " = " ) > = 0 | |
StringFind ( st . reserveSymbol , " | " ) > = 0 | |
StringFind ( st . reserveSymbol , " \r " ) > = 0 | |
StringFind ( st . reserveSymbol , " \n " ) > = 0 )
{
err = " reserved entry symbol contains a forbidden character " ;
return ( false ) ;
}
//--- a reservation is persisted before the order is submitted, so it
//--- legitimately carries no ticket until the server has answered
}
}
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
//--- reservation identity, only present from the identity schema onwards.
//--- A reserved request that cannot be correlated with a broker record
//--- must be recorded as UNRESOLVED; it may never look like a normal
//--- submitted request that the reconciler could quietly release.
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
if ( st . format > = UQ_STATE_IDENTITY_FORMAT )
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
{
if ( st . reserveState = = UQ_RESERVE_NONE )
{
if ( StringLen ( st . reserveId ) > 0 | | st . reserveMagic ! = 0 | |
StringLen ( st . reserveScope ) > 0 | | st . reserveDirection ! = 0 | |
st . reserveVolume ! = 0.0 | | st . reserveEntry ! = 0.0 | |
st . reserveStop ! = 0.0 | | st . reserveDeal ! = 0 | |
st . reservePosition ! = 0 )
{
err = " reservation identity is recorded while its state is empty " ;
return ( false ) ;
}
}
else
{
if ( ! UQReserveTextSafe ( st . reserveId , UQ_RESERVE_ID_MAX_LEN ) )
{
err = " reservation identifier is missing or malformed " ;
return ( false ) ;
}
if ( StringLen ( st . reserveScope ) > UQ_RESERVE_SCOPE_MAX_LEN | |
( StringLen ( st . reserveScope ) > 0 & & ! UQReserveTextSafe ( st . reserveScope , UQ_RESERVE_SCOPE_MAX_LEN ) ) )
{
err = " reservation scope is malformed " ;
return ( false ) ;
}
if ( st . reserveDirection ! = -1 & & st . reserveDirection ! = 0 & &
st . reserveDirection ! = 1 )
{
err = " reservation direction is not buy, sell or unknown " ;
return ( false ) ;
}
if ( ! MathIsValidNumber ( st . reserveVolume ) | | st . reserveVolume < 0.0 | |
! MathIsValidNumber ( st . reserveEntry ) | | st . reserveEntry < 0.0 | |
! MathIsValidNumber ( st . reserveStop ) | | st . reserveStop < 0.0 )
{
err = " reservation price or volume is not a finite non-negative number " ;
return ( false ) ;
}
//--- a request that is still expected to produce exposure must carry
//--- the identity of that exposure, otherwise it is UNRESOLVED
if ( st . reserveState = = UQ_RESERVE_SENT )
{
if ( st . reserveDirection = = 0 | | ! ( st . reserveVolume > 0.0 ) | |
! ( st . reserveEntry > 0.0 ) | | ! ( st . reserveStop > 0.0 ) )
{
err = " a submitted reservation carries no correlatable identity " ;
return ( false ) ;
}
if ( st . reserveDirection > 0 & & ! ( st . reserveStop < st . reserveEntry ) )
{
err = " a buy reservation carries a stop that is not below its entry " ;
return ( false ) ;
}
if ( st . reserveDirection < 0 & & ! ( st . reserveStop > st . reserveEntry ) )
{
err = " a sell reservation carries a stop that is not above its entry " ;
return ( false ) ;
}
}
}
}
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- persistence generation, only present from the generation schema
//--- onwards. A state without a positive generation can neither prove that
//--- it is the newest one nor be ordered against its backup, so it is
//--- refused instead of being trusted.
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
if ( st . format > = UQ_STATE_GENERATION_FORMAT )
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
{
if ( st . stateRevision < 1 )
{
err = " state generation revision is not positive " ;
return ( false ) ;
}
if ( st . recoveryHold & & st . recoveryHoldTime < = 0 )
{
err = " recovery hold is active without a consistent hold event " ;
return ( false ) ;
}
if ( ! st . recoveryHold & & st . recoveryHoldTime ! = 0 )
{
err = " recovery hold time is set while no recovery hold is active " ;
return ( false ) ;
}
}
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
//--- monitoring continuity, only present from the monitoring schema
//--- onwards. A heartbeat in the future, a negative gap or a hold without
//--- an event would make the daily-loss history unverifiable, so the state
//--- is refused instead of being trusted.
2026-10-31 13:28:11 +07:00
if ( st . format > = UQ_STATE_MONITOR_FORMAT )
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
{
if ( st . monitorTime > 0 & & st . monitorTime > UQServerNow ( ) + ( datetime ) UQ_STATE_MAX_CLOCK_SKEW )
{
err = " monitoring heartbeat lies implausibly far in the future " ;
return ( false ) ;
}
if ( st . monitorGapSeconds < 0 | | st . monitorGapSeconds > UQ_MONITOR_GAP_MAX_SECONDS )
{
err = " recorded monitoring gap is out of range " ;
return ( false ) ;
}
if ( st . gapHold & & st . gapHoldTime < = 0 )
{
err = " monitoring-gap hold is active without a consistent hold event " ;
return ( false ) ;
}
if ( ! st . gapHold & & st . gapHoldTime ! = 0 )
{
err = " monitoring-gap hold time is set while no gap hold is active " ;
return ( false ) ;
}
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
return ( true ) ;
}
//+------------------------------------------------------------------+
2026-10-31 13:28:11 +07:00
//| Persistence locators |
//| |
//| The V2 locators are derived from the full 256 bit fingerprint and |
//| are the only locators a new state is ever written to. The legacy |
//| locators keep the exact names every previously published build |
//| used, because an existing installation still owns its state file |
//| under that name and must be found (and validated) for migration. |
//| |
//| The two families are separate functions on purpose: no caller can |
//| accidentally derive a V2 name from the lossy legacy key, and the |
//| legacy names are never recomputed from the new fingerprint. |
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
string UQStateFileName ( UQIdentity & id )
{
2026-10-31 13:28:11 +07:00
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
return ( " " ) ;
return ( UQ_STATE_V2_FILE_PREFIX + id . fingerprintV2 + " .cfg " ) ;
2026-10-30 07:19:59 +07:00
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
string UQStateBackupFileName ( UQIdentity & id )
2026-10-31 13:28:11 +07:00
{
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
return ( " " ) ;
return ( UQ_STATE_V2_FILE_PREFIX + id . fingerprintV2 + UQ_STATE_BACKUP_EXT ) ;
}
//+------------------------------------------------------------------+
//| Legacy locators, byte for byte identical to every previously |
//| published build. They are read for migration and are otherwise |
//| left untouched: a legacy file is never renamed, deleted or |
//| overwritten by this build. |
//+------------------------------------------------------------------+
string UQStateLegacyFileName ( UQIdentity & id )
{
return ( " UQ_EA_STATE_ " + id . key + " .cfg " ) ;
}
string UQStateLegacyBackupFileName ( UQIdentity & id )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
return ( " UQ_EA_STATE_ " + id . key + UQ_STATE_BACKUP_EXT ) ;
}
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
2026-10-31 13:28:11 +07:00
//| Build the persistence identity from explicit components |
//| |
//| Identical to UQBuildIdentity() for the live account; the split |
//| exists so the identity of a synthetic tuple can be built and |
//| verified without touching the account currently connected. |
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
2026-10-31 13:28:11 +07:00
bool UQBuildIdentityFromParts ( const string server , const long login ,
const string portfolio , UQIdentity & id , string & err )
2026-10-30 07:19:59 +07:00
{
err = " " ;
2026-10-31 13:28:11 +07:00
id . server = server ;
id . login = login ;
id . portfolio = portfolio ;
2026-10-31 13:47:47 +07:00
id . key = " " ;
id . hash = " " ;
id . fingerprintV2 = " " ;
id . legacyAmbiguous = false ;
2026-10-30 07:19:59 +07:00
if ( StringLen ( id . server ) < = 0 )
{
err = " account server name is unavailable " ;
return ( false ) ;
}
if ( id . login < = 0 )
{
err = " account login is unavailable " ;
return ( false ) ;
}
if ( StringLen ( id . portfolio ) < = 0 )
{
err = " portfolio identifier is empty " ;
return ( false ) ;
}
2026-10-31 13:28:11 +07:00
//--- legacy identity: preserved byte for byte, still used for the legacy
//--- locators, the legacy validation of persisted state and diagnostics
2026-10-30 07:19:59 +07:00
id . key = UQSanitizeKey ( id . server ) + " _ " + IntegerToString ( id . login ) + " _ " + UQSanitizeKey ( id . portfolio ) ;
id . hash = UQHashString ( id . server + " | " + IntegerToString ( id . login ) + " | " + id . portfolio ) ;
2026-10-31 13:28:11 +07:00
2026-10-31 13:47:47 +07:00
//--- the legacy serialisation is 'server + "|" + login + "|" + portfolio'.
//--- The login is canonical decimal digits, so the only way two different
//--- tuples can produce the same legacy preimage - and therefore the same
//--- legacy key and the same legacy hash - is a separator inside the server
//--- or the portfolio. Such an identity can never prove that a legacy state
//--- found under its legacy locator is its own, so the legacy evidence of an
//--- ambiguous identity is refused instead of being migrated.
id . legacyAmbiguous =
( StringFind ( id . server , " | " , 0 ) > = 0 | | StringFind ( id . portfolio , " | " , 0 ) > = 0 ) ;
2026-10-31 13:28:11 +07:00
//--- V2 identity: built from the ORIGINAL components, so two identities whose
//--- sanitised strings happen to match never share a locator. A failure here
//--- fails closed: there is no fall back to the lossy legacy hash.
string fpErr = " " ;
if ( ! UQComputeIdentityFingerprintV2 ( id . server , id . login , id . portfolio , id . fingerprintV2 , fpErr ) )
{
id . fingerprintV2 = " " ;
err = " identity fingerprint V2 could not be established: " + fpErr ;
return ( false ) ;
}
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
{
id . fingerprintV2 = " " ;
err = " identity fingerprint V2 is malformed " ;
return ( false ) ;
}
2026-10-30 07:19:59 +07:00
return ( true ) ;
}
2026-10-31 13:28:11 +07:00
//+------------------------------------------------------------------+
//| Build the persistence identity from the live account |
//+------------------------------------------------------------------+
bool UQBuildIdentity ( const string portfolio , UQIdentity & id , string & err )
{
return ( UQBuildIdentityFromParts ( AccountInfoString ( ACCOUNT_SERVER ) ,
( long ) AccountInfoInteger ( ACCOUNT_LOGIN ) ,
portfolio , id , err ) ) ;
}
2026-10-30 08:37:46 +07:00
//+------------------------------------------------------------------+
//| Risk-cycle identifier: sequence number plus a nonce/creation hash |
//+------------------------------------------------------------------+
string UQBuildCycleId ( const int sequence , const string seed )
{
return ( StringFormat ( " C%04d-%s " , sequence ,
StringSubstr ( UQHashString ( seed + " | " + IntegerToString ( sequence ) ) , 0 , 8 ) ) ) ;
}
//+------------------------------------------------------------------+
//| Upgrade a state written by an earlier published build to the |
//| current risk-cycle schema. |
//| |
//| The upgrade is deterministic and never invents a new risk cycle: |
//| the cycle the file already describes becomes cycle 1, no reset |
//| nonce has been consumed yet and no prior cycle is referenced. |
//| A pre-cycle reset signature cannot prove which nonce was used, so |
//| it is dropped: a subsequent reset always needs a fresh nonce. |
//+------------------------------------------------------------------+
2026-10-31 13:28:11 +07:00
void UQStateMigrateToMonitor ( UQRiskState & st )
2026-10-30 08:37:46 +07:00
{
2026-10-31 13:28:11 +07:00
if ( st . format > = UQ_STATE_MONITOR_FORMAT )
2026-10-30 08:37:46 +07:00
return ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
//--- 6 -> 7: the monitoring fields are the only addition. The generation,
//--- every lock, the cycle identity and an outstanding reservation are
//--- carried over unchanged; no heartbeat is invented, so the first
//--- monitored moment of the migrated state establishes it.
if ( st . format > = UQ_STATE_GENERATION_FORMAT )
{
st . monitorTime = 0 ;
st . monitorGapSeconds = 0 ;
st . gapHold = false ;
st . gapHoldTime = 0 ;
2026-10-31 13:28:11 +07:00
st . format = UQ_STATE_MONITOR_FORMAT ;
fix: enforce account scope and daily monitoring continuity
Schema 7 adds a persisted monitoring heartbeat, the last recorded gap and a gap hold, with deterministic migration from schema 6 that keeps every lock, cycle identity and reservation.
D0 is no longer invented on startup or on a rollover: a trading day is adopted only when its boundary was observed while the EA was executing, or when the equity path across it is provably constant (no position, no pending order, no account deal). Otherwise the previous D0 is preserved, a monitoring-gap hold blocks every new entry and the exact gap is reported; the hold survives restart and clears only at an observed day boundary or an explicit owner cycle. Static, target and daily thresholds keep their priority, so a breach is still locked and liquidated while a day is unverified.
Account scope is now counted and disclosed for owned and unmanaged positions and pending orders; unmanaged pending orders block entries under the external-exposure guard, and the EA states that the account-wide limits cannot be guaranteed while whole-account liquidation stays disabled.
2026-10-30 11:20:47 +07:00
return ;
}
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- the upgraded state becomes generation 1 of the current schema. An
//--- upgrade is not an event that may clear a lock, so no recovery hold is
//--- invented here, and the migration never discards a lock, a baseline,
//--- a consumed nonce or an outstanding reservation.
st . stateRevision = 1 ;
st . recoveryHold = false ;
st . recoveryHoldTime = 0 ;
2026-10-30 08:52:18 +07:00
if ( st . format < UQ_STATE_CYCLE_FORMAT )
{
st . cycleSequence = 1 ;
st . cycleId = UQBuildCycleId ( 1 , st . identity + " | " + IntegerToString ( ( long ) st . createdAt ) ) ;
st . resetNonceHistory = " " ;
st . priorBaselineBalance = 0.0 ;
st . priorCycleId = " " ;
st . priorLockKind = 0 ;
st . priorLockTime = 0 ;
st . resetSignature = " " ;
}
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
//--- an upgraded state must never lose an outstanding entry risk. A
//--- reservation written by the previous schema keeps its money, symbol and
//--- timestamp, but it carries no order, deal or position identity, so it
//--- can no longer be correlated: it becomes UNRESOLVED instead of being
//--- migrated to NONE, which would silently free the reserved risk
if ( st . format > = UQ_STATE_RESERVE_FORMAT & & st . reserveState ! = UQ_RESERVE_NONE )
{
if ( StringLen ( st . reserveId ) < = 0 )
st . reserveId = UQBuildReserveId ( " legacy " , st . reserveSymbol , st . reserveTime , 0 ) ;
st . reserveMagic = 0 ;
st . reserveScope = " " ;
st . reserveDirection = 0 ;
st . reserveVolume = 0.0 ;
st . reserveEntry = 0.0 ;
st . reserveStop = 0.0 ;
st . reserveDeal = 0 ;
st . reservePosition = 0 ;
st . reserveState = UQ_RESERVE_UNRESOLVED ;
2026-10-31 13:28:11 +07:00
st . format = UQ_STATE_MONITOR_FORMAT ;
fix: correlate outstanding entry risk reservations
A reservation could be released merely because an unrelated position or pending order existed under the same Magic Number, and an order ticket was treated as a position ticket. An entry whose submission result was lost also released its reserved risk.
- UQ_Defs: state schema 5 adds the reservation identity; ENUM_UQ_SUBMIT_RESULT and a retcode classifier that only accepts proven non-execution.
- UQ_State: reservation id, magic, scope, direction, volume, entry, stop, order/deal/position tickets are persisted, checksummed and strictly validated. A previous-schema reservation migrates to UNRESOLVED with its money preserved, never to NONE.
- UQ_Risk: UQReserveCorrelate links a reservation to the broker only through its own order ticket, a deal whose DEAL_ORDER is that ticket, or the traced POSITION_IDENTIFIER. Ambiguity, a disconnected terminal and a missing record all keep the money reserved as UNRESOLVED and block entries.
- UQ_Exec: UQSendMarket reports the submission outcome; the caller withdraws reserved risk only on a local preflight failure or a proven broker rejection.
- Universal_Quant_EA.mq5: deals and order transactions reconcile the reservation immediately; status and startup diagnostics expose the reservation identity.
2026-10-30 10:43:15 +07:00
return ;
}
//--- a schema that predates the reservation field set cannot carry any
//--- entry risk at all
st . reserveState = UQ_RESERVE_NONE ;
st . reserveMoney = 0.0 ;
st . reserveTicket = 0 ;
st . reserveSymbol = " " ;
st . reserveTime = 0 ;
st . reserveId = " " ;
st . reserveMagic = 0 ;
st . reserveScope = " " ;
st . reserveDirection = 0 ;
st . reserveVolume = 0.0 ;
st . reserveEntry = 0.0 ;
st . reserveStop = 0.0 ;
st . reserveDeal = 0 ;
st . reservePosition = 0 ;
2026-10-31 13:28:11 +07:00
st . format = UQ_STATE_MONITOR_FORMAT ;
}
//+------------------------------------------------------------------+
//| Upgrade a state to the current schema. |
//| |
//| Every earlier schema is upgraded in order: the older builds first |
//| reach schema 7 (monitoring continuity), then schema 8 adds the V2 |
//| identity. A migration never invents a baseline, never clears a |
//| lock, never rebuilds a reservation as zero risk and never creates |
//| a new risk cycle. |
//+------------------------------------------------------------------+
void UQStateMigrateToCurrent ( UQRiskState & st )
{
if ( st . format > = UQ_STATE_FORMAT )
return ;
UQStateMigrateToMonitor ( st ) ;
if ( st . format < UQ_STATE_FORMAT )
{
//--- 7 -> 8. The V2 field is staged empty here: the only identity that
//--- may ever enter it is the live fingerprint of the identity that just
//--- passed the legacy identity validation, and UQStateSave() stamps
//--- exactly that value, so no invented identity can reach a file.
st . identityV2 = " " ;
st . format = UQ_STATE_FORMAT ;
}
2026-10-30 08:37:46 +07:00
}
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//| Load and fully validate one state file |
//| UQ_LOAD_ERROR -> file exists but must not be trusted |
//| UQ_LOAD_ABSENT -> no file at this location |
//+------------------------------------------------------------------+
ENUM_UQ_STATE_LOAD UQStateLoadFile ( const string fname , UQIdentity & id ,
UQRiskState & st , string & err )
{
err = " " ;
UQStateReset ( st ) ;
if ( ! FileIsExist ( fname , FILE_COMMON ) )
return ( UQ_LOAD_ABSENT ) ;
const int h = FileOpen ( fname , FILE_READ | FILE_BIN | FILE_COMMON ) ;
if ( h = = INVALID_HANDLE )
{
err = StringFormat ( " state file cannot be opened (err=%d) " , GetLastError ( ) ) ;
return ( UQ_LOAD_ERROR ) ;
}
const int size = ( int ) FileSize ( h ) ;
if ( size < = 0 )
{
FileClose ( h ) ;
err = " state file is empty " ;
return ( UQ_LOAD_ERROR ) ;
}
uchar buf [ ] ;
ArrayResize ( buf , size ) ;
const int read = ( int ) FileReadArray ( h , buf , 0 , size ) ;
FileClose ( h ) ;
if ( read < = 0 )
{
err = " state file could not be read " ;
return ( UQ_LOAD_ERROR ) ;
}
string body = CharArrayToString ( buf , 0 , read , CP_ACP ) ;
//--- strip trailing padding written by earlier builds
int blen = StringLen ( body ) ;
while ( blen > 0 )
{
const ushort c = StringGetCharacter ( body , blen - 1 ) ;
if ( c = = 0 | | c = = ' \r ' | | c = = ' \n ' | | c = = ' ' | | c = = ' \t ' )
{
blen - - ;
continue ;
}
break ;
}
body = StringSubstr ( body , 0 , blen ) ;
if ( blen < = 0 )
{
err = " state file contains no data " ;
return ( UQ_LOAD_ERROR ) ;
}
bool hasChecksum = false ;
string checksumStored = " " ;
if ( ! UQStateParseBody ( body , st , hasChecksum , checksumStored , err ) )
return ( UQ_LOAD_ERROR ) ;
2026-10-30 08:52:18 +07:00
if ( st . format < UQ_STATE_LEGACY_FORMAT | | st . format > UQ_STATE_FORMAT )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
err = StringFormat ( " unsupported state format %d " , st . format ) ;
return ( UQ_LOAD_ERROR ) ;
}
2026-10-30 08:37:46 +07:00
if ( st . format > = UQ_STATE_PREV_FORMAT & & ! hasChecksum )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
err = " integrity checksum is missing " ;
return ( UQ_LOAD_ERROR ) ;
}
if ( st . format = = UQ_STATE_LEGACY_FORMAT & & hasChecksum )
{
err = " legacy state unexpectedly carries an integrity checksum " ;
return ( UQ_LOAD_ERROR ) ;
}
if ( hasChecksum )
{
const string calc = UQHashString ( UQStateCanonicalBody ( st ) ) ;
if ( calc ! = checksumStored )
{
err = " integrity checksum mismatch " ;
return ( UQ_LOAD_ERROR ) ;
}
}
if ( ! UQStateValidate ( id , st , err ) )
return ( UQ_LOAD_ERROR ) ;
2026-10-30 08:37:46 +07:00
//--- a verified state written by an earlier build is upgraded in memory;
//--- the caller persists it in the current schema
const int loadedFormat = st . format ;
if ( loadedFormat < UQ_STATE_FORMAT )
UQStateMigrateToCurrent ( st ) ;
return ( ( loadedFormat = = UQ_STATE_FORMAT ) ? UQ_LOAD_VALID : UQ_LOAD_LEGACY ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
}
//+------------------------------------------------------------------+
//| Copy one common-folder file byte for byte |
//+------------------------------------------------------------------+
bool UQStateCopyFile ( const string src , const string dst )
{
const int hs = FileOpen ( src , FILE_READ | FILE_BIN | FILE_COMMON ) ;
if ( hs = = INVALID_HANDLE )
{
PrintFormat ( " [UQ] ERROR: cannot open '%s' for copy (err=%d) " , src , GetLastError ( ) ) ;
return ( false ) ;
}
const int size = ( int ) FileSize ( hs ) ;
if ( size < = 0 )
{
FileClose ( hs ) ;
return ( false ) ;
}
uchar buf [ ] ;
ArrayResize ( buf , size ) ;
const int read = ( int ) FileReadArray ( hs , buf , 0 , size ) ;
FileClose ( hs ) ;
if ( read < = 0 )
return ( false ) ;
const int hd = FileOpen ( dst , FILE_WRITE | FILE_BIN | FILE_COMMON ) ;
if ( hd = = INVALID_HANDLE )
{
PrintFormat ( " [UQ] ERROR: cannot create '%s' (err=%d) " , dst , GetLastError ( ) ) ;
return ( false ) ;
}
FileWriteArray ( hd , buf , 0 , read ) ;
FileClose ( hd ) ;
return ( true ) ;
}
//+------------------------------------------------------------------+
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//| Display text of a state load result |
//+------------------------------------------------------------------+
string UQStateLoadText ( const ENUM_UQ_STATE_LOAD r )
{
switch ( r )
{
case UQ_LOAD_VALID : return ( " verified " ) ;
case UQ_LOAD_LEGACY : return ( " verified legacy schema " ) ;
case UQ_LOAD_ABSENT : return ( " absent " ) ;
case UQ_LOAD_ERROR : return ( " unusable " ) ;
}
return ( " unknown " ) ;
}
//+------------------------------------------------------------------+
//| Load one state file together with the generation it carries |
//| |
//| An absent or unusable file reports generation 0, so no caller can |
//| mistake "no proven generation" for a generation to build upon. |
//+------------------------------------------------------------------+
ENUM_UQ_STATE_LOAD UQStateLoadGenerational ( const string fname , UQIdentity & id ,
UQRiskState & st , long & rev , string & err )
{
rev = 0 ;
const ENUM_UQ_STATE_LOAD r = UQStateLoadFile ( fname , id , st , err ) ;
if ( r = = UQ_LOAD_VALID | | r = = UQ_LOAD_LEGACY )
rev = ( st . stateRevision > 0 ? st . stateRevision : 0 ) ;
return ( r ) ;
}
//+------------------------------------------------------------------+
//| Verify that one file holds exactly the generation that was |
//| written: usable schema, expected revision and expected content. |
//+------------------------------------------------------------------+
bool UQStateVerifyFile ( const string fname , UQIdentity & id , const long expectRevision ,
const string expectCanonical , const string role )
{
UQRiskState check ;
string err = " " ;
const ENUM_UQ_STATE_LOAD r = UQStateLoadFile ( fname , id , check , err ) ;
if ( r ! = UQ_LOAD_VALID )
{
PrintFormat ( " [UQ] ERROR: the %s state file '%s' failed verification: %s " ,
role , fname , ( StringLen ( err ) > 0 ? err : UQStateLoadText ( r ) ) ) ;
return ( false ) ;
}
if ( check . stateRevision ! = expectRevision )
{
PrintFormat ( " [UQ] ERROR: the %s state file '%s' carries generation %I64d instead of %I64d. " ,
role , fname , check . stateRevision , expectRevision ) ;
return ( false ) ;
}
if ( UQStateCanonicalBody ( check ) ! = expectCanonical )
{
PrintFormat ( " [UQ] ERROR: the %s state file '%s' does not describe the generation that was written. " ,
role , fname ) ;
return ( false ) ;
}
return ( true ) ;
}
//+------------------------------------------------------------------+
//| Persist one state generation. |
//| |
//| The order is write-ahead, so no interruption can leave an entry- |
//| permitted state behind an unverified file: |
//| 1. the next generation is derived from what is proven on disk; |
//| 2. the temporary file is written and read back; |
//| 3. the same verified bytes are written to the backup and read |
//| back, so the backup can never lag the latest update; |
//| 4. the primary is replaced by the verified temporary file and |
//| read back; |
//| 5. primary and backup are reconciled on generation and content. |
//| |
//| A failure at any step returns false, which degrades the risk |
//| controller to unhealthy and blocks every new entry. |
2026-10-30 07:19:59 +07:00
//+------------------------------------------------------------------+
bool UQStateSave ( UQIdentity & id , UQRiskState & st )
{
2026-10-31 13:28:11 +07:00
//--- a state may only be persisted together with the identity it belongs.
//--- Without a valid V2 fingerprint no locator exists, so saving would write
//--- a shared or empty name: the save fails closed instead.
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
{
PrintFormat ( " [UQ] ERROR: the V2 identity fingerprint is unavailable, so no risk state may be persisted for this scope. " ) ;
return ( false ) ;
}
st . format = UQ_STATE_FORMAT ;
st . identity = id . hash ;
st . identityV2 = id . fingerprintV2 ;
st . updatedAt = TimeCurrent ( ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( st . updatedAt < = 0 )
st . updatedAt = UQServerNow ( ) ;
2026-10-30 07:19:59 +07:00
if ( st . createdAt < = 0 )
st . createdAt = st . updatedAt ;
const string fname = UQStateFileName ( id ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
const string tmp = fname + UQ_STATE_TEMP_EXT ;
const string bak = UQStateBackupFileName ( id ) ;
2026-10-30 07:19:59 +07:00
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- 1. the next generation is derived from the files proven on disk, so a
//--- stale in-memory copy can never overwrite a newer verified state
UQRiskState primaryOnDisk ;
UQRiskState backupOnDisk ;
string perr = " " ;
string berr = " " ;
long revPrimary = 0 ;
long revBackup = 0 ;
const ENUM_UQ_STATE_LOAD loadPrimary = UQStateLoadGenerational ( fname , id , primaryOnDisk , revPrimary , perr ) ;
const ENUM_UQ_STATE_LOAD loadBackup = UQStateLoadGenerational ( bak , id , backupOnDisk , revBackup , berr ) ;
if ( loadPrimary = = UQ_LOAD_ERROR )
PrintFormat ( " [UQ] WARNING: the unusable primary state '%s' (%s) is replaced by a verified generation. " ,
fname , perr ) ;
const long provenMax = ( revPrimary > revBackup ? revPrimary : revBackup ) ;
if ( provenMax > st . stateRevision )
{
PrintFormat ( " [UQ] ERROR: the persistent state holds generation %I64d while this instance holds generation %I64d. A newer verified state is never overwritten. " ,
provenMax , st . stateRevision ) ;
return ( false ) ;
}
st . stateRevision = ( ( st . stateRevision > provenMax ) ? st . stateRevision : provenMax ) + 1 ;
//--- 2. serialise and stage
const string canonical = UQStateCanonicalBody ( st ) ;
const string body = canonical +
UQ_STATE_CHECKSUM_KEY + " = " + UQHashString ( canonical ) + " \r \n " ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
const int len = StringLen ( body ) ;
2026-10-30 07:19:59 +07:00
if ( len < = 0 )
{
PrintFormat ( " [UQ] ERROR: state serialisation produced no data " ) ;
return ( false ) ;
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
uchar buf [ ] ;
ArrayResize ( buf , len ) ;
for ( int i = 0 ; i < len ; i + + )
buf [ i ] = ( uchar ) StringGetCharacter ( body , i ) ;
FileDelete ( tmp , FILE_COMMON ) ;
2026-10-30 07:19:59 +07:00
const int h = FileOpen ( tmp , FILE_WRITE | FILE_BIN | FILE_COMMON ) ;
if ( h = = INVALID_HANDLE )
{
PrintFormat ( " [UQ] ERROR: cannot create state file '%s' (err=%d) " , tmp , GetLastError ( ) ) ;
return ( false ) ;
}
FileWriteArray ( h , buf , 0 , len ) ;
FileClose ( h ) ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- 3. the staged generation must verify before it is copied anywhere
if ( ! UQStateVerifyFile ( tmp , id , st . stateRevision , canonical , " staged " ) )
2026-10-30 07:19:59 +07:00
{
FileDelete ( tmp , FILE_COMMON ) ;
return ( false ) ;
}
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- 4. the backup receives exactly the same verified generation
if ( ! UQStateCopyFile ( tmp , bak ) )
2026-10-30 07:19:59 +07:00
{
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
PrintFormat ( " [UQ] ERROR: the verified generation could not be written to the backup '%s'. " , bak ) ;
FileDelete ( tmp , FILE_COMMON ) ;
return ( false ) ;
}
if ( ! UQStateVerifyFile ( bak , id , st . stateRevision , canonical , " backup " ) )
{
FileDelete ( tmp , FILE_COMMON ) ;
return ( false ) ;
2026-10-30 07:19:59 +07:00
}
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- 5. commit the verified generation as the primary
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( ! FileMove ( tmp , FILE_COMMON , fname , FILE_COMMON | FILE_REWRITE ) )
2026-10-30 07:19:59 +07:00
{
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
PrintFormat ( " [UQ] ERROR: cannot commit state file '%s' (err=%d) " , fname , GetLastError ( ) ) ;
FileDelete ( tmp , FILE_COMMON ) ;
2026-10-30 07:19:59 +07:00
return ( false ) ;
}
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
if ( ! UQStateVerifyFile ( fname , id , st . stateRevision , canonical , " primary " ) )
return ( false ) ;
2026-10-30 07:19:59 +07:00
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
//--- 6. primary and backup must now describe the same generation
UQRiskState checkPrimary ;
UQRiskState checkBackup ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
string cerr = " " ;
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
string xerr = " " ;
long revP = 0 ;
long revB = 0 ;
if ( UQStateLoadGenerational ( fname , id , checkPrimary , revP , cerr ) ! = UQ_LOAD_VALID | |
UQStateLoadGenerational ( bak , id , checkBackup , revB , xerr ) ! = UQ_LOAD_VALID | |
revP ! = st . stateRevision | | revB ! = st . stateRevision | |
UQStateCanonicalBody ( checkPrimary ) ! = UQStateCanonicalBody ( checkBackup ) )
2026-10-30 07:19:59 +07:00
{
fix: recover the latest verified persistent state
The backup was built from the previous primary before the new primary was installed, so it could lag the newest successful update, and a missing primary was still treated as a candidate fresh installation. Neither an interrupted commit nor a stale mirror was detectable.
B1 state schema 6 (defines UQ_STATE_FORMAT=6, UQ_STATE_IDENTITY_FORMAT=5) adds a monotonic stateRevision plus recoveryHold/recoveryHoldTime. Formats 1..5 keep their own field block, so every published checksum still verifies over exactly the block that produced it, and a migration sets generation 1 without inventing a lock event.
B2 UQStateSave is write-ahead and generation-aware: the next generation is derived from what is proven on disk (primary and backup), the staged file is written and read back, the same verified bytes go to the backup and are read back, the primary is replaced and read back, and primary and backup are finally reconciled on generation and content. A newer verified state is never overwritten by an older instance; a failure at any step returns false, which blocks every new entry.
B3 LoadPersistentState orders primary and backup by generation: newer primary wins, equal generations must agree on content (otherwise fail closed), and a backup exactly one generation ahead is the interrupted-commit window and is adopted. Anything else is refused.
B4 a backup is evidence of a previous initialization, so a missing primary is never a fresh installation and never creates a replacement B0; a verified backup is restored only under explicit owner authorization.
B5 a recovered, reordered or superseded state always starts behind a persisted recovery hold (UQ_BLOCK_RECOVERY) that survives restart and only an explicit owner-authorized recovery risk cycle (UQ_RESET_RECOVERY, fresh nonce, explicit B0, flat scope) can clear. A recovery never clears a permanent lock the recovered state carries, and a cycle without a prior permanent lock records no prior-cycle reference instead of an inconsistent one.
B6 the initialization marker is written and then verified; a marker that cannot be persisted or verified fails the load closed.
B7 the status panel reports the persistence generation and an active recovery hold.
2026-10-30 11:09:43 +07:00
PrintFormat ( " [UQ] ERROR: the persisted state could not be reconciled (primary generation %I64d, backup generation %I64d, expected %I64d). " ,
revP , revB , st . stateRevision ) ;
2026-10-30 07:19:59 +07:00
return ( false ) ;
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
return ( true ) ;
}
2026-10-30 07:19:59 +07:00
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
//+------------------------------------------------------------------+
//| Redundant initialization marker |
//| |
//| A terminal global variable records that this identity scope has |
//| already been initialized and the creation timestamp it was |
//| initialized with. It is deliberately not stored inside the state |
//| file, so deleting or truncating the file cannot fabricate a |
//| fresh installation while the terminal data folder survives. |
//+------------------------------------------------------------------+
2026-10-31 13:28:11 +07:00
//+------------------------------------------------------------------+
//| V2 initialization marker name |
//| |
//| The name is derived from the V2 fingerprint only. 58 hexadecimal |
//| characters carry 232 of the 256 bits and keep the whole name at |
//| exactly 63 characters, the terminal limit; the complete 256 bit |
//| fingerprint is retained in the persisted state and in the file |
//| locators. The "UQ2M_" namespace is disjoint from the legacy |
//| "UQEA_STATE_MARKER_" namespace, so neither can be read as the |
//| other. |
//+------------------------------------------------------------------+
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
string UQStateMarkerName ( UQIdentity & id )
2026-10-31 13:28:11 +07:00
{
const string shortFp = UQIdentityFingerprintShort ( id . fingerprintV2 ) ;
if ( StringLen ( shortFp ) ! = UQ_IDV2_GV_CHARS )
return ( " " ) ;
const string name = UQ_GV_STATE_MARKER_V2_PREFIX + shortFp ;
if ( StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
return ( " " ) ;
return ( name ) ;
}
//+------------------------------------------------------------------+
//| Legacy initialization marker name |
//| |
//| Byte for byte the name every previously published build used. It |
//| is only ever read (to detect an already initialized legacy scope) |
//| and is never written, renamed or removed by this build. |
//+------------------------------------------------------------------+
string UQStateLegacyMarkerName ( UQIdentity & id )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
2026-10-31 13:02:46 +07:00
const string name = UQ_GV_STATE_MARKER_PREFIX + id . key ;
//--- every scope whose name already fits keeps exactly the name it always
//--- had, so an existing marker stays readable and its scope is never
//--- mistaken for a fresh installation
if ( StringLen ( name ) < = UQ_GV_NAME_MAX_CHARS )
return ( name ) ;
//--- an over-long scope is folded into a bounded deterministic name built
//--- from the identity hash, which covers server|login|portfolio. A
//--- sanitised key always carries '_' separators while a hash is
//--- hexadecimal, so the two forms cannot collide with each other, and a
//--- collision would still fail closed through the timestamp comparison.
return ( UQ_GV_STATE_MARKER_PREFIX +
StringSubstr ( id . hash , 0 , UQ_GV_NAME_MAX_CHARS - StringLen ( UQ_GV_STATE_MARKER_PREFIX ) ) ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
}
2026-10-30 07:19:59 +07:00
2026-10-31 13:28:11 +07:00
//+------------------------------------------------------------------+
//| Read one marker global variable |
//+------------------------------------------------------------------+
ENUM_UQ_MARKER_STATE UQMarkerCheckName ( const string name , const datetime createdAt , datetime & markerTime )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
markerTime = 0 ;
2026-10-31 13:28:11 +07:00
if ( StringLen ( name ) < = 0 )
return ( UQ_MARKER_ABSENT ) ;
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( ! GlobalVariableCheck ( name ) )
return ( UQ_MARKER_ABSENT ) ;
2026-10-30 07:19:59 +07:00
2026-10-31 13:47:47 +07:00
//--- an existing marker with an unusable value is NOT an absent marker.
//--- Absence is "no evidence"; an invalid value is corrupt evidence, and it
//--- proves that this scope was written before by something that cannot be
//--- trusted. Every caller refuses instead of rebuilding the scope.
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
const double v = GlobalVariableGet ( name ) ;
2026-10-31 13:47:47 +07:00
if ( ! MathIsValidNumber ( v ) )
return ( UQ_MARKER_INVALID ) ;
if ( v < = 0.0 )
return ( UQ_MARKER_INVALID ) ;
if ( ( double ) ( long ) v ! = v )
return ( UQ_MARKER_INVALID ) ;
//--- a marker records the creation time of a risk cycle: a value beyond the
//--- clock cannot have been written by a real initialization
if ( v > ( double ) ( TimeLocal ( ) + UQ_STATE_MAX_CLOCK_SKEW ) )
return ( UQ_MARKER_INVALID ) ;
2026-10-30 07:19:59 +07:00
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
markerTime = ( datetime ) ( long ) v ;
if ( markerTime ! = createdAt )
return ( UQ_MARKER_MISMATCH ) ;
2026-10-30 07:19:59 +07:00
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
return ( UQ_MARKER_MATCH ) ;
}
2026-10-31 13:28:11 +07:00
ENUM_UQ_MARKER_STATE UQStateMarkerCheck ( UQIdentity & id , const datetime createdAt , datetime & markerTime )
{
return ( UQMarkerCheckName ( UQStateMarkerName ( id ) , createdAt , markerTime ) ) ;
}
//--- legacy marker evidence. Called with createdAt = 0, so any existing value
//--- reports MISMATCH, which is the "this scope was initialized before" signal.
ENUM_UQ_MARKER_STATE UQStateLegacyMarkerCheck ( UQIdentity & id , const datetime createdAt , datetime & markerTime )
{
return ( UQMarkerCheckName ( UQStateLegacyMarkerName ( id ) , createdAt , markerTime ) ) ;
}
//+------------------------------------------------------------------+
//| Write one marker global variable and prove the terminal kept it |
//| |
//| A write the terminal silently declined to persist must never be |
//| read as a recorded initialization, so the exact name and the exact |
//| stored value are verified by read-back. |
//+------------------------------------------------------------------+
bool UQMarkerSetVerified ( const string name , const datetime createdAt )
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
{
if ( createdAt < = 0 )
2026-10-30 07:19:59 +07:00
return ( false ) ;
2026-10-31 13:28:11 +07:00
if ( StringLen ( name ) < = 0 | | StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
{
PrintFormat ( " [UQ] ERROR: initialization marker name is unusable (%d chars). " , StringLen ( name ) ) ;
return ( false ) ;
}
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
if ( ! GlobalVariableSet ( name , ( double ) ( long ) createdAt ) )
2026-10-30 07:19:59 +07:00
{
fix: validate persistent risk-state integrity and fail closed on missing state
* UQ_State: checksummed schema v2 with strict typed parsing, duplicate/unknown-field rejection and semantic validation of every mandatory field; legacy v1 files are validated and migrated on load
* UQ_State: verified backup retention, staged-file verification before commit and post-commit re-verification
* UQ_State: redundant identity-scoped initialization marker reconciled against the persisted state
* UQ_Risk: a new risk cycle requires an explicit owner authorization and a deliberately declared positive B0; the current account balance is never used as a substitute
* UQ_Risk: missing, empty, corrupt, foreign, version-unsupported or schema-invalid state fails closed; backup recovery only under explicit owner authorization
* UQ_Risk: a failed state write degrades the controller to unhealthy, blocks all new entries, preserves the in-memory lock and retries at a bounded interval
* UQ_Risk: daily rollover keeps permanent locks and reports unmonitored intervals explicitly
* UQ_Defs: state schema constants, marker global-variable prefix and the STATE PERSISTENCE FAILED block reason
* Universal_Quant_EA: first-initialization and backup-restore authorization inputs, input validation and state-health status line
2026-10-30 08:07:18 +07:00
PrintFormat ( " [UQ] ERROR: initialization marker '%s' could not be written (err=%d) " ,
name , GetLastError ( ) ) ;
2026-10-30 07:19:59 +07:00
return ( false ) ;
}
2026-10-31 13:28:11 +07:00
if ( ! GlobalVariableCheck ( name ) )
{
PrintFormat ( " [UQ] ERROR: initialization marker '%s' could not be read back (err=%d) " ,
name , GetLastError ( ) ) ;
return ( false ) ;
}
if ( ( datetime ) ( long ) GlobalVariableGet ( name ) ! = createdAt )
{
PrintFormat ( " [UQ] ERROR: initialization marker '%s' does not hold the value that was written. " ,
name ) ;
return ( false ) ;
}
return ( true ) ;
}
bool UQStateMarkerSet ( UQIdentity & id , const datetime createdAt )
{
return ( UQMarkerSetVerified ( UQStateMarkerName ( id ) , createdAt ) ) ;
}
//+------------------------------------------------------------------+
//| Name of the V2 duplicate-instance guard |
//| |
//| Derived from the V2 fingerprint only, so two logical identities |
//| that share a sanitised legacy key can never share a guard. 58 of |
//| the 64 hexadecimal characters carry 232 of the 256 bits and keep |
//| the whole name at exactly 63 characters. An unusable fingerprint |
//| yields an empty name, which the publisher refuses: an unproven |
//| guard always fails closed. |
//+------------------------------------------------------------------+
string UQInstanceGuardName ( const string fingerprintV2 )
{
const string shortFp = UQIdentityFingerprintShort ( fingerprintV2 ) ;
if ( StringLen ( shortFp ) ! = UQ_IDV2_GV_CHARS )
return ( " " ) ;
const string name = UQ_GV_INSTANCE_V2_PREFIX + shortFp ;
if ( StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
return ( " " ) ;
return ( name ) ;
}
//+------------------------------------------------------------------+
//| Name of the legacy duplicate-instance guard |
//| |
//| Byte for byte the name every previously published build used. It |
//| is only ever read, to prove that no instance started by an older |
//| build is still running, and it is never written, published as the |
//| active guard or deleted: it is derived from the lossy identity |
//| key, and publishing it again would reintroduce exactly the |
//| cross-portfolio false collision this correction removes. |
//| |
//| An identity whose name already fits keeps the name it always had. |
//| An over-long identity is folded into a bounded deterministic name |
//| built from the identity hash (UQHashString), which covers the |
//| complete identity key. A hash collision would still fail closed |
//| through the heartbeat read-back of the publisher. |
//+------------------------------------------------------------------+
string UQInstanceGuardLegacyName ( const string identityKey )
{
const string name = UQ_GV_INSTANCE_PREFIX + identityKey ;
//--- a name that already fits is returned unchanged, so no existing
//--- instance guard is renamed by this correction
if ( StringLen ( name ) < = UQ_GV_NAME_MAX_CHARS )
return ( name ) ;
//--- the folded form is bounded by construction and deterministic: the
//--- whole identity key is hashed, nothing is truncated from the key
const int budget = UQ_GV_NAME_MAX_CHARS - StringLen ( UQ_GV_INSTANCE_PREFIX ) ;
return ( UQ_GV_INSTANCE_PREFIX + StringSubstr ( UQHashString ( identityKey ) , 0 , budget ) ) ;
}
2026-10-31 13:47:47 +07:00
//+------------------------------------------------------------------+
//| Duplicate-instance ownership protocol |
//| |
//| MT5 exposes exactly one atomic primitive over terminal global |
//| variables: GlobalVariableSetOnCondition() is documented as giving |
//| atomic access and as the primitive intended for a mutex between |
//| Expert Advisors running at the same time in one terminal. |
//| GlobalVariableSet() is not atomic - it creates or overwrites with |
//| no condition - so "check, then publish" cannot exclude a second |
//| instance. Every ownership transition below is therefore one |
//| compare-and-swap on one strictly increasing value: |
//| |
//| absent --Set(0)----------------> unclaimed (creation) |
//| unclaimed --CAS(0 -> t)-----------> owned(t) |
//| owned(t) --CAS(t -> t'), t' > t--> owned(t') (heartbeat) |
//| owned(t) --CAS(t -> t'), t' > t--> owned(t') (stale takeover)|
//| owned(t) --CAS(t -> 0), delete---> absent (release) |
//| |
//| Exactly one contender can win a single CAS; because the value only|
//| moves forward, a superseded owner can never pass its own heartbeat|
//| again and a release can never remove a successor's claim. |
//| |
//| The creation of the variable itself cannot be made atomic with |
//| this API, so it is fenced: every contender announces itself in its |
//| own private ballot variable (a name no other instance ever writes, |
//| so the announcement cannot race) before it touches the shared |
//| guard, and a contender that wins the creation CAS gives the guard |
//| back and fails closed while any other live ballot is visible. Two |
//| instances starting at the same moment against an absent guard both |
//| refuse; a retry then acquires normally. |
//+------------------------------------------------------------------+
# define UQ_GUARD_SENTINEL 0.0
# define UQ_GUARD_BALLOT_PREFIX " UQ2B_ "
# define UQ_GUARD_BALLOT_FP_CHARS 40
enum ENUM_UQ_GUARD_STATE
{
UQ_GUARD_ABSENT = 0 , // no variable holds this name
UQ_GUARD_ACTIVE = 1 , // a live ownership record inside the TTL
UQ_GUARD_STALE = 2 , // a valid record whose owner stopped beating
UQ_GUARD_INVALID = 3 // the variable exists but its value is no record
} ;
enum ENUM_UQ_GUARD_RESULT
{
UQ_GUARD_ACQUIRED = 0 , // ownership is proven by this instance
UQ_GUARD_HELD = 1 , // another live instance owns the guard
UQ_GUARD_INVALID_EVIDENCE = 2 , // the guard holds a value that is not a record
UQ_GUARD_LOST_RACE = 3 , // the atomic exchange was won by another contender
UQ_GUARD_CONTENDED = 4 , // another contender was creating the guard concurrently
UQ_GUARD_IO_ERROR = 5 , // the terminal did not store or return the value
UQ_GUARD_UNUSABLE = 7 // the guard or ballot name cannot be derived
} ;
//+------------------------------------------------------------------+
//| Classify one ownership record |
//| |
//| Pure, so the exact meaning of every value can be tested apart |
//| from the terminal: absence, a live claim, an abandoned claim and |
//| a value that is not a record at all are four distinct outcomes. |
//+------------------------------------------------------------------+
ENUM_UQ_GUARD_STATE UQGuardClassify ( const bool exists , const double value ,
const datetime now , const int ttlSeconds )
{
if ( ! exists )
return ( UQ_GUARD_ABSENT ) ;
if ( ! MathIsValidNumber ( value ) )
return ( UQ_GUARD_INVALID ) ;
if ( value < = 0.0 )
return ( UQ_GUARD_INVALID ) ;
//--- a heartbeat is a whole number of seconds; anything else was not written
//--- by this protocol and cannot be interpreted as an ownership record
if ( ( double ) ( long ) value ! = value )
return ( UQ_GUARD_INVALID ) ;
//--- a stamp this far ahead of the clock was never produced by a heartbeat
if ( value > ( double ) ( TimeLocal ( ) + UQ_STATE_MAX_CLOCK_SKEW ) )
return ( UQ_GUARD_INVALID ) ;
//--- a stamp slightly ahead is still a live claim, never a stale one
if ( value > = ( double ) TimeLocal ( ) )
return ( UQ_GUARD_ACTIVE ) ;
if ( ( long ) ( ( datetime ) ( long ) value ) > 0 & &
( long ) ( TimeLocal ( ) - ( datetime ) ( long ) value ) < ( long ) ttlSeconds )
return ( UQ_GUARD_ACTIVE ) ;
return ( UQ_GUARD_STALE ) ;
}
//+------------------------------------------------------------------+
//| Storage behind the ownership protocol |
//| |
//| The acquisition algorithm never calls the terminal directly, so it |
//| can be driven against an adversarial store in a test without |
//| touching one production global variable. |
//+------------------------------------------------------------------+
class CUQGuardStore
{
public :
virtual bool Exists ( const string name ) { return ( false ) ; }
virtual bool Read ( const string name , double & value ) { return ( false ) ; }
virtual bool Write ( const string name , const double value ) { return ( false ) ; }
virtual bool Swap ( const string name , const double value , const double expected ) { return ( false ) ; }
virtual bool Remove ( const string name ) { return ( false ) ; }
virtual int Names ( const string prefix , string & out [ ] ) { return ( 0 ) ; }
} ;
//+------------------------------------------------------------------+
//| Terminal-backed guard store |
//| |
//| Swap() is GlobalVariableSetOnCondition(): the terminal compares |
//| and replaces the value as one indivisible operation, which is the |
//| documented mutex primitive for Expert Advisors in one terminal. |
//+------------------------------------------------------------------+
class CUQTerminalGuardStore : public CUQGuardStore
{
public :
virtual bool Exists ( const string name )
{
return ( GlobalVariableCheck ( name ) ) ;
}
virtual bool Read ( const string name , double & value )
{
value = 0.0 ;
if ( ! GlobalVariableCheck ( name ) )
return ( false ) ;
value = GlobalVariableGet ( name ) ;
return ( true ) ;
}
virtual bool Write ( const string name , const double value )
{
if ( StringLen ( name ) < = 0 | | StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
return ( false ) ;
return ( GlobalVariableSet ( name , value ) ! = 0 ) ;
}
virtual bool Swap ( const string name , const double value , const double expected )
{
if ( StringLen ( name ) < = 0 | | StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
return ( false ) ;
return ( GlobalVariableSetOnCondition ( name , value , expected ) ) ;
}
virtual bool Remove ( const string name )
{
return ( GlobalVariableDel ( name ) ) ;
}
virtual int Names ( const string prefix , string & out [ ] )
{
ArrayResize ( out , 0 ) ;
if ( StringLen ( prefix ) < = 0 )
return ( 0 ) ;
const int total = GlobalVariablesTotal ( ) ;
for ( int i = 0 ; i < total ; i + + )
{
const string name = GlobalVariableName ( i ) ;
if ( StringLen ( name ) < = 0 | | StringFind ( name , prefix , 0 ) ! = 0 )
continue ;
const int n = ArraySize ( out ) ;
ArrayResize ( out , n + 1 ) ;
out [ n ] = name ;
}
return ( ArraySize ( out ) ) ;
}
} ;
//+------------------------------------------------------------------+
//| Ballot locators: bounded, identity scoped, one name per instance |
//+------------------------------------------------------------------+
string UQGuardBallotPrefix ( const string fingerprintV2 )
{
if ( ! UQIdentityFingerprintValid ( fingerprintV2 ) )
return ( " " ) ;
return ( UQ_GUARD_BALLOT_PREFIX + StringSubstr ( fingerprintV2 , 0 , UQ_GUARD_BALLOT_FP_CHARS ) + " _ " ) ;
}
string UQGuardBallotName ( const string fingerprintV2 , const string tag )
{
const string prefix = UQGuardBallotPrefix ( fingerprintV2 ) ;
if ( StringLen ( prefix ) < = 0 | | StringLen ( tag ) < = 0 )
return ( " " ) ;
const string name = prefix + tag ;
if ( StringLen ( name ) > UQ_GV_NAME_MAX_CHARS )
return ( " " ) ;
return ( name ) ;
}
//+------------------------------------------------------------------+
//| Private tag of this program instance |
//| |
//| The chart identity plus a process-local sequence number makes the |
//| ballot name unique among the instances of one terminal, so no |
//| other instance can ever write it. |
//+------------------------------------------------------------------+
string UQGuardInstanceTag ( void )
{
static ulong seq = 0 ;
seq + + ;
const string seed = IntegerToString ( ( long ) ChartID ( ) ) + " | " +
IntegerToString ( ( long ) GetTickCount64 ( ) ) + " | " +
IntegerToString ( ( long ) GetMicrosecondCount ( ) ) + " | " +
IntegerToString ( ( long ) seq ) ;
return ( StringSubstr ( UQHashString ( seed ) , 0 , 8 ) ) ;
}
//+------------------------------------------------------------------+
//| Ownership of one duplicate-instance guard |
//| |
//| The instance keeps the exact value it holds, so every later |
//| transition is a compare-and-swap against that value. A value that |
//| no longer matches proves that another instance took the guard over,|
//| and every method then refuses to touch the guard again. |
//+------------------------------------------------------------------+
class CUQInstanceGuard
{
private :
CUQGuardStore * m_store ;
string m_name ;
string m_prefix ;
string m_ballot ;
bool m_owned ;
double m_value ;
ENUM_UQ_GUARD_RESULT m_result ;
string m_detail ;
void DropBallot ( void )
{
if ( m_store ! = NULL & & StringLen ( m_ballot ) > 0 & & m_store . Exists ( m_ballot ) )
m_store . Remove ( m_ballot ) ;
}
//--- a ballot of another instance that is still inside the TTL proves that
//--- a concurrent contender may be inside its own creation sequence
bool ForeignBallotLive ( const datetime now , const int ttlSeconds , string & rival )
{
rival = " " ;
if ( m_store = = NULL | | StringLen ( m_prefix ) < = 0 )
return ( false ) ;
string names [ ] ;
const int total = m_store . Names ( m_prefix , names ) ;
for ( int i = 0 ; i < total ; i + + )
{
if ( names [ i ] = = m_ballot )
continue ;
double v = 0.0 ;
if ( ! m_store . Read ( names [ i ] , v ) )
continue ;
//--- an unreadable or unusable ballot is still a contender that cannot
//--- be ruled out, so it counts as live
if ( ! MathIsValidNumber ( v ) | | v < = 0.0 | | ( double ) ( long ) v ! = v | |
( long ) ( TimeLocal ( ) - ( datetime ) ( long ) v ) < ( long ) ttlSeconds )
{
rival = names [ i ] ;
return ( true ) ;
}
}
return ( false ) ;
}
public :
CUQInstanceGuard ( void )
{
m_store = NULL ;
m_name = " " ;
m_prefix = " " ;
m_ballot = " " ;
m_owned = false ;
m_value = 0.0 ;
m_result = UQ_GUARD_UNUSABLE ;
m_detail = " " ;
}
bool Configure ( CUQGuardStore * store , const string guardName ,
const string ballotPrefix , const string ballotName )
{
m_store = store ;
m_name = guardName ;
m_prefix = ballotPrefix ;
m_ballot = ballotName ;
m_owned = false ;
m_value = 0.0 ;
m_result = UQ_GUARD_UNUSABLE ;
m_detail = " " ;
return ( m_store ! = NULL & &
StringLen ( m_name ) > 0 & & StringLen ( m_name ) < = UQ_GV_NAME_MAX_CHARS & &
StringLen ( m_ballot ) > 0 & & StringLen ( m_ballot ) < = UQ_GV_NAME_MAX_CHARS ) ;
}
//+---------------------------------------------------------------+
//| Acquire ownership of the guard |
//+---------------------------------------------------------------+
ENUM_UQ_GUARD_RESULT Acquire ( const datetime now , const int ttlSeconds )
{
m_owned = false ;
m_value = 0.0 ;
m_result = UQ_GUARD_UNUSABLE ;
m_detail = " " ;
if ( m_store = = NULL )
{
m_detail = " the instance guard store is unavailable " ;
return ( m_result ) ;
}
if ( StringLen ( m_name ) < = 0 | | StringLen ( m_name ) > UQ_GV_NAME_MAX_CHARS )
{
m_detail = " the duplicate-instance guard name cannot be derived " ;
return ( m_result ) ;
}
if ( StringLen ( m_ballot ) < = 0 | | StringLen ( m_ballot ) > UQ_GV_NAME_MAX_CHARS )
{
m_detail = " the contender ballot name cannot be derived " ;
return ( m_result ) ;
}
//--- 1. announce this contender in its own private variable, before the
//--- shared guard is touched. No other instance writes this name, so
//--- the announcement itself cannot race, and every contender that
//--- could still create the guard becomes visible here.
if ( ! m_store . Write ( m_ballot , ( double ) ( long ) now ) )
{
m_result = UQ_GUARD_IO_ERROR ;
m_detail = " the contender ballot could not be written " ;
return ( m_result ) ;
}
double announced = 0.0 ;
if ( ! m_store . Read ( m_ballot , announced ) | | ( double ) ( long ) announced ! = ( double ) ( long ) now )
{
m_result = UQ_GUARD_IO_ERROR ;
m_detail = " the contender ballot could not be read back " ;
DropBallot ( ) ;
return ( m_result ) ;
}
//--- 2. read the ownership record once and classify it
const bool exists = m_store . Exists ( m_name ) ;
double observed = 0.0 ;
if ( exists & & ! m_store . Read ( m_name , observed ) )
{
m_result = UQ_GUARD_IO_ERROR ;
m_detail = " the instance guard could not be read " ;
DropBallot ( ) ;
return ( m_result ) ;
}
const ENUM_UQ_GUARD_STATE state = UQGuardClassify ( exists , observed , now , ttlSeconds ) ;
if ( state = = UQ_GUARD_INVALID )
{
m_result = UQ_GUARD_INVALID_EVIDENCE ;
m_detail = StringFormat ( " the instance guard '%s' exists but holds no usable ownership record (value %s); it is never treated as stale and never overwritten " ,
m_name , IntegerToString ( ( long ) observed ) ) ;
DropBallot ( ) ;
return ( m_result ) ;
}
if ( state = = UQ_GUARD_ACTIVE )
{
m_result = UQ_GUARD_HELD ;
m_detail = StringFormat ( " another instance holds the instance guard (heartbeat %s) " ,
TimeToString ( ( datetime ) ( long ) observed , TIME_DATE | TIME_SECONDS ) ) ;
DropBallot ( ) ;
return ( m_result ) ;
}
//--- 3. the atomic acquisition. A stale record and an absent record reach
//--- the same compare-and-swap; only its winner may own the scope.
double expected = UQ_GUARD_SENTINEL ;
if ( state = = UQ_GUARD_STALE )
expected = observed ;
else
{
//--- creation: the sentinel carries no ownership, so writing it can
//--- never make this instance the owner by itself
if ( ! m_store . Write ( m_name , UQ_GUARD_SENTINEL ) )
{
m_result = UQ_GUARD_IO_ERROR ;
m_detail = " the instance guard could not be created " ;
DropBallot ( ) ;
return ( m_result ) ;
}
}
double claim = ( double ) ( long ) now ;
if ( claim < = expected )
claim = expected + 1.0 ;
if ( ! m_store . Swap ( m_name , claim , expected ) )
{
double current = 0.0 ;
if ( m_store . Read ( m_name , current ) )
m_detail = StringFormat ( " the ownership exchange was won by another contender (guard now holds %s) " ,
IntegerToString ( ( long ) current ) ) ;
else
m_detail = " the ownership exchange was lost and the guard could not be re-read " ;
m_result = UQ_GUARD_LOST_RACE ;
DropBallot ( ) ;
return ( m_result ) ;
}
//--- 4. the exchange is only an acquisition when the terminal still
//--- returns exactly the value this instance wrote
double stored = 0.0 ;
if ( ! m_store . Read ( m_name , stored ) | | ( double ) ( long ) stored ! = claim )
{
m_result = UQ_GUARD_IO_ERROR ;
m_detail = " the instance guard does not hold the value that was written " ;
DropBallot ( ) ;
return ( m_result ) ;
}
//--- 5. the creation write in step 3 is the one step the terminal API
//--- cannot make atomic. A contender that observed the same absence is
//--- visible in its ballot, so while one is live this claim cannot be
//--- proven exclusive and is given back instead of being trusted.
if ( state = = UQ_GUARD_ABSENT )
{
string rival = " " ;
if ( ForeignBallotLive ( now , ttlSeconds , rival ) )
{
m_detail = StringFormat ( " another contender ('%s') was creating the same guard at the same time; exclusive ownership cannot be proven " , rival ) ;
if ( m_store . Swap ( m_name , UQ_GUARD_SENTINEL , claim ) )
m_store . Remove ( m_name ) ;
m_result = UQ_GUARD_CONTENDED ;
DropBallot ( ) ;
return ( m_result ) ;
}
}
m_value = claim ;
m_owned = true ;
m_result = UQ_GUARD_ACQUIRED ;
m_detail = " " ;
DropBallot ( ) ;
return ( m_result ) ;
}
//+---------------------------------------------------------------+
//| Publish a heartbeat and prove that this instance still owns it|
//+---------------------------------------------------------------+
bool Heartbeat ( const datetime now )
{
if ( ! m_owned | | m_store = = NULL )
return ( false ) ;
double next = ( double ) ( long ) now ;
if ( next < = m_value )
next = m_value + 1.0 ;
if ( ! m_store . Swap ( m_name , next , m_value ) )
{
//--- the value this instance held is gone, so a successor owns the
//--- guard now and this instance must never touch it again
m_owned = false ;
m_detail = " the instance guard is no longer owned by this instance " ;
return ( false ) ;
}
double stored = 0.0 ;
if ( ! m_store . Read ( m_name , stored ) | | ( double ) ( long ) stored ! = next )
{
m_owned = false ;
m_detail = " the instance guard heartbeat could not be verified after the exchange " ;
return ( false ) ;
}
m_value = next ;
return ( true ) ;
}
//+---------------------------------------------------------------+
//| Release ownership at deinitialization |
//+---------------------------------------------------------------+
bool Release ( void )
{
if ( m_store = = NULL | | ! m_owned )
{
m_owned = false ;
return ( true ) ;
}
//--- only a guard that still carries this instance's own value is
//--- released. A claim a successor acquired is never deleted or
//--- overwritten, so a late deinitialization cannot free a live instance.
const bool released = m_store . Swap ( m_name , UQ_GUARD_SENTINEL , m_value ) ;
m_owned = false ;
if ( ! released )
{
m_detail = " the instance guard is held by a successor and was left untouched " ;
return ( false ) ;
}
m_store . Remove ( m_name ) ;
return ( true ) ;
}
bool Owned ( void ) const { return ( m_owned ) ; }
double Value ( void ) const { return ( m_value ) ; }
string Name ( void ) const { return ( m_name ) ; }
string Detail ( void ) const { return ( m_detail ) ; }
ENUM_UQ_GUARD_RESULT Result ( void ) const { return ( m_result ) ; }
} ;
//+------------------------------------------------------------------+
//| Outcome of the initialization ownership gate |
//+------------------------------------------------------------------+
enum ENUM_UQ_OWNERSHIP
{
UQ_OWNERSHIP_READY = 0 , // exclusive ownership is proven
UQ_OWNERSHIP_IDENTITY = 1 , // no usable identity, no guard locator
UQ_OWNERSHIP_LEGACY_ACTIVE = 2 , // an instance started by an older build is live
UQ_OWNERSHIP_LEGACY_INVALID = 3 , // the legacy guard holds no usable record
UQ_OWNERSHIP_V2_HELD = 4 , // another V2 instance owns this scope
UQ_OWNERSHIP_V2_INVALID = 5 , // the V2 guard holds no usable record
UQ_OWNERSHIP_V2_CONTENDED = 6 , // simultaneous first start, not provable
UQ_OWNERSHIP_V2_ERROR = 7 // the guard could not be written or verified
} ;
//+------------------------------------------------------------------+
//| Operator readable name of one ownership outcome |
//+------------------------------------------------------------------+
string UQOwnershipText ( const ENUM_UQ_OWNERSHIP o )
{
switch ( o )
{
case UQ_OWNERSHIP_READY : return ( " exclusive ownership proven " ) ;
case UQ_OWNERSHIP_IDENTITY : return ( " the persistence identity is unusable " ) ;
case UQ_OWNERSHIP_LEGACY_ACTIVE : return ( " a previous-build instance is still running " ) ;
case UQ_OWNERSHIP_LEGACY_INVALID : return ( " the legacy instance guard holds no usable record " ) ;
case UQ_OWNERSHIP_V2_HELD : return ( " another V2 instance owns this scope " ) ;
case UQ_OWNERSHIP_V2_INVALID : return ( " the V2 instance guard holds no usable record " ) ;
case UQ_OWNERSHIP_V2_CONTENDED : return ( " a simultaneous first start could not be proven exclusive " ) ;
case UQ_OWNERSHIP_V2_ERROR : return ( " the V2 instance guard could not be written or verified " ) ;
}
return ( " unrecognized ownership outcome " ) ;
}
//+------------------------------------------------------------------+
//| Establish exclusive instance ownership |
//| |
//| This is the only place where initialization decides that one |
//| instance may touch a persistent risk scope. It returns READY only |
//| when the V2 guard was acquired through the atomic exchange above, |
//| and it refuses on every form of unusable evidence. |
//+------------------------------------------------------------------+
ENUM_UQ_OWNERSHIP UQInstanceOwnershipAcquire ( CUQInstanceGuard & guard , CUQGuardStore * store ,
const UQIdentity & id , const datetime now ,
const int ttlSeconds , string & detail )
{
detail = " " ;
if ( store = = NULL )
{
detail = " the instance guard store is unavailable " ;
return ( UQ_OWNERSHIP_IDENTITY ) ;
}
if ( ! UQIdentityFingerprintValid ( id . fingerprintV2 ) )
{
detail = " the V2 identity fingerprint is unavailable; instance ownership cannot be established " ;
return ( UQ_OWNERSHIP_IDENTITY ) ;
}
const string v2Name = UQInstanceGuardName ( id . fingerprintV2 ) ;
const string ballotName = UQGuardBallotName ( id . fingerprintV2 , UQGuardInstanceTag ( ) ) ;
const string legacyName = UQInstanceGuardLegacyName ( id . key ) ;
if ( StringLen ( v2Name ) < = 0 | | StringLen ( ballotName ) < = 0 )
{
detail = " the duplicate-instance guard name cannot be derived " ;
return ( UQ_OWNERSHIP_IDENTITY ) ;
}
//--- an instance started by a previously published build is inspected before
//--- anything is written. That build cannot recognise the V2 namespace, so
//--- while its heartbeat is fresh the V2 guard is deliberately not acquired.
//--- A guard cannot stop an older binary from starting later: the deployment
//--- constraint that every old instance is stopped first still holds.
if ( StringLen ( legacyName ) > 0 )
{
const bool legacyExists = store . Exists ( legacyName ) ;
double legacyValue = 0.0 ;
if ( legacyExists & & ! store . Read ( legacyName , legacyValue ) )
{
detail = " the legacy instance guard could not be read " ;
return ( UQ_OWNERSHIP_LEGACY_INVALID ) ;
}
const ENUM_UQ_GUARD_STATE legacyState =
UQGuardClassify ( legacyExists , legacyValue , now , ttlSeconds ) ;
if ( legacyState = = UQ_GUARD_INVALID )
{
detail = StringFormat ( " the legacy instance guard '%s' exists but holds no usable ownership record (value %s); it is never treated as stale, never overwritten and never deleted by this build " ,
legacyName , IntegerToString ( ( long ) legacyValue ) ) ;
return ( UQ_OWNERSHIP_LEGACY_INVALID ) ;
}
if ( legacyState = = UQ_GUARD_ACTIVE )
{
detail = StringFormat ( " an instance started by a previous build is still active (legacy guard '%s', heartbeat %s); stop every old instance before deploying this build " ,
legacyName , TimeToString ( ( datetime ) ( long ) legacyValue , TIME_DATE | TIME_SECONDS ) ) ;
return ( UQ_OWNERSHIP_LEGACY_ACTIVE ) ;
}
}
if ( ! guard . Configure ( store , v2Name , UQGuardBallotPrefix ( id . fingerprintV2 ) , ballotName ) )
{
detail = " the duplicate-instance guard could not be configured " ;
return ( UQ_OWNERSHIP_IDENTITY ) ;
}
const ENUM_UQ_GUARD_RESULT acquired = guard . Acquire ( now , ttlSeconds ) ;
detail = guard . Detail ( ) ;
switch ( acquired )
{
case UQ_GUARD_ACQUIRED : return ( UQ_OWNERSHIP_READY ) ;
case UQ_GUARD_HELD : return ( UQ_OWNERSHIP_V2_HELD ) ;
case UQ_GUARD_INVALID_EVIDENCE : return ( UQ_OWNERSHIP_V2_INVALID ) ;
case UQ_GUARD_CONTENDED : return ( UQ_OWNERSHIP_V2_CONTENDED ) ;
default : return ( UQ_OWNERSHIP_V2_ERROR ) ;
}
}
//+------------------------------------------------------------------+
//| May a legacy state found under the legacy locators be migrated? |
//| |
//| Pure decision, so the refusal of ambiguous legacy evidence can be |
//| tested without a file system. A legacy state is only ever migrated|
//| when the legacy serialisation of this identity decodes back to |
//| exactly this tuple: otherwise the same legacy key and the same |
//| legacy hash are shared with a different tuple and the state cannot|
//| be proven to be this identity's own. |
//+------------------------------------------------------------------+
bool UQLegacyMigrationAllowed ( const UQIdentity & id , const bool legacyStatePresent ,
const bool legacyMarkerPresent , string & reason )
{
reason = " " ;
if ( id . legacyAmbiguous )
{
reason = " the legacy identity serialisation of this scope is ambiguous: 'server|login|portfolio' of this tuple is identical to that of a different tuple, so a legacy state found under this legacy locator cannot be proven to belong to this identity " ;
return ( false ) ;
}
if ( legacyMarkerPresent )
{
reason = " a legacy initialization marker exists while no verified legacy state of this exact identity does; this scope was initialized before and must not be treated as a fresh installation " ;
return ( false ) ;
}
if ( ! legacyStatePresent )
{
reason = " no legacy state exists for this identity " ;
return ( false ) ;
}
return ( true ) ;
}
2026-10-31 13:28:11 +07:00
//+------------------------------------------------------------------+
//| Classification of the persistence scope that is about to load |
//| |
//| The decision is pure: it is derived from evidence only, so it can |
//| be reviewed and tested apart from the file system and the terminal |
//| state. Every input is "the evidence exists", never "the evidence |
//| loaded successfully", so an unusable file can never be mistaken |
//| for an absent one. |
//+------------------------------------------------------------------+
enum ENUM_UQ_SCOPE_DECISION
{
UQ_SCOPE_V2_AUTHORITATIVE = 0 , // a V2 persistence scope exists and is authoritative
UQ_SCOPE_V2_MARKER_ONLY = 1 , // V2 marker proven while the V2 state is missing: refuse
UQ_SCOPE_LEGACY_MIGRATE = 2 , // verified legacy state of this exact identity: migrate
UQ_SCOPE_LEGACY_REFUSE = 3 , // legacy evidence that must never be migrated: refuse
UQ_SCOPE_FRESH = 4 // no evidence at all: authorized first initialization only
} ;
//+------------------------------------------------------------------+
//| Decide how a persistence scope may be entered |
//+------------------------------------------------------------------+
ENUM_UQ_SCOPE_DECISION UQScopeDecision ( const bool v2StatePresent , const bool v2MarkerPresent ,
const bool legacyStatePresent , const bool legacyStateUsable ,
2026-10-31 13:47:47 +07:00
const bool legacyMarkerPresent , string & reason ,
const bool v2MarkerInvalid = false ,
const bool legacyMarkerInvalid = false )
2026-10-31 13:28:11 +07:00
{
reason = " " ;
2026-10-31 13:47:47 +07:00
//--- 0. an existing marker whose value is unusable is corrupt evidence, not
//--- absence. It proves that this scope was written before, so neither a
//--- fresh initialization nor a migration may rebuild it from scratch.
if ( legacyMarkerInvalid )
{
reason = " the legacy initialization marker of this scope exists but holds no usable value; the persistence evidence is corrupt and cannot authorize a new risk cycle or a migration " ;
return ( UQ_SCOPE_LEGACY_REFUSE ) ;
}
if ( v2MarkerInvalid )
{
reason = " the V2 initialization marker of this scope exists but holds no usable value; the persistence evidence is corrupt and cannot authorize a new risk cycle " ;
return ( UQ_SCOPE_V2_MARKER_ONLY ) ;
}
2026-10-31 13:28:11 +07:00
//--- A. the V2 scope exists: only V2 evidence is ever consulted from here
if ( v2StatePresent )
return ( UQ_SCOPE_V2_AUTHORITATIVE ) ;
//--- B. the V2 marker proves that this scope was initialized under V2. Its
//--- missing state is a loss of evidence, not a fresh installation, and
//--- it is never repaired from stale legacy data.
if ( v2MarkerPresent )
{
reason = " the V2 initialization marker exists while both V2 state files are absent; this scope was initialized before and it must not be rebuilt from older data " ;
return ( UQ_SCOPE_V2_MARKER_ONLY ) ;
}
//--- C/D. no V2 evidence: a legacy state of this exact identity may migrate
if ( legacyStatePresent & & legacyStateUsable )
return ( UQ_SCOPE_LEGACY_MIGRATE ) ;
//--- E. legacy evidence exists but nothing of it belongs to this identity:
//--- this is the confirmed collision case, and it is a persistence
//--- ownership conflict, never permission to discard existing history
if ( legacyStatePresent )
{
reason = " a legacy state file exists for this locator but it is unusable or belongs to a different identity; it is never overwritten, replaced or treated as a fresh installation " ;
return ( UQ_SCOPE_LEGACY_REFUSE ) ;
}
if ( legacyMarkerPresent )
{
reason = " a legacy initialization marker exists while no legacy state file does; this scope was initialized before and must not be treated as a fresh installation " ;
return ( UQ_SCOPE_LEGACY_REFUSE ) ;
}
return ( UQ_SCOPE_FRESH ) ;
}
//+------------------------------------------------------------------+
//| Prove that a migration preserved every risk-bearing field |
//| |
//| Relocating the persistence scope must not change a single risk |
//| decision. The values are compared through the canonical field |
//| serializer, i.e. exactly the text that is persisted, so a value |
//| that survives the round trip unchanged compares equal. |
//| |
//| The schema version, the update timestamp and the persistence |
//| generation are expected to advance, and the two identity fields |
//| are checked separately against the live identity. |
//+------------------------------------------------------------------+
bool UQStatePreserved ( UQRiskState & before , UQRiskState & after , string & detail )
{
detail = " " ;
for ( int i = 0 ; i < ( int ) UQ_F_COUNT ; i + + )
{
if ( i = = ( int ) UQ_F_FORMAT | | i = = ( int ) UQ_F_UPDATED_AT | |
i = = ( int ) UQ_F_STATE_REVISION )
continue ;
if ( i = = ( int ) UQ_F_IDENTITY | | i = = ( int ) UQ_F_IDENTITY_V2 )
continue ;
const string a = UQStateFieldValue ( before , i ) ;
const string b = UQStateFieldValue ( after , i ) ;
if ( a ! = b )
{
detail = StringFormat ( " field '%s' changed from '%s' to '%s' " ,
UQFieldKey ( i ) , a , b ) ;
return ( false ) ;
}
}
return ( true ) ;
}
//+------------------------------------------------------------------+
//| Prove that a migrated state carries no weaker control than the |
//| state it was migrated from |
//| |
//| A relocation is allowed to keep a lock armed that the evaluation |
//| pass has just re-armed, but it is never allowed to disarm one. |
//+------------------------------------------------------------------+
bool UQStateLocksNotCleared ( UQRiskState & before , UQRiskState & after , string & detail )
{
detail = " " ;
if ( before . staticLock & & ! after . staticLock )
{
detail = " the static-drawdown lock was cleared by the migration " ;
return ( false ) ;
}
if ( before . targetLock & & ! after . targetLock )
{
detail = " the equity-target lock was cleared by the migration " ;
return ( false ) ;
}
if ( before . dailyLock & & ! after . dailyLock & &
before . dailyDate = = after . dailyDate )
{
detail = " the daily-loss lock was cleared inside the same trading day by the migration " ;
return ( false ) ;
}
if ( before . recoveryHold & & ! after . recoveryHold )
{
detail = " the recovery hold was cleared by the migration " ;
return ( false ) ;
}
if ( before . gapHold & & ! after . gapHold & &
before . dailyDate = = after . dailyDate )
{
detail = " the monitoring-gap hold was cleared inside the same trading day by the migration " ;
return ( false ) ;
}
if ( before . reserveState ! = UQ_RESERVE_NONE & & after . reserveState = = UQ_RESERVE_NONE )
{
detail = " an outstanding entry reservation was discarded by the migration " ;
return ( false ) ;
}
if ( before . cycleId ! = after . cycleId | | before . cycleSequence ! = after . cycleSequence )
{
detail = " the risk-cycle identity changed during the migration " ;
return ( false ) ;
}
2026-10-30 07:19:59 +07:00
return ( true ) ;
}
# endif // UQ_STATE_MQH
//+------------------------------------------------------------------+