|
0 registered members (),
1,893
guests, and 1
spider. |
|
Key:
Admin,
Global Mod,
Mod
|
|
|
|
Yesterday at 17:37
You can edit your assetlist "AssetsOanda". Or you can make your own assetList and change the parameteres (like assetlist) in the Strategy\z.ini with the script editor. The .ini file is read at start of the strategy. If you want to trade different Z strategies with different parameters, save Z.ini under the name of the strategy, f.i. Strategy\Z6.ini.
2
85
Read More
|
|
|
09/19/26 21:17
Further observations, following additional tests:
1) The fact that I obtained the same behaviour both in backtesting and live is due to the fact that I indicated an updated Oanda asset list in the z.ini, making the strategy ignore the Assetsz6.csv (in which PIPCost and Multiplier are obviously not consistent to the Oanda asset list). 2) It looks like PIPCost and LotAmount would need a correction if trading Z6+ on Oanda (API), which I understand would not be possible unless changing the Z6+ source code (SET_PATCHing or similar), right? 3) I believe that applying a 1000x factor to Capital could represent a workaround at this stage.
2
85
Read More
|
|
|
09/19/26 17:09
Hello Andrew, i am having lot of trouble, trying to build very simple strategies, for example:
History is QQQ.t6 just OHLC, i want to open 1 trade wednesday at market open and close the same trade at Thrusday market close.
Seem a very simple strategy but i am not able to close at trusday close, as the signal cant be executed at market close, i know it is not realistic strategy because when signal is send, market just close, but i want to do this because i have more years in this QQQ.t6 file than other QQQ tick data files. By the way i copy the claude code i got. I am a totally novice in c-lite, so you have an idea of a novice zorro trader coder
The code:
// IWM_WedThu.c // Simple Zorro Lite-C bot: // - Buy IWM at Wednesday's market open // - Sell (close) that position at Thursday's market open // // Data file used: C:\Zorro\History\IWM_eod.t6 // (Zorro finds it automatically because the asset name below // matches the file name, minus the .t6 extension.) // // IMPORTANT TIMING NOTE: // Zorro evaluates the script at the CLOSE of each daily bar, and any // order placed during that evaluation is filled at the OPEN of the // NEXT bar. So to get filled on Wednesday's open, the enterLong() // signal must be raised on TUESDAY's bar. Likewise, to get filled on // Thursday's open, exitLong() must be raised on WEDNESDAY's bar. // // dow() returns a plain number, not a named constant: // 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday // IWM_WedThu.c // Simple Zorro Lite-C bot: // - Buy IWM at Wednesday's market open // - Sell (close) that position at Thursday's market open // // Data file used: C:\Zorro\History\IWM_eod.t6 // (Zorro finds it automatically because the asset name below // matches the file name, minus the .t6 extension.) // // IMPORTANT TIMING NOTE: // Zorro evaluates the script at the CLOSE of each daily bar, and any // order placed during that evaluation is filled at the OPEN of the // NEXT bar. So to get filled on Wednesday's open, the enterLong() // signal must be raised on TUESDAY's bar. Likewise, to get filled on // Thursday's open, exitLong() must be raised on WEDNESDAY's bar. // // dow() returns a plain number, not a named constant: // 0=Sunday, 1=Monday, 2=Tuesday, 3=Wednesday, 4=Thursday, 5=Friday, 6=Saturday // Trade pointer kept between bars so we can exit the specific trade // we opened, rather than "the current open long position". TRADE *myTrade;
function run() { set(LOGFILE); // write a trade log so you can verify fills BarPeriod = 1440; // 1440 minutes = daily bars (matches EOD data) LookBack = 0; // no indicators, no warm-up period needed StartDate = 2015; // adjust to taste, or delete to use full history EndDate = 2017; Fill = 3; // delayed fill: entry order executes at the OPEN of // the next bar instead of the close of the signal bar //asset("IWM_eod"); // loads C:\Zorro\History\IWM_eod.t6 asset("QQQ"); // loads C:\Zorro\History\QQQ.t6 // --- DIAGNOSTIC: print what Zorro sees on the days that matter --- if(dow(0) == 2 || dow(0) == 3 || dow(0) == 4) printf("\n%s dow=%i Fill=%i Open=%.3f Close=%.3f", strdate("%Y-%m-%d", 0), dow(0), Fill, priceOpen(), priceClose()); // --- Entry: signal on Tuesday (2), fills at Wednesday's open --- // (Standard Zorro behavior: order raised now, filled at next bar's open.) if(dow(0) == 2 && !myTrade) myTrade = enterLong(); // --- Exit: signal on Thursday (4), plain market close. // IMPORTANT: exitTrade() must be called WITHOUT a price argument here. // Passing a price (e.g. exitTrade(myTrade, priceClose())) makes Zorro // register it as a Stop level on the trade (visible in the log as // "Trail ... Stop ..."), and that Stop gets checked against price // action independently of the Fill setting -- which is why changing // Fill from 0 to 3 had no effect on the unwanted stop-outs. A plain, // price-less exitTrade() just closes the position at market with no // Stop object attached, removing that artifact entirely. if(dow(0) == 4 && myTrade) { exitTrade(myTrade);
// --- DIAGNOSTIC: confirm the exit landed on THIS (Thursday) bar // and that no Stop/Trail line appears for this trade in the log. printf("\n >> EXIT check: signaled %s (dow=%i) at Close=%.3f -- check trade log above for Stop/Trail lines", strdate("%Y-%m-%d", 0), dow(0), priceClose());
myTrade = 0; } }
4
553
Read More
|
|
|
09/18/26 12:45
Ok es hat funktioniert. Es lag an meiner Vorgabe im Trade. Ich hatte 1 Lot eingegeben, aber der amount war leer.Jetzt geht partial closing/increasing.
2
76
Read More
|
|
|
09/18/26 10:41
Do I have to choose BarPeriod=1 and increase by TimeFrame (e.g. TimeFrame=1440) for all my strategies? And switch to TimeFrame=1 as recommended in the manual temporarely?
Do I am searching for a ghost? During a barperiod it is always possible to enter and close trades (what is working also with my external trigger), only the scale in/out it is not working.Any other issue?
2
76
Read More
|
|
|
09/18/26 10:34
I am injecting commands to a running Zorro script from a Python gui. That means, I can scale in or scale out triggered from outside. At the moment, I have the issue that Zorro rejects this. I found in the manual: "Trades are automatically skipped during weekends or outside market hours, during the LookBack period, in the inactive period of SKIP or DataSplit, or when the current bar is not the end of a TimeFrame. For trading inside a time frame, set TimeFrame = 1, enter the trade, then set TimeFrame back to its previous value."
"Commands to enter or exit trades in the run function are only executed when the TimeFrame has ended at the current bar. But trade entry, stop, or profit targets are observed at any bar period. The run function itself is executed at any bar period, but the frame function can be used for restricting code execution to the end of the current time frame."
In my running example the barperiod=1440.
I have not set TimeFrame explicitely and I got the rejection. A TimeFrame=1,which I have also tested (which should also set to 1440 min) did not help?
Is there a way to increase/descrease the amount of the order during a bar period (that means at any time)?
2
76
Read More
|
|
|
09/17/26 20:53
Hello,
I've noticed that the z6+, both in backtesting and live trading (I'm with Oanda UK, real), trades extremely small number of lots (like 5, for a Capital slider set at 1000), which seems very odd, especially if compared to Z12+ and Z7 (which I'm also trading).
Below the warnings I get in the log
Z6.31: X PH H5 B0 V1 SF0.0 Algo Std_2 (AUD/USD, 60M) Algo P30_10 (USD/CAD, 60M) Algo WFO8_18 (EUR/JPY, 60M) Algo Phantom10_19 (EUR/JPY, 60M) Warning 054: AUD/USD LotAmount 1000 -> 1.0 Warning 054: AUD/USD PIPCost 0.08561 -> 0.00007395 Load AUD/USD prices.. 599 h, gap 25 h Read Z6+.par Read Z6+.fac Warning 054: USD/CAD LotAmount 1000 -> 1.0 Warning 054: USD/CAD PIPCost 0.06252 -> 0.00005333 Load USD/CAD prices.. 599 h, gap 25 h Warning 054: EUR/JPY LotAmount 1000 -> 1.0 Warning 054: EUR/JPY PIPCost 0.05810 -> 0.00004818 Load EUR/JPY prices.. 599 h, gap 25 h V 3.112 on Sat 26-09-12 22:55:55 (Zorro S Subscription) LookBack set to 300 bars
Can somebody please shed some light on this behaviour?
Thanks.
LoT
2
85
Read More
|
|
|
09/17/26 13:36
Here is the procedure I used with my Zorro S 3.11.2 installation for the Z12+ strategy.
1. Open the z.ini file that is in the Strategy folder. Please note this file name has a lower-case z. 2. Change MaxCapital to whatever you want. I did MaxCapital = 50000 (Starting value of an FXCM demo account). 3. Save the file as Z12+.ini to match the file name of the strategy. DO NOT just overwrite the old z.ini file and mind the capital Z and the + sign at the end. 4. Start Zorro and wait for the price data to be downloaded from the broker. Note that now the Capital slider has a maximum value of 50000.
Please note that sliding Capital to 50000 gets reset to 4000 during the download period, at least for Z12+ My understanding is that this is baked into the strategy in order to protect the capital available. Zorro will not just go and spend the 50000 immediately. How much it will invest in any particular trade is determined by the Z12+ algorithm.
I noticed that sliding Capital to 50000 AFTER downloading ends "sticks" until you restart Zorro. However, I am not sure of the effect of this, since it seems the algorithm (not the user) determines how much risk is taken etc.
Hope this helps!
1
484
Read More
|
|
|
09/16/26 21:31
When I do training of multiple strategies with optimization run, many html pages are popping-up automatically. Is there a way to avoid this without deleting the LOGFILE flag. I need the files but not showing up the html in the browser immediately.
Manual:"When training a strategy in Ascent mode with the LOGFILE flag set, Zorro shows the performance variance over all parameter ranges in parameter charts on a HTML page"
0
60
Read More
|
|
09/16/26 20:20
Working on my shooter scripts. Crazy Alien invasion themed XD https://youtu.be/RCQv9nd9ngkAliens are hard at work transforming the atmosphere.  Edit: a more defined face
5,544
34,178,826
Read More
|
|
|
09/16/26 09:28
Sometimes, visual representation enables solving high order problems more intuitively. With greater ease.
2
834
Read More
|
|
|
09/16/26 05:23
Yes its really funny trying to get this feeling of those games. But also very hard to get the drift feel good etc.
2
371
Read More
|
|
09/15/26 08:27
When considering a free or charitable cataract surgery programme, cost should not be the only consideration. We recommend checking: 1. Whether the facility has qualified ophthalmic professionals 2. Whether a proper preoperative examination is provided 3. Whether the surgical procedure and lens are clearly explained 4. Whether postoperative follow-up is available 5. Whether the programme clearly explains eligibility 6. Whether patients receive written postoperative instructions 7. Whether emergency or complication-management arrangements are available 8. Whether the hospital is an established government or recognized healthcare institution Quality and continuity of care remain essential even when treatment is provided free of charge. WHO's 2026 recommendations specifically emphasize quality across all stages of cataract surgery management.
0
51
Read More
|
|
|
09/13/26 13:20
No it is not working. it is not a blocking due to to heavy calculations, it is a Zorro internal drawing topic. As I said,Zorro is working and trading, but only a restart of the gui helps. Thats why I casked for a dedicated Zorro command to restart just the graphic interface without the interdependencies of the gui restart.
3
195
Read More
|
|
|
09/13/26 10:31
Thanks Andrew
Actually CPU and RAM are not overloaded, it is just a display topic and it is a recurring one. I will try your proposal. It could be also a Windows issue.
I will check.
Martin
3
195
Read More
|
|
|
09/11/26 08:01
I am running 10 Zorros parallel on a trading pc. Very often I have the effect that some Zorro Gui windows are blind (White, broken), but Zorro is running correctly. Is there a dedicated command to repaint the window (also between the TF action for a new bar)?
3
195
Read More
|
|
09/11/26 06:21
Nope, you'll have to move/rotate it by yourself. In action function of ENTITY a, you could create ENTITY b as it's parent. Then in while loop at the very end (after moving ENTITY a) you could check, if ENTITY b exists, and if it does, then move/rotate it with ENTITY a. action move_test()
{
my.parent = ent_create(CUBE_MDL, my.x, NULL);
while(my)
{
// move my
if(my.parent)
{
vec_set(my.parent.x, my.x);
vec_set(my.parent.pan, my.pan);
}
wait(1);
}
}
1
219
Read More
|
|
09/09/26 14:35
// BogieNN_v6_DualTFGNN.cpp
// ============================================================================
// Zorro S 3.11+ / Zorro64 / GPU LibTorch C++
//
// DUAL-TIMEFRAME DIRECT LIBTORCH GRAPH NEURAL NETWORK VERSION
//
// NO ZORRO MACHINE-LEARNING FUNCTIONS ARE USED.
//
// Specifically, this file contains NO:
// any Zorro machine-learning training or prediction interface
//
// Zorro is used only for:
// - market/history simulation,
// - indicators and price series,
// - WFO scheduling,
// - Train/Test/Trade mode,
// - trade execution and account management.
//
// LibTorch directly owns:
// - training sample collection,
// - class/direction balancing,
// - GPU training,
// - model validation / early stopping,
// - WFO model persistence,
// - model loading,
// - graph inference,
// - directional output,
// - learned market-state classification.
//
// WFO lifecycle used by this script:
// [Train]
// Every WFO training cycle is a separate Zorro simulation run.
// Samples are collected bar-by-bar.
// At EXITRUN:
// LibTorch trains the GNN.
// Model is saved to Data\BogieGNN_Direct_WFOxx.pt.
//
// [Test]
// WFOCycle tells us the current OOS test segment.
// The corresponding .pt model is loaded directly by LibTorch.
// Predictions are made directly with model->forward().
//
// [Trade]
// The last trained WFO model is loaded.
//
// Five graph nodes:
// 0 Trend specialist
// 1 Mean-reversion specialist
// 2 Hybrid-trend specialist
// 3 Hybrid-mean specialist
// 4 Market-state specialist
//
// 5 nodes x 8 features = 40 graph signals.
//
// ============================================================================
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef BOGIE_ENABLE_CUDA
#define BOGIE_ENABLE_CUDA 1
#endif
#include <torch/torch.h>
#include <torch/serialize.h>
#if BOGIE_ENABLE_CUDA
#include <torch/cuda.h>
#endif
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
// LibTorch owns namespace `at`; Zorro declares a global function named `at`.
// Preserve the working include arrangement from the confirmed Zorro64 build.
#define at zorro_at
#include <zorro.h>
#undef at
// ============================================================================
// USER SETTINGS
// ============================================================================
cstr BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 5;
int HourlyTimeFrameFactor = 12;
// WFO scheduling is still provided by Zorro.
// It does NOT perform any neural training.
int WFOCycles = 8;
int WFOTrainPercent = 85;
// One GPU should normally be controlled by one training Zorro process.
// Keep WFO GPU training sequential unless you deliberately distribute cycles
// over separate GPUs yourself.
int DirectGPUTrainingCores = 1;
// State-dependent future horizons.
int TrendPredictionHorizonBars = 12;
int MeanPredictionHorizonBars = 4;
int NeutralPredictionHorizonBars = 6;
int MaxPredictionHorizonBars = 12;
// Direction score thresholds; GNN output is scaled to approximately -100..100.
var TrendConfidenceThreshold = 20.0;
var MeanConfidenceThreshold = 20.0;
var NeutralConfidenceThreshold = 35.0;
// Learned state probability required for specialist authority.
var StateProbabilityMin = 0.50;
int AllowNeutralTrades = 0;
int RequireDIConfirmation = 1;
// ------------------------ Trend specialist parameters -------------------------
int NocPeriod = 12;
int EmaPeriod = 5;
int ATRPeriod = 14;
int ADXPeriod = 14;
int AroonPeriod = 25;
int MMIPeriod = 100;
var TrendStandaloneADXMin = 20.0;
var TrendStandaloneMMIMax = 65.0;
var TrendStopATRMult = 2.5;
var TrendTrailATRMult = 3.0;
// --------------------- Mean-reversion specialist parameters -------------------
int RSIPeriod = 14;
int BBPeriod = 20;
int MeanPeriod = 20;
var MeanStandaloneMMIMin = 58.0;
var MeanStandaloneADXMax = 28.0;
var MeanLongExtremeStandalone = -0.45;
var MeanShortExtremeStandalone = 0.45;
var MeanLongRSIMaxStandalone = 40.0;
var MeanShortRSIMinStandalone = 60.0;
var MeanStopATRMult = 1.6;
// -------------------------- Hybrid state parameters ---------------------------
var HybridTrendADXMin = 25.0;
var HybridTrendMMIMax = 64.0;
var HybridMeanADXMax = 20.0;
var HybridMeanMMIMin = 60.0;
var HybridMeanLongExtreme = -0.45;
var HybridMeanShortExtreme = 0.45;
var HybridMeanLongRSIMax = 40.0;
var HybridMeanShortRSIMin = 60.0;
var RegimeScaleADX = 15.0;
var RegimeScaleMMI = 15.0;
// ------------------------------ Trade settings --------------------------------
var NeutralStopATRMult = 2.0;
var TrailGuardPips = 0.5;
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
// MQL4 weekday numbering.
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
// ----------------------------- Target settings --------------------------------
// Continuous target:
// (future price - current price) / (TargetATRScale * ATR)
// clipped to -1..+1.
var TargetATRScale = 2.0;
// --------------------------- LibTorch hyperparameters --------------------------
int GNNHidden = 32;
int GNNEpochs = 80;
int GNNBatchSize = 128;
int GNNPatience = 12;
int GNNValidationPercent = 15;
var GNNLearningRate = 0.001;
var GNNWeightDecay = 0.0001;
// Shared graph learns direction + current market state.
var GNNStateLossWeight = 0.20;
// Explicit replacement for Zorro's old +BALANCED behavior.
// 1 = inverse-frequency weighting of positive/negative direction samples.
int UseBalancedDirectionLoss = 1;
// CPU workers used by LibTorch around GPU operations.
int LibTorchThreads = 2;
// Minimum number of samples required to train a cycle.
int MinTrainingSamples = 200;
// The fast network times entries; the slow network contributes context.
var GNN5MWeight = 0.60;
var GNN1HWeight = 0.40;
int RequireDualTFDirectionAgreement = 1;
// ------------------------------- Model files ----------------------------------
// Relative to the Zorro root folder.
cstr DirectModelPrefix5M = "Data\\BogieGNN_5M_WFO";
cstr DirectModelPrefix1H = "Data\\BogieGNN_1H_WFO";
// ============================================================================
// GRAPH DIMENSIONS
// ============================================================================
static const int GRAPH_NODE_COUNT = 5;
static const int GRAPH_NODE_FEATURES = 8;
static const int GRAPH_SIGNAL_COUNT = 40;
// ============================================================================
// PREDICTION DIAGNOSTICS
// ============================================================================
var GNNStateTrendProbability = 0.0;
var GNNStateMeanProbability = 0.0;
var GNNStateNeutralProbability = 1.0;
var GNNNodeTrendAuthority = 0.0;
var GNNNodeMeanAuthority = 0.0;
var GNNNodeHybridTrendAuthority = 0.0;
var GNNNodeHybridMeanAuthority = 0.0;
var GNNNodeStateAuthority = 0.0;
var GNNScore5M = 0.0;
var GNNScore1H = 0.0;
// ============================================================================
// DIRECT LIBTORCH GLOBAL STATE
// ============================================================================
static torch::Device GNNDevice(torch::kCPU);
#if BOGIE_ENABLE_CUDA
static HMODULE GTorchCudaModule = 0;
#endif
class GraphAttentionBlockImpl;
class BogieGraphNetImpl;
struct DirectGNNContext
{
const char* Label;
const char* ModelPrefix;
int SeedOffset;
std::shared_ptr<BogieGraphNetImpl> Model;
std::vector<float> TrainX;
std::vector<float> TrainY;
std::vector<int64_t> TrainState;
int LoadedCycle;
DirectGNNContext(
const char* ContextLabel,
const char* Prefix,
int Seed)
:
Label(ContextLabel),
ModelPrefix(Prefix),
SeedOffset(Seed),
LoadedCycle(0)
{
}
};
static DirectGNNContext GNN5M(
"5M",
DirectModelPrefix5M,
5000);
static DirectGNNContext GNN1H(
"1H",
DirectModelPrefix1H,
10000);
static int GNNLibTorchReady = 0;
// ============================================================================
// GRAPH ATTENTION LAYER
// ============================================================================
class GraphAttentionBlockImpl : public torch::nn::Module
{
public:
torch::nn::Linear Q{nullptr};
torch::nn::Linear K{nullptr};
torch::nn::Linear V{nullptr};
torch::nn::Linear Out{nullptr};
torch::nn::LayerNorm Norm{nullptr};
torch::Tensor EdgeBias;
int Hidden = 0;
GraphAttentionBlockImpl(int HiddenSize)
{
Hidden = HiddenSize;
Q = register_module(
"q",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
K = register_module(
"k",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
V = register_module(
"v",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
Out = register_module(
"out",
torch::nn::Linear(Hidden,Hidden));
Norm = register_module(
"norm",
torch::nn::LayerNorm(
torch::nn::LayerNormOptions(
std::vector<int64_t>{Hidden})));
// Initial graph topology:
// Trend <-> HybridTrend <-> State
// Mean <-> HybridMean <-> State
//
// All entries remain trainable.
std::vector<float> InitialBias = {
0.50f,-0.75f, 0.75f,-0.75f, 1.00f,
-0.75f, 0.50f,-0.75f, 0.75f, 1.00f,
0.75f,-0.50f, 0.50f,-0.25f, 1.00f,
-0.50f, 0.75f,-0.25f, 0.50f, 1.00f,
1.00f, 1.00f, 1.00f, 1.00f, 0.50f
};
EdgeBias =
torch::tensor(
InitialBias,
torch::TensorOptions().dtype(torch::kFloat32))
.reshape({GRAPH_NODE_COUNT,GRAPH_NODE_COUNT});
EdgeBias =
register_parameter(
"edge_bias",
EdgeBias);
}
std::pair<torch::Tensor,torch::Tensor> forward(torch::Tensor H)
{
torch::Tensor Query;
torch::Tensor Key;
torch::Tensor Value;
torch::Tensor Logits;
torch::Tensor Attention;
torch::Tensor Messages;
torch::Tensor Updated;
Query = Q->forward(H);
Key = K->forward(H);
Value = V->forward(H);
Logits =
torch::matmul(
Query,
Key.transpose(1,2));
Logits =
Logits/
std::sqrt((double)Hidden);
Logits =
Logits+
EdgeBias.unsqueeze(0);
Attention =
torch::softmax(
Logits,
-1);
Messages =
torch::matmul(
Attention,
Value);
Updated =
torch::relu(
Out->forward(Messages));
Updated =
Norm->forward(
H+Updated);
return std::make_pair(
Updated,
Attention);
}
};
// ============================================================================
// FIVE-NODE GRAPH NETWORK
// ============================================================================
class BogieGraphNetImpl : public torch::nn::Module
{
public:
torch::nn::Linear TrendEncoder{nullptr};
torch::nn::Linear MeanEncoder{nullptr};
torch::nn::Linear HybridTrendEncoder{nullptr};
torch::nn::Linear HybridMeanEncoder{nullptr};
torch::nn::Linear StateEncoder{nullptr};
torch::Tensor NodeEmbedding;
std::shared_ptr<GraphAttentionBlockImpl> Graph1;
std::shared_ptr<GraphAttentionBlockImpl> Graph2;
torch::nn::Linear PoolGate{nullptr};
torch::nn::Sequential DirectionHead;
torch::nn::Sequential StateHead;
int Hidden = 0;
BogieGraphNetImpl(int HiddenSize = 32)
{
Hidden = HiddenSize;
TrendEncoder =
register_module(
"trend_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
MeanEncoder =
register_module(
"mean_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
HybridTrendEncoder =
register_module(
"hybrid_trend_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
HybridMeanEncoder =
register_module(
"hybrid_mean_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
StateEncoder =
register_module(
"state_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
NodeEmbedding =
register_parameter(
"node_embedding",
0.05*
torch::randn(
{GRAPH_NODE_COUNT,Hidden},
torch::TensorOptions().dtype(torch::kFloat32)));
Graph1 =
register_module(
"graph1",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
Graph2 =
register_module(
"graph2",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
PoolGate =
register_module(
"pool_gate",
torch::nn::Linear(
Hidden,
1));
DirectionHead =
register_module(
"direction_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,32),
torch::nn::ReLU(),
torch::nn::Dropout(0.10),
torch::nn::Linear(32,16),
torch::nn::ReLU(),
torch::nn::Linear(16,1),
torch::nn::Tanh()));
StateHead =
register_module(
"state_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,16),
torch::nn::ReLU(),
torch::nn::Linear(16,3)));
}
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> forward(torch::Tensor X)
{
torch::Tensor Nodes;
torch::Tensor TrendNode;
torch::Tensor MeanNode;
torch::Tensor HybridTrendNode;
torch::Tensor HybridMeanNode;
torch::Tensor StateNode;
torch::Tensor H;
std::pair<torch::Tensor,torch::Tensor> G1;
std::pair<torch::Tensor,torch::Tensor> G2;
torch::Tensor PoolLogits;
torch::Tensor PoolWeights;
torch::Tensor GraphState;
torch::Tensor Direction;
torch::Tensor StateLogits;
if(X.dim() == 1)
X = X.unsqueeze(0);
Nodes =
X.reshape(
{
X.size(0),
GRAPH_NODE_COUNT,
GRAPH_NODE_FEATURES
});
TrendNode =
torch::relu(
TrendEncoder->forward(
Nodes.select(1,0)));
MeanNode =
torch::relu(
MeanEncoder->forward(
Nodes.select(1,1)));
HybridTrendNode =
torch::relu(
HybridTrendEncoder->forward(
Nodes.select(1,2)));
HybridMeanNode =
torch::relu(
HybridMeanEncoder->forward(
Nodes.select(1,3)));
StateNode =
torch::relu(
StateEncoder->forward(
Nodes.select(1,4)));
H =
torch::stack(
{
TrendNode,
MeanNode,
HybridTrendNode,
HybridMeanNode,
StateNode
},
1);
H =
H+
NodeEmbedding.unsqueeze(0);
G1 = Graph1->forward(H);
H = G1.first;
G2 = Graph2->forward(H);
H = G2.first;
PoolLogits =
PoolGate->forward(H)
.squeeze(-1);
PoolWeights =
torch::softmax(
PoolLogits,
-1);
GraphState =
torch::sum(
PoolWeights.unsqueeze(-1)*H,
1);
Direction =
DirectionHead->forward(GraphState)
.squeeze(-1);
StateLogits =
StateHead->forward(
GraphState);
return std::make_tuple(
Direction,
StateLogits,
PoolWeights);
}
};
// ============================================================================
// LIBTORCH INITIALIZATION
// ============================================================================
static void ClearDirectTrainingData(
DirectGNNContext& Context)
{
Context.TrainX.clear();
Context.TrainY.clear();
Context.TrainState.clear();
}
static int InitializeDirectLibTorch()
{
#if BOGIE_ENABLE_CUDA
// Register CUDA hooks before the first LibTorch API initializes its context.
if(!GTorchCudaModule)
GTorchCudaModule =
LoadLibraryA(
"torch_cuda.dll");
if(!GTorchCudaModule)
{
printf(
"\nDirect GNN: torch_cuda.dll load failed (Windows error %lu)",
GetLastError());
}
#endif
if(LibTorchThreads < 1)
LibTorchThreads = 1;
torch::manual_seed(365);
torch::set_num_threads(
LibTorchThreads);
#if BOGIE_ENABLE_CUDA
if(torch::cuda::is_available())
{
GNNDevice =
torch::Device(
torch::kCUDA);
printf(
"\nDirect LibTorch GNN initialized on CUDA");
}
else
{
GNNDevice =
torch::Device(
torch::kCPU);
printf(
"\nDirect LibTorch GNN: CUDA unavailable; using CPU");
}
#else
GNNDevice =
torch::Device(
torch::kCPU);
printf(
"\nDirect LibTorch GNN initialized on CPU");
#endif
GNN5M.Model.reset();
GNN1H.Model.reset();
GNN5M.LoadedCycle = 0;
GNN1H.LoadedCycle = 0;
ClearDirectTrainingData(GNN5M);
ClearDirectTrainingData(GNN1H);
GNNLibTorchReady = 1;
return 1;
}
static std::shared_ptr<BogieGraphNetImpl> CreateDirectModel(
int SeedOffset)
{
std::shared_ptr<BogieGraphNetImpl> Net;
torch::manual_seed(
365+
SeedOffset);
Net =
std::make_shared<BogieGraphNetImpl>(
GNNHidden);
Net->to(
GNNDevice);
return Net;
}
// ============================================================================
// DIRECT TRAINING SAMPLE COLLECTION
// ============================================================================
static void AddDirectTrainingSample(
DirectGNNContext& Context,
const var* Signals,
var Target,
int TrainingState)
{
int i;
int64_t StateLabel;
if(!Signals)
return;
for(i=0; i<GRAPH_SIGNAL_COUNT; i++)
{
var V = Signals[i];
if(V > 1.0)
V = 1.0;
if(V < -1.0)
V = -1.0;
Context.TrainX.push_back(
(float)V);
}
if(Target > 1.0)
Target = 1.0;
if(Target < -1.0)
Target = -1.0;
Context.TrainY.push_back(
(float)Target);
// 0 = trend
// 1 = mean reversion
// 2 = neutral
StateLabel = 2;
if(TrainingState == 1)
StateLabel = 0;
else if(TrainingState == -1)
StateLabel = 1;
Context.TrainState.push_back(
StateLabel);
}
// ============================================================================
// MODEL PATHS
// ============================================================================
static std::string DirectModelPath(
const DirectGNNContext& Context,
int Cycle)
{
std::ostringstream Path;
Path <<
Context.ModelPrefix;
if(Cycle < 10)
Path << "0";
Path <<
Cycle <<
".pt";
return Path.str();
}
static int DirectFileExists(const std::string& Path)
{
std::ifstream File(
Path.c_str(),
std::ios::binary);
if(File.good())
return 1;
return 0;
}
// ============================================================================
// BEST-MODEL MEMORY SNAPSHOT
// ============================================================================
static std::string DirectModelToMemory(
std::shared_ptr<BogieGraphNetImpl> Net)
{
torch::serialize::OutputArchive Archive;
std::ostringstream Stream(
std::ios::out |
std::ios::binary);
Net->save(
Archive);
Archive.save_to(
Stream);
return Stream.str();
}
static void DirectModelFromMemory(
std::shared_ptr<BogieGraphNetImpl> Net,
const std::string& Blob)
{
torch::serialize::InputArchive Archive;
std::istringstream Stream(
Blob,
std::ios::in |
std::ios::binary);
Archive.load_from(
Stream,
GNNDevice);
Net->load(
Archive);
}
// ============================================================================
// DIRECT MODEL SAVE / LOAD
// ============================================================================
static int SaveDirectModel(
DirectGNNContext& Context,
std::shared_ptr<BogieGraphNetImpl> Net,
int Cycle)
{
torch::serialize::OutputArchive Archive;
std::string Path;
if(!Net)
return 0;
Path =
DirectModelPath(
Context,
Cycle);
try
{
Net->save(
Archive);
Archive.save_to(
Path);
}
catch(const c10::Error& E)
{
printf(
"\nDirect %s GNN SAVE ERROR cycle %i: %s",
Context.Label,
Cycle,
E.what());
return 0;
}
catch(const std::exception& E)
{
printf(
"\nDirect %s GNN SAVE ERROR cycle %i: %s",
Context.Label,
Cycle,
E.what());
return 0;
}
printf(
"\nDirect %s GNN saved WFO cycle %i -> %s",
Context.Label,
Cycle,
Path.c_str());
return 1;
}
static int LoadDirectModel(
DirectGNNContext& Context,
int Cycle)
{
torch::serialize::InputArchive Archive;
std::shared_ptr<BogieGraphNetImpl> Net;
std::string Path;
Path =
DirectModelPath(
Context,
Cycle);
if(!DirectFileExists(Path))
{
printf(
"\nDirect %s GNN LOAD ERROR: model file not found: %s",
Context.Label,
Path.c_str());
return 0;
}
try
{
Archive.load_from(
Path,
GNNDevice);
Net =
CreateDirectModel(
Cycle+
Context.SeedOffset);
Net->load(
Archive);
Net->to(
GNNDevice);
Net->eval();
}
catch(const c10::Error& E)
{
printf(
"\nDirect %s GNN LOAD ERROR cycle %i: %s",
Context.Label,
Cycle,
E.what());
return 0;
}
catch(const std::exception& E)
{
printf(
"\nDirect %s GNN LOAD ERROR cycle %i: %s",
Context.Label,
Cycle,
E.what());
return 0;
}
Context.Model = Net;
Context.LoadedCycle = Cycle;
printf(
"\nDirect %s GNN loaded WFO cycle %i from %s",
Context.Label,
Cycle,
Path.c_str());
return 1;
}
static int DesiredDirectModelCycle()
{
int Cycle;
Cycle = WFOCycle;
// In live mode WFOCycle can be 0; use the last trained cycle.
if(Cycle <= 0)
{
Cycle = WFOCycles;
if(Cycle < 0)
Cycle = -Cycle;
}
if(Cycle <= 0)
Cycle = 1;
return Cycle;
}
static int EnsureDirectModelLoaded(
DirectGNNContext& Context)
{
int Cycle;
Cycle =
DesiredDirectModelCycle();
if(
Context.Model &&
Context.LoadedCycle == Cycle)
{
return 1;
}
return LoadDirectModel(
Context,
Cycle);
}
// ============================================================================
// DIRECT TRAINING
// ============================================================================
static var TrainDirectGNNCycle(
DirectGNNContext& Context,
int Cycle)
{
int Rows;
int ValidRows;
int TrainRows;
int Epoch;
int StaleEpochs;
int PositiveCount;
int NegativeCount;
int i;
double BestValidation;
std::string BestBlob;
std::vector<float> DirectionWeights;
torch::Tensor XAll;
torch::Tensor YAll;
torch::Tensor SAll;
torch::Tensor WAll;
torch::Tensor XTrain;
torch::Tensor YTrain;
torch::Tensor STrain;
torch::Tensor WTrain;
torch::Tensor XValid;
torch::Tensor YValid;
torch::Tensor SValid;
std::shared_ptr<BogieGraphNetImpl> Net;
Rows =
(int)Context.TrainY.size();
if(
Rows < MinTrainingSamples ||
(int)Context.TrainState.size() != Rows ||
(int)Context.TrainX.size() !=
Rows*GRAPH_SIGNAL_COUNT)
{
printf(
"\nDirect %s GNN TRAIN ERROR: cycle %i has %i usable rows",
Context.Label,
Cycle,
Rows);
return 0;
}
// ------------------------------------------------------------------------
// Explicit replacement for +BALANCED:
// calculate inverse-frequency weights by target sign.
// ------------------------------------------------------------------------
PositiveCount = 0;
NegativeCount = 0;
for(i=0; i<Rows; i++)
{
if(Context.TrainY[i] >= 0.0f)
PositiveCount++;
else
NegativeCount++;
}
DirectionWeights.resize(
Rows,
1.0f);
if(
UseBalancedDirectionLoss &&
PositiveCount > 0 &&
NegativeCount > 0)
{
float PositiveWeight;
float NegativeWeight;
PositiveWeight =
(float)Rows/
(2.0f*
(float)PositiveCount);
NegativeWeight =
(float)Rows/
(2.0f*
(float)NegativeCount);
for(i=0; i<Rows; i++)
{
if(Context.TrainY[i] >= 0.0f)
{
DirectionWeights[i] =
PositiveWeight;
}
else
{
DirectionWeights[i] =
NegativeWeight;
}
}
}
// ------------------------------------------------------------------------
// Copy direct C++ vectors into LibTorch tensors.
// ------------------------------------------------------------------------
XAll =
torch::from_blob(
Context.TrainX.data(),
{
Rows,
GRAPH_SIGNAL_COUNT
},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
YAll =
torch::from_blob(
Context.TrainY.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
SAll =
torch::from_blob(
Context.TrainState.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kInt64))
.clone()
.to(GNNDevice);
WAll =
torch::from_blob(
DirectionWeights.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
// Chronological validation block.
ValidRows =
Rows*
GNNValidationPercent/
100;
if(ValidRows < 1)
ValidRows = 1;
if(ValidRows > Rows/3)
ValidRows = Rows/3;
TrainRows =
Rows-
ValidRows;
XTrain =
XAll.narrow(
0,
0,
TrainRows);
YTrain =
YAll.narrow(
0,
0,
TrainRows);
STrain =
SAll.narrow(
0,
0,
TrainRows);
WTrain =
WAll.narrow(
0,
0,
TrainRows);
XValid =
XAll.narrow(
0,
TrainRows,
ValidRows);
YValid =
YAll.narrow(
0,
TrainRows,
ValidRows);
SValid =
SAll.narrow(
0,
TrainRows,
ValidRows);
Net =
CreateDirectModel(
Cycle+
Context.SeedOffset);
torch::optim::AdamW Optimizer(
Net->parameters(),
torch::optim::AdamWOptions(
GNNLearningRate)
.weight_decay(
GNNWeightDecay));
torch::nn::CrossEntropyLoss StateLoss;
BestValidation =
std::numeric_limits<double>::infinity();
StaleEpochs = 0;
// ------------------------------------------------------------------------
// GPU training.
// ------------------------------------------------------------------------
for(Epoch=0; Epoch<GNNEpochs; Epoch++)
{
torch::Tensor Permutation;
int Start;
double EpochLoss;
int Batches;
if(!wait(0))
return 0;
EpochLoss = 0.0;
Batches = 0;
Net->train();
Permutation =
torch::randperm(
TrainRows,
torch::TensorOptions()
.dtype(torch::kInt64)
.device(GNNDevice));
for(
Start=0;
Start<TrainRows;
Start += GNNBatchSize)
{
int Count;
torch::Tensor Index;
torch::Tensor XB;
torch::Tensor YB;
torch::Tensor SB;
torch::Tensor WB;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor DirectionPred;
torch::Tensor StateLogits;
torch::Tensor DirectionError;
torch::Tensor LossDirection;
torch::Tensor LossState;
torch::Tensor Loss;
Count =
GNNBatchSize;
if(
Start+
Count >
TrainRows)
{
Count =
TrainRows-
Start;
}
Index =
Permutation.narrow(
0,
Start,
Count);
XB =
XTrain.index_select(
0,
Index);
YB =
YTrain.index_select(
0,
Index);
SB =
STrain.index_select(
0,
Index);
WB =
WTrain.index_select(
0,
Index);
Output =
Net->forward(
XB);
DirectionPred =
std::get<0>(
Output);
StateLogits =
std::get<1>(
Output);
DirectionError =
DirectionPred-
YB;
LossDirection =
torch::mean(
DirectionError*
DirectionError*
WB);
LossState =
StateLoss(
StateLogits,
SB);
Loss =
LossDirection+
GNNStateLossWeight*
LossState;
Optimizer.zero_grad();
Loss.backward();
torch::nn::utils::clip_grad_norm_(
Net->parameters(),
2.0);
Optimizer.step();
EpochLoss +=
Loss.item<double>();
Batches++;
}
// --------------------------------------------------------------------
// Validation.
// --------------------------------------------------------------------
Net->eval();
{
torch::NoGradGuard NoGrad;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> ValidOutput;
torch::Tensor ValidDirection;
torch::Tensor ValidStateLogits;
torch::Tensor ValidError;
torch::Tensor ValidDirectionLoss;
torch::Tensor ValidStateLoss;
torch::Tensor ValidLoss;
double Validation;
ValidOutput =
Net->forward(
XValid);
ValidDirection =
std::get<0>(
ValidOutput);
ValidStateLogits =
std::get<1>(
ValidOutput);
ValidError =
ValidDirection-
YValid;
ValidDirectionLoss =
torch::mean(
ValidError*
ValidError);
ValidStateLoss =
StateLoss(
ValidStateLogits,
SValid);
ValidLoss =
ValidDirectionLoss+
GNNStateLossWeight*
ValidStateLoss;
Validation =
ValidLoss.item<double>();
if(
Validation <
BestValidation-
0.000001)
{
BestValidation =
Validation;
BestBlob =
DirectModelToMemory(
Net);
StaleEpochs = 0;
}
else
{
StaleEpochs++;
}
if(
Epoch % 10 == 0 ||
Epoch ==
GNNEpochs-1)
{
double AverageTrainLoss;
AverageTrainLoss = 0.0;
if(Batches > 0)
{
AverageTrainLoss =
EpochLoss/
Batches;
}
printf(
"\nDirect %s GNN WFO %i epoch %i train %.6f valid %.6f",
Context.Label,
Cycle,
Epoch,
AverageTrainLoss,
Validation);
}
}
if(
StaleEpochs >=
GNNPatience)
{
printf(
"\nDirect %s GNN WFO %i early stop at epoch %i",
Context.Label,
Cycle,
Epoch);
break;
}
}
if(!BestBlob.empty())
{
DirectModelFromMemory(
Net,
BestBlob);
}
Net->to(
GNNDevice);
Net->eval();
Context.Model = Net;
Context.LoadedCycle = Cycle;
if(!SaveDirectModel(
Context,
Net,
Cycle))
{
return 0;
}
printf(
"\nDirect %s GNN WFO %i trained with %i rows; best validation %.6f; positive %i negative %i",
Context.Label,
Cycle,
Rows,
BestValidation,
PositiveCount,
NegativeCount);
if(BestValidation <= 0.0)
return 0.0001;
return
BestValidation*
100.0;
}
// ============================================================================
// DIRECT INFERENCE
// ============================================================================
struct GNNPrediction
{
var Score;
var TrendProbability;
var MeanProbability;
var NeutralProbability;
var TrendAuthority;
var MeanAuthority;
var HybridTrendAuthority;
var HybridMeanAuthority;
var StateAuthority;
};
static var PredictDirectGNN(
DirectGNNContext& Context,
const var* Signals,
GNNPrediction& Result)
{
std::vector<float> Input;
torch::Tensor X;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor Direction;
torch::Tensor StateLogits;
torch::Tensor PoolWeights;
torch::Tensor StateProb;
int i;
double Score;
Result = GNNPrediction{};
if(
!Context.Model ||
!Signals)
{
return 0.0;
}
Input.resize(
GRAPH_SIGNAL_COUNT);
for(i=0; i<GRAPH_SIGNAL_COUNT; i++)
{
var V = Signals[i];
if(V > 1.0)
V = 1.0;
if(V < -1.0)
V = -1.0;
Input[i] =
(float)V;
}
X =
torch::from_blob(
Input.data(),
{
1,
GRAPH_SIGNAL_COUNT
},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
Context.Model->eval();
{
torch::NoGradGuard NoGrad;
Output =
Context.Model->forward(
X);
Direction =
std::get<0>(
Output);
StateLogits =
std::get<1>(
Output);
PoolWeights =
std::get<2>(
Output);
StateProb =
torch::softmax(
StateLogits,
1);
Score =
Direction
.to(torch::kCPU)
.item<float>()*
100.0;
Result.TrendProbability =
StateProb[0][0]
.to(torch::kCPU)
.item<float>();
Result.MeanProbability =
StateProb[0][1]
.to(torch::kCPU)
.item<float>();
Result.NeutralProbability =
StateProb[0][2]
.to(torch::kCPU)
.item<float>();
Result.TrendAuthority =
PoolWeights[0][0]
.to(torch::kCPU)
.item<float>();
Result.MeanAuthority =
PoolWeights[0][1]
.to(torch::kCPU)
.item<float>();
Result.HybridTrendAuthority =
PoolWeights[0][2]
.to(torch::kCPU)
.item<float>();
Result.HybridMeanAuthority =
PoolWeights[0][3]
.to(torch::kCPU)
.item<float>();
Result.StateAuthority =
PoolWeights[0][4]
.to(torch::kCPU)
.item<float>();
}
Result.Score = Score;
return Score;
}
// ============================================================================
// BOGIE / MARKET FEATURES
// ============================================================================
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest =
HH(
NocPeriod,
0);
Lowest =
LL(
NocPeriod,
0);
Range =
Highest-
Lowest;
if(Range <= 0.0)
return 0.0;
Position =
2.0*
(priceC(0)-Lowest)/
Range-
1.0;
return
clamp(
Position,
-1.0,
1.0);
}
// Return 0..1 causal trend authority.
var TrendAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A =
clamp(
(ADXNow-ADXThreshold)/
RegimeScaleADX,
0.0,
1.0);
B =
clamp(
(MMIThreshold-MMINow)/
RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
// Return 0..1 causal mean-reversion authority.
var MeanAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A =
clamp(
(ADXThreshold-ADXNow)/
RegimeScaleADX,
0.0,
1.0);
B =
clamp(
(MMINow-MMIThreshold)/
RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
// ============================================================================
// BUILD 5 x 8 GRAPH
// ============================================================================
void BuildFiveNodeGraph(
var RawBogie,
var* SmoothSeries,
var ATRNow,
var PlusNow,
var MinusNow,
var ADXNow,
var AroonNow,
var MMINow,
var RSINow,
var BBOscNow,
var MeanNow,
var* Signals)
{
var ATRSafe;
var Momentum6;
var Momentum3;
var DISpread;
var MeanDeviation;
var TrendStandaloneAuth;
var MeanStandaloneAuth;
var HybridTrendAuth;
var HybridMeanAuth;
var StateSigned;
var StateStrength;
ATRSafe = ATRNow;
if(ATRSafe < PIP)
ATRSafe = PIP;
Momentum6 =
(priceC(0)-priceC(6))/
(3.0*ATRSafe);
Momentum3 =
(priceC(0)-priceC(3))/
(2.0*ATRSafe);
DISpread =
(PlusNow-MinusNow)/
100.0;
MeanDeviation =
(priceC(0)-MeanNow)/
(2.0*ATRSafe);
TrendStandaloneAuth =
TrendAuthority(
ADXNow,
MMINow,
TrendStandaloneADXMin,
TrendStandaloneMMIMax);
MeanStandaloneAuth =
MeanAuthority(
ADXNow,
MMINow,
MeanStandaloneADXMax,
MeanStandaloneMMIMin);
HybridTrendAuth =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
HybridMeanAuth =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
StateSigned =
HybridTrendAuth-
HybridMeanAuth;
StateStrength =
HybridTrendAuth;
if(
HybridMeanAuth >
StateStrength)
{
StateStrength =
HybridMeanAuth;
}
// Node 0: standalone TrendML.
Signals[0] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[1] =
clamp(
2.0*
(
SmoothSeries[0]-
SmoothSeries[1]
),
-1.0,
1.0);
Signals[2] =
clamp(
SmoothSeries[0]-
SmoothSeries[3],
-1.0,
1.0);
Signals[3] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[4] =
clamp(
DISpread,
-1.0,
1.0);
Signals[5] =
clamp(
(ADXNow-25.0)/
25.0,
-1.0,
1.0);
Signals[6] =
clamp(
AroonNow/
100.0,
-1.0,
1.0);
Signals[7] =
clamp(
(75.0-MMINow)/
25.0,
-1.0,
1.0);
// Node 1: standalone MeanReversionML.
Signals[8] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[9] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[10] =
clamp(
(RSINow-50.0)/
50.0,
-1.0,
1.0);
Signals[11] =
clamp(
(BBOscNow-50.0)/
50.0,
-1.0,
1.0);
Signals[12] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[13] =
clamp(
Momentum3,
-1.0,
1.0);
Signals[14] =
clamp(
(MMINow-50.0)/
25.0,
-1.0,
1.0);
Signals[15] =
clamp(
(25.0-ADXNow)/
25.0,
-1.0,
1.0);
// Node 2: Hybrid trend specialist.
Signals[16] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[17] =
clamp(
2.0*
(
SmoothSeries[0]-
SmoothSeries[1]
),
-1.0,
1.0);
Signals[18] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[19] =
clamp(
DISpread,
-1.0,
1.0);
Signals[20] =
clamp(
AroonNow/
100.0,
-1.0,
1.0);
Signals[21] =
clamp(
2.0*
HybridTrendAuth-
1.0,
-1.0,
1.0);
Signals[22] =
clamp(
(ADXNow-
HybridTrendADXMin)/
25.0,
-1.0,
1.0);
Signals[23] =
clamp(
(HybridTrendMMIMax-
MMINow)/
25.0,
-1.0,
1.0);
// Node 3: Hybrid mean specialist.
Signals[24] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[25] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[26] =
clamp(
(RSINow-50.0)/
50.0,
-1.0,
1.0);
Signals[27] =
clamp(
(BBOscNow-50.0)/
50.0,
-1.0,
1.0);
Signals[28] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[29] =
clamp(
2.0*
HybridMeanAuth-
1.0,
-1.0,
1.0);
Signals[30] =
clamp(
(HybridMeanADXMax-
ADXNow)/
25.0,
-1.0,
1.0);
Signals[31] =
clamp(
(MMINow-
HybridMeanMMIMin)/
25.0,
-1.0,
1.0);
// Node 4: Market-state node.
Signals[32] =
clamp(
HybridTrendAuth,
-1.0,
1.0);
Signals[33] =
clamp(
HybridMeanAuth,
-1.0,
1.0);
Signals[34] =
clamp(
StateSigned,
-1.0,
1.0);
Signals[35] =
clamp(
StateStrength,
-1.0,
1.0);
Signals[36] =
clamp(
(ADXNow-25.0)/
25.0,
-1.0,
1.0);
Signals[37] =
clamp(
(MMINow-50.0)/
25.0,
-1.0,
1.0);
Signals[38] =
clamp(
DISpread,
-1.0,
1.0);
Signals[39] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
}
struct GraphFrameFeatures
{
var RawBogie;
var SmoothNow;
var ATRNow;
var PlusNow;
var MinusNow;
var ADXNow;
var AroonNow;
var MMINow;
var RSINow;
var BBOscNow;
var MeanNow;
var Signals[GRAPH_SIGNAL_COUNT];
};
void BuildCurrentFrameFeatures(
GraphFrameFeatures& Out)
{
var* PriceSeries;
var* RawSeries;
var* SmoothSeries;
PriceSeries =
series(
priceC(0),
256);
Out.RawBogie =
BogieRangePosition();
RawSeries =
series(
Out.RawBogie,
64);
Out.SmoothNow =
EMA(
RawSeries,
EmaPeriod);
SmoothSeries =
series(
Out.SmoothNow,
64);
Out.ATRNow = ATR(ATRPeriod);
Out.PlusNow = PlusDI(ADXPeriod);
Out.MinusNow = MinusDI(ADXPeriod);
Out.ADXNow = ADX(ADXPeriod);
Out.AroonNow = AroonOsc(AroonPeriod);
Out.MMINow = MMI(PriceSeries,MMIPeriod);
Out.RSINow = RSI(PriceSeries,RSIPeriod);
Out.BBOscNow =
BBOsc(
PriceSeries,
BBPeriod,
2.0,
MAType_SMA);
Out.MeanNow = EMA(PriceSeries,MeanPeriod);
BuildFiveNodeGraph(
Out.RawBogie,
SmoothSeries,
Out.ATRNow,
Out.PlusNow,
Out.MinusNow,
Out.ADXNow,
Out.AroonNow,
Out.MMINow,
Out.RSINow,
Out.BBOscNow,
Out.MeanNow,
Out.Signals);
}
// ============================================================================
// CAUSAL TRAINING STATE
// ============================================================================
int CausalTrainingState(
var ADXNow,
var MMINow)
{
var TrendState;
var MeanState;
TrendState =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
MeanState =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
if(
TrendState >= 0.35 &&
TrendState >=
MeanState+
0.10)
{
return 1;
}
if(
MeanState >= 0.35 &&
MeanState >=
TrendState+
0.10)
{
return -1;
}
return 0;
}
void CollectCurrentFrameTrainingSample(
DirectGNNContext& Context,
const GraphFrameFeatures& Features)
{
int TrainingState;
int TargetHorizon;
var TargetScale;
var Target;
TrainingState =
CausalTrainingState(
Features.ADXNow,
Features.MMINow);
TargetHorizon = NeutralPredictionHorizonBars;
if(TrainingState == 1)
TargetHorizon = TrendPredictionHorizonBars;
else if(TrainingState == -1)
TargetHorizon = MeanPredictionHorizonBars;
TargetScale =
TargetATRScale*
Features.ATRNow;
if(TargetScale < PIP)
TargetScale = PIP;
Target =
(
priceC(-TargetHorizon)-
priceC(0)
)/
TargetScale;
AddDirectTrainingSample(
Context,
Features.Signals,
clamp(Target,-1.0,1.0),
TrainingState);
}
// ============================================================================
// LEARNED GNN STATE
// ============================================================================
int LearnedGNNState()
{
if(
GNNStateTrendProbability >=
StateProbabilityMin &&
GNNStateTrendProbability >
GNNStateMeanProbability &&
GNNStateTrendProbability >
GNNStateNeutralProbability)
{
return 1;
}
if(
GNNStateMeanProbability >=
StateProbabilityMin &&
GNNStateMeanProbability >
GNNStateTrendProbability &&
GNNStateMeanProbability >
GNNStateNeutralProbability)
{
return -1;
}
return 0;
}
// ============================================================================
// CALENDAR
// ============================================================================
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow =
dow(0);
if(
DayNow ==
MQLDayToZorro(
NoTradeDay_1))
{
return 0;
}
if(
DayNow ==
MQLDayToZorro(
NoTradeDay_2))
{
return 0;
}
return 1;
}
// ============================================================================
// POSITION SIZING
// ============================================================================
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin =
Equity-
MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue =
FreeMargin*
RiskPercent/
100.0/
1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureTrendTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
TrendStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureMeanTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
MeanStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureNeutralTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
NeutralStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
// ============================================================================
// TREND TRAILING TMF
// ============================================================================
int GraphTrendTrailTMF(
var TrailDistance,
var GuardDistance)
{
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailDistance <= 0.0)
return 0;
if(TradeIsShort)
{
Candidate =
priceC(0)+
TrailDistance;
if(
TradeStopLimit >
Candidate+
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
else
{
Candidate =
priceC(0)-
TrailDistance;
if(
TradeStopLimit <
Candidate-
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
return 0;
}
// ============================================================================
// DIAGNOSTICS
// ============================================================================
void LogGraphEntry(
cstr Side,
int Regime,
var GNNScore,
var ADXNow,
var MMINow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | DirectGNN %.2f | State %i "
"| P(T) %.3f P(M) %.3f P(N) %.3f "
"| Nodes T %.3f M %.3f HT %.3f HM %.3f S %.3f "
"| ADX %.2f MMI %.2f | Amount %.3f",
Asset,
Bar,
Side,
GNNScore,
Regime,
GNNStateTrendProbability,
GNNStateMeanProbability,
GNNStateNeutralProbability,
GNNNodeTrendAuthority,
GNNNodeMeanAuthority,
GNNNodeHybridTrendAuthority,
GNNNodeHybridMeanAuthority,
GNNNodeStateAuthority,
ADXNow,
MMINow,
Amount);
}
// ============================================================================
// ZORRO STRATEGY
// ============================================================================
DLLFUNC void run()
{
var GNNScore;
var TrailDistance;
var GuardDistance;
GraphFrameFeatures Features5M = {};
GraphFrameFeatures Features1H = {};
GNNPrediction Prediction5M = {};
GNNPrediction Prediction1H = {};
int LearnedState;
int Allowed;
int LongSignal;
int ShortSignal;
int Cycle;
if(is(FIRSTINITRUN))
require(-3.11);
// ------------------------------------------------------------------------
// Zorro simulation / WFO configuration only.
// No Zorro machine-learning subsystem is enabled.
// ------------------------------------------------------------------------
if(is(INITRUN))
{
set(RECALCULATE);
set(TICKS);
set(LOGFILE);
set(OPENEND);
if(Train)
set(PEEK);
// GPU LibTorch training should normally be sequential.
if(Train)
NumCores =
DirectGPUTrainingCores;
if(!InitializeDirectLibTorch())
{
printf(
"\nDirect LibTorch initialization failed");
return;
}
}
BarPeriod =
StrategyBarPeriod;
// 300 hourly bars require 3600 five-minute base bars.
LookBack = 3600;
Capital = 10000;
StartDate =
BacktestStart;
EndDate =
BacktestEnd;
NumWFOCycles =
WFOCycles;
DataSplit =
WFOTrainPercent;
// This is a general Zorro WFO control, not a neural function.
// It prevents trading at the start of the OOS segment after future-peeking
// targets were used in the preceding training period.
DataHorizon =
MaxPredictionHorizonBars*
HourlyTimeFrameFactor;
asset(
(string)BogieAsset);
algo(
(string)"BogieDual");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
// ------------------------------------------------------------------------
// Direct training finalization.
//
// Zorro documents that each WFO training cycle has its own EXITRUN.
// Train LibTorch directly here at the end of the WFO training run.
// ------------------------------------------------------------------------
if(is(EXITRUN))
{
if(Train)
{
Cycle =
WFOCycle;
if(Cycle <= 0)
Cycle = 1;
TrainDirectGNNCycle(
GNN5M,
Cycle);
TrainDirectGNNCycle(
GNN1H,
Cycle);
}
GNN5M.Model.reset();
GNN1H.Model.reset();
GNN5M.LoadedCycle = 0;
GNN1H.LoadedCycle = 0;
return;
}
// Build independent feature graphs at 5M and synchronized 1H frames.
TimeFrame = 1;
BuildCurrentFrameFeatures(Features5M);
TimeFrame =
frameSync(
HourlyTimeFrameFactor);
BuildCurrentFrameFeatures(Features1H);
TimeFrame = 1;
if(is(LOOKBACK))
return;
if(Train)
{
TimeFrame = 1;
CollectCurrentFrameTrainingSample(
GNN5M,
Features5M);
// Store one hourly row at the synchronized hour boundary.
if(minute(0) == 0)
{
TimeFrame =
frameSync(
HourlyTimeFrameFactor);
CollectCurrentFrameTrainingSample(
GNN1H,
Features1H);
}
TimeFrame = 1;
return;
}
// ------------------------------------------------------------------------
// TEST / TRADE MODE:
// load the active WFO model and run direct LibTorch inference.
// ------------------------------------------------------------------------
if(!GNNLibTorchReady)
{
if(!InitializeDirectLibTorch())
return;
}
if(!EnsureDirectModelLoaded(GNN5M))
return;
if(!EnsureDirectModelLoaded(GNN1H))
return;
GNNScore5M =
PredictDirectGNN(
GNN5M,
Features5M.Signals,
Prediction5M);
GNNScore1H =
PredictDirectGNN(
GNN1H,
Features1H.Signals,
Prediction1H);
GNNScore =
GNN5MWeight*GNNScore5M+
GNN1HWeight*GNNScore1H;
if(
RequireDualTFDirectionAgreement &&
GNNScore5M*GNNScore1H <= 0.0)
{
GNNScore = 0.0;
}
GNNStateTrendProbability =
GNN5MWeight*Prediction5M.TrendProbability+
GNN1HWeight*Prediction1H.TrendProbability;
GNNStateMeanProbability =
GNN5MWeight*Prediction5M.MeanProbability+
GNN1HWeight*Prediction1H.MeanProbability;
GNNStateNeutralProbability =
GNN5MWeight*Prediction5M.NeutralProbability+
GNN1HWeight*Prediction1H.NeutralProbability;
GNNNodeTrendAuthority =
GNN5MWeight*Prediction5M.TrendAuthority+
GNN1HWeight*Prediction1H.TrendAuthority;
GNNNodeMeanAuthority =
GNN5MWeight*Prediction5M.MeanAuthority+
GNN1HWeight*Prediction1H.MeanAuthority;
GNNNodeHybridTrendAuthority =
GNN5MWeight*Prediction5M.HybridTrendAuthority+
GNN1HWeight*Prediction1H.HybridTrendAuthority;
GNNNodeHybridMeanAuthority =
GNN5MWeight*Prediction5M.HybridMeanAuthority+
GNN1HWeight*Prediction1H.HybridMeanAuthority;
GNNNodeStateAuthority =
GNN5MWeight*Prediction5M.StateAuthority+
GNN1HWeight*Prediction1H.StateAuthority;
LearnedState =
LearnedGNNState();
// ------------------------------------------------------------------------
// Diagnostic plots.
// ------------------------------------------------------------------------
plot(
"GNN Fused",
GNNScore,
NEW,
BLUE);
plot(
"GNN 5M",
GNNScore5M,
0,
GREEN);
plot(
"GNN 1H",
GNNScore1H,
0,
RED);
plot(
"P Trend",
100.0*
GNNStateTrendProbability,
0,
GREEN);
plot(
"P Mean",
-100.0*
GNNStateMeanProbability,
0,
RED);
plot(
"State Node",
100.0*
GNNNodeStateAuthority,
0,
BLACK);
// ------------------------------------------------------------------------
// Regime-aware mean-reversion exits.
// ------------------------------------------------------------------------
if(LearnedState == -1)
{
if(
NumOpenLong > 0 &&
priceC(0) >= Features5M.MeanNow)
{
exitLong();
return;
}
if(
NumOpenShort > 0 &&
priceC(0) <= Features5M.MeanNow)
{
exitShort();
return;
}
}
// ------------------------------------------------------------------------
// Direction + learned state -> trade signal.
// ------------------------------------------------------------------------
LongSignal = 0;
ShortSignal = 0;
if(LearnedState == 1)
{
if(
GNNScore >
TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
Features5M.PlusNow >
Features5M.MinusNow)
{
LongSignal = 1;
}
}
else if(
GNNScore <
-TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
Features5M.MinusNow >
Features5M.PlusNow)
{
ShortSignal = 1;
}
}
}
else if(LearnedState == -1)
{
if(
GNNScore >
MeanConfidenceThreshold &&
Features5M.SmoothNow <=
HybridMeanLongExtreme &&
Features5M.RSINow <=
HybridMeanLongRSIMax)
{
LongSignal = 1;
}
else if(
GNNScore <
-MeanConfidenceThreshold &&
Features5M.SmoothNow >=
HybridMeanShortExtreme &&
Features5M.RSINow >=
HybridMeanShortRSIMin)
{
ShortSignal = 1;
}
}
else if(AllowNeutralTrades)
{
if(
GNNScore >
NeutralConfidenceThreshold)
{
LongSignal = 1;
}
else if(
GNNScore <
-NeutralConfidenceThreshold)
{
ShortSignal = 1;
}
}
// ------------------------------------------------------------------------
// Calendar.
// ------------------------------------------------------------------------
Allowed =
TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
// ------------------------------------------------------------------------
// LONG.
// ------------------------------------------------------------------------
if(LongSignal)
{
if(NumOpenShort > 0)
{
exitShort();
return;
}
if(NumOpenLong == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
Features5M.ATRNow);
TrailDistance =
TrendTrailATRMult*
Features5M.ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterLong(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
Features5M.ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterLong();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
Features5M.ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterLong();
}
}
return;
}
// ------------------------------------------------------------------------
// SHORT.
// ------------------------------------------------------------------------
if(ShortSignal)
{
if(NumOpenLong > 0)
{
exitLong();
return;
}
if(NumOpenShort == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
Features5M.ATRNow);
TrailDistance =
TrendTrailATRMult*
Features5M.ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterShort(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
Features5M.ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterShort();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
Features5M.ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
Features5M.ADXNow,
Features5M.MMINow);
enterShort();
}
}
return;
}
}
258
93,969
Read More
|
|
09/09/26 13:58
// BogieNN_v5_DirectLibTorchGNN.cpp
// ============================================================================
// Zorro S 3.11+ / Zorro64 / GPU LibTorch C++
//
// DIRECT LIBTORCH GRAPH NEURAL NETWORK VERSION
//
// NO ZORRO MACHINE-LEARNING FUNCTIONS ARE USED.
//
// Specifically, this file contains NO:
// any Zorro machine-learning training or prediction interface
//
// Zorro is used only for:
// - market/history simulation,
// - indicators and price series,
// - WFO scheduling,
// - Train/Test/Trade mode,
// - trade execution and account management.
//
// LibTorch directly owns:
// - training sample collection,
// - class/direction balancing,
// - GPU training,
// - model validation / early stopping,
// - WFO model persistence,
// - model loading,
// - graph inference,
// - directional output,
// - learned market-state classification.
//
// WFO lifecycle used by this script:
// [Train]
// Every WFO training cycle is a separate Zorro simulation run.
// Samples are collected bar-by-bar.
// At EXITRUN:
// LibTorch trains the GNN.
// Model is saved to Data\BogieGNN_Direct_WFOxx.pt.
//
// [Test]
// WFOCycle tells us the current OOS test segment.
// The corresponding .pt model is loaded directly by LibTorch.
// Predictions are made directly with model->forward().
//
// [Trade]
// The last trained WFO model is loaded.
//
// Five graph nodes:
// 0 Trend specialist
// 1 Mean-reversion specialist
// 2 Hybrid-trend specialist
// 3 Hybrid-mean specialist
// 4 Market-state specialist
//
// 5 nodes x 8 features = 40 graph signals.
//
// ============================================================================
#ifndef NOMINMAX
#define NOMINMAX
#endif
#ifndef BOGIE_ENABLE_CUDA
#define BOGIE_ENABLE_CUDA 1
#endif
#include <torch/torch.h>
#include <torch/serialize.h>
#if BOGIE_ENABLE_CUDA
#include <torch/cuda.h>
#endif
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
// LibTorch owns namespace `at`; Zorro declares a global function named `at`.
// Preserve the working include arrangement from the confirmed Zorro64 build.
#define at zorro_at
#include <zorro.h>
#undef at
// ============================================================================
// USER SETTINGS
// ============================================================================
cstr BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 60;
// WFO scheduling is still provided by Zorro.
// It does NOT perform any neural training.
int WFOCycles = 8;
int WFOTrainPercent = 85;
// One GPU should normally be controlled by one training Zorro process.
// Keep WFO GPU training sequential unless you deliberately distribute cycles
// over separate GPUs yourself.
int DirectGPUTrainingCores = 1;
// State-dependent future horizons.
int TrendPredictionHorizonBars = 12;
int MeanPredictionHorizonBars = 4;
int NeutralPredictionHorizonBars = 6;
int MaxPredictionHorizonBars = 12;
// Direction score thresholds; GNN output is scaled to approximately -100..100.
var TrendConfidenceThreshold = 20.0;
var MeanConfidenceThreshold = 20.0;
var NeutralConfidenceThreshold = 35.0;
// Learned state probability required for specialist authority.
var StateProbabilityMin = 0.50;
int AllowNeutralTrades = 0;
int RequireDIConfirmation = 1;
// ------------------------ Trend specialist parameters -------------------------
int NocPeriod = 12;
int EmaPeriod = 5;
int ATRPeriod = 14;
int ADXPeriod = 14;
int AroonPeriod = 25;
int MMIPeriod = 100;
var TrendStandaloneADXMin = 20.0;
var TrendStandaloneMMIMax = 65.0;
var TrendStopATRMult = 2.5;
var TrendTrailATRMult = 3.0;
// --------------------- Mean-reversion specialist parameters -------------------
int RSIPeriod = 14;
int BBPeriod = 20;
int MeanPeriod = 20;
var MeanStandaloneMMIMin = 58.0;
var MeanStandaloneADXMax = 28.0;
var MeanLongExtremeStandalone = -0.45;
var MeanShortExtremeStandalone = 0.45;
var MeanLongRSIMaxStandalone = 40.0;
var MeanShortRSIMinStandalone = 60.0;
var MeanStopATRMult = 1.6;
// -------------------------- Hybrid state parameters ---------------------------
var HybridTrendADXMin = 25.0;
var HybridTrendMMIMax = 64.0;
var HybridMeanADXMax = 20.0;
var HybridMeanMMIMin = 60.0;
var HybridMeanLongExtreme = -0.45;
var HybridMeanShortExtreme = 0.45;
var HybridMeanLongRSIMax = 40.0;
var HybridMeanShortRSIMin = 60.0;
var RegimeScaleADX = 15.0;
var RegimeScaleMMI = 15.0;
// ------------------------------ Trade settings --------------------------------
var NeutralStopATRMult = 2.0;
var TrailGuardPips = 0.5;
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
// MQL4 weekday numbering.
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
// ----------------------------- Target settings --------------------------------
// Continuous target:
// (future price - current price) / (TargetATRScale * ATR)
// clipped to -1..+1.
var TargetATRScale = 2.0;
// --------------------------- LibTorch hyperparameters --------------------------
int GNNHidden = 32;
int GNNEpochs = 80;
int GNNBatchSize = 128;
int GNNPatience = 12;
int GNNValidationPercent = 15;
var GNNLearningRate = 0.001;
var GNNWeightDecay = 0.0001;
// Shared graph learns direction + current market state.
var GNNStateLossWeight = 0.20;
// Explicit replacement for Zorro's old +BALANCED behavior.
// 1 = inverse-frequency weighting of positive/negative direction samples.
int UseBalancedDirectionLoss = 1;
// CPU workers used by LibTorch around GPU operations.
int LibTorchThreads = 2;
// Minimum number of samples required to train a cycle.
int MinTrainingSamples = 200;
// ------------------------------- Model files ----------------------------------
// Relative to the Zorro root folder.
cstr DirectModelPrefix = "Data\\BogieGNN_Direct_WFO";
// ============================================================================
// GRAPH DIMENSIONS
// ============================================================================
static const int GRAPH_NODE_COUNT = 5;
static const int GRAPH_NODE_FEATURES = 8;
static const int GRAPH_SIGNAL_COUNT = 40;
// ============================================================================
// PREDICTION DIAGNOSTICS
// ============================================================================
var GNNStateTrendProbability = 0.0;
var GNNStateMeanProbability = 0.0;
var GNNStateNeutralProbability = 1.0;
var GNNNodeTrendAuthority = 0.0;
var GNNNodeMeanAuthority = 0.0;
var GNNNodeHybridTrendAuthority = 0.0;
var GNNNodeHybridMeanAuthority = 0.0;
var GNNNodeStateAuthority = 0.0;
// ============================================================================
// DIRECT LIBTORCH GLOBAL STATE
// ============================================================================
static torch::Device GNNDevice(torch::kCPU);
#if BOGIE_ENABLE_CUDA
static HMODULE GTorchCudaModule = 0;
#endif
class GraphAttentionBlockImpl;
class BogieGraphNetImpl;
static std::shared_ptr<BogieGraphNetImpl> GNNModel;
// Direct training dataset for the current Zorro WFO training run.
static std::vector<float> GNNTrainX;
static std::vector<float> GNNTrainY;
static std::vector<int64_t> GNNTrainState;
// Current model lifecycle.
static int GNNLoadedCycle = 0;
static int GNNLibTorchReady = 0;
// ============================================================================
// GRAPH ATTENTION LAYER
// ============================================================================
class GraphAttentionBlockImpl : public torch::nn::Module
{
public:
torch::nn::Linear Q{nullptr};
torch::nn::Linear K{nullptr};
torch::nn::Linear V{nullptr};
torch::nn::Linear Out{nullptr};
torch::nn::LayerNorm Norm{nullptr};
torch::Tensor EdgeBias;
int Hidden = 0;
GraphAttentionBlockImpl(int HiddenSize)
{
Hidden = HiddenSize;
Q = register_module(
"q",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
K = register_module(
"k",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
V = register_module(
"v",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
Out = register_module(
"out",
torch::nn::Linear(Hidden,Hidden));
Norm = register_module(
"norm",
torch::nn::LayerNorm(
torch::nn::LayerNormOptions(
std::vector<int64_t>{Hidden})));
// Initial graph topology:
// Trend <-> HybridTrend <-> State
// Mean <-> HybridMean <-> State
//
// All entries remain trainable.
std::vector<float> InitialBias = {
0.50f,-0.75f, 0.75f,-0.75f, 1.00f,
-0.75f, 0.50f,-0.75f, 0.75f, 1.00f,
0.75f,-0.50f, 0.50f,-0.25f, 1.00f,
-0.50f, 0.75f,-0.25f, 0.50f, 1.00f,
1.00f, 1.00f, 1.00f, 1.00f, 0.50f
};
EdgeBias =
torch::tensor(
InitialBias,
torch::TensorOptions().dtype(torch::kFloat32))
.reshape({GRAPH_NODE_COUNT,GRAPH_NODE_COUNT});
EdgeBias =
register_parameter(
"edge_bias",
EdgeBias);
}
std::pair<torch::Tensor,torch::Tensor> forward(torch::Tensor H)
{
torch::Tensor Query;
torch::Tensor Key;
torch::Tensor Value;
torch::Tensor Logits;
torch::Tensor Attention;
torch::Tensor Messages;
torch::Tensor Updated;
Query = Q->forward(H);
Key = K->forward(H);
Value = V->forward(H);
Logits =
torch::matmul(
Query,
Key.transpose(1,2));
Logits =
Logits/
std::sqrt((double)Hidden);
Logits =
Logits+
EdgeBias.unsqueeze(0);
Attention =
torch::softmax(
Logits,
-1);
Messages =
torch::matmul(
Attention,
Value);
Updated =
torch::relu(
Out->forward(Messages));
Updated =
Norm->forward(
H+Updated);
return std::make_pair(
Updated,
Attention);
}
};
// ============================================================================
// FIVE-NODE GRAPH NETWORK
// ============================================================================
class BogieGraphNetImpl : public torch::nn::Module
{
public:
torch::nn::Linear TrendEncoder{nullptr};
torch::nn::Linear MeanEncoder{nullptr};
torch::nn::Linear HybridTrendEncoder{nullptr};
torch::nn::Linear HybridMeanEncoder{nullptr};
torch::nn::Linear StateEncoder{nullptr};
torch::Tensor NodeEmbedding;
std::shared_ptr<GraphAttentionBlockImpl> Graph1;
std::shared_ptr<GraphAttentionBlockImpl> Graph2;
torch::nn::Linear PoolGate{nullptr};
torch::nn::Sequential DirectionHead;
torch::nn::Sequential StateHead;
int Hidden = 0;
BogieGraphNetImpl(int HiddenSize = 32)
{
Hidden = HiddenSize;
TrendEncoder =
register_module(
"trend_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
MeanEncoder =
register_module(
"mean_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
HybridTrendEncoder =
register_module(
"hybrid_trend_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
HybridMeanEncoder =
register_module(
"hybrid_mean_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
StateEncoder =
register_module(
"state_encoder",
torch::nn::Linear(
GRAPH_NODE_FEATURES,
Hidden));
NodeEmbedding =
register_parameter(
"node_embedding",
0.05*
torch::randn(
{GRAPH_NODE_COUNT,Hidden},
torch::TensorOptions().dtype(torch::kFloat32)));
Graph1 =
register_module(
"graph1",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
Graph2 =
register_module(
"graph2",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
PoolGate =
register_module(
"pool_gate",
torch::nn::Linear(
Hidden,
1));
DirectionHead =
register_module(
"direction_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,32),
torch::nn::ReLU(),
torch::nn::Dropout(0.10),
torch::nn::Linear(32,16),
torch::nn::ReLU(),
torch::nn::Linear(16,1),
torch::nn::Tanh()));
StateHead =
register_module(
"state_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,16),
torch::nn::ReLU(),
torch::nn::Linear(16,3)));
}
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> forward(torch::Tensor X)
{
torch::Tensor Nodes;
torch::Tensor TrendNode;
torch::Tensor MeanNode;
torch::Tensor HybridTrendNode;
torch::Tensor HybridMeanNode;
torch::Tensor StateNode;
torch::Tensor H;
std::pair<torch::Tensor,torch::Tensor> G1;
std::pair<torch::Tensor,torch::Tensor> G2;
torch::Tensor PoolLogits;
torch::Tensor PoolWeights;
torch::Tensor GraphState;
torch::Tensor Direction;
torch::Tensor StateLogits;
if(X.dim() == 1)
X = X.unsqueeze(0);
Nodes =
X.reshape(
{
X.size(0),
GRAPH_NODE_COUNT,
GRAPH_NODE_FEATURES
});
TrendNode =
torch::relu(
TrendEncoder->forward(
Nodes.select(1,0)));
MeanNode =
torch::relu(
MeanEncoder->forward(
Nodes.select(1,1)));
HybridTrendNode =
torch::relu(
HybridTrendEncoder->forward(
Nodes.select(1,2)));
HybridMeanNode =
torch::relu(
HybridMeanEncoder->forward(
Nodes.select(1,3)));
StateNode =
torch::relu(
StateEncoder->forward(
Nodes.select(1,4)));
H =
torch::stack(
{
TrendNode,
MeanNode,
HybridTrendNode,
HybridMeanNode,
StateNode
},
1);
H =
H+
NodeEmbedding.unsqueeze(0);
G1 = Graph1->forward(H);
H = G1.first;
G2 = Graph2->forward(H);
H = G2.first;
PoolLogits =
PoolGate->forward(H)
.squeeze(-1);
PoolWeights =
torch::softmax(
PoolLogits,
-1);
GraphState =
torch::sum(
PoolWeights.unsqueeze(-1)*H,
1);
Direction =
DirectionHead->forward(GraphState)
.squeeze(-1);
StateLogits =
StateHead->forward(
GraphState);
return std::make_tuple(
Direction,
StateLogits,
PoolWeights);
}
};
// ============================================================================
// LIBTORCH INITIALIZATION
// ============================================================================
static void ClearDirectTrainingData()
{
GNNTrainX.clear();
GNNTrainY.clear();
GNNTrainState.clear();
}
static int InitializeDirectLibTorch()
{
#if BOGIE_ENABLE_CUDA
// Register CUDA hooks before the first LibTorch API initializes its context.
if(!GTorchCudaModule)
GTorchCudaModule =
LoadLibraryA(
"torch_cuda.dll");
if(!GTorchCudaModule)
{
printf(
"\nDirect GNN: torch_cuda.dll load failed (Windows error %lu)",
GetLastError());
}
#endif
if(LibTorchThreads < 1)
LibTorchThreads = 1;
torch::manual_seed(365);
torch::set_num_threads(
LibTorchThreads);
#if BOGIE_ENABLE_CUDA
if(torch::cuda::is_available())
{
GNNDevice =
torch::Device(
torch::kCUDA);
printf(
"\nDirect LibTorch GNN initialized on CUDA");
}
else
{
GNNDevice =
torch::Device(
torch::kCPU);
printf(
"\nDirect LibTorch GNN: CUDA unavailable; using CPU");
}
#else
GNNDevice =
torch::Device(
torch::kCPU);
printf(
"\nDirect LibTorch GNN initialized on CPU");
#endif
GNNModel.reset();
GNNLoadedCycle = 0;
ClearDirectTrainingData();
GNNLibTorchReady = 1;
return 1;
}
static std::shared_ptr<BogieGraphNetImpl> CreateDirectModel(int SeedOffset)
{
std::shared_ptr<BogieGraphNetImpl> Net;
torch::manual_seed(
365+
SeedOffset);
Net =
std::make_shared<BogieGraphNetImpl>(
GNNHidden);
Net->to(
GNNDevice);
return Net;
}
// ============================================================================
// DIRECT TRAINING SAMPLE COLLECTION
// ============================================================================
static void AddDirectTrainingSample(
const var* Signals,
var Target,
int TrainingState)
{
int i;
int64_t StateLabel;
if(!Signals)
return;
for(i=0; i<GRAPH_SIGNAL_COUNT; i++)
{
var V = Signals[i];
if(V > 1.0)
V = 1.0;
if(V < -1.0)
V = -1.0;
GNNTrainX.push_back(
(float)V);
}
if(Target > 1.0)
Target = 1.0;
if(Target < -1.0)
Target = -1.0;
GNNTrainY.push_back(
(float)Target);
// 0 = trend
// 1 = mean reversion
// 2 = neutral
StateLabel = 2;
if(TrainingState == 1)
StateLabel = 0;
else if(TrainingState == -1)
StateLabel = 1;
GNNTrainState.push_back(
StateLabel);
}
// ============================================================================
// MODEL PATHS
// ============================================================================
static std::string DirectModelPath(int Cycle)
{
std::ostringstream Path;
Path <<
DirectModelPrefix;
if(Cycle < 10)
Path << "0";
Path <<
Cycle <<
".pt";
return Path.str();
}
static int DirectFileExists(const std::string& Path)
{
std::ifstream File(
Path.c_str(),
std::ios::binary);
if(File.good())
return 1;
return 0;
}
// ============================================================================
// BEST-MODEL MEMORY SNAPSHOT
// ============================================================================
static std::string DirectModelToMemory(
std::shared_ptr<BogieGraphNetImpl> Net)
{
torch::serialize::OutputArchive Archive;
std::ostringstream Stream(
std::ios::out |
std::ios::binary);
Net->save(
Archive);
Archive.save_to(
Stream);
return Stream.str();
}
static void DirectModelFromMemory(
std::shared_ptr<BogieGraphNetImpl> Net,
const std::string& Blob)
{
torch::serialize::InputArchive Archive;
std::istringstream Stream(
Blob,
std::ios::in |
std::ios::binary);
Archive.load_from(
Stream,
GNNDevice);
Net->load(
Archive);
}
// ============================================================================
// DIRECT MODEL SAVE / LOAD
// ============================================================================
static int SaveDirectModel(
std::shared_ptr<BogieGraphNetImpl> Net,
int Cycle)
{
torch::serialize::OutputArchive Archive;
std::string Path;
if(!Net)
return 0;
Path =
DirectModelPath(
Cycle);
try
{
Net->save(
Archive);
Archive.save_to(
Path);
}
catch(const c10::Error& E)
{
printf(
"\nDirect GNN SAVE ERROR cycle %i: %s",
Cycle,
E.what());
return 0;
}
catch(const std::exception& E)
{
printf(
"\nDirect GNN SAVE ERROR cycle %i: %s",
Cycle,
E.what());
return 0;
}
printf(
"\nDirect GNN saved WFO cycle %i -> %s",
Cycle,
Path.c_str());
return 1;
}
static int LoadDirectModel(int Cycle)
{
torch::serialize::InputArchive Archive;
std::shared_ptr<BogieGraphNetImpl> Net;
std::string Path;
Path =
DirectModelPath(
Cycle);
if(!DirectFileExists(Path))
{
printf(
"\nDirect GNN LOAD ERROR: model file not found: %s",
Path.c_str());
return 0;
}
try
{
Archive.load_from(
Path,
GNNDevice);
Net =
CreateDirectModel(
Cycle);
Net->load(
Archive);
Net->to(
GNNDevice);
Net->eval();
}
catch(const c10::Error& E)
{
printf(
"\nDirect GNN LOAD ERROR cycle %i: %s",
Cycle,
E.what());
return 0;
}
catch(const std::exception& E)
{
printf(
"\nDirect GNN LOAD ERROR cycle %i: %s",
Cycle,
E.what());
return 0;
}
GNNModel = Net;
GNNLoadedCycle = Cycle;
printf(
"\nDirect GNN loaded WFO cycle %i from %s",
Cycle,
Path.c_str());
return 1;
}
static int DesiredDirectModelCycle()
{
int Cycle;
Cycle = WFOCycle;
// In live mode WFOCycle can be 0; use the last trained cycle.
if(Cycle <= 0)
{
Cycle = WFOCycles;
if(Cycle < 0)
Cycle = -Cycle;
}
if(Cycle <= 0)
Cycle = 1;
return Cycle;
}
static int EnsureDirectModelLoaded()
{
int Cycle;
Cycle =
DesiredDirectModelCycle();
if(
GNNModel &&
GNNLoadedCycle == Cycle)
{
return 1;
}
return LoadDirectModel(
Cycle);
}
// ============================================================================
// DIRECT TRAINING
// ============================================================================
static var TrainDirectGNNCycle(int Cycle)
{
int Rows;
int ValidRows;
int TrainRows;
int Epoch;
int StaleEpochs;
int PositiveCount;
int NegativeCount;
int i;
double BestValidation;
std::string BestBlob;
std::vector<float> DirectionWeights;
torch::Tensor XAll;
torch::Tensor YAll;
torch::Tensor SAll;
torch::Tensor WAll;
torch::Tensor XTrain;
torch::Tensor YTrain;
torch::Tensor STrain;
torch::Tensor WTrain;
torch::Tensor XValid;
torch::Tensor YValid;
torch::Tensor SValid;
std::shared_ptr<BogieGraphNetImpl> Net;
Rows =
(int)GNNTrainY.size();
if(
Rows < MinTrainingSamples ||
(int)GNNTrainState.size() != Rows ||
(int)GNNTrainX.size() !=
Rows*GRAPH_SIGNAL_COUNT)
{
printf(
"\nDirect GNN TRAIN ERROR: cycle %i has %i usable rows",
Cycle,
Rows);
return 0;
}
// ------------------------------------------------------------------------
// Explicit replacement for +BALANCED:
// calculate inverse-frequency weights by target sign.
// ------------------------------------------------------------------------
PositiveCount = 0;
NegativeCount = 0;
for(i=0; i<Rows; i++)
{
if(GNNTrainY[i] >= 0.0f)
PositiveCount++;
else
NegativeCount++;
}
DirectionWeights.resize(
Rows,
1.0f);
if(
UseBalancedDirectionLoss &&
PositiveCount > 0 &&
NegativeCount > 0)
{
float PositiveWeight;
float NegativeWeight;
PositiveWeight =
(float)Rows/
(2.0f*
(float)PositiveCount);
NegativeWeight =
(float)Rows/
(2.0f*
(float)NegativeCount);
for(i=0; i<Rows; i++)
{
if(GNNTrainY[i] >= 0.0f)
{
DirectionWeights[i] =
PositiveWeight;
}
else
{
DirectionWeights[i] =
NegativeWeight;
}
}
}
// ------------------------------------------------------------------------
// Copy direct C++ vectors into LibTorch tensors.
// ------------------------------------------------------------------------
XAll =
torch::from_blob(
GNNTrainX.data(),
{
Rows,
GRAPH_SIGNAL_COUNT
},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
YAll =
torch::from_blob(
GNNTrainY.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
SAll =
torch::from_blob(
GNNTrainState.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kInt64))
.clone()
.to(GNNDevice);
WAll =
torch::from_blob(
DirectionWeights.data(),
{Rows},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
// Chronological validation block.
ValidRows =
Rows*
GNNValidationPercent/
100;
if(ValidRows < 1)
ValidRows = 1;
if(ValidRows > Rows/3)
ValidRows = Rows/3;
TrainRows =
Rows-
ValidRows;
XTrain =
XAll.narrow(
0,
0,
TrainRows);
YTrain =
YAll.narrow(
0,
0,
TrainRows);
STrain =
SAll.narrow(
0,
0,
TrainRows);
WTrain =
WAll.narrow(
0,
0,
TrainRows);
XValid =
XAll.narrow(
0,
TrainRows,
ValidRows);
YValid =
YAll.narrow(
0,
TrainRows,
ValidRows);
SValid =
SAll.narrow(
0,
TrainRows,
ValidRows);
Net =
CreateDirectModel(
Cycle);
torch::optim::AdamW Optimizer(
Net->parameters(),
torch::optim::AdamWOptions(
GNNLearningRate)
.weight_decay(
GNNWeightDecay));
torch::nn::CrossEntropyLoss StateLoss;
BestValidation =
std::numeric_limits<double>::infinity();
StaleEpochs = 0;
// ------------------------------------------------------------------------
// GPU training.
// ------------------------------------------------------------------------
for(Epoch=0; Epoch<GNNEpochs; Epoch++)
{
torch::Tensor Permutation;
int Start;
double EpochLoss;
int Batches;
if(!wait(0))
return 0;
EpochLoss = 0.0;
Batches = 0;
Net->train();
Permutation =
torch::randperm(
TrainRows,
torch::TensorOptions()
.dtype(torch::kInt64)
.device(GNNDevice));
for(
Start=0;
Start<TrainRows;
Start += GNNBatchSize)
{
int Count;
torch::Tensor Index;
torch::Tensor XB;
torch::Tensor YB;
torch::Tensor SB;
torch::Tensor WB;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor DirectionPred;
torch::Tensor StateLogits;
torch::Tensor DirectionError;
torch::Tensor LossDirection;
torch::Tensor LossState;
torch::Tensor Loss;
Count =
GNNBatchSize;
if(
Start+
Count >
TrainRows)
{
Count =
TrainRows-
Start;
}
Index =
Permutation.narrow(
0,
Start,
Count);
XB =
XTrain.index_select(
0,
Index);
YB =
YTrain.index_select(
0,
Index);
SB =
STrain.index_select(
0,
Index);
WB =
WTrain.index_select(
0,
Index);
Output =
Net->forward(
XB);
DirectionPred =
std::get<0>(
Output);
StateLogits =
std::get<1>(
Output);
DirectionError =
DirectionPred-
YB;
LossDirection =
torch::mean(
DirectionError*
DirectionError*
WB);
LossState =
StateLoss(
StateLogits,
SB);
Loss =
LossDirection+
GNNStateLossWeight*
LossState;
Optimizer.zero_grad();
Loss.backward();
torch::nn::utils::clip_grad_norm_(
Net->parameters(),
2.0);
Optimizer.step();
EpochLoss +=
Loss.item<double>();
Batches++;
}
// --------------------------------------------------------------------
// Validation.
// --------------------------------------------------------------------
Net->eval();
{
torch::NoGradGuard NoGrad;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> ValidOutput;
torch::Tensor ValidDirection;
torch::Tensor ValidStateLogits;
torch::Tensor ValidError;
torch::Tensor ValidDirectionLoss;
torch::Tensor ValidStateLoss;
torch::Tensor ValidLoss;
double Validation;
ValidOutput =
Net->forward(
XValid);
ValidDirection =
std::get<0>(
ValidOutput);
ValidStateLogits =
std::get<1>(
ValidOutput);
ValidError =
ValidDirection-
YValid;
ValidDirectionLoss =
torch::mean(
ValidError*
ValidError);
ValidStateLoss =
StateLoss(
ValidStateLogits,
SValid);
ValidLoss =
ValidDirectionLoss+
GNNStateLossWeight*
ValidStateLoss;
Validation =
ValidLoss.item<double>();
if(
Validation <
BestValidation-
0.000001)
{
BestValidation =
Validation;
BestBlob =
DirectModelToMemory(
Net);
StaleEpochs = 0;
}
else
{
StaleEpochs++;
}
if(
Epoch % 10 == 0 ||
Epoch ==
GNNEpochs-1)
{
double AverageTrainLoss;
AverageTrainLoss = 0.0;
if(Batches > 0)
{
AverageTrainLoss =
EpochLoss/
Batches;
}
printf(
"\nDirect GNN WFO %i epoch %i train %.6f valid %.6f",
Cycle,
Epoch,
AverageTrainLoss,
Validation);
}
}
if(
StaleEpochs >=
GNNPatience)
{
printf(
"\nDirect GNN WFO %i early stop at epoch %i",
Cycle,
Epoch);
break;
}
}
if(!BestBlob.empty())
{
DirectModelFromMemory(
Net,
BestBlob);
}
Net->to(
GNNDevice);
Net->eval();
GNNModel = Net;
GNNLoadedCycle = Cycle;
if(!SaveDirectModel(
Net,
Cycle))
{
return 0;
}
printf(
"\nDirect GNN WFO %i trained with %i rows; best validation %.6f; positive %i negative %i",
Cycle,
Rows,
BestValidation,
PositiveCount,
NegativeCount);
if(BestValidation <= 0.0)
return 0.0001;
return
BestValidation*
100.0;
}
// ============================================================================
// DIRECT INFERENCE
// ============================================================================
static var PredictDirectGNN(
const var* Signals)
{
std::vector<float> Input;
torch::Tensor X;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor Direction;
torch::Tensor StateLogits;
torch::Tensor PoolWeights;
torch::Tensor StateProb;
int i;
double Score;
if(
!GNNModel ||
!Signals)
{
return 0.0;
}
Input.resize(
GRAPH_SIGNAL_COUNT);
for(i=0; i<GRAPH_SIGNAL_COUNT; i++)
{
var V = Signals[i];
if(V > 1.0)
V = 1.0;
if(V < -1.0)
V = -1.0;
Input[i] =
(float)V;
}
X =
torch::from_blob(
Input.data(),
{
1,
GRAPH_SIGNAL_COUNT
},
torch::TensorOptions()
.dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
GNNModel->eval();
{
torch::NoGradGuard NoGrad;
Output =
GNNModel->forward(
X);
Direction =
std::get<0>(
Output);
StateLogits =
std::get<1>(
Output);
PoolWeights =
std::get<2>(
Output);
StateProb =
torch::softmax(
StateLogits,
1);
Score =
Direction
.to(torch::kCPU)
.item<float>()*
100.0;
GNNStateTrendProbability =
StateProb[0][0]
.to(torch::kCPU)
.item<float>();
GNNStateMeanProbability =
StateProb[0][1]
.to(torch::kCPU)
.item<float>();
GNNStateNeutralProbability =
StateProb[0][2]
.to(torch::kCPU)
.item<float>();
GNNNodeTrendAuthority =
PoolWeights[0][0]
.to(torch::kCPU)
.item<float>();
GNNNodeMeanAuthority =
PoolWeights[0][1]
.to(torch::kCPU)
.item<float>();
GNNNodeHybridTrendAuthority =
PoolWeights[0][2]
.to(torch::kCPU)
.item<float>();
GNNNodeHybridMeanAuthority =
PoolWeights[0][3]
.to(torch::kCPU)
.item<float>();
GNNNodeStateAuthority =
PoolWeights[0][4]
.to(torch::kCPU)
.item<float>();
}
return Score;
}
// ============================================================================
// BOGIE / MARKET FEATURES
// ============================================================================
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest =
HH(
NocPeriod,
0);
Lowest =
LL(
NocPeriod,
0);
Range =
Highest-
Lowest;
if(Range <= 0.0)
return 0.0;
Position =
2.0*
(priceC(0)-Lowest)/
Range-
1.0;
return
clamp(
Position,
-1.0,
1.0);
}
// Return 0..1 causal trend authority.
var TrendAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A =
clamp(
(ADXNow-ADXThreshold)/
RegimeScaleADX,
0.0,
1.0);
B =
clamp(
(MMIThreshold-MMINow)/
RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
// Return 0..1 causal mean-reversion authority.
var MeanAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A =
clamp(
(ADXThreshold-ADXNow)/
RegimeScaleADX,
0.0,
1.0);
B =
clamp(
(MMINow-MMIThreshold)/
RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
// ============================================================================
// BUILD 5 x 8 GRAPH
// ============================================================================
void BuildFiveNodeGraph(
var RawBogie,
var* SmoothSeries,
var ATRNow,
var PlusNow,
var MinusNow,
var ADXNow,
var AroonNow,
var MMINow,
var RSINow,
var BBOscNow,
var MeanNow,
var* Signals)
{
var ATRSafe;
var Momentum6;
var Momentum3;
var DISpread;
var MeanDeviation;
var TrendStandaloneAuth;
var MeanStandaloneAuth;
var HybridTrendAuth;
var HybridMeanAuth;
var StateSigned;
var StateStrength;
ATRSafe = ATRNow;
if(ATRSafe < PIP)
ATRSafe = PIP;
Momentum6 =
(priceC(0)-priceC(6))/
(3.0*ATRSafe);
Momentum3 =
(priceC(0)-priceC(3))/
(2.0*ATRSafe);
DISpread =
(PlusNow-MinusNow)/
100.0;
MeanDeviation =
(priceC(0)-MeanNow)/
(2.0*ATRSafe);
TrendStandaloneAuth =
TrendAuthority(
ADXNow,
MMINow,
TrendStandaloneADXMin,
TrendStandaloneMMIMax);
MeanStandaloneAuth =
MeanAuthority(
ADXNow,
MMINow,
MeanStandaloneADXMax,
MeanStandaloneMMIMin);
HybridTrendAuth =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
HybridMeanAuth =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
StateSigned =
HybridTrendAuth-
HybridMeanAuth;
StateStrength =
HybridTrendAuth;
if(
HybridMeanAuth >
StateStrength)
{
StateStrength =
HybridMeanAuth;
}
// Node 0: standalone TrendML.
Signals[0] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[1] =
clamp(
2.0*
(
SmoothSeries[0]-
SmoothSeries[1]
),
-1.0,
1.0);
Signals[2] =
clamp(
SmoothSeries[0]-
SmoothSeries[3],
-1.0,
1.0);
Signals[3] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[4] =
clamp(
DISpread,
-1.0,
1.0);
Signals[5] =
clamp(
(ADXNow-25.0)/
25.0,
-1.0,
1.0);
Signals[6] =
clamp(
AroonNow/
100.0,
-1.0,
1.0);
Signals[7] =
clamp(
(75.0-MMINow)/
25.0,
-1.0,
1.0);
// Node 1: standalone MeanReversionML.
Signals[8] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[9] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[10] =
clamp(
(RSINow-50.0)/
50.0,
-1.0,
1.0);
Signals[11] =
clamp(
(BBOscNow-50.0)/
50.0,
-1.0,
1.0);
Signals[12] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[13] =
clamp(
Momentum3,
-1.0,
1.0);
Signals[14] =
clamp(
(MMINow-50.0)/
25.0,
-1.0,
1.0);
Signals[15] =
clamp(
(25.0-ADXNow)/
25.0,
-1.0,
1.0);
// Node 2: Hybrid trend specialist.
Signals[16] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[17] =
clamp(
2.0*
(
SmoothSeries[0]-
SmoothSeries[1]
),
-1.0,
1.0);
Signals[18] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[19] =
clamp(
DISpread,
-1.0,
1.0);
Signals[20] =
clamp(
AroonNow/
100.0,
-1.0,
1.0);
Signals[21] =
clamp(
2.0*
HybridTrendAuth-
1.0,
-1.0,
1.0);
Signals[22] =
clamp(
(ADXNow-
HybridTrendADXMin)/
25.0,
-1.0,
1.0);
Signals[23] =
clamp(
(HybridTrendMMIMax-
MMINow)/
25.0,
-1.0,
1.0);
// Node 3: Hybrid mean specialist.
Signals[24] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[25] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[26] =
clamp(
(RSINow-50.0)/
50.0,
-1.0,
1.0);
Signals[27] =
clamp(
(BBOscNow-50.0)/
50.0,
-1.0,
1.0);
Signals[28] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[29] =
clamp(
2.0*
HybridMeanAuth-
1.0,
-1.0,
1.0);
Signals[30] =
clamp(
(HybridMeanADXMax-
ADXNow)/
25.0,
-1.0,
1.0);
Signals[31] =
clamp(
(MMINow-
HybridMeanMMIMin)/
25.0,
-1.0,
1.0);
// Node 4: Market-state node.
Signals[32] =
clamp(
HybridTrendAuth,
-1.0,
1.0);
Signals[33] =
clamp(
HybridMeanAuth,
-1.0,
1.0);
Signals[34] =
clamp(
StateSigned,
-1.0,
1.0);
Signals[35] =
clamp(
StateStrength,
-1.0,
1.0);
Signals[36] =
clamp(
(ADXNow-25.0)/
25.0,
-1.0,
1.0);
Signals[37] =
clamp(
(MMINow-50.0)/
25.0,
-1.0,
1.0);
Signals[38] =
clamp(
DISpread,
-1.0,
1.0);
Signals[39] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
}
// ============================================================================
// CAUSAL TRAINING STATE
// ============================================================================
int CausalTrainingState(
var ADXNow,
var MMINow)
{
var TrendState;
var MeanState;
TrendState =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
MeanState =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
if(
TrendState >= 0.35 &&
TrendState >=
MeanState+
0.10)
{
return 1;
}
if(
MeanState >= 0.35 &&
MeanState >=
TrendState+
0.10)
{
return -1;
}
return 0;
}
// ============================================================================
// LEARNED GNN STATE
// ============================================================================
int LearnedGNNState()
{
if(
GNNStateTrendProbability >=
StateProbabilityMin &&
GNNStateTrendProbability >
GNNStateMeanProbability &&
GNNStateTrendProbability >
GNNStateNeutralProbability)
{
return 1;
}
if(
GNNStateMeanProbability >=
StateProbabilityMin &&
GNNStateMeanProbability >
GNNStateTrendProbability &&
GNNStateMeanProbability >
GNNStateNeutralProbability)
{
return -1;
}
return 0;
}
// ============================================================================
// CALENDAR
// ============================================================================
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow =
dow(0);
if(
DayNow ==
MQLDayToZorro(
NoTradeDay_1))
{
return 0;
}
if(
DayNow ==
MQLDayToZorro(
NoTradeDay_2))
{
return 0;
}
return 1;
}
// ============================================================================
// POSITION SIZING
// ============================================================================
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin =
Equity-
MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue =
FreeMargin*
RiskPercent/
100.0/
1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureTrendTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
TrendStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureMeanTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
MeanStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureNeutralTrade(var ATRNow)
{
Amount =
LegacyBogieAmount();
Risk = 0;
Stop =
NeutralStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
// ============================================================================
// TREND TRAILING TMF
// ============================================================================
int GraphTrendTrailTMF(
var TrailDistance,
var GuardDistance)
{
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailDistance <= 0.0)
return 0;
if(TradeIsShort)
{
Candidate =
priceC(0)+
TrailDistance;
if(
TradeStopLimit >
Candidate+
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
else
{
Candidate =
priceC(0)-
TrailDistance;
if(
TradeStopLimit <
Candidate-
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
return 0;
}
// ============================================================================
// DIAGNOSTICS
// ============================================================================
void LogGraphEntry(
cstr Side,
int Regime,
var GNNScore,
var ADXNow,
var MMINow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | DirectGNN %.2f | State %i "
"| P(T) %.3f P(M) %.3f P(N) %.3f "
"| Nodes T %.3f M %.3f HT %.3f HM %.3f S %.3f "
"| ADX %.2f MMI %.2f | Amount %.3f",
Asset,
Bar,
Side,
GNNScore,
Regime,
GNNStateTrendProbability,
GNNStateMeanProbability,
GNNStateNeutralProbability,
GNNNodeTrendAuthority,
GNNNodeMeanAuthority,
GNNNodeHybridTrendAuthority,
GNNNodeHybridMeanAuthority,
GNNNodeStateAuthority,
ADXNow,
MMINow,
Amount);
}
// ============================================================================
// ZORRO STRATEGY
// ============================================================================
DLLFUNC void run()
{
var RawBogie;
var SmoothNow;
var ATRNow;
var PlusNow;
var MinusNow;
var ADXNow;
var AroonNow;
var MMINow;
var RSINow;
var BBOscNow;
var MeanNow;
var GNNTarget;
var GNNScore;
var FutureMove;
var TargetScale;
var TrailDistance;
var GuardDistance;
var GraphSignals[GRAPH_SIGNAL_COUNT] = {
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0
};
var* PriceSeries;
var* RawSeries;
var* SmoothSeries;
int TrainingState;
int LearnedState;
int TargetHorizon;
int Allowed;
int LongSignal;
int ShortSignal;
int Cycle;
if(is(FIRSTINITRUN))
require(-3.11);
// ------------------------------------------------------------------------
// Zorro simulation / WFO configuration only.
// No Zorro machine-learning subsystem is enabled.
// ------------------------------------------------------------------------
if(is(INITRUN))
{
set(RECALCULATE);
set(TICKS);
set(LOGFILE);
set(OPENEND);
if(Train)
set(PEEK);
// GPU LibTorch training should normally be sequential.
if(Train)
NumCores =
DirectGPUTrainingCores;
if(!InitializeDirectLibTorch())
{
printf(
"\nDirect LibTorch initialization failed");
return;
}
}
BarPeriod =
StrategyBarPeriod;
LookBack = 300;
Capital = 10000;
StartDate =
BacktestStart;
EndDate =
BacktestEnd;
NumWFOCycles =
WFOCycles;
DataSplit =
WFOTrainPercent;
// This is a general Zorro WFO control, not a neural function.
// It prevents trading at the start of the OOS segment after future-peeking
// targets were used in the preceding training period.
DataHorizon =
MaxPredictionHorizonBars;
asset(
(string)BogieAsset);
algo(
(string)"BogieDirectGNN");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
// ------------------------------------------------------------------------
// Direct training finalization.
//
// Zorro documents that each WFO training cycle has its own EXITRUN.
// Train LibTorch directly here at the end of the WFO training run.
// ------------------------------------------------------------------------
if(is(EXITRUN))
{
if(Train)
{
Cycle =
WFOCycle;
if(Cycle <= 0)
Cycle = 1;
TrainDirectGNNCycle(
Cycle);
}
GNNModel.reset();
GNNLoadedCycle = 0;
return;
}
// ------------------------------------------------------------------------
// Feature pipeline.
// ------------------------------------------------------------------------
PriceSeries =
series(
priceC(0),
256);
RawBogie =
BogieRangePosition();
RawSeries =
series(
RawBogie,
64);
SmoothNow =
EMA(
RawSeries,
EmaPeriod);
SmoothSeries =
series(
SmoothNow,
64);
ATRNow =
ATR(
ATRPeriod);
PlusNow =
PlusDI(
ADXPeriod);
MinusNow =
MinusDI(
ADXPeriod);
ADXNow =
ADX(
ADXPeriod);
AroonNow =
AroonOsc(
AroonPeriod);
MMINow =
MMI(
PriceSeries,
MMIPeriod);
RSINow =
RSI(
PriceSeries,
RSIPeriod);
BBOscNow =
BBOsc(
PriceSeries,
BBPeriod,
2.0,
MAType_SMA);
MeanNow =
EMA(
PriceSeries,
MeanPeriod);
BuildFiveNodeGraph(
RawBogie,
SmoothSeries,
ATRNow,
PlusNow,
MinusNow,
ADXNow,
AroonNow,
MMINow,
RSINow,
BBOscNow,
MeanNow,
GraphSignals);
if(is(LOOKBACK))
return;
// ------------------------------------------------------------------------
// Causal state determines training target horizon.
// ------------------------------------------------------------------------
TrainingState =
CausalTrainingState(
ADXNow,
MMINow);
TargetHorizon =
NeutralPredictionHorizonBars;
if(TrainingState == 1)
{
TargetHorizon =
TrendPredictionHorizonBars;
}
else if(TrainingState == -1)
{
TargetHorizon =
MeanPredictionHorizonBars;
}
// ------------------------------------------------------------------------
// TRAIN MODE:
// collect samples directly into C++ vectors.
// Samples go directly into the LibTorch training dataset.
// ------------------------------------------------------------------------
if(Train)
{
FutureMove =
priceC(
-TargetHorizon)-
priceC(0);
TargetScale =
TargetATRScale*
ATRNow;
if(TargetScale < PIP)
TargetScale = PIP;
GNNTarget =
FutureMove/
TargetScale;
GNNTarget =
clamp(
GNNTarget,
-1.0,
1.0);
AddDirectTrainingSample(
GraphSignals,
GNNTarget,
TrainingState);
return;
}
// ------------------------------------------------------------------------
// TEST / TRADE MODE:
// load the active WFO model and run direct LibTorch inference.
// ------------------------------------------------------------------------
if(!GNNLibTorchReady)
{
if(!InitializeDirectLibTorch())
return;
}
if(!EnsureDirectModelLoaded())
return;
GNNScore =
PredictDirectGNN(
GraphSignals);
LearnedState =
LearnedGNNState();
// ------------------------------------------------------------------------
// Diagnostic plots.
// ------------------------------------------------------------------------
plot(
"Direct GNN",
GNNScore,
NEW,
BLUE);
plot(
"P Trend",
100.0*
GNNStateTrendProbability,
0,
GREEN);
plot(
"P Mean",
-100.0*
GNNStateMeanProbability,
0,
RED);
plot(
"State Node",
100.0*
GNNNodeStateAuthority,
0,
BLACK);
// ------------------------------------------------------------------------
// Regime-aware mean-reversion exits.
// ------------------------------------------------------------------------
if(LearnedState == -1)
{
if(
NumOpenLong > 0 &&
priceC(0) >= MeanNow)
{
exitLong();
return;
}
if(
NumOpenShort > 0 &&
priceC(0) <= MeanNow)
{
exitShort();
return;
}
}
// ------------------------------------------------------------------------
// Direction + learned state -> trade signal.
// ------------------------------------------------------------------------
LongSignal = 0;
ShortSignal = 0;
if(LearnedState == 1)
{
if(
GNNScore >
TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
PlusNow >
MinusNow)
{
LongSignal = 1;
}
}
else if(
GNNScore <
-TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
MinusNow >
PlusNow)
{
ShortSignal = 1;
}
}
}
else if(LearnedState == -1)
{
if(
GNNScore >
MeanConfidenceThreshold &&
SmoothNow <=
HybridMeanLongExtreme &&
RSINow <=
HybridMeanLongRSIMax)
{
LongSignal = 1;
}
else if(
GNNScore <
-MeanConfidenceThreshold &&
SmoothNow >=
HybridMeanShortExtreme &&
RSINow >=
HybridMeanShortRSIMin)
{
ShortSignal = 1;
}
}
else if(AllowNeutralTrades)
{
if(
GNNScore >
NeutralConfidenceThreshold)
{
LongSignal = 1;
}
else if(
GNNScore <
-NeutralConfidenceThreshold)
{
ShortSignal = 1;
}
}
// ------------------------------------------------------------------------
// Calendar.
// ------------------------------------------------------------------------
Allowed =
TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
// ------------------------------------------------------------------------
// LONG.
// ------------------------------------------------------------------------
if(LongSignal)
{
if(NumOpenShort > 0)
{
exitShort();
return;
}
if(NumOpenLong == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
ATRNow);
TrailDistance =
TrendTrailATRMult*
ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong();
}
}
return;
}
// ------------------------------------------------------------------------
// SHORT.
// ------------------------------------------------------------------------
if(ShortSignal)
{
if(NumOpenLong > 0)
{
exitLong();
return;
}
if(NumOpenShort == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
ATRNow);
TrailDistance =
TrendTrailATRMult*
ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort();
}
}
return;
}
}
258
93,969
Read More
|
|
09/09/26 13:36
// BogieNN_v4_LibTorchGNN.cpp
// ============================================================================
// Zorro S 3.11+ / Zorro64 / LibTorch C++
// Five-node Graph Attention Network for Bogie-NN.
//
// NO PYTHON IS USED.
//
// This strategy keeps Zorro's adviseLong(NEURAL,...) workflow so that Zorro
// still controls:
// - sample collection,
// - WFO cycle separation,
// - model indexing,
// - model save/load timing,
// - Test/Trade prediction calls.
//
// The custom neural() callback below implements all machine learning directly
// with LibTorch.
//
// -----------------------------------------------------------------------------
// FIVE GRAPH NODES
//
// Node 0 - TREND SPECIALIST
// Parameters/features from BogieNN_v3_TrendML.c
//
// Node 1 - MEAN-REVERSION SPECIALIST
// Parameters/features from BogieNN_v3_MeanReversionML.c
//
// Node 2 - HYBRID TREND SPECIALIST
// Trend side and stricter regime parameters from
// BogieNN_v3_RegimeHybridML.c
//
// Node 3 - HYBRID MEAN SPECIALIST
// Mean-reversion side and stricter regime parameters from
// BogieNN_v3_RegimeHybridML.c
//
// Node 4 - MARKET STATE
// Continuous regime authority and directional state.
//
// 5 nodes x 8 features = 40 input signals.
//
// -----------------------------------------------------------------------------
// GNN
//
// 5 x 8 inputs
// |
// separate node encoders
// |
// learned node identity embeddings
// |
// Graph Attention Layer 1
// |
// Graph Attention Layer 2
// |
// learned attention pooling
// |----------------------|
// | |
// Direction head State head
// tanh [-1,+1] trend / mean / neutral
// | |
// Zorro score trade-regime authority
// -100..+100
//
// The state head is trained with an auxiliary classification loss derived from
// the causal market-state features. This forces the shared graph representation
// to learn market condition as well as future direction.
//
// -----------------------------------------------------------------------------
// IMPORTANT ABOUT THE PREVIOUS V3 PERCEPTRONS
//
// The v3 source strategies define their FEATURES and PARAMETERS, but their
// actual trained PERCEPTRON coefficients are generated only after Zorro [Train]
// and stored by Zorro in Data\*.c files. Those generated coefficients were not
// provided here.
//
// Therefore this version incorporates the v3 feature definitions, horizons,
// regime thresholds, and trade-management parameters. It does NOT pretend to
// import unavailable trained perceptron coefficients.
//
// If those generated v3 rule files are later supplied, their coefficients can
// be used to seed the node encoders in a subsequent version.
//
// -----------------------------------------------------------------------------
// LIBTORCH BUILD REQUIREMENTS
//
// Use Zorro64. Zorro64 compiles .cpp scripts with Visual C++.
//
// Add your LibTorch installation to the generated VC++ project:
//
// C/C++ -> Additional Include Directories:
// <LIBTORCH>\include
// <LIBTORCH>\include\torch\csrc\api\include
//
// Linker -> Additional Library Directories:
// <LIBTORCH>\lib
//
// Linker -> Additional Dependencies (typical shared CPU LibTorch):
// c10.lib
// torch.lib
// torch_cpu.lib
//
// The exact dependency set can vary with the LibTorch build/version.
// Put the required LibTorch DLLs in a directory on PATH or beside the compiled
// strategy DLL.
//
// For CUDA LibTorch, set BOGIE_ENABLE_CUDA to 1 below and link the CUDA LibTorch
// dependencies supplied with your package.
//
// ============================================================================
#ifndef NOMINMAX
#define NOMINMAX
#endif
// Set to 1 only when compiling/linking against a CUDA-enabled LibTorch build.
// CPU is intentionally the default because Zorro backtests predict one sample
// at a time, where CPU inference often has lower overhead.
#ifndef BOGIE_ENABLE_CUDA
#define BOGIE_ENABLE_CUDA 1
#endif
#include <torch/torch.h>
#include <torch/serialize.h>
#if BOGIE_ENABLE_CUDA
#include <torch/cuda.h>
#endif
#include <algorithm>
#include <cmath>
#include <cstdint>
#include <cstdlib>
#include <fstream>
#include <limits>
#include <memory>
#include <sstream>
#include <string>
#include <tuple>
#include <vector>
// LibTorch owns the global `at` namespace, while Zorro declares a global
// function with the same name. Keep the unused Zorro declaration out of that
// namespace without changing either library's ABI.
#define at zorro_at
#include <zorro.h>
#undef at
// ----------------------------- User settings ---------------------------------
cstr BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 60;
int WFOCycles = 8;
int WFOTrainPercent = 85;
// State-specific prediction horizons inherited from the v3 branches.
int TrendPredictionHorizonBars = 12;
int MeanPredictionHorizonBars = 4;
int NeutralPredictionHorizonBars = 6;
// Maximum future look-ahead used by any target.
// Used by DataHorizon to protect the following WFO OOS segment.
int MaxPredictionHorizonBars = 12;
// GNN directional thresholds. Returned score is approximately -100..+100.
var TrendConfidenceThreshold = 20.0;
var MeanConfidenceThreshold = 20.0;
var NeutralConfidenceThreshold = 35.0;
// Minimum probability from the learned state head before a specialist gets
// authority. If neither trend nor mean reaches this level, state is neutral.
var StateProbabilityMin = 0.50;
// Neutral state normally does not open trades.
int AllowNeutralTrades = 0;
// Optional directional confirmation for trend entries.
int RequireDIConfirmation = 1;
// ------------------------ Parameters from TrendML ------------------------------
int NocPeriod = 12;
int EmaPeriod = 5;
int ATRPeriod = 14;
int ADXPeriod = 14;
int AroonPeriod = 25;
int MMIPeriod = 100;
var TrendStandaloneADXMin = 20.0;
var TrendStandaloneMMIMax = 65.0;
var TrendStopATRMult = 2.5;
var TrendTrailATRMult = 3.0;
// -------------------- Parameters from MeanReversionML --------------------------
int RSIPeriod = 14;
int BBPeriod = 20;
int MeanPeriod = 20;
var MeanStandaloneMMIMin = 58.0;
var MeanStandaloneADXMax = 28.0;
var MeanLongExtremeStandalone = -0.45;
var MeanShortExtremeStandalone = 0.45;
var MeanLongRSIMaxStandalone = 40.0;
var MeanShortRSIMinStandalone = 60.0;
var MeanStopATRMult = 1.6;
// ----------------------- Parameters from HybridML ------------------------------
var HybridTrendADXMin = 25.0;
var HybridTrendMMIMax = 64.0;
var HybridMeanADXMax = 20.0;
var HybridMeanMMIMin = 60.0;
var HybridMeanLongExtreme = -0.45;
var HybridMeanShortExtreme = 0.45;
var HybridMeanLongRSIMax = 40.0;
var HybridMeanShortRSIMin = 60.0;
// Continuous state authority.
// These values create smooth causal state features for the graph.
var RegimeScaleADX = 15.0;
var RegimeScaleMMI = 15.0;
// ----------------------------- Trade settings --------------------------------
var NeutralStopATRMult = 2.0;
var TrailGuardPips = 0.5;
// Legacy Bogie sizing retained so the signal architecture can be compared with
// the earlier versions without changing every subsystem simultaneously.
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
// MQL4 weekday numbering:
// Sunday=0, Monday=1, ... Saturday=6.
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
// --------------------------- Training settings --------------------------------
// Target:
// future move / (TargetATRScale * current ATR), clipped to -1..+1.
var TargetATRScale = 2.0;
// GNN hyperparameters.
int GNNHidden = 32;
int GNNEpochs = 80;
int GNNBatchSize = 128;
int GNNPatience = 12;
int GNNValidationPercent = 15;
var GNNLearningRate = 0.001;
var GNNWeightDecay = 0.0001;
// Auxiliary market-state loss weight.
var GNNStateLossWeight = 0.20;
// Regime label derived from signed state feature.
// > +threshold = trend, < -threshold = mean, otherwise neutral.
var GNNStateLabelThreshold = 0.15;
// CPU thread count for LibTorch.
// Keep conservative when Zorro itself is running parallel WFO processes.
int LibTorchThreads = 2;
// --------------------------- Graph dimensions --------------------------------
static const int GRAPH_NODE_COUNT = 5;
static const int GRAPH_NODE_FEATURES = 8;
static const int GRAPH_SIGNAL_COUNT = 40;
// State node starts at signal 32.
// Within State node:
// 0 Trend authority
// 1 Mean authority
// 2 Signed authority (Trend - Mean)
// 3 Strongest authority
// 4 normalized ADX
// 5 normalized MMI
// 6 DI spread
// 7 Bogie smooth level
static const int STATE_SIGNED_SIGNAL_INDEX = 34;
// ---------------------- GNN prediction diagnostics ----------------------------
// Updated by neural(NEURAL_PREDICT,...) after every Zorro advise call.
var GNNStateTrendProbability = 0.0;
var GNNStateMeanProbability = 0.0;
var GNNStateNeutralProbability = 1.0;
// Learned graph pooling weights / node authority.
var GNNNodeTrendAuthority = 0.0;
var GNNNodeMeanAuthority = 0.0;
var GNNNodeHybridTrendAuthority = 0.0;
var GNNNodeHybridMeanAuthority = 0.0;
var GNNNodeStateAuthority = 0.0;
// ----------------------------- LibTorch state --------------------------------
static torch::Device GNNDevice(torch::kCPU);
#if BOGIE_ENABLE_CUDA
static HMODULE GTorchCudaModule = 0;
#endif
class GraphAttentionBlockImpl;
class BogieGraphNetImpl;
static std::vector<std::shared_ptr<BogieGraphNetImpl> > GNNModels;
// ============================================================================
// LIBTORCH GRAPH NEURAL NETWORK
// ============================================================================
class GraphAttentionBlockImpl : public torch::nn::Module
{
public:
torch::nn::Linear Q{nullptr};
torch::nn::Linear K{nullptr};
torch::nn::Linear V{nullptr};
torch::nn::Linear Out{nullptr};
torch::nn::LayerNorm Norm{nullptr};
torch::Tensor EdgeBias;
int Hidden = 0;
GraphAttentionBlockImpl(int HiddenSize)
{
Hidden = HiddenSize;
Q = register_module(
"q",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
K = register_module(
"k",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
V = register_module(
"v",
torch::nn::Linear(
torch::nn::LinearOptions(Hidden,Hidden).bias(false)));
Out = register_module(
"out",
torch::nn::Linear(Hidden,Hidden));
Norm = register_module(
"norm",
torch::nn::LayerNorm(
torch::nn::LayerNormOptions(
std::vector<int64_t>{Hidden})));
// Five-node prior.
//
// Nodes:
// 0 Trend
// 1 Mean
// 2 HybridTrend
// 3 HybridMean
// 4 State
//
// Strong initial edges:
// Trend <-> HybridTrend <-> State
// Mean <-> HybridMean <-> State
//
// All edge biases remain trainable.
std::vector<float> InitialBias = {
0.50f,-0.75f, 0.75f,-0.75f, 1.00f,
-0.75f, 0.50f,-0.75f, 0.75f, 1.00f,
0.75f,-0.50f, 0.50f,-0.25f, 1.00f,
-0.50f, 0.75f,-0.25f, 0.50f, 1.00f,
1.00f, 1.00f, 1.00f, 1.00f, 0.50f
};
EdgeBias = torch::tensor(
InitialBias,
torch::TensorOptions().dtype(torch::kFloat32))
.reshape({GRAPH_NODE_COUNT,GRAPH_NODE_COUNT});
EdgeBias = register_parameter("edge_bias",EdgeBias);
}
std::pair<torch::Tensor,torch::Tensor> forward(torch::Tensor H)
{
torch::Tensor Query;
torch::Tensor Key;
torch::Tensor Value;
torch::Tensor Logits;
torch::Tensor Attention;
torch::Tensor Messages;
torch::Tensor Updated;
Query = Q->forward(H);
Key = K->forward(H);
Value = V->forward(H);
Logits = torch::matmul(
Query,
Key.transpose(1,2));
Logits = Logits/std::sqrt((double)Hidden);
Logits = Logits+EdgeBias.unsqueeze(0);
Attention = torch::softmax(Logits,-1);
Messages = torch::matmul(Attention,Value);
Updated = torch::relu(Out->forward(Messages));
Updated = Norm->forward(H+Updated);
return std::make_pair(Updated,Attention);
}
};
class BogieGraphNetImpl : public torch::nn::Module
{
public:
// Separate encoders preserve node specialization.
torch::nn::Linear TrendEncoder{nullptr};
torch::nn::Linear MeanEncoder{nullptr};
torch::nn::Linear HybridTrendEncoder{nullptr};
torch::nn::Linear HybridMeanEncoder{nullptr};
torch::nn::Linear StateEncoder{nullptr};
torch::Tensor NodeEmbedding;
std::shared_ptr<GraphAttentionBlockImpl> Graph1;
std::shared_ptr<GraphAttentionBlockImpl> Graph2;
torch::nn::Linear PoolGate{nullptr};
torch::nn::Sequential DirectionHead;
torch::nn::Sequential StateHead;
int Hidden = 0;
BogieGraphNetImpl(int HiddenSize = 32)
{
Hidden = HiddenSize;
TrendEncoder = register_module(
"trend_encoder",
torch::nn::Linear(GRAPH_NODE_FEATURES,Hidden));
MeanEncoder = register_module(
"mean_encoder",
torch::nn::Linear(GRAPH_NODE_FEATURES,Hidden));
HybridTrendEncoder = register_module(
"hybrid_trend_encoder",
torch::nn::Linear(GRAPH_NODE_FEATURES,Hidden));
HybridMeanEncoder = register_module(
"hybrid_mean_encoder",
torch::nn::Linear(GRAPH_NODE_FEATURES,Hidden));
StateEncoder = register_module(
"state_encoder",
torch::nn::Linear(GRAPH_NODE_FEATURES,Hidden));
NodeEmbedding = register_parameter(
"node_embedding",
0.05*torch::randn(
{GRAPH_NODE_COUNT,Hidden},
torch::TensorOptions().dtype(torch::kFloat32)));
Graph1 = register_module(
"graph1",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
Graph2 = register_module(
"graph2",
std::make_shared<GraphAttentionBlockImpl>(Hidden));
PoolGate = register_module(
"pool_gate",
torch::nn::Linear(Hidden,1));
DirectionHead = register_module(
"direction_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,32),
torch::nn::ReLU(),
torch::nn::Dropout(0.10),
torch::nn::Linear(32,16),
torch::nn::ReLU(),
torch::nn::Linear(16,1),
torch::nn::Tanh()));
StateHead = register_module(
"state_head",
torch::nn::Sequential(
torch::nn::Linear(Hidden,16),
torch::nn::ReLU(),
torch::nn::Linear(16,3)));
}
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> forward(torch::Tensor X)
{
torch::Tensor Nodes;
torch::Tensor TrendNode;
torch::Tensor MeanNode;
torch::Tensor HybridTrendNode;
torch::Tensor HybridMeanNode;
torch::Tensor StateNode;
torch::Tensor H;
std::pair<torch::Tensor,torch::Tensor> G1;
std::pair<torch::Tensor,torch::Tensor> G2;
torch::Tensor PoolLogits;
torch::Tensor PoolWeights;
torch::Tensor GraphState;
torch::Tensor Direction;
torch::Tensor StateLogits;
if(X.dim() == 1)
X = X.unsqueeze(0);
Nodes = X.reshape(
{X.size(0),GRAPH_NODE_COUNT,GRAPH_NODE_FEATURES});
TrendNode = torch::relu(
TrendEncoder->forward(Nodes.select(1,0)));
MeanNode = torch::relu(
MeanEncoder->forward(Nodes.select(1,1)));
HybridTrendNode = torch::relu(
HybridTrendEncoder->forward(Nodes.select(1,2)));
HybridMeanNode = torch::relu(
HybridMeanEncoder->forward(Nodes.select(1,3)));
StateNode = torch::relu(
StateEncoder->forward(Nodes.select(1,4)));
H = torch::stack(
{
TrendNode,
MeanNode,
HybridTrendNode,
HybridMeanNode,
StateNode
},
1);
H = H+NodeEmbedding.unsqueeze(0);
G1 = Graph1->forward(H);
H = G1.first;
G2 = Graph2->forward(H);
H = G2.first;
PoolLogits = PoolGate->forward(H).squeeze(-1);
PoolWeights = torch::softmax(PoolLogits,-1);
GraphState = torch::sum(
PoolWeights.unsqueeze(-1)*H,
1);
Direction = DirectionHead->forward(GraphState).squeeze(-1);
StateLogits = StateHead->forward(GraphState);
return std::make_tuple(
Direction,
StateLogits,
PoolWeights);
}
};
// ============================================================================
// LIBTORCH TRAINING DATA
// ============================================================================
struct GNNTrainingData
{
std::vector<float> X;
std::vector<float> Y;
std::vector<int64_t> StateClass;
int Rows = 0;
int NumSignals = 0;
};
// Parse Zorro's NEURAL_TRAIN CSV string.
//
// Columns:
// Signal[0] ... Signal[NumSignals-1], Objective
//
// State labels are generated only from CAUSAL state features.
// They do not look into the future.
static int ParseTrainingCSV(
const char* Text,
int NumSignals,
GNNTrainingData& Out)
{
std::istringstream Input;
std::string Line;
int GoodRows = 0;
if(!Text)
return 0;
Input.str(std::string(Text));
Out.X.clear();
Out.Y.clear();
Out.StateClass.clear();
Out.Rows = 0;
Out.NumSignals = NumSignals;
while(std::getline(Input,Line))
{
const char* P;
char* End;
std::vector<float> Row;
double Value;
int Col;
int Valid;
if(Line.empty())
continue;
P = Line.c_str();
Row.assign(NumSignals+1,0.0f);
Valid = 1;
for(Col=0; Col<NumSignals+1; Col++)
{
while(*P == ' ' || *P == '\t' || *P == ',')
P++;
if(!*P)
{
Valid = 0;
break;
}
End = 0;
Value = std::strtod(P,&End);
if(End == P || !std::isfinite(Value))
{
Valid = 0;
break;
}
Row[Col] = (float)Value;
P = End;
}
if(!Valid)
continue;
for(Col=0; Col<NumSignals; Col++)
{
float V = Row[Col];
if(V > 1.0f)
V = 1.0f;
if(V < -1.0f)
V = -1.0f;
Out.X.push_back(V);
}
{
float Target = Row[NumSignals];
if(Target > 1.0f)
Target = 1.0f;
if(Target < -1.0f)
Target = -1.0f;
Out.Y.push_back(Target);
}
// Auxiliary state class:
// 0 Trend
// 1 Mean reversion
// 2 Neutral
{
float SignedState = Row[STATE_SIGNED_SIGNAL_INDEX];
int64_t Label = 2;
if(SignedState > (float)GNNStateLabelThreshold)
Label = 0;
else if(SignedState < -(float)GNNStateLabelThreshold)
Label = 1;
Out.StateClass.push_back(Label);
}
GoodRows++;
}
Out.Rows = GoodRows;
return GoodRows;
}
// ============================================================================
// LIBTORCH MODEL UTILITIES
// ============================================================================
static std::shared_ptr<BogieGraphNetImpl> CreateGNNModel(int ModelIndex)
{
std::shared_ptr<BogieGraphNetImpl> Net;
torch::manual_seed(365+ModelIndex);
Net = std::make_shared<BogieGraphNetImpl>(GNNHidden);
Net->to(GNNDevice);
return Net;
}
static void StoreModel(
int ModelIndex,
std::shared_ptr<BogieGraphNetImpl> Net)
{
while((int)GNNModels.size() <= ModelIndex)
GNNModels.push_back(
std::shared_ptr<BogieGraphNetImpl>());
GNNModels[ModelIndex] = Net;
}
// Serialize one model to an in-memory string.
// Used for early stopping / restoring best validation weights.
static std::string ModelToMemory(
std::shared_ptr<BogieGraphNetImpl> Net)
{
torch::serialize::OutputArchive Archive;
std::ostringstream Stream(
std::ios::out | std::ios::binary);
Net->save(Archive);
Archive.save_to(Stream);
return Stream.str();
}
static void ModelFromMemory(
std::shared_ptr<BogieGraphNetImpl> Net,
const std::string& Blob)
{
torch::serialize::InputArchive Archive;
std::istringstream Stream(
Blob,
std::ios::in | std::ios::binary);
Archive.load_from(Stream,GNNDevice);
Net->load(Archive);
}
// ============================================================================
// LIBTORCH MODEL TRAINING
// ============================================================================
static var TrainGNNModel(
int ModelIndex,
int NumSignals,
const char* CSVText)
{
GNNTrainingData DataSet;
torch::Tensor XAll;
torch::Tensor YAll;
torch::Tensor StateAll;
torch::Tensor XTrain;
torch::Tensor YTrain;
torch::Tensor StateTrain;
torch::Tensor XValid;
torch::Tensor YValid;
torch::Tensor StateValid;
int Rows;
int ValidRows;
int TrainRows;
int Epoch;
int StaleEpochs;
double BestValidation;
std::string BestBlob;
std::shared_ptr<BogieGraphNetImpl> Net;
if(NumSignals != GRAPH_SIGNAL_COUNT)
{
printf(
"\nLibTorch GNN ERROR: expected %i signals, received %i",
GRAPH_SIGNAL_COUNT,
NumSignals);
return 0;
}
Rows = ParseTrainingCSV(
CSVText,
NumSignals,
DataSet);
if(Rows < 100)
{
printf(
"\nLibTorch GNN ERROR: only %i valid training rows",
Rows);
return 0;
}
XAll = torch::from_blob(
DataSet.X.data(),
{Rows,NumSignals},
torch::TensorOptions().dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
YAll = torch::from_blob(
DataSet.Y.data(),
{Rows},
torch::TensorOptions().dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
StateAll = torch::from_blob(
DataSet.StateClass.data(),
{Rows},
torch::TensorOptions().dtype(torch::kInt64))
.clone()
.to(GNNDevice);
ValidRows = Rows*GNNValidationPercent/100;
if(ValidRows < 1)
ValidRows = 1;
if(ValidRows > Rows/3)
ValidRows = Rows/3;
TrainRows = Rows-ValidRows;
XTrain = XAll.narrow(0,0,TrainRows);
YTrain = YAll.narrow(0,0,TrainRows);
StateTrain = StateAll.narrow(0,0,TrainRows);
XValid = XAll.narrow(0,TrainRows,ValidRows);
YValid = YAll.narrow(0,TrainRows,ValidRows);
StateValid = StateAll.narrow(0,TrainRows,ValidRows);
Net = CreateGNNModel(ModelIndex);
torch::optim::AdamW Optimizer(
Net->parameters(),
torch::optim::AdamWOptions(GNNLearningRate)
.weight_decay(GNNWeightDecay));
torch::nn::MSELoss DirectionLoss;
torch::nn::CrossEntropyLoss StateLoss;
BestValidation = std::numeric_limits<double>::infinity();
StaleEpochs = 0;
for(Epoch=0; Epoch<GNNEpochs; Epoch++)
{
torch::Tensor Permutation;
int Start;
double EpochLoss = 0.0;
int Batches = 0;
if(!wait(0))
return 0;
Net->train();
Permutation = torch::randperm(
TrainRows,
torch::TensorOptions()
.dtype(torch::kInt64)
.device(GNNDevice));
for(Start=0; Start<TrainRows; Start += GNNBatchSize)
{
int Count;
torch::Tensor Index;
torch::Tensor XB;
torch::Tensor YB;
torch::Tensor SB;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor DirectionPred;
torch::Tensor StateLogits;
torch::Tensor LossDirection;
torch::Tensor LossState;
torch::Tensor Loss;
Count = GNNBatchSize;
if(Start+Count > TrainRows)
Count = TrainRows-Start;
Index = Permutation.narrow(0,Start,Count);
XB = XTrain.index_select(0,Index);
YB = YTrain.index_select(0,Index);
SB = StateTrain.index_select(0,Index);
Output = Net->forward(XB);
DirectionPred = std::get<0>(Output);
StateLogits = std::get<1>(Output);
LossDirection = DirectionLoss(
DirectionPred,
YB);
LossState = StateLoss(
StateLogits,
SB);
Loss =
LossDirection+
GNNStateLossWeight*LossState;
Optimizer.zero_grad();
Loss.backward();
torch::nn::utils::clip_grad_norm_(
Net->parameters(),
2.0);
Optimizer.step();
EpochLoss += Loss.item<double>();
Batches++;
}
// Chronological validation block at the end of the WFO training sample.
Net->eval();
{
torch::NoGradGuard NoGrad;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> ValidOutput;
torch::Tensor ValidDirection;
torch::Tensor ValidStateLogits;
torch::Tensor ValidDirectionLoss;
torch::Tensor ValidStateLoss;
torch::Tensor ValidTotalLoss;
double Validation;
ValidOutput = Net->forward(XValid);
ValidDirection = std::get<0>(ValidOutput);
ValidStateLogits = std::get<1>(ValidOutput);
ValidDirectionLoss = DirectionLoss(
ValidDirection,
YValid);
ValidStateLoss = StateLoss(
ValidStateLogits,
StateValid);
ValidTotalLoss =
ValidDirectionLoss+
GNNStateLossWeight*ValidStateLoss;
Validation = ValidTotalLoss.item<double>();
if(
Validation <
BestValidation-0.000001)
{
BestValidation = Validation;
BestBlob = ModelToMemory(Net);
StaleEpochs = 0;
}
else
{
StaleEpochs++;
}
if(
Epoch % 10 == 0 ||
Epoch == GNNEpochs-1)
{
double AverageTrainLoss = 0.0;
if(Batches > 0)
AverageTrainLoss =
EpochLoss/Batches;
printf(
"\nLibTorch GNN model %i epoch %i "
"train %.6f valid %.6f",
ModelIndex,
Epoch,
AverageTrainLoss,
Validation);
}
}
if(StaleEpochs >= GNNPatience)
{
printf(
"\nLibTorch GNN model %i early stop at epoch %i",
ModelIndex,
Epoch);
break;
}
}
if(!BestBlob.empty())
ModelFromMemory(Net,BestBlob);
Net->eval();
StoreModel(ModelIndex,Net);
printf(
"\nLibTorch GNN model %i trained: %i rows, best loss %.6f",
ModelIndex,
Rows,
BestValidation);
// Zorro interprets 0 as training failure.
// Return a positive percentage-like loss.
if(BestValidation <= 0.0)
return 0.0001;
return BestValidation*100.0;
}
// ============================================================================
// LIBTORCH PREDICTION
// ============================================================================
static var PredictGNNModel(
int ModelIndex,
int NumSignals,
const double* Signals)
{
std::vector<float> Input;
torch::Tensor X;
std::tuple<
torch::Tensor,
torch::Tensor,
torch::Tensor> Output;
torch::Tensor Direction;
torch::Tensor StateLogits;
torch::Tensor PoolWeights;
torch::Tensor StateProb;
double Score;
int i;
if(
ModelIndex < 0 ||
ModelIndex >= (int)GNNModels.size() ||
!GNNModels[ModelIndex])
{
printf(
"\nLibTorch GNN ERROR: model %i unavailable",
ModelIndex);
return 0;
}
if(
NumSignals != GRAPH_SIGNAL_COUNT ||
!Signals)
{
printf(
"\nLibTorch GNN ERROR: bad prediction input");
return 0;
}
Input.resize(NumSignals);
for(i=0; i<NumSignals; i++)
{
double V = Signals[i];
if(V > 1.0)
V = 1.0;
if(V < -1.0)
V = -1.0;
Input[i] = (float)V;
}
X = torch::from_blob(
Input.data(),
{1,NumSignals},
torch::TensorOptions().dtype(torch::kFloat32))
.clone()
.to(GNNDevice);
GNNModels[ModelIndex]->eval();
{
torch::NoGradGuard NoGrad;
Output = GNNModels[ModelIndex]->forward(X);
Direction = std::get<0>(Output);
StateLogits = std::get<1>(Output);
PoolWeights = std::get<2>(Output);
StateProb = torch::softmax(
StateLogits,
1);
// Direction score for adviseLong().
Score =
Direction
.to(torch::kCPU)
.item<float>()*100.0;
// Learned state probabilities.
GNNStateTrendProbability =
StateProb[0][0]
.to(torch::kCPU)
.item<float>();
GNNStateMeanProbability =
StateProb[0][1]
.to(torch::kCPU)
.item<float>();
GNNStateNeutralProbability =
StateProb[0][2]
.to(torch::kCPU)
.item<float>();
// Learned node authorities.
GNNNodeTrendAuthority =
PoolWeights[0][0]
.to(torch::kCPU)
.item<float>();
GNNNodeMeanAuthority =
PoolWeights[0][1]
.to(torch::kCPU)
.item<float>();
GNNNodeHybridTrendAuthority =
PoolWeights[0][2]
.to(torch::kCPU)
.item<float>();
GNNNodeHybridMeanAuthority =
PoolWeights[0][3]
.to(torch::kCPU)
.item<float>();
GNNNodeStateAuthority =
PoolWeights[0][4]
.to(torch::kCPU)
.item<float>();
}
return Score;
}
// ============================================================================
// LIBTORCH WFO MODEL SAVE / LOAD
// ============================================================================
static int SaveGNNModels(const char* FileName)
{
torch::serialize::OutputArchive Root;
torch::Tensor CountTensor;
int i;
if(!FileName)
return 0;
CountTensor = torch::tensor(
{(int64_t)GNNModels.size()},
torch::TensorOptions().dtype(torch::kInt64));
Root.write("model_count",CountTensor);
for(i=0; i<(int)GNNModels.size(); i++)
{
if(!GNNModels[i])
continue;
torch::serialize::OutputArchive Child;
std::string Key;
GNNModels[i]->save(Child);
Key =
std::string("model_")+
std::to_string(i);
Root.write(Key,Child);
}
try
{
Root.save_to(std::string(FileName));
}
catch(const c10::Error& E)
{
printf(
"\nLibTorch GNN SAVE ERROR: %s",
E.what());
return 0;
}
printf(
"\nStored %i LibTorch GNN model(s) to %s",
(int)GNNModels.size(),
FileName);
// Next WFO training cycle starts with a clean model list.
GNNModels.clear();
return 1;
}
static int LoadGNNModels(const char* FileName)
{
torch::serialize::InputArchive Root;
torch::Tensor CountTensor;
int Count;
int i;
if(!FileName)
return 0;
try
{
Root.load_from(
std::string(FileName),
GNNDevice);
Root.read(
"model_count",
CountTensor);
Count =
(int)CountTensor
.to(torch::kCPU)
.item<int64_t>();
GNNModels.clear();
for(i=0; i<Count; i++)
{
torch::serialize::InputArchive Child;
std::string Key;
std::shared_ptr<BogieGraphNetImpl> Net;
Key =
std::string("model_")+
std::to_string(i);
Root.read(Key,Child);
Net = CreateGNNModel(i);
Net->load(Child);
Net->to(GNNDevice);
Net->eval();
StoreModel(i,Net);
}
}
catch(const c10::Error& E)
{
printf(
"\nLibTorch GNN LOAD ERROR: %s",
E.what());
GNNModels.clear();
return 0;
}
catch(const std::exception& E)
{
printf(
"\nLibTorch GNN LOAD ERROR: %s",
E.what());
GNNModels.clear();
return 0;
}
printf(
"\nLoaded %i LibTorch GNN model(s) from %s",
(int)GNNModels.size(),
FileName);
return 1;
}
// ============================================================================
// ZORRO NEURAL CALLBACK
// ============================================================================
//
// Verified Zorro contract:
//
// NEURAL_INIT
// initialize ML system, return 1 on success.
//
// NEURAL_TRAIN
// Model = Zorro model index
// NumSignals = feature count
// Data = CSV text, signals + target in last column.
//
// NEURAL_PREDICT
// Data = double array of NumSignals signal values.
//
// NEURAL_SAVE / NEURAL_LOAD
// Data = suggested .ml filename for current WFO cycle.
//
// NEURAL_EXIT
// release resources.
//
// The function is intentionally named exactly "neural" because advise(NEURAL)
// calls this user-supplied implementation.
DLLFUNC var neural(
int Status,
int Model,
int NumSignals,
void* Data)
{
try
{
if(Status == NEURAL_INIT)
{
torch::manual_seed(365);
if(LibTorchThreads < 1)
LibTorchThreads = 1;
torch::set_num_threads(
LibTorchThreads);
#if BOGIE_ENABLE_CUDA
if(!GTorchCudaModule)
GTorchCudaModule = LoadLibraryA("torch_cuda.dll");
if(!GTorchCudaModule)
printf(
"\nLibTorch GNN: torch_cuda.dll load failed (Windows error %lu)",
GetLastError());
if(torch::cuda::is_available())
{
GNNDevice =
torch::Device(torch::kCUDA);
printf(
"\nLibTorch GNN initialized on CUDA");
}
else
{
GNNDevice =
torch::Device(torch::kCPU);
printf(
"\nLibTorch GNN: CUDA requested but unavailable; using CPU");
}
#else
GNNDevice =
torch::Device(torch::kCPU);
printf(
"\nLibTorch GNN initialized on CPU");
#endif
GNNModels.clear();
return 1;
}
if(Status == NEURAL_EXIT)
{
GNNModels.clear();
return 1;
}
if(Status == NEURAL_TRAIN)
{
if(!wait(0))
return 0;
return TrainGNNModel(
Model,
NumSignals,
(const char*)Data);
}
if(Status == NEURAL_PREDICT)
{
return PredictGNNModel(
Model,
NumSignals,
(const double*)Data);
}
if(Status == NEURAL_SAVE)
{
return SaveGNNModels(
(const char*)Data);
}
if(Status == NEURAL_LOAD)
{
return LoadGNNModels(
(const char*)Data);
}
}
catch(const c10::Error& E)
{
printf(
"\nLibTorch GNN ERROR: %s",
E.what());
return 0;
}
catch(const std::exception& E)
{
printf(
"\nLibTorch GNN ERROR: %s",
E.what());
return 0;
}
return 1;
}
// ============================================================================
// BOGIE / MARKET FEATURES
// ============================================================================
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest = HH(NocPeriod,0);
Lowest = LL(NocPeriod,0);
Range = Highest-Lowest;
if(Range <= 0.0)
return 0.0;
Position =
2.0*(priceC(0)-Lowest)/Range-1.0;
return clamp(
Position,
-1.0,
1.0);
}
// Continuous authority helpers.
// Return 0..1.
var TrendAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A = clamp(
(ADXNow-ADXThreshold)/RegimeScaleADX,
0.0,
1.0);
B = clamp(
(MMIThreshold-MMINow)/RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
var MeanAuthority(
var ADXNow,
var MMINow,
var ADXThreshold,
var MMIThreshold)
{
var A;
var B;
A = clamp(
(ADXThreshold-ADXNow)/RegimeScaleADX,
0.0,
1.0);
B = clamp(
(MMINow-MMIThreshold)/RegimeScaleMMI,
0.0,
1.0);
if(A < B)
return A;
return B;
}
// Construct all five graph nodes.
// Every value is clipped to -1..+1.
void BuildFiveNodeGraph(
var RawBogie,
var* SmoothSeries,
var ATRNow,
var PlusNow,
var MinusNow,
var ADXNow,
var AroonNow,
var MMINow,
var RSINow,
var BBOscNow,
var MeanNow,
var* Signals)
{
var ATRSafe;
var Momentum6;
var Momentum3;
var DISpread;
var MeanDeviation;
var TrendStandaloneAuth;
var MeanStandaloneAuth;
var HybridTrendAuth;
var HybridMeanAuth;
var StateSigned;
var StateStrength;
ATRSafe = ATRNow;
if(ATRSafe < PIP)
ATRSafe = PIP;
Momentum6 =
(priceC(0)-priceC(6))/
(3.0*ATRSafe);
Momentum3 =
(priceC(0)-priceC(3))/
(2.0*ATRSafe);
DISpread =
(PlusNow-MinusNow)/100.0;
MeanDeviation =
(priceC(0)-MeanNow)/
(2.0*ATRSafe);
TrendStandaloneAuth =
TrendAuthority(
ADXNow,
MMINow,
TrendStandaloneADXMin,
TrendStandaloneMMIMax);
MeanStandaloneAuth =
MeanAuthority(
ADXNow,
MMINow,
MeanStandaloneADXMax,
MeanStandaloneMMIMin);
HybridTrendAuth =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
HybridMeanAuth =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
StateSigned =
HybridTrendAuth-
HybridMeanAuth;
StateStrength =
HybridTrendAuth;
if(HybridMeanAuth > StateStrength)
StateStrength = HybridMeanAuth;
// ------------------------------------------------------------------------
// NODE 0 - Standalone TrendML
// ------------------------------------------------------------------------
Signals[0] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[1] =
clamp(
2.0*(
SmoothSeries[0]-
SmoothSeries[1]),
-1.0,
1.0);
Signals[2] =
clamp(
SmoothSeries[0]-
SmoothSeries[3],
-1.0,
1.0);
Signals[3] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[4] =
clamp(
DISpread,
-1.0,
1.0);
Signals[5] =
clamp(
(ADXNow-25.0)/25.0,
-1.0,
1.0);
Signals[6] =
clamp(
AroonNow/100.0,
-1.0,
1.0);
Signals[7] =
clamp(
(75.0-MMINow)/25.0,
-1.0,
1.0);
// ------------------------------------------------------------------------
// NODE 1 - Standalone MeanReversionML
// ------------------------------------------------------------------------
Signals[8] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[9] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[10] =
clamp(
(RSINow-50.0)/50.0,
-1.0,
1.0);
Signals[11] =
clamp(
(BBOscNow-50.0)/50.0,
-1.0,
1.0);
Signals[12] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[13] =
clamp(
Momentum3,
-1.0,
1.0);
Signals[14] =
clamp(
(MMINow-50.0)/25.0,
-1.0,
1.0);
Signals[15] =
clamp(
(25.0-ADXNow)/25.0,
-1.0,
1.0);
// ------------------------------------------------------------------------
// NODE 2 - Hybrid Trend specialist
//
// Combines TrendML directional inputs with HybridML's stricter state
// boundaries.
// ------------------------------------------------------------------------
Signals[16] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[17] =
clamp(
2.0*(
SmoothSeries[0]-
SmoothSeries[1]),
-1.0,
1.0);
Signals[18] =
clamp(
Momentum6,
-1.0,
1.0);
Signals[19] =
clamp(
DISpread,
-1.0,
1.0);
Signals[20] =
clamp(
AroonNow/100.0,
-1.0,
1.0);
Signals[21] =
clamp(
2.0*HybridTrendAuth-1.0,
-1.0,
1.0);
Signals[22] =
clamp(
(ADXNow-HybridTrendADXMin)/25.0,
-1.0,
1.0);
Signals[23] =
clamp(
(HybridTrendMMIMax-MMINow)/25.0,
-1.0,
1.0);
// ------------------------------------------------------------------------
// NODE 3 - Hybrid Mean specialist
// ------------------------------------------------------------------------
Signals[24] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
Signals[25] =
clamp(
RawBogie,
-1.0,
1.0);
Signals[26] =
clamp(
(RSINow-50.0)/50.0,
-1.0,
1.0);
Signals[27] =
clamp(
(BBOscNow-50.0)/50.0,
-1.0,
1.0);
Signals[28] =
clamp(
MeanDeviation,
-1.0,
1.0);
Signals[29] =
clamp(
2.0*HybridMeanAuth-1.0,
-1.0,
1.0);
Signals[30] =
clamp(
(HybridMeanADXMax-ADXNow)/25.0,
-1.0,
1.0);
Signals[31] =
clamp(
(MMINow-HybridMeanMMIMin)/25.0,
-1.0,
1.0);
// ------------------------------------------------------------------------
// NODE 4 - Market-state node
// ------------------------------------------------------------------------
Signals[32] =
clamp(
HybridTrendAuth,
-1.0,
1.0);
Signals[33] =
clamp(
HybridMeanAuth,
-1.0,
1.0);
Signals[34] =
clamp(
StateSigned,
-1.0,
1.0);
Signals[35] =
clamp(
StateStrength,
-1.0,
1.0);
Signals[36] =
clamp(
(ADXNow-25.0)/25.0,
-1.0,
1.0);
Signals[37] =
clamp(
(MMINow-50.0)/25.0,
-1.0,
1.0);
Signals[38] =
clamp(
DISpread,
-1.0,
1.0);
Signals[39] =
clamp(
SmoothSeries[0],
-1.0,
1.0);
}
// ============================================================================
// MARKET STATE FOR TRAINING TARGET
// ============================================================================
//
// Target horizon must be chosen causally in Train mode.
// We use the deterministic HybridML state features here because the learned
// state head does not yet exist before training.
//
// Test/Trade regime selection later uses the LEARNED state probabilities.
int CausalTrainingState(
var ADXNow,
var MMINow)
{
var TrendState;
var MeanState;
TrendState =
TrendAuthority(
ADXNow,
MMINow,
HybridTrendADXMin,
HybridTrendMMIMax);
MeanState =
MeanAuthority(
ADXNow,
MMINow,
HybridMeanADXMax,
HybridMeanMMIMin);
if(
TrendState >= 0.35 &&
TrendState >= MeanState+0.10)
return 1;
if(
MeanState >= 0.35 &&
MeanState >= TrendState+0.10)
return -1;
return 0;
}
// Learned state:
// +1 Trend
// -1 Mean reversion
// 0 Neutral / uncertain
int LearnedGNNState()
{
if(
GNNStateTrendProbability >=
StateProbabilityMin &&
GNNStateTrendProbability >
GNNStateMeanProbability &&
GNNStateTrendProbability >
GNNStateNeutralProbability)
return 1;
if(
GNNStateMeanProbability >=
StateProbabilityMin &&
GNNStateMeanProbability >
GNNStateTrendProbability &&
GNNStateMeanProbability >
GNNStateNeutralProbability)
return -1;
return 0;
}
// ============================================================================
// CALENDAR
// ============================================================================
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow = dow(0);
if(
DayNow ==
MQLDayToZorro(NoTradeDay_1))
return 0;
if(
DayNow ==
MQLDayToZorro(NoTradeDay_2))
return 0;
return 1;
}
// ============================================================================
// POSITION SIZING
// ============================================================================
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin = Equity-MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue =
FreeMargin*
RiskPercent/
100.0/
1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue =
roundto(
AmountValue,
Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureTrendTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop =
TrendStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureMeanTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop =
MeanStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureNeutralTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop =
NeutralStopATRMult*
ATRNow;
TakeProfit = 0;
Trail = 0;
}
// ============================================================================
// TREND TRAILING TMF
// ============================================================================
int GraphTrendTrailTMF(
var TrailDistance,
var GuardDistance)
{
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailDistance <= 0.0)
return 0;
if(TradeIsShort)
{
Candidate =
priceC(0)+
TrailDistance;
if(
TradeStopLimit >
Candidate+
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
else
{
Candidate =
priceC(0)-
TrailDistance;
if(
TradeStopLimit <
Candidate-
GuardDistance)
{
TradeStopLimit =
Candidate;
}
}
return 0;
}
// ============================================================================
// DIAGNOSTICS
// ============================================================================
void LogGraphEntry(
cstr Side,
int Regime,
var GNNScore,
var ADXNow,
var MMINow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | GNN %.2f | State %i "
"| P(T) %.3f P(M) %.3f P(N) %.3f "
"| Nodes T %.3f M %.3f HT %.3f HM %.3f S %.3f "
"| ADX %.2f MMI %.2f | Amount %.3f",
Asset,
Bar,
Side,
GNNScore,
Regime,
GNNStateTrendProbability,
GNNStateMeanProbability,
GNNStateNeutralProbability,
GNNNodeTrendAuthority,
GNNNodeMeanAuthority,
GNNNodeHybridTrendAuthority,
GNNNodeHybridMeanAuthority,
GNNNodeStateAuthority,
ADXNow,
MMINow,
Amount);
}
// ============================================================================
// ZORRO STRATEGY
// ============================================================================
DLLFUNC void run()
{
var RawBogie;
var SmoothNow;
var ATRNow;
var PlusNow;
var MinusNow;
var ADXNow;
var AroonNow;
var MMINow;
var RSINow;
var BBOscNow;
var MeanNow;
var GNNTarget;
var GNNScore;
var FutureMove;
var TargetScale;
var TrailDistance;
var GuardDistance;
var GraphSignals[GRAPH_SIGNAL_COUNT] = {
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0,
0,0,0,0,0,0,0,0
};
var* PriceSeries;
var* RawSeries;
var* SmoothSeries;
int TrainingState;
int LearnedState;
int TargetHorizon;
int Allowed;
int LongSignal;
int ShortSignal;
if(is(FIRSTINITRUN))
require(-3.11);
// RULES is required by advise(NEURAL).
// OPENEND is recommended for WFO ML so trades are not artificially closed
// at training-period boundaries.
set(RULES);
set(RECALCULATE);
set(TICKS);
set(LOGFILE);
set(OPENEND);
// Future target access is permitted only while training.
if(Train)
set(PEEK);
BarPeriod = StrategyBarPeriod;
LookBack = 300;
Capital = 10000;
StartDate = BacktestStart;
EndDate = BacktestEnd;
NumWFOCycles = WFOCycles;
DataSplit = WFOTrainPercent;
DataHorizon =
MaxPredictionHorizonBars;
asset((string)BogieAsset);
algo((string)"BogieGNN");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
// ------------------------------------------------------------------------
// Feature pipeline
// ------------------------------------------------------------------------
PriceSeries =
series(
priceC(0),
256);
RawBogie =
BogieRangePosition();
RawSeries =
series(
RawBogie,
64);
SmoothNow =
EMA(
RawSeries,
EmaPeriod);
SmoothSeries =
series(
SmoothNow,
64);
ATRNow =
ATR(
ATRPeriod);
PlusNow =
PlusDI(
ADXPeriod);
MinusNow =
MinusDI(
ADXPeriod);
ADXNow =
ADX(
ADXPeriod);
AroonNow =
AroonOsc(
AroonPeriod);
MMINow =
MMI(
PriceSeries,
MMIPeriod);
RSINow =
RSI(
PriceSeries,
RSIPeriod);
BBOscNow =
BBOsc(
PriceSeries,
BBPeriod,
2.0,
MAType_SMA);
MeanNow =
EMA(
PriceSeries,
MeanPeriod);
BuildFiveNodeGraph(
RawBogie,
SmoothSeries,
ATRNow,
PlusNow,
MinusNow,
ADXNow,
AroonNow,
MMINow,
RSINow,
BBOscNow,
MeanNow,
GraphSignals);
// ------------------------------------------------------------------------
// State-aware future target
// ------------------------------------------------------------------------
GNNTarget = 0.0;
TrainingState =
CausalTrainingState(
ADXNow,
MMINow);
TargetHorizon =
NeutralPredictionHorizonBars;
if(TrainingState == 1)
{
TargetHorizon =
TrendPredictionHorizonBars;
}
else if(TrainingState == -1)
{
TargetHorizon =
MeanPredictionHorizonBars;
}
if(Train)
{
FutureMove =
priceC(
-TargetHorizon)-
priceC(0);
TargetScale =
TargetATRScale*
ATRNow;
if(TargetScale < PIP)
TargetScale = PIP;
GNNTarget =
FutureMove/
TargetScale;
GNNTarget =
clamp(
GNNTarget,
-1.0,
1.0);
}
// ------------------------------------------------------------------------
// Zorro -> custom LibTorch neural() -> GNN
// ------------------------------------------------------------------------
GNNScore =
adviseLong(
NEURAL+BALANCED,
GNNTarget,
GraphSignals,
GRAPH_SIGNAL_COUNT);
// advise() collects the full training set.
// neural(NEURAL_TRAIN) is called by Zorro after the WFO training cycle.
if(Train)
return;
if(is(LOOKBACK))
return;
// ------------------------------------------------------------------------
// Learned graph state
// ------------------------------------------------------------------------
LearnedState =
LearnedGNNState();
plot(
"GNN Direction",
GNNScore,
NEW,
BLUE);
plot(
"P Trend",
100.0*
GNNStateTrendProbability,
0,
GREEN);
plot(
"P Mean",
-100.0*
GNNStateMeanProbability,
0,
RED);
plot(
"State Node",
100.0*
GNNNodeStateAuthority,
0,
BLACK);
// ------------------------------------------------------------------------
// Regime-aware exits
// ------------------------------------------------------------------------
// When the GNN currently classifies the environment as mean-reverting,
// a position is allowed to realize its snap-back at EMA(20).
if(LearnedState == -1)
{
if(
NumOpenLong > 0 &&
priceC(0) >= MeanNow)
{
exitLong();
return;
}
if(
NumOpenShort > 0 &&
priceC(0) <= MeanNow)
{
exitShort();
return;
}
}
// ------------------------------------------------------------------------
// Direction + state -> trade signal
// ------------------------------------------------------------------------
LongSignal = 0;
ShortSignal = 0;
if(LearnedState == 1)
{
if(
GNNScore >
TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
PlusNow > MinusNow)
{
LongSignal = 1;
}
}
else if(
GNNScore <
-TrendConfidenceThreshold)
{
if(
!RequireDIConfirmation ||
MinusNow > PlusNow)
{
ShortSignal = 1;
}
}
}
else if(LearnedState == -1)
{
if(
GNNScore >
MeanConfidenceThreshold &&
SmoothNow <=
HybridMeanLongExtreme &&
RSINow <=
HybridMeanLongRSIMax)
{
LongSignal = 1;
}
else if(
GNNScore <
-MeanConfidenceThreshold &&
SmoothNow >=
HybridMeanShortExtreme &&
RSINow >=
HybridMeanShortRSIMin)
{
ShortSignal = 1;
}
}
else if(AllowNeutralTrades)
{
if(
GNNScore >
NeutralConfidenceThreshold)
{
LongSignal = 1;
}
else if(
GNNScore <
-NeutralConfidenceThreshold)
{
ShortSignal = 1;
}
}
// ------------------------------------------------------------------------
// Calendar
// ------------------------------------------------------------------------
Allowed =
TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
// ------------------------------------------------------------------------
// Long
// ------------------------------------------------------------------------
if(LongSignal)
{
if(NumOpenShort > 0)
{
exitShort();
return;
}
if(NumOpenLong == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
ATRNow);
TrailDistance =
TrendTrailATRMult*
ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
ATRNow);
LogGraphEntry(
"LONG",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterLong();
}
}
return;
}
// ------------------------------------------------------------------------
// Short
// ------------------------------------------------------------------------
if(ShortSignal)
{
if(NumOpenLong > 0)
{
exitLong();
return;
}
if(NumOpenShort == 0)
{
if(LearnedState == 1)
{
ConfigureTrendTrade(
ATRNow);
TrailDistance =
TrendTrailATRMult*
ATRNow;
GuardDistance =
TrailGuardPips*
PIP;
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort(
GraphTrendTrailTMF,
TrailDistance,
GuardDistance);
}
else if(LearnedState == -1)
{
ConfigureMeanTrade(
ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort();
}
else if(AllowNeutralTrades)
{
ConfigureNeutralTrade(
ATRNow);
LogGraphEntry(
"SHORT",
LearnedState,
GNNScore,
ADXNow,
MMINow);
enterShort();
}
}
return;
}
// Neutral/no-signal state:
// no new entry. Existing positions remain protected by their stops/TMF.
}
258
93,969
Read More
|
|
09/09/26 12:58
// BogieNN_v3_RegimeHybridML.c
// -----------------------------------------------------------------------------
// Direction 3: Bogie-NN Regime-Adaptive Hybrid ML
// Zorro S 3.11+ lite-C
//
// This branch trains TWO separate native models:
//
// adviseLong -> TREND continuation model
// adviseShort -> MEAN-REVERSION model
//
// The Zorro manual permits adviseLong/adviseShort to generate two different
// models for the same asset/algo when an explicit Objective is supplied.
//
// Regime selector:
// TREND regime:
// ADX >= TrendADXMin and MMI <= TrendMMIMax
//
// MEAN regime:
// ADX <= MeanADXMax and MMI >= MeanMMIMin
//
// otherwise:
// NEUTRAL, no new trade.
//
// This is deliberately a model-of-models architecture:
// market regime -> choose specialist ML score -> trade.
// -----------------------------------------------------------------------------
string BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 60;
int WFOCycles = 8;
int WFOTrainPercent = 85;
int TrendPredictionHorizonBars = 12;
int MeanPredictionHorizonBars = 4;
var TrendConfidenceThreshold = 20.0;
var MeanConfidenceThreshold = 20.0;
int NocPeriod = 12;
int EmaPeriod = 5;
int FeatureCount = 8;
int ADXPeriod = 14;
int AroonPeriod = 25;
int MMIPeriod = 100;
int RSIPeriod = 14;
int BBPeriod = 20;
int MeanPeriod = 20;
var TrendADXMin = 25.0;
var TrendMMIMax = 64.0;
var MeanADXMax = 20.0;
var MeanMMIMin = 60.0;
var MeanLongExtreme = -0.45;
var MeanShortExtreme = 0.45;
var MeanLongRSIMax = 40.0;
var MeanShortRSIMin = 60.0;
var TrendStopATRMult = 2.5;
var TrendTrailATRMult = 3.0;
var MeanStopATRMult = 1.6;
var TrailGuardPips = 0.5;
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest = HH(NocPeriod,0);
Lowest = LL(NocPeriod,0);
Range = Highest-Lowest;
if(Range <= 0.0)
return 0.0;
Position = 2.0*(priceC(0)-Lowest)/Range-1.0;
return clamp(Position,-1.0,1.0);
}
void BuildTrendSignals(
var* SmoothSeries,
var ATRNow,
var PlusNow,
var MinusNow,
var ADXNow,
var AroonNow,
var MMINow,
var* Signals)
{
var ATRSafe;
var Momentum6;
var DISpread;
var TrendStrength;
var Trendiness;
ATRSafe = max(ATRNow,PIP);
Momentum6 = (priceC(0)-priceC(6))/(3.0*ATRSafe);
DISpread = (PlusNow-MinusNow)/100.0;
TrendStrength = (ADXNow-25.0)/25.0;
Trendiness = (75.0-MMINow)/25.0;
Signals[0] = clamp(SmoothSeries[0],-1.0,1.0);
Signals[1] = clamp(2.0*(SmoothSeries[0]-SmoothSeries[1]),-1.0,1.0);
Signals[2] = clamp(SmoothSeries[0]-SmoothSeries[3],-1.0,1.0);
Signals[3] = clamp(Momentum6,-1.0,1.0);
Signals[4] = clamp(DISpread,-1.0,1.0);
Signals[5] = clamp(TrendStrength,-1.0,1.0);
Signals[6] = clamp(AroonNow/100.0,-1.0,1.0);
Signals[7] = clamp(Trendiness,-1.0,1.0);
}
void BuildMeanSignals(
var RawBogie,
var SmoothNow,
var RSINow,
var BBOscNow,
var MeanNow,
var ATRNow,
var MMINow,
var ADXNow,
var* Signals)
{
var ATRSafe;
var MeanDeviation;
var Momentum3;
var MeanTendency;
var LowTrendStrength;
ATRSafe = max(ATRNow,PIP);
MeanDeviation = (priceC(0)-MeanNow)/(2.0*ATRSafe);
Momentum3 = (priceC(0)-priceC(3))/(2.0*ATRSafe);
MeanTendency = (MMINow-50.0)/25.0;
LowTrendStrength = (25.0-ADXNow)/25.0;
Signals[0] = clamp(SmoothNow,-1.0,1.0);
Signals[1] = clamp(RawBogie,-1.0,1.0);
Signals[2] = clamp((RSINow-50.0)/50.0,-1.0,1.0);
Signals[3] = clamp((BBOscNow-50.0)/50.0,-1.0,1.0);
Signals[4] = clamp(MeanDeviation,-1.0,1.0);
Signals[5] = clamp(Momentum3,-1.0,1.0);
Signals[6] = clamp(MeanTendency,-1.0,1.0);
Signals[7] = clamp(LowTrendStrength,-1.0,1.0);
}
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow = dow(0);
if(DayNow == MQLDayToZorro(NoTradeDay_1))
return 0;
if(DayNow == MQLDayToZorro(NoTradeDay_2))
return 0;
return 1;
}
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin = Equity-MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue = FreeMargin*RiskPercent/100.0/1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureTrendTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop = TrendStopATRMult*ATRNow;
TakeProfit = 0;
Trail = 0;
}
void ConfigureMeanTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop = MeanStopATRMult*ATRNow;
TakeProfit = 0;
Trail = 0;
}
int HybridTrendTrailTMF(var TrailDistance,var GuardDistance)
{
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailDistance <= 0.0)
return 0;
if(TradeIsShort)
{
Candidate = priceC(0)+TrailDistance;
if(TradeStopLimit > Candidate+GuardDistance)
TradeStopLimit = Candidate;
}
else
{
Candidate = priceC(0)-TrailDistance;
if(TradeStopLimit < Candidate-GuardDistance)
TradeStopLimit = Candidate;
}
return 0;
}
void LogHybrid(
string Side,
string RegimeName,
var ActiveScore,
var ADXNow,
var MMINow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | Regime %s | Score %.2f | ADX %.2f | MMI %.2f | Amount %.3f",
Asset,Bar,Side,RegimeName,ActiveScore,ADXNow,MMINow,Amount);
}
function run()
{
var RawBogie;
var SmoothNow;
var ATRNow;
var PlusNow;
var MinusNow;
var ADXNow;
var AroonNow;
var MMINow;
var RSINow;
var BBOscNow;
var MeanNow;
var TrendTarget;
var MeanTarget;
var TrendScore;
var MeanScore;
var TrendSignals[8];
var MeanSignals[8];
var* PriceSeries;
var* RawSeries;
var* SmoothSeries;
var TrailDistance;
var GuardDistance;
int Allowed;
int Regime;
int LongSignal;
int ShortSignal;
if(is(FIRSTINITRUN))
require(-3.11);
set(RULES);
set(TICKS);
set(RECALCULATE);
set(LOGFILE);
if(Train)
set(PEEK);
BarPeriod = StrategyBarPeriod;
LookBack = 300;
Capital = 10000;
StartDate = BacktestStart;
EndDate = BacktestEnd;
NumWFOCycles = WFOCycles;
DataSplit = WFOTrainPercent;
// The longest future target determines the leakage guard.
DataHorizon = TrendPredictionHorizonBars;
asset(BogieAsset);
algo("BogieHybridML");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
PriceSeries = series(priceC(0),256);
RawBogie = BogieRangePosition();
RawSeries = series(RawBogie,64);
SmoothNow = EMA(RawSeries,EmaPeriod);
SmoothSeries = series(SmoothNow,64);
ATRNow = ATR(14);
PlusNow = PlusDI(ADXPeriod);
MinusNow = MinusDI(ADXPeriod);
ADXNow = ADX(ADXPeriod);
AroonNow = AroonOsc(AroonPeriod);
MMINow = MMI(PriceSeries,MMIPeriod);
RSINow = RSI(PriceSeries,RSIPeriod);
BBOscNow = BBOsc(PriceSeries,BBPeriod,2.0,MAType_SMA);
MeanNow = EMA(PriceSeries,MeanPeriod);
BuildTrendSignals(
SmoothSeries,
ATRNow,
PlusNow,
MinusNow,
ADXNow,
AroonNow,
MMINow,
TrendSignals);
BuildMeanSignals(
RawBogie,
SmoothNow,
RSINow,
BBOscNow,
MeanNow,
ATRNow,
MMINow,
ADXNow,
MeanSignals);
TrendTarget = 0.0;
MeanTarget = 0.0;
if(Train)
{
if(priceC(-TrendPredictionHorizonBars) > priceC(0))
TrendTarget = 1.0;
else
TrendTarget = -1.0;
if(priceC(-MeanPredictionHorizonBars) > priceC(0))
MeanTarget = 1.0;
else
MeanTarget = -1.0;
}
// Explicit objectives are supplied. Therefore adviseLong and adviseShort
// act as two separately trained models rather than long/short trade-return
// targets.
TrendScore = adviseLong(
PERCEPTRON+FUZZY+BALANCED,
TrendTarget,
TrendSignals,
FeatureCount);
MeanScore = adviseShort(
PERCEPTRON+FUZZY+BALANCED,
MeanTarget,
MeanSignals,
FeatureCount);
if(Train)
return;
if(is(LOOKBACK))
return;
plot("Trend Model",TrendScore,NEW,BLUE);
plot("Mean Model",MeanScore,0,RED);
Regime = 0;
if(ADXNow >= TrendADXMin && MMINow <= TrendMMIMax)
Regime = 1;
else if(ADXNow <= MeanADXMax && MMINow >= MeanMMIMin)
Regime = -1;
// Mean-reversion regime positions exit at the center. Since no new mean
// trade can be opened outside Regime -1, this also prevents stale mean
// positions from remaining after the snap-back has completed.
if(Regime == -1)
{
if(NumOpenLong > 0 && priceC(0) >= MeanNow)
{
exitLong();
return;
}
if(NumOpenShort > 0 && priceC(0) <= MeanNow)
{
exitShort();
return;
}
}
LongSignal = 0;
ShortSignal = 0;
// Specialist 1: continuation.
if(Regime == 1)
{
if(TrendScore > TrendConfidenceThreshold && PlusNow > MinusNow)
LongSignal = 1;
else if(TrendScore < -TrendConfidenceThreshold && MinusNow > PlusNow)
ShortSignal = 1;
}
// Specialist 2: snap-back.
if(Regime == -1)
{
if(
MeanScore > MeanConfidenceThreshold
&& SmoothNow <= MeanLongExtreme
&& RSINow <= MeanLongRSIMax)
LongSignal = 1;
else if(
MeanScore < -MeanConfidenceThreshold
&& SmoothNow >= MeanShortExtreme
&& RSINow >= MeanShortRSIMin)
ShortSignal = 1;
}
Allowed = TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
if(LongSignal)
{
if(NumOpenShort > 0)
{
exitShort();
return;
}
if(NumOpenLong == 0)
{
if(Regime == 1)
{
ConfigureTrendTrade(ATRNow);
TrailDistance = TrendTrailATRMult*ATRNow;
GuardDistance = TrailGuardPips*PIP;
LogHybrid("LONG","TREND",TrendScore,ADXNow,MMINow);
enterLong(HybridTrendTrailTMF,TrailDistance,GuardDistance);
}
else if(Regime == -1)
{
ConfigureMeanTrade(ATRNow);
LogHybrid("LONG","MEAN",MeanScore,ADXNow,MMINow);
enterLong();
}
}
return;
}
if(ShortSignal)
{
if(NumOpenLong > 0)
{
exitLong();
return;
}
if(NumOpenShort == 0)
{
if(Regime == 1)
{
ConfigureTrendTrade(ATRNow);
TrailDistance = TrendTrailATRMult*ATRNow;
GuardDistance = TrailGuardPips*PIP;
LogHybrid("SHORT","TREND",TrendScore,ADXNow,MMINow);
enterShort(HybridTrendTrailTMF,TrailDistance,GuardDistance);
}
else if(Regime == -1)
{
ConfigureMeanTrade(ATRNow);
LogHybrid("SHORT","MEAN",MeanScore,ADXNow,MMINow);
enterShort();
}
}
return;
}
// Neutral regime: no new entries.
// Existing trend trades keep their stop/trailing protection.
// Existing mean trades keep their ATR stop and wait for the next bar's
// regime/mean-exit evaluation.
}
258
93,969
Read More
|
|
09/09/26 12:57
// BogieNN_v3_MeanReversionML.c
// -----------------------------------------------------------------------------
// Direction 2: Bogie-NN Mean-Reversion ML
// Zorro S 3.11+ lite-C
//
// Goal:
// Trade snap-back moves from short-term extremes rather than continuation.
//
// ML:
// PERCEPTRON + FUZZY + BALANCED
//
// 8 engineered features, approximately normalized to -1..+1:
// 1 smoothed Bogie range position
// 2 raw Bogie range position
// 3 RSI(14)
// 4 Bollinger-band oscillator
// 5 price deviation from EMA(20), normalized by ATR
// 6 3-bar momentum, normalized by ATR
// 7 MMI mean-reversion tendency
// 8 inverse ADX (positive when trend strength is low)
//
// Entry regime:
// MMI >= MeanMMIMin
// ADX <= MeanADXMax
// Bogie + RSI must be at an extreme
// ML must predict the reversal direction
//
// Exit:
// Price reaches EMA(20), or ML strongly predicts the opposite direction.
//
// Trade management:
// ATR stop. No trailing stop; the mean itself is the profit objective.
// -----------------------------------------------------------------------------
string BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 60;
int WFOCycles = 8;
int WFOTrainPercent = 85;
int PredictionHorizonBars = 4;
var ConfidenceThreshold = 20.0;
var OppositeExitThreshold = 15.0;
int NocPeriod = 12;
int EmaPeriod = 5;
int FeatureCount = 8;
int RSIPeriod = 14;
int BBPeriod = 20;
int MeanPeriod = 20;
int MMIPeriod = 100;
int ADXPeriod = 14;
var MeanMMIMin = 58.0;
var MeanADXMax = 28.0;
var LongBogieExtreme = -0.45;
var ShortBogieExtreme = 0.45;
var LongRSIMax = 40.0;
var ShortRSIMin = 60.0;
var StopATRMult = 1.6;
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest = HH(NocPeriod,0);
Lowest = LL(NocPeriod,0);
Range = Highest-Lowest;
if(Range <= 0.0)
return 0.0;
Position = 2.0*(priceC(0)-Lowest)/Range-1.0;
return clamp(Position,-1.0,1.0);
}
void BuildMeanSignals(
var RawBogie,
var SmoothNow,
var RSINow,
var BBOscNow,
var MeanNow,
var ATRNow,
var MMINow,
var ADXNow,
var* Signals)
{
var ATRSafe;
var MeanDeviation;
var Momentum3;
var MeanTendency;
var LowTrendStrength;
ATRSafe = max(ATRNow,PIP);
MeanDeviation = (priceC(0)-MeanNow)/(2.0*ATRSafe);
Momentum3 = (priceC(0)-priceC(3))/(2.0*ATRSafe);
MeanTendency = (MMINow-50.0)/25.0;
LowTrendStrength = (25.0-ADXNow)/25.0;
Signals[0] = clamp(SmoothNow,-1.0,1.0);
Signals[1] = clamp(RawBogie,-1.0,1.0);
Signals[2] = clamp((RSINow-50.0)/50.0,-1.0,1.0);
Signals[3] = clamp((BBOscNow-50.0)/50.0,-1.0,1.0);
Signals[4] = clamp(MeanDeviation,-1.0,1.0);
Signals[5] = clamp(Momentum3,-1.0,1.0);
Signals[6] = clamp(MeanTendency,-1.0,1.0);
Signals[7] = clamp(LowTrendStrength,-1.0,1.0);
}
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow = dow(0);
if(DayNow == MQLDayToZorro(NoTradeDay_1))
return 0;
if(DayNow == MQLDayToZorro(NoTradeDay_2))
return 0;
return 1;
}
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin = Equity-MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue = FreeMargin*RiskPercent/100.0/1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureMeanTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop = StopATRMult*ATRNow;
TakeProfit = 0;
Trail = 0;
}
void LogEntry(
string Side,
var Score,
var RawBogie,
var RSINow,
var ADXNow,
var MMINow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | MeanScore %.2f | Bogie %.3f | RSI %.2f | ADX %.2f | MMI %.2f | Amount %.3f",
Asset,Bar,Side,Score,RawBogie,RSINow,ADXNow,MMINow,Amount);
}
function run()
{
var RawBogie;
var SmoothNow;
var ATRNow;
var RSINow;
var BBOscNow;
var MeanNow;
var MMINow;
var ADXNow;
var MLTarget;
var MLScore;
var Signals[8];
var* PriceSeries;
var* RawSeries;
int Allowed;
int MeanRegime;
int LongSignal;
int ShortSignal;
if(is(FIRSTINITRUN))
require(-3.11);
set(RULES);
set(RECALCULATE);
set(LOGFILE);
if(Train)
set(PEEK);
BarPeriod = StrategyBarPeriod;
LookBack = 300;
Capital = 10000;
StartDate = BacktestStart;
EndDate = BacktestEnd;
NumWFOCycles = WFOCycles;
DataSplit = WFOTrainPercent;
DataHorizon = PredictionHorizonBars;
asset(BogieAsset);
algo("BogieMeanML");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
PriceSeries = series(priceC(0),256);
RawBogie = BogieRangePosition();
RawSeries = series(RawBogie,64);
SmoothNow = EMA(RawSeries,EmaPeriod);
ATRNow = ATR(14);
RSINow = RSI(PriceSeries,RSIPeriod);
BBOscNow = BBOsc(PriceSeries,BBPeriod,2.0,MAType_SMA);
MeanNow = EMA(PriceSeries,MeanPeriod);
MMINow = MMI(PriceSeries,MMIPeriod);
ADXNow = ADX(ADXPeriod);
BuildMeanSignals(
RawBogie,
SmoothNow,
RSINow,
BBOscNow,
MeanNow,
ATRNow,
MMINow,
ADXNow,
Signals);
MLTarget = 0.0;
if(Train)
{
if(priceC(-PredictionHorizonBars) > priceC(0))
MLTarget = 1.0;
else
MLTarget = -1.0;
}
MLScore = adviseLong(
PERCEPTRON+FUZZY+BALANCED,
MLTarget,
Signals,
FeatureCount);
if(Train)
return;
if(is(LOOKBACK))
return;
plot("Mean ML",MLScore,NEW,BLUE);
plot("Long Gate",ConfidenceThreshold,0,BLACK);
plot("Short Gate",-ConfidenceThreshold,0,BLACK);
// Mean-reversion positions have an explicit economic exit:
// close when price has returned to its EMA center.
if(NumOpenLong > 0)
{
if(priceC(0) >= MeanNow || MLScore < -OppositeExitThreshold)
{
exitLong();
return;
}
}
if(NumOpenShort > 0)
{
if(priceC(0) <= MeanNow || MLScore > OppositeExitThreshold)
{
exitShort();
return;
}
}
MeanRegime = 0;
if(MMINow >= MeanMMIMin && ADXNow <= MeanADXMax)
MeanRegime = 1;
LongSignal = 0;
ShortSignal = 0;
if(MeanRegime)
{
if(
MLScore > ConfidenceThreshold
&& SmoothNow <= LongBogieExtreme
&& RSINow <= LongRSIMax)
LongSignal = 1;
else if(
MLScore < -ConfidenceThreshold
&& SmoothNow >= ShortBogieExtreme
&& RSINow >= ShortRSIMin)
ShortSignal = 1;
}
Allowed = TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
if(NumOpenLong > 0 || NumOpenShort > 0)
return;
if(LongSignal)
{
ConfigureMeanTrade(ATRNow);
LogEntry("LONG",MLScore,RawBogie,RSINow,ADXNow,MMINow);
enterLong();
}
else if(ShortSignal)
{
ConfigureMeanTrade(ATRNow);
LogEntry("SHORT",MLScore,RawBogie,RSINow,ADXNow,MMINow);
enterShort();
}
}
258
93,969
Read More
|
|
09/09/26 12:20
// BogieNN_v3_TrendML.c
// -----------------------------------------------------------------------------
// Direction 1: Bogie-NN Trend / Continuation ML
// Zorro S 3.11+ lite-C
//
// Goal:
// Predict continuation over a medium H1 horizon, but only trade when the
// market already exhibits directional structure.
//
// ML:
// PERCEPTRON + FUZZY + BALANCED
//
// 8 engineered features, all approximately normalized to -1..+1:
// 1 Bogie EMA oscillator level
// 2 1-bar Bogie slope
// 3 3-bar Bogie slope
// 4 6-bar ATR-normalized price momentum
// 5 +DI/-DI directional spread
// 6 ADX trend strength
// 7 Aroon oscillator
// 8 MMI "trendiness" (higher when MMI is lower)
//
// Entry regime:
// ADX >= TrendADXMin
// MMI <= TrendMMIMax
// DI direction agrees with ML prediction
//
// Trade management:
// ATR-based stop and ATR-based trailing distance.
// -----------------------------------------------------------------------------
string BogieAsset = "EUR/USD";
int BacktestStart = 2010;
int BacktestEnd = 2026;
int StrategyBarPeriod = 60;
int WFOCycles = 8;
int WFOTrainPercent = 85;
int PredictionHorizonBars = 12;
var ConfidenceThreshold = 20.0;
int NocPeriod = 12;
int EmaPeriod = 5;
int FeatureCount = 8;
int ADXPeriod = 14;
int AroonPeriod = 25;
int MMIPeriod = 100;
var TrendADXMin = 20.0;
var TrendMMIMax = 65.0;
var StopATRMult = 2.5;
var TrailATRMult = 3.0;
var TrailGuardPips = 0.5;
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
int CloseOnNoTradeDay = 0;
int UseDiagnostics = 1;
var BogieRangePosition()
{
var Highest;
var Lowest;
var Range;
var Position;
Highest = HH(NocPeriod,0);
Lowest = LL(NocPeriod,0);
Range = Highest-Lowest;
if(Range <= 0.0)
return 0.0;
Position = 2.0*(priceC(0)-Lowest)/Range-1.0;
return clamp(Position,-1.0,1.0);
}
void BuildTrendSignals(
var* SmoothSeries,
var ATRNow,
var PlusNow,
var MinusNow,
var ADXNow,
var AroonNow,
var MMINow,
var* Signals)
{
var ATRSafe;
var Momentum6;
var DISpread;
var TrendStrength;
var Trendiness;
ATRSafe = max(ATRNow,PIP);
Momentum6 = (priceC(0)-priceC(6))/(3.0*ATRSafe);
DISpread = (PlusNow-MinusNow)/100.0;
TrendStrength = (ADXNow-25.0)/25.0;
Trendiness = (75.0-MMINow)/25.0;
Signals[0] = clamp(SmoothSeries[0],-1.0,1.0);
Signals[1] = clamp(2.0*(SmoothSeries[0]-SmoothSeries[1]),-1.0,1.0);
Signals[2] = clamp(SmoothSeries[0]-SmoothSeries[3],-1.0,1.0);
Signals[3] = clamp(Momentum6,-1.0,1.0);
Signals[4] = clamp(DISpread,-1.0,1.0);
Signals[5] = clamp(TrendStrength,-1.0,1.0);
Signals[6] = clamp(AroonNow/100.0,-1.0,1.0);
Signals[7] = clamp(Trendiness,-1.0,1.0);
}
int MQLDayToZorro(int MqlDay)
{
if(MqlDay == 0)
return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int DayNow;
DayNow = dow(0);
if(DayNow == MQLDayToZorro(NoTradeDay_1))
return 0;
if(DayNow == MQLDayToZorro(NoTradeDay_2))
return 0;
return 1;
}
var LegacyBogieAmount()
{
var AmountValue;
var FreeMargin;
var Step;
if(!UseMM)
return FixedAmount;
FreeMargin = Equity-MarginVal;
if(FreeMargin < 0.0)
FreeMargin = 0.0;
AmountValue = FreeMargin*RiskPercent/100.0/1000.0;
if(MiniAcct)
{
Step = 0.01;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.01)
AmountValue = 0.01;
}
else
{
Step = 0.10;
AmountValue = roundto(AmountValue,Step);
if(AmountValue < 0.10)
AmountValue = 0.10;
}
if(AmountValue > 50.0)
AmountValue = 50.0;
return AmountValue;
}
void ConfigureTrendTrade(var ATRNow)
{
Amount = LegacyBogieAmount();
Risk = 0;
Stop = StopATRMult*ATRNow;
TakeProfit = 0;
Trail = 0;
}
// Custom fixed-distance trailing based on ATR measured at entry.
// Parameter is a PRICE DISTANCE, not pips.
int TrendTrailTMF(var TrailDistance,var GuardDistance)
{
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailDistance <= 0.0)
return 0;
if(TradeIsShort)
{
Candidate = priceC(0)+TrailDistance;
if(TradeStopLimit > Candidate+GuardDistance)
TradeStopLimit = Candidate;
}
else
{
Candidate = priceC(0)-TrailDistance;
if(TradeStopLimit < Candidate-GuardDistance)
TradeStopLimit = Candidate;
}
return 0;
}
void LogEntry(string Side,var Score,var ADXNow,var MMINow,var ATRNow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s | TrendScore %.2f | ADX %.2f | MMI %.2f | ATR %.5f | Amount %.3f",
Asset,Bar,Side,Score,ADXNow,MMINow,ATRNow,Amount);
}
function run()
{
var RawBogie;
var SmoothNow;
var ATRNow;
var PlusNow;
var MinusNow;
var ADXNow;
var AroonNow;
var MMINow;
var MLTarget;
var MLScore;
var TrailDistance;
var GuardDistance;
var Signals[8];
var* PriceSeries;
var* RawSeries;
var* SmoothSeries;
int Allowed;
int TrendRegime;
int LongSignal;
int ShortSignal;
if(is(FIRSTINITRUN))
require(-3.11);
set(RULES);
set(TICKS);
set(RECALCULATE);
set(LOGFILE);
if(Train)
set(PEEK);
BarPeriod = StrategyBarPeriod;
LookBack = 300;
Capital = 10000;
StartDate = BacktestStart;
EndDate = BacktestEnd;
NumWFOCycles = WFOCycles;
DataSplit = WFOTrainPercent;
DataHorizon = PredictionHorizonBars;
asset(BogieAsset);
algo("BogieTrendML");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
PriceSeries = series(priceC(0),256);
RawBogie = BogieRangePosition();
RawSeries = series(RawBogie,64);
SmoothNow = EMA(RawSeries,EmaPeriod);
SmoothSeries = series(SmoothNow,64);
ATRNow = ATR(14);
PlusNow = PlusDI(ADXPeriod);
MinusNow = MinusDI(ADXPeriod);
ADXNow = ADX(ADXPeriod);
AroonNow = AroonOsc(AroonPeriod);
MMINow = MMI(PriceSeries,MMIPeriod);
BuildTrendSignals(
SmoothSeries,
ATRNow,
PlusNow,
MinusNow,
ADXNow,
AroonNow,
MMINow,
Signals);
MLTarget = 0.0;
if(Train)
{
if(priceC(-PredictionHorizonBars) > priceC(0))
MLTarget = 1.0;
else
MLTarget = -1.0;
}
MLScore = adviseLong(
PERCEPTRON+FUZZY+BALANCED,
MLTarget,
Signals,
FeatureCount);
if(Train)
return;
if(is(LOOKBACK))
return;
plot("Trend ML",MLScore,NEW,BLUE);
plot("Long Gate",ConfidenceThreshold,0,BLACK);
plot("Short Gate",-ConfidenceThreshold,0,BLACK);
TrendRegime = 0;
if(ADXNow >= TrendADXMin && MMINow <= TrendMMIMax)
TrendRegime = 1;
LongSignal = 0;
ShortSignal = 0;
if(TrendRegime)
{
if(MLScore > ConfidenceThreshold && PlusNow > MinusNow)
LongSignal = 1;
else if(MLScore < -ConfidenceThreshold && MinusNow > PlusNow)
ShortSignal = 1;
}
Allowed = TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort > 0)
exitShort();
}
return;
}
if(LongSignal)
{
if(NumOpenShort > 0)
exitShort();
if(NumOpenLong == 0 && NumOpenShort == 0)
{
ConfigureTrendTrade(ATRNow);
TrailDistance = TrailATRMult*ATRNow;
GuardDistance = TrailGuardPips*PIP;
LogEntry("LONG",MLScore,ADXNow,MMINow,ATRNow);
enterLong(TrendTrailTMF,TrailDistance,GuardDistance);
}
return;
}
if(ShortSignal)
{
if(NumOpenLong > 0)
exitLong();
if(NumOpenShort == 0 && NumOpenLong == 0)
{
ConfigureTrendTrade(ATRNow);
TrailDistance = TrailATRMult*ATRNow;
GuardDistance = TrailGuardPips*PIP;
LogEntry("SHORT",MLScore,ADXNow,MMINow,ATRNow);
enterShort(TrendTrailTMF,TrailDistance,GuardDistance);
}
return;
}
// When trend structure disappears, do not immediately force an exit.
// The position remains protected by its ATR stop and trailing rule.
}
258
93,969
Read More
|
|
|