299 行
9.9 KiB
MQL5
299 行
9.9 KiB
MQL5
#ifndef NEURAL_NET_MQH
|
|
#define NEURAL_NET_MQH
|
|
|
|
#include "NNFeatures.mqh"
|
|
|
|
string NN_Trim(const string s)
|
|
{
|
|
string x = s;
|
|
StringTrimLeft(x);
|
|
StringTrimRight(x);
|
|
return x;
|
|
}
|
|
|
|
string NN_BoolJson(const bool value)
|
|
{
|
|
return value ? "true" : "false";
|
|
}
|
|
|
|
int NN_SessionCode()
|
|
{
|
|
if(GW_Session_Name == "Asia") return 0;
|
|
if(GW_Session_Name == "London") return 1;
|
|
if(GW_Session_Name == "NY") return 2;
|
|
return 3;
|
|
}
|
|
|
|
int NN_PhaseCode()
|
|
{
|
|
if(GW_Session_Phase == "A") return 0;
|
|
if(GW_Session_Phase == "M") return 1;
|
|
if(GW_Session_Phase == "D") return 2;
|
|
return 0;
|
|
}
|
|
|
|
int NN_SymbolCode()
|
|
{
|
|
string cls = g_symbol_policy_ready ? g_symbol_policy.symbol_class : Symbol_Profile_Name;
|
|
if(cls == "XAUUSD") return 10;
|
|
if(cls == "US30") return 20;
|
|
if(cls == "NAS100") return 30;
|
|
if(cls == "USOIL") return 40;
|
|
if(cls == "CRYPTO") return 50;
|
|
if(cls == "JPY") return 60;
|
|
if(cls == "COMMODITY_FX") return 70;
|
|
if(cls == "MAJOR_FX") return 80;
|
|
return 0;
|
|
}
|
|
|
|
double NN_CurrentVolume()
|
|
{
|
|
long vol = (long)iVolume(_Symbol, PERIOD_CURRENT, 0);
|
|
return (double)vol;
|
|
}
|
|
|
|
string NN_BuildPayload(const double &feat[])
|
|
{
|
|
string tf = EnumToString((ENUM_TIMEFRAMES)Period());
|
|
int n = ArraySize(feat);
|
|
double close0 = iClose(_Symbol, PERIOD_CURRENT, 0);
|
|
double open0 = iOpen(_Symbol, PERIOD_CURRENT, 0);
|
|
double high0 = iHigh(_Symbol, PERIOD_CURRENT, 0);
|
|
double low0 = iLow(_Symbol, PERIOD_CURRENT, 0);
|
|
double bid = SymbolInfoDouble(_Symbol, SYMBOL_BID);
|
|
double ask = SymbolInfoDouble(_Symbol, SYMBOL_ASK);
|
|
double spread_points = (ask - bid) / _Point;
|
|
double equity = AccountInfoDouble(ACCOUNT_EQUITY);
|
|
double free_margin = AccountInfoDouble(ACCOUNT_MARGIN_FREE);
|
|
double balance = AccountInfoDouble(ACCOUNT_BALANCE);
|
|
double free_margin_pct = (equity > 0.0) ? (free_margin / equity) * 100.0 : 0.0;
|
|
double margin_headroom_pct = (equity > 0.0) ? ((free_margin - g_symbol_margin_required) / equity) * 100.0 : 0.0;
|
|
double risk_amount_pct = (equity > 0.0) ? (g_symbol_risk_amount / equity) * 100.0 : 0.0;
|
|
double price_above_ma50 = (close0 > MA_Current[3]) ? 1.0 : 0.0;
|
|
double ma_stack_bull = (MA_Current[0] > MA_Current[1] && MA_Current[1] > MA_Current[2] &&
|
|
MA_Current[2] > MA_Current[3] && MA_Current[3] > MA_Current[4]) ? 1.0 : 0.0;
|
|
double ma_stack_bear = (MA_Current[0] < MA_Current[1] && MA_Current[1] < MA_Current[2] &&
|
|
MA_Current[2] < MA_Current[3] && MA_Current[3] < MA_Current[4]) ? 1.0 : 0.0;
|
|
|
|
string json = "{";
|
|
json += "\"symbol\":\"" + _Symbol + "\",";
|
|
json += "\"tf\":\"" + tf + "\",";
|
|
|
|
json += "\"features\":{";
|
|
json += "\"ma7\":" + DoubleToString(MA_Current[0], 8) + ",";
|
|
json += "\"ma14\":" + DoubleToString(MA_Current[1], 8) + ",";
|
|
json += "\"ma21\":" + DoubleToString(MA_Current[2], 8) + ",";
|
|
json += "\"ma50\":" + DoubleToString(MA_Current[3], 8) + ",";
|
|
json += "\"ma140\":" + DoubleToString(MA_Current[4], 8) + ",";
|
|
json += "\"ma230\":" + DoubleToString(MA_Current[5], 8) + ",";
|
|
json += "\"ma500\":" + DoubleToString(MA_Current[6], 8) + ",";
|
|
json += "\"ma1400\":" + DoubleToString(MA_Current[7], 8) + ",";
|
|
json += "\"ma7_slope\":" + DoubleToString(feat[4], 8) + ",";
|
|
json += "\"ma14_slope\":" + DoubleToString(feat[5], 8) + ",";
|
|
json += "\"ma21_slope\":" + DoubleToString((MA_Current[2] - MA_Previous[2]) / _Point, 8) + ",";
|
|
json += "\"ma50_slope\":" + DoubleToString((MA_Current[3] - MA_Previous[3]) / _Point, 8) + ",";
|
|
json += "\"stoch_k\":" + DoubleToString(Stoch_K_Current, 8) + ",";
|
|
json += "\"stoch_d\":" + DoubleToString(Stoch_D_Current, 8) + ",";
|
|
json += "\"stoch_k_prev\":" + DoubleToString(Stoch_K_Previous, 8) + ",";
|
|
json += "\"adx\":" + DoubleToString(Current_ADX, 8) + ",";
|
|
json += "\"atr\":" + DoubleToString(Current_ATR, 8) + ",";
|
|
json += "\"fib_032\":" + DoubleToString(PriceLevels[2], 8) + ",";
|
|
json += "\"fib_050\":" + DoubleToString(PriceLevels[3], 8) + ",";
|
|
json += "\"fib_618\":" + DoubleToString(PriceLevels[4], 8) + ",";
|
|
json += "\"close\":" + DoubleToString(close0, 8) + ",";
|
|
json += "\"open\":" + DoubleToString(open0, 8) + ",";
|
|
json += "\"high\":" + DoubleToString(high0, 8) + ",";
|
|
json += "\"low\":" + DoubleToString(low0, 8) + ",";
|
|
json += "\"volume\":" + DoubleToString(NN_CurrentVolume(), 0) + ",";
|
|
json += "\"spread_points\":" + DoubleToString(spread_points, 2) + ",";
|
|
json += "\"free_margin_pct\":" + DoubleToString(free_margin_pct, 2) + ",";
|
|
json += "\"margin_headroom_pct\":" + DoubleToString(margin_headroom_pct, 2) + ",";
|
|
json += "\"risk_amount_pct\":" + DoubleToString(risk_amount_pct, 2) + ",";
|
|
json += "\"session_code\":" + IntegerToString(NN_SessionCode()) + ",";
|
|
json += "\"phase_code\":" + IntegerToString(NN_PhaseCode()) + ",";
|
|
json += "\"news_bias\":" + IntegerToString(News_Bias_Direction) + ",";
|
|
json += "\"news_strength\":" + DoubleToString(News_Bias_Strength, 2) + ",";
|
|
json += "\"news_block\":" + NN_BoolJson(News_Trade_Block_Active || GW_News_Block_Flag) + ",";
|
|
json += "\"news_allowed_direction\":" + IntegerToString(News_Trade_Allowed_Direction) + ",";
|
|
json += "\"symbol_code\":" + IntegerToString(NN_SymbolCode()) + ",";
|
|
json += "\"news_sensitivity\":" + DoubleToString(g_symbol_policy.news_sensitivity, 2) + ",";
|
|
json += "\"cluster_strength\":" + DoubleToString(Coordinator_Cluster_Strength, 2) + ",";
|
|
json += "\"conflict_score\":" + DoubleToString(Coordinator_Conflict_Score, 2) + ",";
|
|
json += "\"warnings\":" + IntegerToString(Active_Warnings) + ",";
|
|
json += "\"praise\":" + IntegerToString(Active_Praise_Signals) + ",";
|
|
json += "\"warn_count\":" + IntegerToString(Active_Warnings) + ",";
|
|
json += "\"praise_count\":" + IntegerToString(Active_Praise_Signals) + ",";
|
|
json += "\"continuation\":" + NN_BoolJson(Current_State == STATE_CONTINUATION) + ",";
|
|
json += "\"price_above_ma50\":" + DoubleToString(price_above_ma50, 1) + ",";
|
|
json += "\"ma_stack_bull\":" + DoubleToString(ma_stack_bull, 1) + ",";
|
|
json += "\"ma_stack_bear\":" + DoubleToString(ma_stack_bear, 1);
|
|
json += "},";
|
|
|
|
json += "\"feature_vector\":[";
|
|
for(int j = 0; j < n; j++)
|
|
{
|
|
json += DoubleToString(feat[j], 8);
|
|
if(j < n - 1) json += ",";
|
|
}
|
|
json += "]}";
|
|
|
|
return json;
|
|
}
|
|
|
|
bool NN_ExtractNumber(const string &json, const string &key, double &out)
|
|
{
|
|
int k = StringFind(json, "\"" + key + "\"");
|
|
if(k < 0) return false;
|
|
|
|
int colon = StringFind(json, ":", k);
|
|
if(colon < 0) return false;
|
|
|
|
int endComma = StringFind(json, ",", colon);
|
|
int endBrace = StringFind(json, "}", colon);
|
|
int end = endComma;
|
|
if(end < 0 || (endBrace >= 0 && endBrace < end)) end = endBrace;
|
|
if(end < 0) return false;
|
|
|
|
string val = StringSubstr(json, colon + 1, end - (colon + 1));
|
|
val = NN_Trim(val);
|
|
out = StringToDouble(val);
|
|
return true;
|
|
}
|
|
|
|
bool NN_ExtractInt(const string &json, const string &key, int &out)
|
|
{
|
|
double tmp = 0.0;
|
|
if(!NN_ExtractNumber(json, key, tmp)) return false;
|
|
out = (int)MathRound(tmp);
|
|
return true;
|
|
}
|
|
|
|
bool NN_ExtractString(const string &json, const string &key, string &out)
|
|
{
|
|
int k = StringFind(json, "\"" + key + "\"");
|
|
if(k < 0) return false;
|
|
|
|
int colon = StringFind(json, ":", k);
|
|
if(colon < 0) return false;
|
|
|
|
int q1 = StringFind(json, "\"", colon + 1);
|
|
if(q1 < 0) return false;
|
|
|
|
int q2 = StringFind(json, "\"", q1 + 1);
|
|
if(q2 < 0) return false;
|
|
|
|
out = StringSubstr(json, q1 + 1, q2 - q1 - 1);
|
|
return true;
|
|
}
|
|
|
|
bool NN_WebPredict(const string url, const string jsonPayload, string &response)
|
|
{
|
|
char post[], result[];
|
|
string headers = "Content-Type: application/json\r\n";
|
|
int timeout = NN_TimeoutMs;
|
|
|
|
StringToCharArray(jsonPayload, post);
|
|
|
|
ResetLastError();
|
|
string result_headers;
|
|
int res = WebRequest("POST", url, headers, timeout, post, result, result_headers);
|
|
|
|
if(res == -1)
|
|
{
|
|
if(NN_DebugPrint)
|
|
Print("NN WebRequest failed. Error=", GetLastError(), " | URL=", url);
|
|
return false;
|
|
}
|
|
|
|
response = CharArrayToString(result);
|
|
return true;
|
|
}
|
|
|
|
bool InitializeNeuralNet()
|
|
{
|
|
if(!Use_NeuralNet) return false;
|
|
|
|
NN_Initialized = true;
|
|
NN_LastRun = 0;
|
|
NN_Bias = 0;
|
|
NN_Confidence = 0.0;
|
|
NN_RiskScore = 50.0;
|
|
NN_Explain = "NN init ok";
|
|
return true;
|
|
}
|
|
|
|
bool UpdateNeuralNetSignal()
|
|
{
|
|
NN_UsedLastTick = false;
|
|
|
|
if(!Use_NeuralNet || !NN_Initialized)
|
|
return false;
|
|
|
|
datetime now = TimeCurrent();
|
|
if(NN_LastRun > 0 && (now - NN_LastRun) < NN_CooldownSeconds)
|
|
return false;
|
|
|
|
// g_nn_url is set by AutoDetectDeployment() in OnInit
|
|
if(StringLen(g_nn_url) < 10)
|
|
{
|
|
NN_Explain = "NN URL not resolved";
|
|
return false;
|
|
}
|
|
|
|
double feat[];
|
|
int n = BuildNNFeatures(feat);
|
|
if(n <= 0)
|
|
return false;
|
|
|
|
string payload = NN_BuildPayload(feat);
|
|
string resp;
|
|
|
|
if(!NN_WebPredict(g_nn_url, payload, resp))
|
|
{
|
|
NN_Explain = "NN request failed";
|
|
return false;
|
|
}
|
|
|
|
int bias = 0;
|
|
double conf = 0.0, risk = 50.0;
|
|
string explain = "";
|
|
|
|
bool ok1 = NN_ExtractInt(resp, "bias", bias);
|
|
bool ok2 = NN_ExtractNumber(resp, "confidence", conf);
|
|
bool ok3 = NN_ExtractNumber(resp, "risk", risk);
|
|
NN_ExtractString(resp, "explain", explain);
|
|
|
|
if(!(ok1 && ok2 && ok3))
|
|
{
|
|
NN_Explain = "NN parse failed: " + resp;
|
|
return false;
|
|
}
|
|
|
|
if(bias > 1) bias = 1;
|
|
if(bias < -1) bias = -1;
|
|
|
|
NN_Bias = bias;
|
|
NN_Confidence = NN_Clamp(conf, 0.0, 100.0);
|
|
NN_RiskScore = NN_Clamp(risk, 0.0, 100.0);
|
|
NN_Explain = (explain == "" ? "ok" : explain);
|
|
NN_LastRun = now;
|
|
NN_UsedLastTick = true;
|
|
|
|
if(NN_DebugPrint)
|
|
Print("NN => bias=", NN_Bias,
|
|
" conf=", DoubleToString(NN_Confidence, 1),
|
|
" risk=", DoubleToString(NN_RiskScore, 1),
|
|
" | ", NN_Explain);
|
|
|
|
return true;
|
|
}
|
|
|
|
bool NN_IsUsable()
|
|
{
|
|
if(!Use_NeuralNet || !NN_Initialized) return false;
|
|
if(NN_Confidence < NN_MinConfidenceToUse) return false;
|
|
if(NN_RiskScore > NN_MaxRiskToUse) return false;
|
|
return true;
|
|
}
|
|
|
|
#endif
|