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