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.
}