2026-07-13 03:23:39 -04:00 | | | //+------------------------------------------------------------------+
|
| | | //| Warrior_EA |
|
| | | //| AnimateDread |
|
| | | //| |
|
| | | //+------------------------------------------------------------------+
|
| | | #include "..\System\PrintVerbose.mqh"
|
| | | #define DB_RETRY_ATTEMPTS 3
|
| | | #define DB_RETRY_DELAY_MS 20
|
2026-08-12 13:26:40 -04:00 | | | //--- ERR_DATABASE_NO_MORE_DATA: what GetLastError() reports after DatabaseRead() steps a statement to
|
| | | //--- completion with nothing to read back (SQLite's DONE). For INSERT/UPDATE that is the SUCCESS
|
| | | //--- outcome, not a failure - see PrepareAndExecuteBound().
|
| | | #define DB_NO_MORE_DATA 5126
|
2026-07-13 03:23:39 -04:00 | | | class CDatabaseOperationsManager
|
| | | {
|
| | | private:
|
| | | bool m_verboseMode;
|
| | | int m_databaseHandle;
|
| | | // Table/column names can't be bound as SQL parameters, so they must be
|
| | | // restricted to a safe character set before being concatenated into a query.
|
| | | bool IsValidIdentifier(const string name)
|
| | | {
|
| | | int len = StringLen(name);
|
| | | if(len == 0)
|
| | | return false;
|
| | | for(int i = 0; i < len; i++)
|
| | | {
|
| | | ushort c = StringGetCharacter(name, i);
|
| | | bool isAlnum = (c >= '0' && c <= '9') || (c >= 'A' && c <= 'Z') || (c >= 'a' && c <= 'z') || c == '_';
|
| | | if(!isAlnum)
|
| | | return false;
|
| | | }
|
| | | return true;
|
| | | }
|
2026-08-24 00:58:45 -04:00 | | | // Shared guard for every public method below: refuse an identifier that failed IsValidIdentifier(),
|
| | | // printing which operation was refused (the verb) and which identifier failed.
|
| | | bool RequireValidIdentifier(const string tableName, const string verb)
|
| | | {
|
| | | if(IsValidIdentifier(tableName))
|
| | | return true;
|
| | | Print("Refusing to " + verb + " table with invalid identifier: " + tableName);
|
| | | return false;
|
| | | }
|
2026-08-24 03:04:23 -04:00 | | | // Join array elements into one comma-separated string, appending `suffix` to each element
|
| | | // first (e.g. suffix "=?" turns column names into an UPDATE ... SET list). Shared by every
|
| | | // column/placeholder list built below so the "comma unless this is the last element" shape
|
| | | // exists exactly once.
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | //--- A QUOTED IDENTIFIER. Pattern tables are named after the filter id, and "2WD_Pattern_0_Sell"
|
| | | //--- starts with a digit, which SQLite reads as a malformed number unless the name is quoted.
|
| | | //--- Measured 2026-09-08 on the first tester pass with the database on: every Second Wind table
|
| | | //--- failed to create, three retries each, and the rows were dropped.
|
| | | string Q(const string name)
|
| | | {
|
| | | return "\"" + name + "\"";
|
| | | }
|
2026-08-24 03:04:23 -04:00 | | | string JoinWithCommas(const string &items[], const string suffix = "")
|
| | | {
|
| | | string result = "";
|
| | | int n = ArraySize(items);
|
| | | for(int i = 0; i < n; i++)
|
| | | {
|
| | | result += items[i] + suffix;
|
| | | if(i < n - 1)
|
| | | result += ", ";
|
| | | }
|
| | | return result;
|
| | | }
|
| | | // Build a "?, ?, ..." bound-parameter placeholder list of the given length.
|
| | | string PlaceholderList(int count)
|
| | | {
|
| | | string items[];
|
| | | ArrayResize(items, count);
|
| | | for(int i = 0; i < count; i++)
|
| | | items[i] = "?";
|
| | | return JoinWithCommas(items);
|
| | | }
|
2026-07-13 03:23:39 -04:00 | | | // Prepare and execute a query that has no bound parameters (DDL, or already-safe DML)
|
| | | bool PrepareAndExecuteQuery(string query)
|
| | | {
|
| | | if(m_databaseHandle == INVALID_HANDLE)
|
| | | return false;
|
| | | int attempts = 0;
|
| | | while(attempts < DB_RETRY_ATTEMPTS)
|
| | | {
|
| | | if(DatabaseExecute(m_databaseHandle, query))
|
| | | return true;
|
| | | Print("DatabaseExecute error: Attempt " + IntegerToString(attempts + 1) + " failed for query: " + query);
|
| | | Sleep(DB_RETRY_DELAY_MS);
|
| | | attempts++;
|
| | | }
|
| | | return false;
|
| | | }
|
2026-08-25 22:51:50 -04:00 | | | // Is this GetLastError() code the kind a retry can actually fix - the DB momentarily busy/locked
|
| | | // by another chart on the shared COMMON database (DatabaseConnectionManager.mqh opens it
|
| | | // DATABASE_OPEN_COMMON) - versus a genuine defect (constraint, misuse, bad SQL) that will fail
|
| | | // identically on attempt 2? Only PrepareAndExecuteQuery had this retry before; every trade-journal
|
| | | // row and pattern-weight update went through PrepareAndExecuteBound with none.
|
| | | bool IsRetryableDbError(const int error)
|
| | | {
|
| | | return (error == ERR_DATABASE_BUSY || error == ERR_DATABASE_LOCKED || error == ERR_DATABASE_CONNECT);
|
| | | }
|
| | | // Prepare a statement, bind parameters in order, execute it once, and finalize. Retries the WHOLE
|
| | | // prepare/bind/step sequence (not just the step) on a busy/locked error, same DB_RETRY_ATTEMPTS/
|
| | | // DB_RETRY_DELAY_MS as PrepareAndExecuteQuery - a stale prepared handle from a failed attempt is
|
| | | // never reused.
|
2026-07-13 03:23:39 -04:00 | | | bool PrepareAndExecuteBound(string query, const string ¶ms[])
|
| | | {
|
| | | if(m_databaseHandle == INVALID_HANDLE)
|
| | | return false;
|
2026-08-25 22:51:50 -04:00 | | | int attempts = 0;
|
| | | while(true)
|
2026-07-13 03:23:39 -04:00 | | | {
|
2026-08-25 22:51:50 -04:00 | | | int handle = DatabasePrepare(m_databaseHandle, query);
|
| | | if(handle == INVALID_HANDLE)
|
2026-07-13 03:23:39 -04:00 | | | {
|
2026-08-25 22:51:50 -04:00 | | | int prepErr = GetLastError();
|
| | | if(IsRetryableDbError(prepErr) && attempts + 1 < DB_RETRY_ATTEMPTS)
|
| | | {
|
| | | attempts++;
|
| | | Sleep(DB_RETRY_DELAY_MS);
|
| | | continue;
|
| | | }
|
| | | Print("Failed to prepare bound query: " + query);
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
| | | }
|
2026-08-25 22:51:50 -04:00 | | | bool bindFailed = false;
|
| | | for(int i = 0; i < ArraySize(params); i++)
|
| | | {
|
| | | if(!DatabaseBind(handle, i, params[i]))
|
| | | {
|
| | | Print("Failed to bind parameter " + IntegerToString(i) + " for query: " + query);
|
| | | DatabaseFinalize(handle);
|
| | | bindFailed = true;
|
| | | break;
|
| | | }
|
| | | }
|
| | | if(bindFailed)
|
| | | return false;
|
| | | // INSERT/UPDATE have no result set, so DatabaseRead() returns false on
|
| | | // success too (nothing to read) - the real signal is the error code. A
|
| | | // completed DML step can surface as either 0 or DB_NO_MORE_DATA (5126,
|
| | | // SQLite's DONE): the statement RAN, there is just nothing to read back.
|
| | | // The tester agent reports 5126 where the live terminal reports 0 for
|
| | | // the same completed step; treating it as failure printed a phantom
|
| | | // "Failed to insert/update" pair for EVERY row a backtest journaled
|
| | | // (~11.7k lines per run) while all of the data landed correctly - and
|
| | | // that spam would bury any real error. Genuine failures (constraint,
|
| | | // misuse) surface as other codes and still fail here; busy/locked
|
| | | // retries the whole statement instead.
|
| | | ResetLastError();
|
| | | DatabaseRead(handle);
|
| | | int error = GetLastError();
|
| | | DatabaseFinalize(handle);
|
| | | if(error != 0 && error != DB_NO_MORE_DATA)
|
| | | {
|
| | | if(IsRetryableDbError(error) && attempts + 1 < DB_RETRY_ATTEMPTS)
|
| | | {
|
| | | attempts++;
|
| | | Sleep(DB_RETRY_DELAY_MS);
|
| | | continue;
|
| | | }
|
| | | Print("Failed to execute bound query (error " + IntegerToString(error) + "): " + query);
|
| | | return false;
|
| | | }
|
| | | return true;
|
2026-07-13 03:23:39 -04:00 | | | }
|
2026-08-25 22:51:50 -04:00 | | | return false; // unreachable - every path through the loop above returns or continues
|
2026-07-13 03:23:39 -04:00 | | | }
|
| | | public:
|
| | | //constructor
|
| | | CDatabaseOperationsManager(bool verbose = false)
|
| | | {
|
| | | m_verboseMode = verbose;
|
| | | m_databaseHandle = INVALID_HANDLE;
|
| | | }
|
| | | void SetDatabaseHandle(int dbHandle)
|
| | | {
|
| | | m_databaseHandle = dbHandle;
|
| | | }
|
| | | // Create a table in the database
|
| | | bool CreateTable(string tableName, string tableSchema)
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "create"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
| | | if(!DatabaseTableExists(m_databaseHandle, tableName))
|
| | | {
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string createTableQuery = "CREATE TABLE " + Q(tableName) + " (" + tableSchema + ")";
|
2026-07-13 03:23:39 -04:00 | | | if(PrepareAndExecuteQuery(createTableQuery))
|
| | | return true;
|
| | | Print("Failed to create table " + tableName);
|
| | | return false;
|
| | | }
|
| | | return true; // Table already exists
|
| | | }
|
 feat(journal+signals): the journal becomes a research journal, the setup becomes a voter, and the exit adapts
THE VOTER FIX, found in the strategy tester and worth stating plainly because it
will bite the next standalone setup too. CExpertSignalCustom weights each child
by VoteCapableWeight(), which is m_weight only when GetPatternCount() > 0 and
ZERO otherwise: a child with no patterns is a news/session-style VETO with no say
in the consensus. CSignalInsideBarGap declared none, so its first tester run
fired 192 times over 2019-2022 and placed nothing - it voted 100 with a capable
weight of 0 and the accumulator divided by zero into 0.00 on every bar. It now
carries one pattern at weight 100, an ID, and its used-series mask, exactly as
every classic module's constructor does. Tester validation of the fixed build is
in progress; this commit is compile-verified only (0 errors, 0 warnings, in a
scratch copy inside the MQL5 tree - the repo path cannot resolve Network.cl).
THE JOURNAL. Two TEXT columns, added IN PLACE with ALTER TABLE (new
EnsureColumn on the database managers) rather than a dbVersion bump, which wipes
the directory:
context key=value;... the setup's view at signal time, the same terms the
research conditioned on, named identically (PublishContext). Accepted
by the journal only when published within 180 s of the position
opening, so an expired order's line cannot be inherited by a later
position from another signal.
passages first-touch MINUTE of every stop and target level on the research
grid plus signed R at each time-stop horizon - the exact fields
research/pricing.py prices an exit from. A synthetic round-trip
proved the live format re-prices the traded exit identically to the
research (max |diff| 4e-7 R) once the horizons carry six decimals.
One honest limit is written into the file header: a live trade is closed by its
own barriers, so from live rows an exit can only be re-selected TIGHTER.
THE ADAPTIVE EXIT. CSignalInsideBarGap reads
Common\Files\Warrior_EA\Adapt\{SYMBOL}_{PERIOD}_inside_bar_u.cfg once per server
day, written by research/journal_adapt.py with the research's discipline (choose
on the older trades, confirm on the newer, placeable stop, stable after dropping
the best 5%). No file means the validated defaults; a file outside the research
grid is refused and logged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:23:27 -04:00 | | | //--- SCHEMA EVOLUTION WITHOUT A VERSION WIPE (2026-09-06). CreateTable() is IF-NOT-EXISTS, so a
|
| | | //--- column added to a schema string never reaches a table that already exists - and the only
|
| | | //--- other path, bumping dbVersion, CLEANS THE WHOLE DATABASE DIRECTORY, journal included. Neither
|
| | | //--- is acceptable for adding a column to the trade journal, which is exactly the table whose
|
| | | //--- history is the point. SQLite's ALTER TABLE ADD COLUMN appends at the END, which is also the
|
| | | //--- only place a new column can go without breaking DatabaseReadBind()'s positional mapping.
|
| | | //--- Existence is probed with a zero-row SELECT rather than PRAGMA table_info so no result-set
|
| | | //--- struct is needed; a failed prepare means "no such column", nothing else.
|
| | | bool ColumnExists(string tableName, string columnName)
|
| | | {
|
| | | if(!RequireValidIdentifier(tableName, "inspect") || !RequireValidIdentifier(columnName, "inspect column of"))
|
| | | return false;
|
| | | if(m_databaseHandle == INVALID_HANDLE)
|
| | | return false;
|
| | | ResetLastError();
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | int handle = DatabasePrepare(m_databaseHandle, "SELECT " + columnName + " FROM " + Q(tableName) + " LIMIT 0");
|
 feat(journal+signals): the journal becomes a research journal, the setup becomes a voter, and the exit adapts
THE VOTER FIX, found in the strategy tester and worth stating plainly because it
will bite the next standalone setup too. CExpertSignalCustom weights each child
by VoteCapableWeight(), which is m_weight only when GetPatternCount() > 0 and
ZERO otherwise: a child with no patterns is a news/session-style VETO with no say
in the consensus. CSignalInsideBarGap declared none, so its first tester run
fired 192 times over 2019-2022 and placed nothing - it voted 100 with a capable
weight of 0 and the accumulator divided by zero into 0.00 on every bar. It now
carries one pattern at weight 100, an ID, and its used-series mask, exactly as
every classic module's constructor does. Tester validation of the fixed build is
in progress; this commit is compile-verified only (0 errors, 0 warnings, in a
scratch copy inside the MQL5 tree - the repo path cannot resolve Network.cl).
THE JOURNAL. Two TEXT columns, added IN PLACE with ALTER TABLE (new
EnsureColumn on the database managers) rather than a dbVersion bump, which wipes
the directory:
context key=value;... the setup's view at signal time, the same terms the
research conditioned on, named identically (PublishContext). Accepted
by the journal only when published within 180 s of the position
opening, so an expired order's line cannot be inherited by a later
position from another signal.
passages first-touch MINUTE of every stop and target level on the research
grid plus signed R at each time-stop horizon - the exact fields
research/pricing.py prices an exit from. A synthetic round-trip
proved the live format re-prices the traded exit identically to the
research (max |diff| 4e-7 R) once the horizons carry six decimals.
One honest limit is written into the file header: a live trade is closed by its
own barriers, so from live rows an exit can only be re-selected TIGHTER.
THE ADAPTIVE EXIT. CSignalInsideBarGap reads
Common\Files\Warrior_EA\Adapt\{SYMBOL}_{PERIOD}_inside_bar_u.cfg once per server
day, written by research/journal_adapt.py with the research's discipline (choose
on the older trades, confirm on the newer, placeable stop, stable after dropping
the best 5%). No file means the validated defaults; a file outside the research
grid is refused and logged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:23:27 -04:00 | | | if(handle == INVALID_HANDLE)
|
| | | return false;
|
| | | DatabaseFinalize(handle);
|
| | | return true;
|
| | | }
|
| | | //--- columnType is one of our own literals (TEXT / REAL / INTEGER), never user data.
|
| | | bool AddColumn(string tableName, string columnName, string columnType)
|
| | | {
|
| | | if(!RequireValidIdentifier(tableName, "alter") || !RequireValidIdentifier(columnName, "add column to"))
|
| | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | if(PrepareAndExecuteQuery("ALTER TABLE " + Q(tableName) + " ADD COLUMN " + columnName + " " + columnType))
|
 feat(journal+signals): the journal becomes a research journal, the setup becomes a voter, and the exit adapts
THE VOTER FIX, found in the strategy tester and worth stating plainly because it
will bite the next standalone setup too. CExpertSignalCustom weights each child
by VoteCapableWeight(), which is m_weight only when GetPatternCount() > 0 and
ZERO otherwise: a child with no patterns is a news/session-style VETO with no say
in the consensus. CSignalInsideBarGap declared none, so its first tester run
fired 192 times over 2019-2022 and placed nothing - it voted 100 with a capable
weight of 0 and the accumulator divided by zero into 0.00 on every bar. It now
carries one pattern at weight 100, an ID, and its used-series mask, exactly as
every classic module's constructor does. Tester validation of the fixed build is
in progress; this commit is compile-verified only (0 errors, 0 warnings, in a
scratch copy inside the MQL5 tree - the repo path cannot resolve Network.cl).
THE JOURNAL. Two TEXT columns, added IN PLACE with ALTER TABLE (new
EnsureColumn on the database managers) rather than a dbVersion bump, which wipes
the directory:
context key=value;... the setup's view at signal time, the same terms the
research conditioned on, named identically (PublishContext). Accepted
by the journal only when published within 180 s of the position
opening, so an expired order's line cannot be inherited by a later
position from another signal.
passages first-touch MINUTE of every stop and target level on the research
grid plus signed R at each time-stop horizon - the exact fields
research/pricing.py prices an exit from. A synthetic round-trip
proved the live format re-prices the traded exit identically to the
research (max |diff| 4e-7 R) once the horizons carry six decimals.
One honest limit is written into the file header: a live trade is closed by its
own barriers, so from live rows an exit can only be re-selected TIGHTER.
THE ADAPTIVE EXIT. CSignalInsideBarGap reads
Common\Files\Warrior_EA\Adapt\{SYMBOL}_{PERIOD}_inside_bar_u.cfg once per server
day, written by research/journal_adapt.py with the research's discipline (choose
on the older trades, confirm on the newer, placeable stop, stable after dropping
the best 5%). No file means the validated defaults; a file outside the research
grid is refused and logged.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-06 11:23:27 -04:00 | | | return true;
|
| | | Print("Failed to add column " + columnName + " to " + tableName);
|
| | | return false;
|
| | | }
|
| | | bool EnsureColumn(string tableName, string columnName, string columnType)
|
| | | {
|
| | | if(ColumnExists(tableName, columnName))
|
| | | return true;
|
| | | return AddColumn(tableName, columnName, columnType);
|
| | | }
|
2026-07-13 03:23:39 -04:00 | | | bool DeleteTable(string tableName)
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "delete"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string deleteTableQuery = "DROP TABLE IF EXISTS " + Q(tableName);
|
2026-07-13 03:23:39 -04:00 | | | if(PrepareAndExecuteQuery(deleteTableQuery))
|
| | | {
|
| | | PrintVerbose("Table " + tableName + " deleted successfully.");
|
| | | return true;
|
| | | }
|
| | | Print("Failed to delete table " + tableName);
|
| | | return false;
|
| | | }
|
| | | // Insert a trade record into the database. Column names are our own fixed
|
| | | // schema field names (never user data); values are bound as parameters.
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | //--- a statement with no result set and no parameters (CREATE INDEX ...), through the same retry
|
| | | bool Execute(const string query)
|
| | | {
|
| | | return PrepareAndExecuteQuery(query);
|
| | | }
|
 feat(journal): the tester fills the database, and the journal can skip a categorical loser
Operator: "is there not a meta labeling neural network in the EA? why
did it keep taking a losing pattern? The journal is there for that."
There is no meta-label net (CNNFilter is a loader nobody instantiates;
the offline net was never built), the adaptive layer learns management
only by design, and it could not read anyway: SignalDatabaseActive()
switched the DB off for every tester run on the argument that "a tester
run's DB is written and never read" - a premise AdaptiveExitWriter had
already deleted. Every run ended with 93 verbose-level "journal read
failed - database not initialized" lines and learned nothing.
- The DB is ON in a single tester pass; optimisation and forward stay
off (12 agents on one FILE_COMMON SQLite file finished zero passes).
- A closed trade REPLACES its own row on the natural key (symbol, open
minute, side, entry price): tester tickets restart at 1 on every run,
so a re-run over the same history no longer double-counts.
- AdaptiveExitWriter writes skip_long / skip_short when a setup-side is
a categorical loser on this chart: n >= 60 and t <= -2.5 on realised
R x risk in bp, gross of commission. Not ranking - the measurement
that made selection anti-predictive ranked marginal cells against
each other; this is the shape Second Wind showed on forex (t -4.0).
A skipped side never arms, so it never counts toward agreement.
- The reader adopts the flags with the exit, once per server day, and
prints the verdict when it changes.
- "Could not read" is printed once at normal level with the reason.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 17:36:02 -04:00 | | | //--- DELETE the rows matching a bound WHERE clause. Built for the journal's replace-on-key: a
|
| | | //--- tester re-run over the same history must not double-count the trades it already recorded.
|
| | | bool DeleteWhere(string tableName, const string whereClause, const string ¶ms[])
|
| | | {
|
| | | if(!RequireValidIdentifier(tableName, "delete from"))
|
| | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | return PrepareAndExecuteBound("DELETE FROM " + Q(tableName) + " WHERE " + whereClause, params);
|
 feat(journal): the tester fills the database, and the journal can skip a categorical loser
Operator: "is there not a meta labeling neural network in the EA? why
did it keep taking a losing pattern? The journal is there for that."
There is no meta-label net (CNNFilter is a loader nobody instantiates;
the offline net was never built), the adaptive layer learns management
only by design, and it could not read anyway: SignalDatabaseActive()
switched the DB off for every tester run on the argument that "a tester
run's DB is written and never read" - a premise AdaptiveExitWriter had
already deleted. Every run ended with 93 verbose-level "journal read
failed - database not initialized" lines and learned nothing.
- The DB is ON in a single tester pass; optimisation and forward stay
off (12 agents on one FILE_COMMON SQLite file finished zero passes).
- A closed trade REPLACES its own row on the natural key (symbol, open
minute, side, entry price): tester tickets restart at 1 on every run,
so a re-run over the same history no longer double-counts.
- AdaptiveExitWriter writes skip_long / skip_short when a setup-side is
a categorical loser on this chart: n >= 60 and t <= -2.5 on realised
R x risk in bp, gross of commission. Not ranking - the measurement
that made selection anti-predictive ranked marginal cells against
each other; this is the shape Second Wind showed on forex (t -4.0).
A skipped side never arms, so it never counts toward agreement.
- The reader adopts the flags with the exit, once per server day, and
prints the verdict when it changes.
- "Could not read" is printed once at normal level with the reason.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 17:36:02 -04:00 | | | }
|
2026-07-13 03:23:39 -04:00 | | | bool InsertTradeRecord(string tableName, const string &columns[], const string &values[])
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "insert into"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string insertQuery = "INSERT INTO " + Q(tableName) + " (" + JoinWithCommas(columns) +
|
2026-08-24 03:04:23 -04:00 | | | ") VALUES (" + PlaceholderList(ArraySize(columns)) + ")";
|
2026-07-13 03:23:39 -04:00 | | | if(PrepareAndExecuteBound(insertQuery, values))
|
| | | return true;
|
| | | Print("Failed to insert trade record into " + tableName);
|
| | | return false;
|
| | | }
|
| | | struct RecordCount
|
| | | {
|
| | | int count;
|
| | | };
|
| | | // Fetch the number of records in the specified table
|
| | | bool FetchRecordCount(string tableName, int &count)
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "query"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
| | | RecordCount countStruct;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string countQuery = "SELECT COUNT(*) as count FROM " + Q(tableName);
|
2026-07-13 03:23:39 -04:00 | | | int handle = DatabasePrepare(m_databaseHandle, countQuery);
|
| | | if(handle == INVALID_HANDLE)
|
| | | {
|
| | | Print("Failed to prepare COUNT query for " + tableName);
|
| | | return false;
|
| | | }
|
| | | bool ok = DatabaseReadBind(handle, countStruct);
|
| | | if(ok)
|
| | | count = countStruct.count;
|
| | | DatabaseFinalize(handle);
|
| | | return ok;
|
| | | }
|
2026-08-12 18:53:04 -04:00 | | | //--- Targeted lookups. These answer ProcessSignal/UpdateSignalsWeights' questions inside SQLite
|
| | | //--- and return one row or one pair of numbers, so per-signal cost stays flat no matter how large
|
| | | //--- a table grows. They exist because the original design pulled ENTIRE tables into MQL struct
|
| | | //--- arrays for every question - the real constraint behind the historical 1000-row cap: SQLite
|
| | | //--- has no such limit, but materializing thousands of string-bearing structs per signal does.
|
| | | struct OpenTradeRow
|
| | | {
|
| | | double entryPrice;
|
| | | };
|
| | | // Entry price of the still-open ("NA") trade for pattern+direction, if one exists
|
| | | bool FetchOpenTradeEntry(string tableName, string pattern, string direction,
|
| | | double &entryPrice, bool &found)
|
| | | {
|
| | | found = false;
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "query"))
|
2026-08-12 18:53:04 -04:00 | | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string q = "SELECT entryPrice FROM " + Q(tableName) +
|
2026-08-12 18:53:04 -04:00 | | | " WHERE pattern=? AND direction=? AND result='NA' LIMIT 1";
|
| | | int handle = DatabasePrepare(m_databaseHandle, q);
|
| | | if(handle == INVALID_HANDLE)
|
| | | {
|
| | | Print("Failed to prepare open-trade query for " + tableName);
|
| | | return false;
|
| | | }
|
| | | if(!DatabaseBind(handle, 0, pattern) || !DatabaseBind(handle, 1, direction))
|
| | | {
|
| | | Print("Failed to bind open-trade query for " + tableName);
|
| | | DatabaseFinalize(handle);
|
| | | return false;
|
| | | }
|
| | | OpenTradeRow row;
|
| | | if(DatabaseReadBind(handle, row))
|
| | | {
|
| | | entryPrice = row.entryPrice;
|
| | | found = true;
|
| | | }
|
| | | DatabaseFinalize(handle);
|
| | | return true;
|
| | | }
|
| | | struct TimeKeyRow
|
| | | {
|
| | | long k;
|
| | | };
|
| | | // Time key (yyyymmddhhmm as a number) of the table's NEWEST row. Rows are inserted in
|
2026-08-12 23:56:26 -04:00 | | | // chronological order, so max ROWID carries the latest timestamp. `quiet` suppresses the
|
| | | // prepare-failure print so existence probes over possibly-absent tables don't spam the log.
|
| | | bool FetchNewestTimeKey(string tableName, long &key, bool &found, const bool quiet = false)
|
2026-08-12 18:53:04 -04:00 | | | {
|
| | | found = false;
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "query"))
|
2026-08-12 18:53:04 -04:00 | | | return false;
|
| | | string q = "SELECT (((year*100+month)*100+day)*100+hour)*100+minutes AS k FROM " +
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | Q(tableName) + " ORDER BY ROWID DESC LIMIT 1";
|
2026-08-12 18:53:04 -04:00 | | | int handle = DatabasePrepare(m_databaseHandle, q);
|
| | | if(handle == INVALID_HANDLE)
|
| | | {
|
2026-08-12 23:56:26 -04:00 | | | if(!quiet)
|
| | | Print("Failed to prepare newest-time query for " + tableName);
|
2026-08-12 18:53:04 -04:00 | | | return false;
|
| | | }
|
| | | TimeKeyRow row;
|
| | | if(DatabaseReadBind(handle, row))
|
| | | {
|
| | | key = row.k;
|
| | | found = true;
|
| | | }
|
| | | DatabaseFinalize(handle);
|
| | | return true;
|
| | | }
|
2026-09-13 14:32:40 -04:00 | | | struct ExpectancyRow
|
| | | {
|
| | | int n;
|
| | | double sumR;
|
| | | };
|
| | | //--- MEAN R PER FIRING, not win rate. A pattern that is right 30% of the time and wins three
|
| | | //--- times what it loses beats one that is right 70% and does the opposite, and a win-rate
|
| | | //--- ranking cannot tell them apart - it scores the first 30 and the second 70, exactly
|
| | | //--- backwards. Same beforeKey bound as the counts below, for the same reason.
|
| | | //--- A TABLE THAT DOES NOT EXIST IS NOT AN ERROR HERE, it is the normal state of a pattern that
|
| | | //--- has not fired yet. Without this guard DatabasePrepare() fails and the TERMINAL logs
|
| | | //--- "database error, no such table" itself - once per absent table, per module, per daily
|
| | | //--- rerank. Over a 16-year pass that is tens of thousands of lines that mean nothing, burying
|
| | | //--- the ones that do. It also skips the wasted prepare.
|
| | | bool TableExists(const string tableName)
|
| | | {
|
| | | return ::DatabaseTableExists(m_databaseHandle, tableName);
|
| | | }
|
| | | bool FetchExpectancy(string tableName, long beforeKey, int &n, double &meanR,
|
| | | const int regime = -1)
|
| | | {
|
| | | n = 0;
|
| | | meanR = 0.0;
|
| | | if(!RequireValidIdentifier(tableName, "query"))
|
| | | return false;
|
| | | if(!TableExists(tableName))
|
| | | return false;
|
| | | //--- `regime` < 0 means "every regime" - the unconditional record. Otherwise the count is
|
| | | //--- restricted to firings that happened under the SAME conditions as now, which is the
|
| | | //--- whole point: a trend-following pattern is good in a trend and bad in a range, and
|
| | | //--- averaging the two produces the coin flip that was measured across every module.
|
| | | string q = "SELECT COUNT(rMultiple), COALESCE(SUM(rMultiple),0) FROM " + Q(tableName) +
|
| | | " WHERE rMultiple IS NOT NULL AND "
|
| | | "(((year*100+month)*100+day)*100+hour)*100+minutes < " + IntegerToString(beforeKey);
|
| | | if(regime >= 0)
|
| | | q += " AND regime = " + IntegerToString(regime);
|
| | | int handle = DatabasePrepare(m_databaseHandle, q);
|
| | | if(handle == INVALID_HANDLE)
|
| | | return false;
|
| | | ExpectancyRow row;
|
| | | bool ok = DatabaseReadBind(handle, row);
|
| | | if(ok && row.n > 0)
|
| | | {
|
| | | n = row.n;
|
| | | meanR = row.sumR / row.n;
|
| | | }
|
| | | DatabaseFinalize(handle);
|
| | | return ok;
|
| | | }
|
2026-08-12 18:53:04 -04:00 | | | struct WinLossRow
|
| | | {
|
| | | int wins;
|
| | | int losses;
|
| | | };
|
| | | // Closed-trade outcome counts with open time strictly before beforeKey (yyyymmddhhmm). The bound
|
| | | // is what keeps a resumed/mixed database's not-yet-current rows out of the weights in the tester.
|
| | | bool FetchWinLossCounts(string tableName, long beforeKey, int &wins, int &losses)
|
| | | {
|
| | | wins = 0;
|
| | | losses = 0;
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "query"))
|
2026-08-12 18:53:04 -04:00 | | | return false;
|
2026-09-13 14:32:40 -04:00 | | | if(!TableExists(tableName))
|
| | | return false; // see TableExists - an absent table is a pattern, not a fault
|
2026-08-12 18:53:04 -04:00 | | | string q = "SELECT COALESCE(SUM(result='Profit'),0), COALESCE(SUM(result='Loss'),0) FROM " +
|
| | | tableName + " WHERE result!='NA' AND (((year*100+month)*100+day)*100+hour)*100+minutes < " +
|
| | | IntegerToString(beforeKey);
|
| | | int handle = DatabasePrepare(m_databaseHandle, q);
|
| | | if(handle == INVALID_HANDLE)
|
| | | {
|
| | | Print("Failed to prepare win-loss query for " + tableName);
|
| | | return false;
|
| | | }
|
| | | WinLossRow row;
|
| | | bool ok = DatabaseReadBind(handle, row);
|
| | | if(ok)
|
| | | {
|
| | | wins = row.wins;
|
| | | losses = row.losses;
|
| | | }
|
| | | DatabaseFinalize(handle);
|
| | | return ok;
|
| | | }
|
2026-07-13 03:23:39 -04:00 | | | // Fetch trade records from specified table and read into a structure, then populate the dynamic array with the data
|
| | | template <typename T>
|
| | | bool FetchTradeRecords(string tableName, T &tradeRecordStructure, T &tradeRecords[])
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "query"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
| | | int recordCount;
|
| | | if(!FetchRecordCount(tableName, recordCount))
|
| | | return false;
|
| | | ArrayResize(tradeRecords, recordCount);
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string selectQuery = "SELECT * FROM " + Q(tableName);
|
2026-07-13 03:23:39 -04:00 | | | int handle = DatabasePrepare(m_databaseHandle, selectQuery);
|
| | | if(handle == INVALID_HANDLE)
|
| | | {
|
| | | Print("Failed to prepare SELECT query for " + tableName);
|
| | | return false;
|
| | | }
|
2026-07-26 12:12:14 -04:00 | | | int i = 0;
|
| | | for(; i < recordCount && DatabaseReadBind(handle, tradeRecordStructure); i++)
|
2026-07-13 03:23:39 -04:00 | | | tradeRecords[i] = tradeRecordStructure;
|
| | | DatabaseFinalize(handle);
|
2026-07-26 12:12:14 -04:00 | | | if(i < recordCount)
|
| | | {
|
| | | // The read loop can exit early (a concurrent delete on the shared COMMON db between the
|
| | | // COUNT and SELECT above, or a transient read failure) - trim the array to what was
|
| | | // actually read instead of leaving trailing zero/empty-string structs that downstream
|
| | | // reporting would otherwise silently count as real (empty-symbol, 0-profit) trades.
|
| | | Print(__FUNCTION__ + ": only read " + IntegerToString(i) + " of " + IntegerToString(recordCount) +
|
| | | " expected rows from " + tableName + " - trimming to actual rows read.");
|
| | | ArrayResize(tradeRecords, i);
|
| | | }
|
2026-07-13 03:23:39 -04:00 | | | return true;
|
| | | }
|
| | | // Update the exit price/result of the still-open ("NA") trade matching pattern+direction
|
| | | bool UpdateTradeRecord(string tableName, const string &columns[], const string &values[], string pattern, string direction)
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "update"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string updateQuery = "UPDATE " + Q(tableName) + " SET " + JoinWithCommas(columns, "=?") +
|
2026-08-24 03:04:23 -04:00 | | | " WHERE pattern=? AND direction=? AND result='NA'";
|
2026-07-13 03:23:39 -04:00 | | | string params[];
|
| | | ArrayResize(params, ArraySize(values) + 2);
|
| | | for(int i = 0; i < ArraySize(values); i++)
|
| | | params[i] = values[i];
|
| | | params[ArraySize(values)] = pattern;
|
| | | params[ArraySize(values) + 1] = direction;
|
| | | if(PrepareAndExecuteBound(updateQuery, params))
|
| | | return true;
|
| | | Print("Failed to update trade records in " + tableName);
|
| | | return false;
|
| | | }
|
| | | // Delete the oldest entry from a specified table
|
| | | bool DeleteOldestEntry(string tableName)
|
| | | {
|
2026-08-24 00:58:45 -04:00 | | | if(!RequireValidIdentifier(tableName, "delete from"))
|
2026-07-13 03:23:39 -04:00 | | | return false;
|
 feat(journal): the shadow journal - every firing, independently, next to the trades taken
Operator: "meta labeling should drastically change the outcome anyways.
unless the journal logs every patterns independently (I think it should
and also voted trades outcomes for comparison)."
Until now the journal recorded POSITIONS - what the agreement gate let
through - so a meta-labeller would have learned from a few dozen trades
per setup. Every arming is now a virtual trade: a pending order at the
setup's own entry and stop, filled when price reaches it inside the
setup's own window, tracked through the same first-passage ladder as a
real position (one shared AdvanceTrack), closed at the ladder's last
horizon, written to the same table as filterID="Virtual:<setup>". The
parent annotates each bar's firings with agree=N, opposed and the armed
combination. Real trades stay filterID="Book", so the two populations
sit on one table. Closed virtual rows are written 200 per commit.
Context now also carries headroom, minutes to the forced close and the
order type. The `journal` global lives in the header so setups and the
signal base reach the one instance the expert feeds.
Also: SQL identifiers are quoted - "2WD_Pattern_0_Sell" starts with a
digit and every Second Wind pattern table failed to create the moment
the database was on in the tester; an index on the natural key so the
replace-on-key scales to a decade of firings; "no closed trades yet" is
verbose again.
Smoke, BTCUSD H1 one month: 52 firings, 26 filled, 26 rows, annotated,
no database errors.
Co-Authored-By: Claude Fable 5.1 <noreply@anthropic.com>
2026-09-08 18:32:38 -04:00 | | | string deleteQuery = "DELETE FROM " + Q(tableName) + " WHERE ROWID = (SELECT MIN(ROWID) FROM " + Q(tableName) + ")";
|
2026-07-13 03:23:39 -04:00 | | | if(PrepareAndExecuteQuery(deleteQuery))
|
| | | {
|
| | | PrintVerbose("Oldest entry deleted from " + tableName + " successfully.");
|
| | | return true;
|
| | | }
|
| | | Print("Failed to delete oldest entry from " + tableName);
|
| | | return false;
|
| | | }
|
| | | };
|
| | | //+------------------------------------------------------------------+
|