//+------------------------------------------------------------------+ //| Test.mq5 | //| Copyright 2026, DNG | //| https://www.mql5.com/ru/users/dng | //+------------------------------------------------------------------+ #property copyright "Copyright 2026, DNG" #property link "https://www.mql5.com/ru/users/dng" #property version "1.00" #property description "AC-SRM Stage 03/05: инференс замороженной политики с оценкой на LIVE-счете." #property description "На старте бара Actor принимает решение; реальные ордера исполняются на тестерном счете." #property description "Источник — LIVE-котировки; результат — журнал PASS/FAIL и манифест-проверка." #define Online #include "Trajectory.mqh" //--- int Epochs = 0; //--- input group "---- AC-SRM inference ----" input int InpDealHorizon = 24; //Deal horizon (manifest validation symmetry) input double MinBalance = 50.0; //Minimum account balance (irrecoverable threshold) //--- input group "---- SRM critic head ----" input int InpRiskType = defSRM_MEAN_CVAR; //Risk measure (defSRM_*) input float InpRiskParameter = 0.1f; //Risk parameter (alpha/lambda/nu) input float InpMeanWeight = 0.0f; //Mean-CVaR omega input int InpQuantiles = 32; //Head quantiles (factory-fixed) //--- input group "---- AC-SRM production checkpoint ----" input ENUM_OMPB_STAGE InpOMPBStage = OMPB_STAGE_BASE_POLICY; //Chain stage: keep 03 (base policy) //--- CNet Actor; CNet Q1; CNet Q2; CBufferFloat State; CBufferFloat TimeState; CBufferFloat Account; CBufferFloat CurrentAction; CBufferFloat CriticInput; CBufferFloat SRMValue; CBufferFloat PrevAccount; CBufferFloat PrevAction; int TestBars = 0; ulong TestTerminal = 0; ulong TestExecutable = 0; ulong TestNoTrade = 0; double RewardSum = 0; double SRM1Sum = 0; double SRM2Sum = 0; double BalanceMin = 0; double BalanceMax = 0; double EquityMin = 0; double EquityMax = 0; ulong Irrecoverable = 0; //--- LIVE stream state: previous bar post-execution snapshots. double LivePrevBalance = 0; double LivePrevEquity = 0; bool LiveReady = false; bool LiveFinalized = false; //+------------------------------------------------------------------+ //| Validates the AC-SRM policy shapes (no D2 banks, ACSRM heads). | //+------------------------------------------------------------------+ bool ValidatePolicyShapeACSRM(CNet &actor, CNet &q1, CNet &q2) { CNeuronBaseOCL *a0 = actor.Layer(0); CNeuronBaseOCL *a1 = actor.Layer(1); CNeuronBaseOCL *a3 = actor.Layer(3); if(!a0 || !a1 || !a3 || a0.getOutput().Total() != (int)AccountDescr || a1.Type() != defNeuronScenarioCrossAttention || a3.getOutput().Total() != (int)NActions) ReturnFalse; for(int i = 0; i < 2; i++) { CNet *critic = (i == 0 ? GetPointer(q1) : GetPointer(q2)); CNeuronBaseOCL *c0 = critic.Layer(0); CNeuronBaseOCL *c3 = critic.Layer(3); CNeuronBaseOCL *c4 = critic.Layer(4); if(!c0 || !c3 || !c4 || c0.getOutput().Total() != (int)(AccountDescr + NActions) || c3.getOutput().Total() != 32 || c4.Type() != defNeuronACSRM || c4.getOutput().Total() != 1) ReturnFalse; } return(true); } //+------------------------------------------------------------------------------------------------------------+ //| Inference requires the published Stage 03 checkpoint; creation is intentionally impossible in Test mode. | //+------------------------------------------------------------------------------------------------------------+ bool LoadPoliciesInference(void) { if(!SkillACRecoverTransaction(Skill_ACTOR_FILE, Skill_Q1_FILE, Skill_Q2_FILE, Skill_AC_MANIFEST_FILE, Skill_ACTOR_NEXT_FILE, Skill_Q1_NEXT_FILE, Skill_Q2_NEXT_FILE, Skill_AC_MANIFEST_NEXT_FILE, Skill_ACTOR_PREVIOUS_FILE, Skill_Q1_PREVIOUS_FILE, Skill_Q2_PREVIOUS_FILE, Skill_AC_MANIFEST_PREVIOUS_FILE, Skill_AC_TRANSACTION_FILE)) { Print("Skill policy recovery=FAIL; incomplete checkpoint requires repair"); ReturnFalse; } if(!FileIsExist(Skill_AC_MANIFEST_FILE, FILE_COMMON)) { Print("Skill inference policy=FAIL reason=manifest_missing"); ReturnFalse; } if(!SkillValidateACManifestFile(true, Skill_AC_MANIFEST_FILE, false) || !SkillLoadPolicyNet(Actor, Skill_ACTOR_FILE) || !SkillLoadPolicyNet(Q1, Skill_Q1_FILE) || !SkillLoadPolicyNet(Q2, Skill_Q2_FILE)) { Print("Skill inference policy=FAIL reason=tuple_invalid"); ReturnFalse; } if(!SkillBindPolicyOpenCLChecked(Actor, Q1, Q2) || !ValidatePolicyShapeACSRM(Actor, Q1, Q2)) ReturnFalse; Actor.TrainMode(false); Q1.TrainMode(false); Q2.TrainMode(false); if(!Actor.SetWeightsUpdate(false) || !Q1.SetWeightsUpdate(false) || !Q2.SetWeightsUpdate(false)) ReturnFalse; return(true); } //+------------------------------------------------------------------+ //| Returns the SRM risk score of one critic head distribution. | //+------------------------------------------------------------------+ bool CriticRiskValue(CNet &critic, double &value) { CNeuronBaseOCL *head = critic.Layer(4); if(!head || head.Type() != defNeuronACSRM) ReturnFalse; CNeuronACSRM *acsrm = (CNeuronACSRM*)head; if(!SRMValue.BufferInit(1, 0) || !SRMValue.BufferCreate(SkillMarket.GetOpenCL()) || !SRMValue.BufferWrite()) ReturnFalse; if(!acsrm.SRMForward(GetPointer(SRMValue), 1, InpRiskType, InpRiskParameter, InpMeanWeight)) ReturnFalse; if(!SRMValue.BufferRead()) ReturnFalse; value = double(SRMValue[0]); return(MathIsValidNumber(value)); } //+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ //| Refreshes the live market state from the last CLOSED bar and the scenario (forecast) context consumed by the actor cross- attention. SkillRefreshLiveMarket fills the Rates/indicator window; the explicit Market feed refreshes z/u/pi. | //+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ bool LiveRefreshState(CBufferFloat *state, CBufferFloat *time_state) { if(!SkillRefreshLiveMarket(state, time_state)) ReturnFalse; return(SkillMarket.feedForward(state, 1, false, (CBufferFloat*)NULL) && SkillForecast != NULL); } //+------------------------------------------------------------------------------------------------------------------------------------+ //| Keeps the PREVIOUS (account, action) host copies: the action is reported with its own bar and classified by executable contract. | //+------------------------------------------------------------------------------------------------------------------------------------+ bool LiveKeepPrevious(void) { if(PrevAccount.Total() != (int)AccountDescr && !PrevAccount.BufferInit((int)AccountDescr, 0)) ReturnFalse; if(PrevAction.Total() != (int)NActions && !PrevAction.BufferInit((int)NActions, 0)) ReturnFalse; return(PrevAccount.AssignArray(GetPointer(Account)) && PrevAction.AssignArray(GetPointer(CurrentAction))); } //+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ //| Executes the bar decision on the LIVE account: builds the real account vector, runs the frozen Actor, retains this bar's (state, action) critic tuple for next-bar diagnostics and re- targets this expert's positions (CogDriver). The post-execution LIVE balance/equity snapshot is the reward base of this bar. | //+---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ bool LiveExecuteBar(const double previous_balance, const double previous_equity) { //--- Fresh CLOSED-bar market context: LiveRefreshState runs //--- SkillRefreshLiveMarket (CopyRates offset 1 + Symb.RefreshRates) //--- which SkillExecuteAction needs to derive valid SL/TP distances. if(!LiveRefreshState(GetPointer(State), GetPointer(TimeState))) ReturnFalse; datetime now = TimeCurrent(); double buy_value = 0, sell_value = 0; if(!SkillBuildLiveAccount(previous_balance, previous_equity, now, GetPointer(Account), buy_value, sell_value)) ReturnFalse; if(Account.GetIndex() < 0 && !Account.BufferCreate(SkillMarket.GetOpenCL())) ReturnFalse; if(Account.GetIndex() >= 0 && !Account.BufferWrite()) ReturnFalse; if(!Actor.Clear() || !Actor.feedForward(GetPointer(Account), 1, false, GetPointer(SkillMarket), -1) || !ReadAction(Actor, GetPointer(CurrentAction))) ReturnFalse; CNeuronBaseOCL *context_layer = Actor.Layer(0); CNeuronBaseOCL *actor_layer = Actor.Layer(3); if(!context_layer || !actor_layer) ReturnFalse; if(!BuildCriticInput(context_layer.getOutput(), actor_layer.getOutput(), GetPointer(CriticInput))) ReturnFalse; double margin_penalty = 0; bool market_closed = false; if(!SkillExecuteAction(GetPointer(CurrentAction), buy_value, sell_value, margin_penalty, market_closed)) ReturnFalse; //--- Post-execution LIVE snapshot: the reward of this bar is evaluated //--- from it on the next bar open. LivePrevBalance = AccountInfoDouble(ACCOUNT_BALANCE); LivePrevEquity = AccountInfoDouble(ACCOUNT_EQUITY); return(LiveKeepPrevious()); } //+------------------------------------------------------------------+ //| First bar: no previous bar exists, only the opening decision. | //+------------------------------------------------------------------+ bool LiveFirstBar(void) { LiveReady = true; LivePrevBalance = AccountInfoDouble(ACCOUNT_BALANCE); LivePrevEquity = AccountInfoDouble(ACCOUNT_EQUITY); if(!LiveExecuteBar(LivePrevBalance, LivePrevEquity)) ReturnFalse; PrintFormat("ACSRM_TEST_LIVE_BAR1 time=%s balance=%.2f equity=%.2f act=(%.8f %.8f %.8f %.8f %.8f %.8f)", TimeToString(TimeCurrent(), TIME_DATE | TIME_SECONDS), LivePrevBalance, LivePrevEquity, double(PrevAction[0]), double(PrevAction[1]), double(PrevAction[2]), double(PrevAction[3]), double(PrevAction[4]), double(PrevAction[5])); return(true); } //+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ //| One new-bar live transition: evaluates the just-closed bar from the LIVE account (reward = equity change vs the post-execution snapshot of the previous bar), emits critic SRM diagnostics on the retained tuple and places the new bar order target. | //+-----------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------+ bool LiveEvaluateBar(void) { if(!LiveReady) return(LiveFirstBar()); //--- LIVE result of the just-closed bar. const double balance_now = AccountInfoDouble(ACCOUNT_BALANCE); const double equity_now = AccountInfoDouble(ACCOUNT_EQUITY); const double reward = equity_now - LivePrevEquity; if(!MathIsValidNumber(reward) || !MathIsValidNumber(balance_now) || !MathIsValidNumber(equity_now)) ReturnFalse; TestBars++; double srm1 = 0, srm2 = 0; //--- Critic diagnostics on the retained previous (state, action) tuple. if(!Q1.feedForward(GetPointer(CriticInput), 1, false, GetPointer(SkillMarket), -1) || !Q2.feedForward(GetPointer(CriticInput), 1, false, GetPointer(SkillMarket), -1) || !CriticRiskValue(Q1, srm1) || !CriticRiskValue(Q2, srm2)) ReturnFalse; const double buy_lot = MathMax(0.0, double(PrevAction[0] - PrevAction[3])); const double sell_lot = MathMax(0.0, double(PrevAction[3] - PrevAction[0])); const bool executable = (IsExecutableOrder(buy_lot, PrevAction[1], PrevAction[2]) || IsExecutableOrder(sell_lot, PrevAction[4], PrevAction[5])); if(executable) TestExecutable++; else TestNoTrade++; RewardSum += reward; SRM1Sum += srm1; SRM2Sum += srm2; const bool terminal = (balance_now <= MinBalance); if(terminal) TestTerminal++; if(balance_now <= MinBalance) Irrecoverable++; if(TestBars == 1) { BalanceMin = balance_now; BalanceMax = balance_now; EquityMin = equity_now; EquityMax = equity_now; } else { BalanceMin = MathMin(BalanceMin, balance_now); BalanceMax = MathMax(BalanceMax, balance_now); EquityMin = MathMin(EquityMin, equity_now); EquityMax = MathMax(EquityMax, equity_now); } if(TestBars <= 4 || TestBars % 1000 == 0) PrintFormat("ACSRM_TEST_BAR bar=%d balance=%.2f equity=%.2f reward=%.8f srm1=%.8f srm2=%.8f act=(%.8f %.8f %.8f %.8f %.8f %.8f) terminal=%s", TestBars, balance_now, equity_now, reward, srm1, srm2, double(PrevAction[0]), double(PrevAction[1]), double(PrevAction[2]), double(PrevAction[3]), double(PrevAction[4]), double(PrevAction[5]), (terminal ? "true" : "false")); //--- The new bar decision is made from the post-execution LIVE account //--- of the previous bar (relative change fields) and the closed-bar //--- market state. return(LiveExecuteBar(LivePrevBalance, LivePrevEquity)); } //+------------------------------------------------------------------+ //| Emits the final live evaluation report once. | //+------------------------------------------------------------------+ void LiveFinalize(void) { if(LiveFinalized) return; LiveFinalized = true; if(TestBars <= 0) { Print("ACSRM_TEST_FAIL reason=no_bars"); return; } const double r_mean = RewardSum / double(TestBars); const double srm1_mean = SRM1Sum / double(TestBars); const double srm2_mean = SRM2Sum / double(TestBars); PrintFormat("ACSRM_TEST_PASS bars=%d episodes=1 terminal=%I64u executable=%I64u no_trade=%I64u " + "r_mean=%.8f srm1_mean=%.8f srm2_mean=%.8f balance_min=%.2f balance_max=%.2f " + "equity_min=%.2f equity_max=%.2f irrecoverable=%I64u", TestBars, TestTerminal, TestExecutable, TestNoTrade, r_mean, srm1_mean, srm2_mean, BalanceMin, BalanceMax, EquityMin, EquityMax, Irrecoverable); //--- Leave a clean tester account. CloseByDirection(POSITION_TYPE_BUY); CloseByDirection(POSITION_TYPE_SELL); } //+------------------------------------------------------------------+ //| Initializes the Stage 03 inference Expert. | //+------------------------------------------------------------------+ int OnInit() { ResetLastError(); //--- Pin the deal-evaluation horizon for manifest validation symmetry. SkillManifestDealHorizon = MathMax(1, InpDealHorizon); //--- Confirm Stage 03, the frozen Forecast checkpoint and the policy tuple. if(InpOMPBStage != OMPB_STAGE_BASE_POLICY || !SkillInitIndicators() || !SkillLoadForecastInference() || !SkillConfigureProductionACSRMCheckpoint() || !SkillValidateProductionACSRMCheckpoint() || !SkillCaptureProductionACSRMFingerprints() || !LoadPoliciesInference() || !SkillVerifyFrozenForecastExact() || !SkillVerifyProductionACSRMFingerprints()) { PrintFormat("ACSRM inference initialization failed at line %d error=%d", __LINE__, GetLastError()); return(INIT_FAILED); } PrintFormat("ACSRM_TEST_INIT_PASS tester=%d", MQLInfoInteger(MQL_TESTER)); return(INIT_SUCCEEDED); } //+------------------------------------------------------------------+ //| Function OnDeinit. | //+------------------------------------------------------------------+ void OnDeinit(const int reason) { if(MQLInfoInteger(MQL_TESTER) == 1 && !LiveFinalized) { PrintFormat("%s live finalize reason=%d bars=%d", ACSRM_LOG_PREFIX, reason, TestBars); LiveFinalize(); } if(SkillForecast != NULL && !SkillVerifyFrozenForecastExact()) PrintFormat("%s -> %d forecast mutation", __FUNCTION__, __LINE__); if(SkillProductionSignatureReady && !SkillVerifyProductionACSRMFingerprints()) PrintFormat("%s -> %d ACSRM production signature mutation", __FUNCTION__, __LINE__); SkillForecast = NULL; } //+--------------------------------------------------------------------------------------------------------------------------------+ //| LIVE bar-driven evaluation on the tester account stream. Each new bar triggers one decision + one next-bar live evaluation. | //+--------------------------------------------------------------------------------------------------------------------------------+ void OnTick(void) { if(!IsNewBar()) return; if(!LiveEvaluateBar()) { PrintFormat("ACSRM_TEST_FAIL reason=live_transition_failed bars=%d", TestBars); ExpertRemove(); } } //+------------------------------------------------------------------+