Warrior_EA/Signals/SignalNeural.mqh
AnimateDread 47a5ef338b Refactor Warrior EA: Integrate custom signal modules, enhance voting mechanism, and improve management features
- Replaced standard library signal modules with custom implementations to allow for named patterns and improved voting.
- Added new input parameters for module weights, allowing for optimization of individual signal contributions.
- Enhanced the management of trades with new options for breakeven and management cut.
- Introduced a mechanism for dynamic ranking of signal weights based on historical performance.
- Improved initialization logic to ensure proper registration of filters and handling of trading conditions.
- Added detailed logging for trading permissions and account status during initialization.
2026-09-13 14:32:40 -04:00

532 lines
27 KiB
MQL5

//+------------------------------------------------------------------+
//| SignalNeural.mqh |
//| AnimateDread |
//| |
//| A NEURAL NETWORK AS AN ORDINARY SIGNAL MODULE. |
//| |
//| It derives from CWarriorSignal, implements LongCondition() and |
//| ShortCondition() returning 0..100, and that is the whole of its |
//| contract with the rest of the EA. The vote cannot tell it apart |
//| from CSignalMA, and nothing in the parent knows a network exists. |
//| |
//| That is deliberate, and it is a return to how this repo started. |
//| At 0a527b0 an NN module did exactly this. At 2f5adb4 it gained |
//| SignedAIConfidence(), and from then on the parent had to special- |
//| case it: confidence-scaled stops, confidence-scaled lots, tier |
//| ladders, LiveVote(), VoteCapableWeight(), IsAIFilter(). None of |
//| that made money and all of it made the net unrankable, because a |
//| thing that votes in its own private currency cannot be compared |
//| with the modules it votes alongside. |
//| |
//| IT BUILDS ITS OWN INPUTS. There is no separate feature-builder |
//| file: a module that votes owns what it votes on. The names below |
//| are the contract the model file is checked against, so a model |
//| trained on a different column set is refused rather than fed. |
//| |
//| WHAT IT PREDICTS. Not direction - this project measured direction |
//| dead at bar level (rho 0.00-0.02). It predicts whether the bar's |
//| low survives the next two bars: the unknown half of a Bill |
//| Williams fractal. Measured 2026-09-11 on 20 instrument/timeframe |
//| series, out of sample, chronological split: AUC 0.772 (range |
//| 0.756-0.792). Mean R by predicted-probability quintile ran -0.230 |
//| / -0.089 / -0.026 / +0.002 / +0.017, monotone on 20 of 20. |
//| |
//| ⚠ SO ITS VALUE IS A REFUSAL, NOT A SELECTION. |
//| Its value is in refusing the worst quintile, not in selecting the |
//| best: the top quintile is breakeven before costs. A module that |
//| returned a big number on a high score would be claiming an edge |
//| the measurement does not support. |
//| |
//| The standard library has no way to refuse ONE side, and the note |
//| above ShortCondition() works through what it does offer and what |
//| this module therefore assumes. When a comment and the code |
//| disagree, believe the journal: it counts what the module said. |
//+------------------------------------------------------------------+
#ifndef WARRIOR_SIGNALNEURAL_MQH
#define WARRIOR_SIGNALNEURAL_MQH
#include "..\Expert\WarriorSignal.mqh"
#include "..\System\WarriorNet.mqh"
#include "Wyckoff\WyckoffFeed.mqh"
#include "..\System\AltDataFeed.mqh"
//--- 9 price shape + 9 Wyckoff + 3 volume + 4 calendar + 16 alt data.
#define NEURAL_PRICE_F 9
#define NEURAL_WYK_F 9
#define NEURAL_VOL_F 3
#define NEURAL_TIME_F 4
#define NEURAL_FEATURES (NEURAL_PRICE_F + NEURAL_WYK_F + NEURAL_VOL_F + NEURAL_TIME_F + ALT_COLUMNS)
class CSignalNeural : public CWarriorSignal
{
protected:
CWarriorNet m_net;
CiATR m_atr;
int m_atrPeriod;
int m_pattern_0; // the refusal, voted as a SHORT - see ShortCondition()
int m_pattern_1; // the mild confirmation a high score earns
double m_cut; // p <= 1-cut is the refusal band, p >= cut the confirmation
bool m_trained;
//--- THE SCORE, COMPUTED ONCE PER BAR. LongCondition() and ShortCondition() are both called on
//--- the same evaluation by Direction() (ExpertSignal.mqh:431) and must read the SAME number -
//--- scoring twice could answer two different things if anything underneath refreshed between
//--- the calls, and the two bands would then no longer be mutually exclusive.
datetime m_scoreBar;
double m_score; // <0 when this bar has no usable score
double Score(void);
//--- Monotone, bounded, stateless: maps any real into (-1,1) while preserving order. Used where
//--- a feature has no natural scale and a stored mean/variance would be one more thing to keep
//--- in step with the model file.
static double Squash(const double v) { return v / (1.0 + MathAbs(v)); }
//--- DEFERRED TRAINING. In the tester Bars() at OnInit returns almost nothing - history accrues
//--- AS THE RUN PROGRESSES - so training at init can never work in a backtest. Training partway
//--- through instead is not a workaround, it is the correct shape: Bars() then returns
//--- history-so-far, so the model can only ever have been fitted on the past and every bar it
//--- votes on is genuinely out of sample. Retraining on an interval makes it walk-forward.
int m_minBars; // do not train until this much history exists
int m_retrainBars; // retrain every N bars (0 = train once)
datetime m_lastTrainBar;
int m_trainCount;
int m_trainedAtBars;
void TrainIfDue(void);
//--- The column contract. Order is the contract; append only, never insert.
static string FeatureName(const int i);
bool BuildFeatures(double &x[], const int shift);
//--- P(this bar's low survives the next two) - the fractal's unknown half.
bool LabelAt(const int shift, double &label);
bool TrainFromHistory(void);
string ModelPath(void) const
{ return "Warrior_EA\\Nets\\" + m_symbol.Name() + "_" +
IntegerToString(m_period) + "_fractal.net"; }
public:
CSignalNeural(void);
~CSignalNeural(void) {}
void AtrPeriod(const int v) { m_atrPeriod = v; }
void MinBars(const int v) { m_minBars = v; }
void RetrainBars(const int v) { m_retrainBars = v; }
void Confidence(const double v) { m_cut = v; }
void Pattern_0(const int v) { m_pattern_0 = v; }
void Pattern_1(const int v) { m_pattern_1 = v; }
virtual void ApplyPatternWeight(int pattern, int weight)
{
if(pattern == 0) m_pattern_0 = weight;
if(pattern == 1) m_pattern_1 = weight;
}
virtual bool InitIndicators(CIndicators *indicators) override;
virtual int LongCondition(void) override;
virtual int ShortCondition(void) override;
};
//+------------------------------------------------------------------+
CSignalNeural::CSignalNeural(void) : m_atrPeriod(14), m_pattern_0(40), m_pattern_1(15),
m_cut(0.50), m_trained(false),
m_minBars(750), m_retrainBars(500),
m_lastTrainBar(0), m_trainCount(0), m_trainedAtBars(0),
m_scoreBar(0), m_score(-1.0)
{
m_id = "NEURAL";
m_pattern_count = 2;
m_used_series = USE_SERIES_OPEN + USE_SERIES_HIGH + USE_SERIES_LOW + USE_SERIES_CLOSE +
USE_SERIES_TICK_VOLUME;
}
//+------------------------------------------------------------------+
string CSignalNeural::FeatureName(const int i)
{
switch(i)
{
//--- Price shape, all scale-free. A raw price here would teach the net the era, not the market.
case 0: return "close_in_bar"; // where the close sat in its own range
case 1: return "range_atr"; // this bar's range, in ATR
case 2: return "below_prior_low_atr"; // how far the low undercut the previous one
case 3: return "ret1_atr"; // last return
case 4: return "ret3_atr";
case 5: return "dist_sma50_atr"; // trend context
case 6: return "prior_range_atr";
case 7: return "upper_wick_atr";
case 8: return "lower_wick_atr";
//--- Wyckoff structure, read through the shared feed.
case 9: return "wyk_event_signed";
case 10: return "wyk_phase_signed";
case 11: return "wyk_spring_grade";
case 12: return "wyk_character";
case 13: return "wyk_range_open";
case 14: return "wyk_range_pos";
case 15: return "wyk_dist_creek_atr";
case 16: return "wyk_fail_value";
case 17: return "wyk_bar_quality";
//--- VOLUME, and never raw. Tick volume trends upward over a decade as the feed densifies,
//--- so a raw count teaches the net which YEAR it is looking at - the same trap as a raw
//--- price. All three are ratios against the recent past, which is scale-free and era-free.
case 18: return "vol_rel_sma20";
case 19: return "vol_rel_sma5";
case 20: return "vol_chg1";
//--- TIME, CYCLICALLY ENCODED. The previous generation of nets fed CLOCK time as an ordinal,
//--- which asserts that Friday is five times Monday and that December is twelve times
//--- January, and puts a discontinuity between the last bar of one week and the first of the
//--- next. sin/cos pairs make the wrap continuous and the ordering honest. On D1 there is no
//--- intraday session to encode - the useful periodicities are the week and the year.
case 21: return "dow_sin";
case 22: return "dow_cos";
case 23: return "month_sin";
case 24: return "month_cos";
}
//--- THE ALT BLOCK, appended last so the existing columns keep their indices - the model file is
//--- validated against these names, so an insert would invalidate every net ever trained.
if(i >= 25 && i < 25 + ALT_COLUMNS)
return "alt_" + CAltDataFeed::ColumnName(i - 25);
return "?";
}
//+------------------------------------------------------------------+
bool CSignalNeural::BuildFeatures(double &x[], const int shift)
{
ArrayResize(x, NEURAL_FEATURES);
ArrayInitialize(x, 0.0);
const double atr = m_atr.Main(shift);
if(atr <= 0.0 || !MathIsValidNumber(atr))
return false;
const double o = Open(shift), h = High(shift), l = Low(shift), c = Close(shift);
const double rng = h - l;
if(rng <= 0.0)
return false;
double sma = 0.0;
for(int k = 0; k < 50; k++)
sma += Close(shift + k);
sma /= 50.0;
int i = 0;
x[i++] = (c - l) / rng;
x[i++] = rng / atr;
x[i++] = (Low(shift + 1) - l) / atr;
x[i++] = (c - Close(shift + 1)) / atr;
x[i++] = (c - Close(shift + 3)) / atr;
x[i++] = (c - sma) / atr;
x[i++] = (High(shift + 1) - Low(shift + 1)) / atr;
x[i++] = (h - MathMax(o, c)) / atr;
x[i++] = (MathMin(o, c) - l) / atr;
//--- The Wyckoff half. The feed zeroes what it cannot say, and zero is a legitimate "no
//--- structure here" for every one of these - so no have/haven't flag is needed.
x[i++] = g_wyckoffFeed.Event(shift) / 9.0;
x[i++] = g_wyckoffFeed.Phase(shift) / 5.0;
x[i++] = g_wyckoffFeed.SpringGrade(shift) / 3.0;
x[i++] = g_wyckoffFeed.Character(shift);
const bool open = g_wyckoffFeed.RangeOpen(shift);
x[i++] = open ? 1.0 : 0.0;
const double top = g_wyckoffFeed.ZoneTop(shift), bot = g_wyckoffFeed.ZoneBottom(shift);
//--- 0.5 when there is no range: a ratio against a zero-width range is not a small number, it is
//--- a wrong one, and the midpoint is the honest "no information" value for a position feature.
x[i++] = (open && (top - bot) > 0.0) ? ((c - bot) / (top - bot)) : 0.5;
const double creek = g_wyckoffFeed.Creek(shift);
x[i++] = (creek != 0.0) ? ((c - creek) / atr) : 0.0;
x[i++] = g_wyckoffFeed.FailValue(shift) / 2.5;
x[i++] = g_wyckoffFeed.BarQuality(shift) / 2.0;
//--- VOLUME. A bar that moved on twice its usual participation is a different bar from one that
//--- drifted on nothing, and the price-shape columns above cannot express that at all.
double v20 = 0.0, v5 = 0.0;
for(int k = 0; k < 20; k++)
{
const double v = (double)TickVolume(shift + k);
v20 += v;
if(k < 5)
v5 += v;
}
v20 /= 20.0;
v5 /= 5.0;
const double vNow = (double)TickVolume(shift);
const double vPrev = (double)TickVolume(shift + 1);
//--- A dead bar would divide by zero; refuse the row rather than emit an infinity that
//--- MathIsValidNumber would pass and the trainer would choke on.
if(v20 <= 0.0 || v5 <= 0.0 || vPrev <= 0.0)
return false;
x[i++] = Squash(vNow / v20 - 1.0);
x[i++] = Squash(vNow / v5 - 1.0);
x[i++] = Squash(vNow / vPrev - 1.0);
//--- CALENDAR POSITION, cyclic. See FeatureName() for why this is not an ordinal.
MqlDateTime t;
TimeToStruct(iTime(m_symbol.Name(), m_period, shift), t);
const double dow = 2.0 * M_PI * t.day_of_week / 7.0;
const double mon = 2.0 * M_PI * (t.mon - 1) / 12.0;
x[i++] = MathSin(dow);
x[i++] = MathCos(dow);
x[i++] = MathSin(mon);
x[i++] = MathCos(mon);
//--- THE ALT BLOCK. Absent (pre-2010) means the ROW IS DROPPED, never zero-filled - see the
//--- header of AltDataFeed.mqh. Squashed rather than z-scored: v/(1+|v|) is monotone, bounded,
//--- and stateless, so it needs no training-set statistics to be stored in the model file and
//--- cannot leak one fold's scale into another.
double alt[];
if(!g_altData.Lookup(iTime(m_symbol.Name(), m_period, shift), alt))
return false;
for(int c2 = 0; c2 < ALT_COLUMNS; c2++)
x[i++] = Squash(alt[c2]);
return (i == NEURAL_FEATURES);
}
//| THE LABEL. Known only at shift-2, so training must never use a |
//| bar newer than that - which is what the offset in the loop below |
//| enforces. Predicting it at `shift` is a genuine two-bar-ahead |
//| question, not a restatement of the present. |
//+------------------------------------------------------------------+
bool CSignalNeural::LabelAt(const int shift, double &label)
{
if(shift < 2)
return false;
const double l = Low(shift);
label = (l < Low(shift - 1) && l < Low(shift - 2)) ? 1.0 : 0.0;
return true;
}
//+------------------------------------------------------------------+
bool CSignalNeural::TrainFromHistory(void)
{
const int bars = Bars(m_symbol.Name(), m_period);
//--- Leave the newest bars alone: the label needs two bars after it, and the features need the
//--- 50 before it. Both ends are trimmed rather than clamped, so no row is built from a window
//--- that does not exist.
const int first = 60, last = bars - 3;
if(last - first < 200)
{
Print("CSignalNeural: not enough history to train.");
return false;
}
//--- REACH PAST THE STANDARD LIBRARY'S 1024-BAR BUFFER BEFORE WALKING HISTORY.
//---
//--- Without this the sweep below reads 0.0 for every shift past 1023 and the rows simply stop
//--- appearing - which is how this module trained on exactly 915 rows of an 11-year run, four
//--- times, without ever printing a warning. Ask for what the run has actually accrued, never
//--- more: in the tester Bars() is history-so-far, so this cannot reach past the bar being
//--- decided and the fit stays walk-forward.
//---
//--- THE TWO MUST GROW TOGETHER OR NOT AT ALL. The Wyckoff half of the feature vector reports
//--- 0.0 for "no structure here", and a buffer that has run out reports the same 0.0. So deep
//--- prices with a shallow feed would not lose rows - it would manufacture ~2,000 rows whose
//--- nine structure features are all zero, and teach the net that old bars are featureless.
//--- That is strictly worse than the shortfall it replaces, so a half-failure refuses.
//--- The ATR is in the same group and for the same reason: BuildFeatures() divides nine of its
//--- eighteen columns by it and refuses the row when it is not positive, so an ATR left at 1024
//--- would re-impose the exact ceiling the other two just lifted.
const int want = MathMin(bars, WARRIOR_NET_HISTORY);
const bool deepAtr = (want <= m_atr.BufferSize()) || m_atr.BufferResize(want);
if(!DeepenPrices(want) || !deepAtr || !g_wyckoffFeed.Deepen(want))
{
PrintFormat("CSignalNeural: could not deepen series to %d bar(s) - not trained rather than "
"trained on a truncated or zero-filled window.", want);
return false;
}
CMatrixDouble xy(last - first, NEURAL_FEATURES + 1);
int rows = 0;
double x[], label;
for(int s = last; s >= first; s--) // oldest to newest: the split below is chronological
{
if(!BuildFeatures(x, s) || !LabelAt(s, label))
continue;
bool ok = true;
for(int f = 0; f < NEURAL_FEATURES; f++)
if(!MathIsValidNumber(x[f]))
{ ok = false; break; }
if(!ok)
continue;
for(int f = 0; f < NEURAL_FEATURES; f++)
xy.Set(rows, f, x[f]);
xy.Set(rows, NEURAL_FEATURES, label);
rows++;
}
if(rows < 100)
{
Print("CSignalNeural: only ", rows, " usable row(s) - not trained.");
return false;
}
//--- SAY WHAT WAS DROPPED. A sweep that silently yields a third of its candidates is exactly the
//--- failure this module already shipped once, and it was invisible because the only number ever
//--- printed was the one that survived. A row count means nothing without its denominator.
const int candidates = last - first;
if(rows < candidates)
PrintFormat("CSignalNeural: %d of %d candidate bar(s) usable (%.0f%%) - %d dropped for an "
"invalid ATR, a zero range or a non-finite feature.",
rows, candidates, 100.0 * rows / candidates, candidates - rows);
string names[];
ArrayResize(names, NEURAL_FEATURES);
for(int f = 0; f < NEURAL_FEATURES; f++)
names[f] = FeatureName(f);
//--- Embargo of two rows: the label needs the two bars AFTER its own, so the last two training
//--- rows share their answer with the first validation rows. Small here - a 2-bar window against
//--- the management net's 60 - but the same leak, and a 0.5% cost to be sure of the tail.
if(!m_net.Train(xy, rows, NEURAL_FEATURES, names, 2))
{
Print("CSignalNeural: training refused - ", m_net.Why());
return false;
}
m_net.Save(ModelPath(), names,
StringFormat("fractal-low survival, %s %s, %d rows", m_symbol.Name(),
EnumToString((ENUM_TIMEFRAMES)m_period), rows));
return true;
}
//+------------------------------------------------------------------+
bool CSignalNeural::InitIndicators(CIndicators *indicators)
{
if(indicators == NULL || !CWarriorSignal::InitIndicators(indicators))
return false;
if(!indicators.Add(GetPointer(m_atr)) || !m_atr.Create(m_symbol.Name(), m_period, m_atrPeriod))
{
Print("CSignalNeural: could not create ATR");
return false;
}
if(!WyckoffFeedEnsure(indicators, m_symbol.Name(), m_period))
{
Print("CSignalNeural: Wyckoff feed unavailable - ", g_wyckoffFeed.Why());
return false;
}
//--- THE ALT BLOCK IS MANDATORY, not best-effort. Sixteen of the forty-one inputs come from it,
//--- and a net silently trained on sixteen zero columns is worse than no net: it would look
//--- trained, pass every check, and have spent a third of its width on nothing.
if(!g_altData.Load(m_symbol.Name()))
{
Print("CSignalNeural: alt data unavailable - ", g_altData.Why(),
" - needs Common/Files/ADAltData/<symbol>_D1.csv");
return false;
}
PrintFormat("CSignalNeural: alt data loaded, %d daily row(s), %d column(s).",
g_altData.Rows(), ALT_COLUMNS);
string names[];
ArrayResize(names, NEURAL_FEATURES);
for(int f = 0; f < NEURAL_FEATURES; f++)
names[f] = FeatureName(f);
//--- LOAD ONLY. Training is deferred to TrainIfDue() - see m_minBars. A refused load (wrong
//--- columns) is NOT retrained over silently: Load() has already said why, and quietly replacing
//--- a model the operator may be mid-way through evaluating would destroy what is being measured.
m_trained = m_net.Load(ModelPath(), names);
if(m_cut <= 0.50)
Print("CSignalNeural: confidence cut is 0.50 (no cut) - this module is INERT by design and "
"will abstain on every bar. Raise it to vote.");
if(m_trained)
Print("CSignalNeural: model loaded; no training this run.");
else
PrintFormat("CSignalNeural: no model yet (%s). Will train once %d bars of history exist,"
" then every %d bars - each fit uses only bars older than itself.",
m_net.Why(), m_minBars, m_retrainBars);
return true;
}
//+------------------------------------------------------------------+
//| Train when enough history has accrued, then every m_retrainBars. |
//| Called from the vote, so "now" is always the bar being decided and |
//| Bars() is always history-so-far. |
//+------------------------------------------------------------------+
void CSignalNeural::TrainIfDue(void)
{
const datetime bar = iTime(m_symbol.Name(), m_period, 0);
if(bar == m_lastTrainBar)
return; // at most one attempt per bar
const int bars = Bars(m_symbol.Name(), m_period);
if(bars < m_minBars)
return;
if(m_trainedAtBars > 0 && (m_retrainBars <= 0 || bars < m_trainedAtBars + m_retrainBars))
return;
m_lastTrainBar = bar;
//--- BACK OFF WHETHER IT SUCCEEDS OR FAILS. Without this a refusal retries on every single bar
//--- and prints the same sentence hundreds of times - which buries the one line that matters and
//--- costs a full history walk each time.
m_trainedAtBars = bars;
if(TrainFromHistory())
{
m_trained = true;
m_trainCount++;
}
}
//+------------------------------------------------------------------+
//| WHAT A MODULE CAN ACTUALLY SAY, read out of the standard library |
//| rather than assumed (Include\Expert\ExpertSignal.mqh): |
//| |
//| Direction() is m_weight*(LongCondition()-ShortCondition()), |
//| line 431, and the root averages it over the number of filters |
//| that answered, line 458. So a module has exactly THREE moves: |
//| vote long, vote short, or return 0/0 - and 0/0 is not silence, |
//| the parent still counts it in `number` and it dilutes everyone |
//| else's vote. |
//| |
//| THERE IS NO PER-SIDE VETO. EMPTY_VALUE is not one. Returned by |
//| a filter it makes the root return EMPTY_VALUE immediately |
//| (line 449-450), and the top-level m_direction==EMPTY_VALUE |
//| makes CheckOpenLong, CheckOpenShort, CheckCloseLong AND |
//| CheckCloseShort all return false (lines 231, 254, 317, 340). |
//| It is the NO-ACTION state - the constructor initialises |
//| m_direction to EMPTY_VALUE as "nothing computed yet" (line |
//| 103). So one filter returning it silences all twelve others for |
//| that bar, and suppresses signal-driven exits too. |
//| |
//| I built the refusal that way and measured it: -98.49 over 46 |
//| trades against +81.30 over 60 with the module off. It is the |
//| bluntest instrument in the framework and the measurement does not |
//| ask for it - so it is gone. |
//| |
//| WHAT IS LEFT IS THE HONEST TENSION. The quintile study measured |
//| mean R BY QUINTILE TAKEN LONG: -0.230 / -0.089 / -0.026 / +0.002 |
//| / +0.017. Its only claim is "the bottom quintile is a bad LONG". |
//| Expressing that as a short vote assumes the payoff is |
//| antisymmetric - that what a long loses a short earns - which the |
//| study never tested and the spread contradicts. But the arithmetic |
//| above offers no way to say "not long" without saying "short": |
//| the same subtraction that pulls the average below the long |
//| threshold pushes it toward the short one. |
//| |
//| So the short vote stays, and it is labelled for what it is: an |
//| ASSUMPTION, not a measurement. Whether it pays is the question |
//| the multi-symbol sweep exists to answer, because at 60 trades the |
//| standard error on the mean is ~2.3 per trade and every result in |
//| this file so far - +81.30, +30.44, -98.49 - sits inside it. |
//+------------------------------------------------------------------+
double CSignalNeural::Score(void)
{
//--- CONF_50 MEANS OFF, and it has to mean off HERE or it means the opposite. The bands are
//--- `p >= cut` and `p <= 1-cut`; at cut = 0.50 those are `p >= 0.5` and `p <= 0.5`, one of
//--- which is true for EVERY p - so the module would speak on every bar instead of abstaining.
//--- A dead band at 0.50 is not a tidy-up: without it, "no cut" is the most opinionated setting.
if(!m_trained || m_cut <= 0.50)
return -1.0;
const datetime bar = iTime(m_symbol.Name(), m_period, 0);
if(bar == m_scoreBar)
return m_score;
m_scoreBar = bar;
m_score = -1.0;
double x[];
if(!BuildFeatures(x, 1))
return -1.0;
const double p = m_net.Score(x);
m_score = (p < 0.0) ? -1.0 : p;
return m_score;
}
//+------------------------------------------------------------------+
int CSignalNeural::LongCondition(void)
{
TrainIfDue();
const double p = Score();
//--- The confirmation, deliberately small: the top quintile measured +0.017 R, breakeven before
//--- costs. A module returning a big number here would claim an edge the measurement lacks.
if(p >= m_cut)
{
m_active_pattern = "Pattern_1";
m_active_direction = "Buy";
return m_pattern_1;
}
return 0;
}
//+------------------------------------------------------------------+
int CSignalNeural::ShortCondition(void)
{
//--- TrainIfDue() is NOT called here: LongCondition() runs first on every evaluation and has
//--- already called it. Score() is cached per bar, so both sides read the same number.
const double p = Score();
//--- THE ASSUMPTION, carrying the heavier weight because the bottom quintile is where the
//--- measured separation is. See the note above: "bad long" -> "good short" is a step the
//--- quintile study does not license, and this is the line that takes it.
if(p >= 0.0 && p <= 1.0 - m_cut)
{
m_active_pattern = "Pattern_0";
m_active_direction = "Sell";
return m_pattern_0;
}
return 0;
}
#endif // WARRIOR_SIGNALNEURAL_MQH