5 registered members (TipmyPip , AndrewAMD , Quad , 2 invisible),
4,696
guests, and 0
spiders.
Key:
Admin ,
Global Mod ,
Mod
NonlinearPriceTimeManifold v.1.02
[Re: TipmyPip ]
#489615
08/29/26 06:56
08/29/26 06:56
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
If any one has any doubts about the process, we shall all learn from you... : (there was an error in the previous file, so I uploaded a new code without the error.) I suppose LibTorch will do much better...
Attached Files
Last edited by TipmyPip; 08/29/26 08:34 .
BogieNN v 0.01
[Re: TipmyPip ]
#489624
1 hour ago
1 hour ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// BogieNN_v8_Zorro.c
// -----------------------------------------------------------------------------
// Self-contained Zorro lite-C reconstruction of Bogie-NN-v8 + Bogie-NN-IND-v8.
//
// Signal path preserved from the supplied MQL4 sources:
// H1 OHLC -> 12-bar NOC normalization -> EMA(5)
// -> 17 lagged inputs (offsets 2..18 in indicator space)
// -> neural net 17 -> 17 -> 5 -> 1, sigmoid activations
// -> output 0..100
// -> BUY on upward cross of 54, SELL on downward cross of 74.
//
// Trade logic preserved:
// - one position at a time
// - default SL 200 pips, TP disabled, trailing distance 170 pips
// - opposite signal closes/reverses
// - no-trade-day behavior preserves the original asymmetry:
// long positions close on a no-trade day, short positions do not
// - MT4-style money management is mapped to Zorro Amount, where Amount=1
// is approximately one standard FX lot (100,000 units).
//
// Important source correction:
// The MQL4 source declared gda_128[305] but writes indices 0..305.
// The mathematically correct first-layer parameter count is 306, used here.
// -----------------------------------------------------------------------------
// ----------------------------- User settings ---------------------------------
string BogieAsset = "EUR/USD";
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.0; // MT4-style standard lots when UseMM == 0
var TakeProfitPips = 0.0;
var StopLossPips = 200.0;
var TrailingStopPips = 170.0;
var BuyTrigger = 54.0;
var SellTrigger = 74.0;
// MQL4 DayOfWeek: Sunday=0, Monday=1, ..., Saturday=6.
// These inputs intentionally retain that numbering.
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
// Original code used 5*Point as the minimum stop adjustment.
// On a typical 5-digit FX quote, that is 0.5 pip. Set to 5.0 for an old 4-digit
// quote if strict historical emulation is required.
var TrailGuardPips = 0.5;
// Indicator constants from Bogie-NN-IND-v8.
int NocPeriod = 12;
var NocMinRange = 0.012;
int EmaPeriod = 5;
// -------------------------- Embedded NN parameters ----------------------------
var W1[306] = {
0.376436, 0.690657, 0.512335, 0.786179, 0.671377, 0.614279,
0.53975, 0.82038, 0.750566, 0.70789, 0.396094, 0.72572,
0.555349, 0.395257, 0.22728, 0.128274, 0.289072, 0.387067,
0.66245, 1.019812, 0.761206, 0.98428, 0.878235, 0.829384,
0.786234, 1.189799, 1.112219, 1.069424, 0.640762, 0.73067,
0.067006, -0.320953, -0.60088, -0.371686, 0.261142, 0.063323,
0.413053, 0.737687, 0.555759, 0.815068, 0.692078, 0.630406,
0.556191, 0.84638, 0.763236, 0.715549, 0.390297, 0.705948,
0.482014, 0.291501, 0.101741, 0.048724, 0.280508, 0.428389,
-6.065296, 0.559039, 1.732407, 2.446417, 2.226664, 1.043498,
-0.892194, 0.53867, 1.696599, 2.895421, 1.936742, 0.857415,
1.777173, 1.489486, 0.990452, -0.312611, -2.485575, 5.784152,
0.691233, 1.003065, 0.74676, 0.973191, 0.868018, 0.821039,
0.770604, 1.139881, 1.057454, 1.00439, 0.595049, 0.733458,
0.17767, -0.170966, -0.443584, -0.34547, 0.135814, -0.010206,
0.585687, 1.040471, 0.778059, 0.994592, 0.882501, 0.81993,
0.783313, 1.225092, 1.138965, 1.112052, 0.667895, 0.725504,
-0.042205, -0.470107, -0.758346, -0.356139, 0.477461, 0.159831,
0.465873, 0.827723, 0.635881, 0.876557, 0.751904, 0.689548,
0.624036, 0.943642, 0.844483, 0.793347, 0.439269, 0.710473,
0.362673, 0.107347, -0.123129, -0.079149, 0.300953, 0.33311,
36.878339, -34.157785, 13.570499, -9.445193, 5.906855, -2.631383,
-3.46685, 1.387212, 1.16875, -1.25244, -4.167107, 7.462264,
4.218665, -10.616881, 2.816792, -0.854816, 0.465124, 3.805423,
17.371809, -10.758329, 4.288758, -3.3009, -1.387066, -0.545407,
-0.627846, 0.359405, -0.910465, 1.672222, -1.087077, 1.71169,
1.892743, 1.370835, -1.074922, -2.069598, 2.132245, 3.323435,
3.192245, 0.722216, 0.595036, 2.562026, 4.118245, 2.533825,
0.661547, -0.522384, -0.923482, -0.744284, -0.531758, -1.737114,
-0.894963, 0.335197, 2.767129, 0.169424, -1.86823, 2.057215,
0.408584, 0.730536, 0.549013, 0.810092, 0.687577, 0.62613,
0.551439, 0.839836, 0.758454, 0.711269, 0.388133, 0.706349,
0.491137, 0.305519, 0.119262, 0.059367, 0.280111, 0.432421,
11.3829, -2.355699, 0.800231, -0.812782, -1.725548, 0.506914,
3.685745, -1.57036, -0.67807, -1.385545, 0.665419, -2.403472,
0.348713, 0.948801, 3.943966, 0.60901, -2.954932, 6.570737,
1.09868, 0.457162, 0.047123, 0.314413, 0.270546, 0.271241,
0.44434, 0.685449, 0.881795, 0.904727, 0.538997, 0.274688,
-0.10726, -0.182735, -0.297917, -0.722563, -1.004438, 2.114029,
0.625319, 1.073737, 0.794572, 1.012257, 0.909403, 0.851104,
0.819041, 1.284499, 1.213157, 1.195116, 0.731964, 0.733472,
-0.119051, -0.57439, -0.865588, -0.405175, 0.494158, 0.142718,
0.44362, 0.790221, 0.60428, 0.852407, 0.728354, 0.666281,
0.59699, 0.903026, 0.80912, 0.758782, 0.416953, 0.708992,
0.41504, 0.186673, -0.028387, -0.027435, 0.289143, 0.376176,
0.377669, 0.691528, 0.513124, 0.786421, 0.670892, 0.613345,
0.538605, 0.819386, 0.749119, 0.706133, 0.394004, 0.723393,
0.551766, 0.390955, 0.222564, 0.125193, 0.287931, 0.397175,
0.390177, 0.704718, 0.524964, 0.793337, 0.673926, 0.614109,
0.538623, 0.821537, 0.747044, 0.702102, 0.386249, 0.712341,
0.526445, 0.357474, 0.183363, 0.099539, 0.282065, 0.431269
};
var W2[90] = {
0.313367, 0.600233, 0.413307, 0.723853, 0.604417, 0.564724,
0.494233, 0.776964, 0.7137, 0.689037, 0.403412, 0.74366,
0.681661, 0.582135, 0.470205, 0.320594, 0.360557, 0.505358,
0.207009, -0.600315, 0.292799, -0.486397, -0.698483, -0.466675,
0.16168, 1.267072, 0.942115, 1.601429, 0.294829, 2.066898,
1.149577, -0.608109, 0.236174, 0.223691, 0.281142, 2.639872,
0.298441, 0.585156, 0.397331, 0.714002, 0.591642, 0.547087,
0.477829, 0.768086, 0.709662, 0.679375, 0.387499, 0.736123,
0.665259, 0.56451, 0.453956, 0.305558, 0.345016, 0.50831,
-0.677255, -1.032676, -0.744642, -0.925155, -1.197128, -0.992372,
-0.855172, 7.016197, 5.226784, 2.411577, -0.733721, 3.402664,
1.729731, -0.991801, -0.815011, -0.677321, -0.693829, 9.138765,
-0.24649, -0.391846, -0.34772, -1.096698, -0.001496, -0.693606,
-0.468658, 6.178259, 4.306803, 2.40946, -0.336563, 2.374341,
1.504236, -0.726152, -0.426187, -0.25484, -0.293797, -1.89273
};
var W3[6] = {
-0.62616, 1.717378, -0.485501, 2.751261, 2.323994, 2.047616
};
// ----------------------------- Neural network --------------------------------
var Sigmoid(var x)
{
var ex;
// Algebraically equivalent to the original tanh-like expression followed
// by (x+1)/2, but numerically safer for large magnitudes.
if(x > 40.0) return 0.999999999;
if(x < -40.0) return 0.000000001;
ex = pow(2.7182818,x);
return ex/(1.0 + ex);
}
var ApplyNN(vars EmaSeries, int StartOffset)
{
var H1[17];
var H2[5];
var sum;
int neuron;
int input;
int k;
// Layer 1: 17 inputs -> 17 hidden neurons.
// Each neuron owns 17 weights followed by one bias. The MQL code SUBTRACTS
// the stored bias, so we preserve sum(weights*inputs) - bias.
k = 0;
for(neuron = 0; neuron < 17; neuron++) {
sum = 0.0;
for(input = 0; input < 17; input++) {
sum += W1[k] * EmaSeries[StartOffset + input];
k++;
}
sum -= W1[k];
k++;
H1[neuron] = Sigmoid(sum);
}
// Layer 2: 17 -> 5.
k = 0;
for(neuron = 0; neuron < 5; neuron++) {
sum = 0.0;
for(input = 0; input < 17; input++) {
sum += W2[k] * H1[input];
k++;
}
sum -= W2[k];
k++;
H2[neuron] = Sigmoid(sum);
}
// Output layer: 5 -> 1.
sum = 0.0;
for(input = 0; input < 5; input++)
sum += W3[input] * H2[input];
sum -= W3[5];
return 100.0 * Sigmoid(sum);
}
// ----------------------------- Indicator front-end ----------------------------
var NocValue()
{
var Highest = priceH(0);
var Lowest = priceL(0);
var CloseNow = priceC(0);
var Range;
int i;
for(i = 1; i < NocPeriod; i++) {
if(priceH(i) > Highest) Highest = priceH(i);
if(priceL(i) < Lowest) Lowest = priceL(i);
}
Range = Highest - Lowest;
// Exact MQL4 expression, not a simplified approximation. When Range is
// below 0.012, the denominator remains fixed at 0.012 and the value is
// compressed around 0.5.
if(Range > NocMinRange)
return (CloseNow - Lowest - (Highest - CloseNow)) / Range / 2.0 + 0.5;
else
return (CloseNow - Lowest - (Highest - CloseNow)) / NocMinRange / 2.0 + 0.5;
}
// ----------------------------- Calendar mapping -------------------------------
int MQLDayToZorro(int MqlDay)
{
// MQL4: Sunday=0. Zorro: Monday=1 ... Sunday=7.
if(MqlDay == 0) return 7;
return MqlDay;
}
int TradeAllowedToday()
{
int d = dow(0);
if(d == MQLDayToZorro(NoTradeDay_1)) return 0;
if(d == MQLDayToZorro(NoTradeDay_2)) return 0;
return 1;
}
// ----------------------------- Position sizing --------------------------------
var LotsOptimizedAmount()
{
var amount;
var freeMargin;
var step;
if(!UseMM)
return FixedAmount;
// Closest Zorro analogue of AccountFreeMargin().
freeMargin = Equity - MarginVal;
if(freeMargin < 0.0) freeMargin = 0.0;
// Original MQL4 formula:
// AccountFreeMargin() * Risk / 100 / 1000
amount = freeMargin * RiskPercent / 100.0 / 1000.0;
if(MiniAcct) {
step = 0.01;
amount = roundto(amount,step);
if(amount < 0.01) amount = 0.01;
} else {
step = 0.1;
amount = roundto(amount,step);
if(amount < 0.1) amount = 0.1;
}
if(amount > 50.0) amount = 50.0;
return amount;
}
// ----------------------------- Exact trailing ---------------------------------
int BogieTrailTMF(var TrailPips, var GuardPips)
{
var distance;
var guard;
var candidate;
if(!TradeIsOpen) return 0;
if(TrailPips <= 0.0) return 0;
distance = TrailPips * PIP;
guard = GuardPips * PIP;
if(TradeIsShort) {
// Original SELL rule:
// if(OrderStopLoss() > Ask + TrailingStop + 5*Point)
// SL = Ask + TrailingStop;
candidate = priceC(0) + distance;
if(TradeStopLimit > candidate + guard)
TradeStopLimit = candidate;
} else {
// Zorro stop limits are expressed on the ask-price scale; this preserves
// the original Bid-relative long trailing distance after spread handling.
candidate = priceC(0) - distance;
if(TradeStopLimit < candidate - guard)
TradeStopLimit = candidate;
}
return 0;
}
// ------------------------------- Strategy -------------------------------------
function run()
{
vars NocSeries;
vars EmaSeries;
var NocNow;
var EmaNow;
var NN_Shift1;
var NN_Shift2;
var TradeAmount;
int BuySignal;
int SellSignal;
int Allowed;
set(TICKS); // TMF executes on incoming ticks/quotes.
BarPeriod = 60; // Original iCustom() signal timeframe: PERIOD_H1.
LookBack = 250; // >= original 200-bar warmup + NN/EMA history.
Capital = 10000;
asset(BogieAsset);
algo("BogieNNv8");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
// Create the normalized oscillator and its EMA every bar. Series calls stay
// unconditional, as required by Zorro.
NocNow = NocValue();
NocSeries = series(NocNow,64);
EmaNow = EMA(NocSeries,EmaPeriod);
EmaSeries = series(EmaNow,64);
// MQL4 EA reads indicator buffer 1 at shifts 1 and 2.
// Indicator output at shift s uses EMA values s+2 ... s+18.
// Therefore:
// shift 1 -> EmaSeries[3..19]
// shift 2 -> EmaSeries[4..20]
NN_Shift1 = ApplyNN(EmaSeries,3);
NN_Shift2 = ApplyNN(EmaSeries,4);
BuySignal = (NN_Shift2 <= BuyTrigger) && (NN_Shift1 >= BuyTrigger);
SellSignal = (NN_Shift2 >= SellTrigger) && (NN_Shift1 <= SellTrigger);
plot("BogieNN",NN_Shift1,NEW,RED);
plot("Buy54",BuyTrigger,0,BLACK);
plot("Sell74",SellTrigger,0,BLACK);
if(is(LOOKBACK)) return;
Allowed = TradeAllowedToday();
// Preserve the original exit asymmetry exactly:
// BUY closes on SELL signal OR no-trade day.
if(NumOpenLong > 0) {
if(SellSignal || !Allowed)
exitLong();
}
// SELL closes only on BUY signal; no-trade-day alone does not close it.
if(NumOpenShort > 0) {
if(BuySignal)
exitShort();
}
// No new entries on forbidden days.
if(!Allowed) return;
// Original EA allows only one symbol+magic position at once.
if(NumOpenLong == 0 && NumOpenShort == 0) {
TradeAmount = LotsOptimizedAmount();
Amount = TradeAmount;
// Disable Zorro's risk-based sizing; Amount reproduces MT4-lot sizing.
Risk = 0;
if(StopLossPips > 0.0) Stop = StopLossPips * PIP;
else Stop = 0;
if(TakeProfitPips > 0.0) TakeProfit = TakeProfitPips * PIP;
else TakeProfit = 0;
// Built-in Zorro Trail has different semantics from the MT4 EA.
Trail = 0;
if(BuySignal)
enterLong(BogieTrailTMF,TrailingStopPips,TrailGuardPips);
else if(SellSignal)
enterShort(BogieTrailTMF,TrailingStopPips,TrailGuardPips);
}
}
BogieNN v0.02
[Re: TipmyPip ]
#489625
1 hour ago
1 hour ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// BogieNN_v2_NativeML.c
// -----------------------------------------------------------------------------
// Zorro S 3.11+ lite-C native machine-learning version of Bogie-NN.
//
// Purpose:
// Replace the frozen 2008 17->17->5->1 neural weights with Zorro's native
// PERCEPTRON + FUZZY + BALANCED training through adviseLong().
//
// Baseline feature path:
// H1 OHLC
// -> 12-bar close position inside Highest/Lowest range
// -> natural normalization to -1..+1
// -> EMA(5)
// -> 17 historical observations, preserving Bogie's original 2-bar gap
// (EMA offsets 2..18)
// -> Zorro PERCEPTRON
//
// Training target:
// +1 when price is higher PredictionHorizonBars into the future
// -1 otherwise
//
// Important:
// 1. Run [Train] before [Test] or [Trade].
// 2. If PredictionHorizonBars, NumWFOCycles, dates, feature construction,
// asset, or algo identifier are changed, TRAIN AGAIN.
// 3. PEEK is enabled only in Train mode. DataHorizon blocks the first
// PredictionHorizonBars of every WFO test segment to prevent leakage.
// 4. The old BogieNN_v8_Zorro file should be kept separately as benchmark.
// -----------------------------------------------------------------------------
// ----------------------------- User settings ---------------------------------
string BogieAsset = "EUR/USD";
// Reproducible WFO evaluation window.
// A year value is accepted by Zorro. Change these only if the required history
// is available, then retrain.
int BacktestStart = 2010;
int BacktestEnd = 2026;
// Original signal timeframe.
int StrategyBarPeriod = 60;
// WFO / machine-learning settings.
int WFOCycles = 8;
int WFOTrainPercent = 85;
int PredictionHorizonBars = 6;
// FUZZY PERCEPTRON normally returns approximately -100..+100.
// Long above +threshold, short below -threshold.
var ConfidenceThreshold = 25.0;
// Bogie front-end.
int NocPeriod = 12;
int EmaPeriod = 5;
int FeatureCount = 17;
int FeatureStartOffset = 2; // preserve original Bogie NN lag gap
// Trade management - retained initially for benchmark comparability.
var StopLossPips = 200.0;
var TakeProfitPips = 0.0;
var TrailingStopPips = 170.0;
var TrailGuardPips = 0.5;
// Original-style position sizing.
// This is retained deliberately so v8 and v2 can be compared with the signal
// model as the main changed variable.
int UseMM = 1;
int MiniAcct = 0;
var RiskPercent = 5.0;
var FixedAmount = 0.10;
// MQL4-style no-trade-day numbering:
// Sunday=0, Monday=1, ... Saturday=6.
int NoTradeDay_1 = 0;
int NoTradeDay_2 = 0;
// Improved symmetric behavior:
// 0 = only block NEW entries on a forbidden day.
// 1 = close both long and short positions on a forbidden day.
int CloseOnNoTradeDay = 0;
// Logging.
int UseDiagnostics = 1;
// --------------------------- Bogie feature front-end ---------------------------
// Returns the current close position inside the last NocPeriod high/low range
// directly in -1..+1.
//
// -1 = close at the lowest low
// 0 = close at the middle of the range
// +1 = close at the highest high
//
// Unlike the 2008 indicator, there is no fixed 0.012 absolute denominator.
// This makes the feature scale portable across volatility regimes and assets.
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);
}
// Fill the native ML feature vector.
//
// The original Bogie network used 17 smoothed values with a 2-bar offset.
// We preserve that structure here:
// Signals[0] = EMA value 2 bars ago
// ...
// Signals[16] = EMA value 18 bars ago
void BuildBogieSignals(var* SmoothSeries,var* Signals)
{
int i;
for(i=0; i<FeatureCount; i++)
Signals[i] = clamp(SmoothSeries[FeatureStartOffset+i],-1.0,1.0);
}
// ----------------------------- Calendar mapping -------------------------------
int MQLDayToZorro(int MqlDay)
{
// MQL4: Sunday=0.
// Zorro: Monday=1 ... Sunday=7.
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 --------------------------------
// Preserve the old EA sizing formula for the first controlled comparison.
//
// MT4 formula:
// AccountFreeMargin() * RiskPercent / 100 / 1000
//
// Zorro Amount is similar to an MT4 FX lot: Amount=1 is about 100,000 units.
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;
}
// Configure the subsequent Zorro entry.
void ConfigureTradeParameters()
{
Amount = LegacyBogieAmount();
// Amount, not Zorro's built-in Risk, controls the position size in this
// benchmark version.
Risk = 0;
if(StopLossPips > 0.0)
Stop = StopLossPips*PIP;
else
Stop = 0;
if(TakeProfitPips > 0.0)
TakeProfit = TakeProfitPips*PIP;
else
TakeProfit = 0;
// Use the custom TMF below, not Zorro's standard Trail algorithm.
Trail = 0;
}
// ---------------------------- MT4-style trailing -------------------------------
// The old EA continuously moved the stop to:
//
// BUY -> current Bid - TrailingStop
// SELL -> current Ask + TrailingStop
//
// but only if the new level improves the old stop by more than the guard.
//
// Zorro's trade prices/stops are maintained on its internal ask-price scale.
// The formulas below preserve the equivalent distance.
int BogieTrailTMF(var TrailPips,var GuardPips)
{
var Distance;
var Guard;
var Candidate;
if(!TradeIsOpen)
return 0;
if(TrailPips <= 0.0)
return 0;
Distance = TrailPips*PIP;
Guard = GuardPips*PIP;
if(TradeIsShort)
{
Candidate = priceC(0) + Distance;
if(TradeStopLimit > Candidate + Guard)
TradeStopLimit = Candidate;
}
else
{
Candidate = priceC(0) - Distance;
if(TradeStopLimit < Candidate - Guard)
TradeStopLimit = Candidate;
}
return 0;
}
// ----------------------------- Diagnostics ------------------------------------
void LogLongEntry(var MLScore,var SmoothNow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i LONG entry | ML %.2f | BogieSmooth %.4f | Amount %.3f",
Asset,Bar,MLScore,SmoothNow,Amount);
}
void LogShortEntry(var MLScore,var SmoothNow)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i SHORT entry | ML %.2f | BogieSmooth %.4f | Amount %.3f",
Asset,Bar,MLScore,SmoothNow,Amount);
}
void LogExit(string Side,var MLScore)
{
if(!UseDiagnostics)
return;
printf(
"\n%s Bar %i %s exit/reversal | ML %.2f",
Asset,Bar,Side,MLScore);
}
// -------------------------------- Strategy ------------------------------------
function run()
{
var RawBogie;
var SmoothNow;
var MLTarget;
var MLScore;
var Signals[17];
var* RawSeries;
var* SmoothSeries;
int Allowed;
int LongSignal;
int ShortSignal;
// ----------------------- Global/session setup -----------------------------
if(is(FIRSTINITRUN))
require(-3.11); // Zorro S 3.11 or newer
// RULES is mandatory for advise training/loading.
// TICKS is used by the custom trade management function.
// RECALCULATE rebuilds indicator history for each WFO cycle.
set(RULES);
set(TICKS);
set(RECALCULATE);
set(LOGFILE);
// PEEK is needed only while TRAINING the future-price target.
// Never access negative price offsets in Test or Trade mode.
if(Train)
set(PEEK);
BarPeriod = StrategyBarPeriod;
LookBack = 250;
Capital = 10000;
StartDate = BacktestStart;
EndDate = BacktestEnd;
NumWFOCycles = WFOCycles;
DataSplit = WFOTrainPercent;
// Training target looks this many bars into the future.
// Block the same number of bars at the start of each OOS segment.
DataHorizon = PredictionHorizonBars;
// Select component before advise().
asset(BogieAsset);
algo("BogieML");
Hedge = 0;
MaxLong = 1;
MaxShort = 1;
// -------------------------- Feature pipeline -----------------------------
// All series-producing calls remain unconditional and in fixed order.
RawBogie = BogieRangePosition();
RawSeries = series(RawBogie,64);
SmoothNow = EMA(RawSeries,EmaPeriod);
SmoothSeries = series(SmoothNow,64);
BuildBogieSignals(SmoothSeries,Signals);
// --------------------------- Training target -----------------------------
MLTarget = 0.0;
if(Train)
{
// PEEK makes the negative offset legal in Train mode.
// Target is always +1 or -1; it is never persistently zero.
if(priceC(-PredictionHorizonBars) > priceC(0))
MLTarget = 1.0;
else
MLTarget = -1.0;
}
// One directional model is sufficient because our custom Objective predicts
// future direction directly. Positive score = bullish, negative = bearish.
//
// FUZZY provides an analog prediction strength, normally around -100..+100.
// BALANCED duplicates minority-class samples during training.
MLScore = adviseLong(
PERCEPTRON+FUZZY+BALANCED,
MLTarget,
Signals,
FeatureCount);
// advise trains on every eligible bar in Train mode. Do not trade in the
// training run; trading is not required because we use an explicit target.
if(Train)
return;
// No prediction/trading during lookback.
if(is(LOOKBACK))
return;
// ------------------------------- Plots -----------------------------------
plot("ML Score",MLScore,NEW,BLUE);
plot("Long Gate",ConfidenceThreshold,0,BLACK);
plot("Short Gate",-ConfidenceThreshold,0,BLACK);
// --------------------------- Signal decisions ----------------------------
LongSignal = 0;
ShortSignal = 0;
if(MLScore > ConfidenceThreshold)
LongSignal = 1;
else if(MLScore < -ConfidenceThreshold)
ShortSignal = 1;
Allowed = TradeAllowedToday();
if(!Allowed)
{
if(CloseOnNoTradeDay)
{
if(NumOpenLong > 0)
{
LogExit("LONG",MLScore);
exitLong();
}
if(NumOpenShort > 0)
{
LogExit("SHORT",MLScore);
exitShort();
}
}
return;
}
// -------------------------- Long prediction ------------------------------
if(LongSignal)
{
// Reverse/close an existing short first.
if(NumOpenShort > 0)
{
LogExit("SHORT",MLScore);
exitShort();
}
// Enter only when completely flat.
if(NumOpenLong == 0)
{
if(NumOpenShort == 0)
{
ConfigureTradeParameters();
LogLongEntry(MLScore,SmoothNow);
enterLong(
BogieTrailTMF,
TrailingStopPips,
TrailGuardPips);
}
}
return;
}
// -------------------------- Short prediction -----------------------------
if(ShortSignal)
{
// Reverse/close an existing long first.
if(NumOpenLong > 0)
{
LogExit("LONG",MLScore);
exitLong();
}
// Enter only when completely flat.
if(NumOpenShort == 0)
{
if(NumOpenLong == 0)
{
ConfigureTradeParameters();
LogShortEntry(MLScore,SmoothNow);
enterShort(
BogieTrailTMF,
TrailingStopPips,
TrailGuardPips);
}
}
return;
}
// Neutral zone:
// -ConfidenceThreshold <= MLScore <= +ConfidenceThreshold
//
// No new position is opened and an existing position is held. This follows
// the proposed baseline behavior. A neutral-exit rule should be tested as
// a separate experiment rather than mixed into the first ML comparison.
}
BogieNN v0.03
[Re: TipmyPip ]
#489626
1 hour ago
1 hour ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// 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.
}
BogieNN v0.04
[Re: TipmyPip ]
#489627
57 minutes ago
57 minutes ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// 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();
}
}
BogieNN v0.05
[Re: TipmyPip ]
#489628
56 minutes ago
56 minutes ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// 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.
}
BogieNN v0.06 GPU Torch
[Re: TipmyPip ]
#489629
19 minutes ago
19 minutes ago
Joined: Sep 2017
Posts: 334
TipmyPip
OP
Senior Member
OP
Senior Member
Joined: Sep 2017
Posts: 334
// 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.
}