Gamestudio Links
Zorro Links
Newest Posts
ZorroGPT
by TipmyPip. 09/09/26 13:36
Parent/child object
by Frederick_Lim. 09/07/26 07:00
[MED] Import from FBX (2010) missing!!!
by USER0328. 09/03/26 18:03
Purchase A8 full licence version
by ukgamer. 08/30/26 14:27
Retrieve optimize() variables order (edited)
by NorbertSz. 08/25/26 13:24
Max Number of Strategies in /Strategy folder
by Martin_HH. 08/17/26 07:54
Pause button for evaluation shell?
by AndrewAMD. 08/13/26 17:16
AUM Magazine
Latest Screens
Dorifto samurai
Shadow 2
Rocker`s Revenge
Stug 3 Stormartillery
Who's Online Now
5 registered members (TipmyPip, AndrewAMD, Quad, 2 invisible), 4,696 guests, and 0 spiders.
Key: Admin, Global Mod, Mod
Newest Members
speedx, liop, Bauer097, riggi89, shuhari
19233 Registered Users
Previous Thread
Next Thread
Print Thread
Rate Thread
Page 26 of 26 1 2 24 25 26
NonlinearPriceTimeManifold v.1.02 [Re: TipmyPip] #489615
08/29/26 06:56
08/29/26 06:56
Joined: Sep 2017
Posts: 334
TipmyPip Online OP
Senior Member
TipmyPip  Online 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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 Online OP
Senior Member
TipmyPip  Online OP
Senior Member

Joined: Sep 2017
Posts: 334
Code
// 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.
}

Page 26 of 26 1 2 24 25 26

Moderated by  Petra 

Powered by UBB.threads™ PHP Forum Software 7.7.1