NN_in_Trading/Experts/AC-SRM/Test.mq5

369 行
18 KiB
MQL5

2026-09-29 00:57:33 +03:00
//+------------------------------------------------------------------+
2026-09-28 01:52:58 +03:00
//| 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 и манифест-проверка."
2026-09-29 00:57:33 +03:00
#define Online
2026-09-28 01:52:58 +03:00
#include "Trajectory.mqh"
//---
2026-09-29 00:57:33 +03:00
int Epochs = 0;
2026-09-28 01:52:58 +03:00
//---
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();
}
}
2026-09-29 18:12:11 +03:00
//+------------------------------------------------------------------+