361 lines
15 KiB
MQL5
361 lines
15 KiB
MQL5
|
//+------------------------------------------------------------------+
|
||
|
//| Enhanced Multi-Symbol Trading Story Dashboard |
|
||
|
//| Displays comprehensive portfolio story on single chart |
|
||
|
//+------------------------------------------------------------------+
|
||
|
#include <Utils/JAson.mqh>
|
||
|
|
||
|
#define COLOR_PROFIT C'32,133,66'
|
||
|
#define COLOR_LOSS C'187,44,44'
|
||
|
#define COLOR_NEUTRAL C'128,128,128'
|
||
|
#define COLOR_WARNING C'255,165,0'
|
||
|
#define COLOR_CRYPTO C'255,215,0'
|
||
|
#define COLOR_GOLD C'255,223,0'
|
||
|
#define FONT_SIZE_NORMAL 9
|
||
|
#define FONT_SIZE_LARGE 11
|
||
|
#define FONT_SIZE_SMALL 8
|
||
|
|
||
|
struct SymbolStory {
|
||
|
string symbol;
|
||
|
string narrative;
|
||
|
string signal;
|
||
|
double confidence;
|
||
|
bool hasExecutionPlan;
|
||
|
double riskContribution;
|
||
|
int chaptersCount;
|
||
|
datetime lastUpdate;
|
||
|
};
|
||
|
|
||
|
struct PortfolioStory {
|
||
|
string globalNarrative;
|
||
|
string marketRegime;
|
||
|
double riskAppetite;
|
||
|
string dominantTheme;
|
||
|
SymbolStory symbols[5];
|
||
|
int activeExecutionPlans;
|
||
|
double totalRiskExposure;
|
||
|
datetime lastUpdate;
|
||
|
};
|
||
|
|
||
|
class EnhancedMultiSymbolDashboard {
|
||
|
private:
|
||
|
string m_symbol;
|
||
|
string m_fontName;
|
||
|
int m_window;
|
||
|
string m_serverUrl;
|
||
|
|
||
|
// Enhanced layout for multi-symbol display
|
||
|
struct DashboardLayout {
|
||
|
int x, y;
|
||
|
int width, height;
|
||
|
int margin;
|
||
|
int rowHeight;
|
||
|
int symbolPanelHeight;
|
||
|
int portfolioPanelHeight;
|
||
|
} layout;
|
||
|
|
||
|
PortfolioStory portfolioStory;
|
||
|
|
||
|
public:
|
||
|
EnhancedMultiSymbolDashboard(string symbol, string serverUrl = "http://localhost:8000") {
|
||
|
m_symbol = symbol;
|
||
|
m_serverUrl = serverUrl;
|
||
|
m_fontName = "Consolas";
|
||
|
InitializeLayout();
|
||
|
m_window = 0;
|
||
|
InitializePortfolioStory();
|
||
|
}
|
||
|
|
||
|
void Update() {
|
||
|
FetchPortfolioStory();
|
||
|
DrawEnhancedDashboard();
|
||
|
}
|
||
|
|
||
|
private:
|
||
|
void InitializeLayout() {
|
||
|
layout.x = 15;
|
||
|
layout.y = 15;
|
||
|
layout.width = 420;
|
||
|
layout.height = 650;
|
||
|
layout.margin = 8;
|
||
|
layout.rowHeight = 20;
|
||
|
layout.symbolPanelHeight = 100;
|
||
|
layout.portfolioPanelHeight = 120;
|
||
|
}
|
||
|
|
||
|
void InitializePortfolioStory() {
|
||
|
portfolioStory.globalNarrative = "Initializing multi-symbol trading story engine...";
|
||
|
portfolioStory.marketRegime = "DISCOVERY";
|
||
|
portfolioStory.riskAppetite = 0.5;
|
||
|
portfolioStory.dominantTheme = "INITIALIZATION";
|
||
|
portfolioStory.activeExecutionPlans = 0;
|
||
|
portfolioStory.totalRiskExposure = 0.0;
|
||
|
portfolioStory.lastUpdate = TimeCurrent();
|
||
|
|
||
|
// Initialize symbol stories
|
||
|
string symbols[] = {"EURUSD", "USDJPY", "AUDUSD", "BTCUSD", "XAUUSD"};
|
||
|
for(int i = 0; i < 5; i++) {
|
||
|
portfolioStory.symbols[i].symbol = symbols[i];
|
||
|
portfolioStory.symbols[i].narrative = "Establishing baseline analysis...";
|
||
|
portfolioStory.symbols[i].signal = "HOLD";
|
||
|
portfolioStory.symbols[i].confidence = 0.5;
|
||
|
portfolioStory.symbols[i].hasExecutionPlan = false;
|
||
|
portfolioStory.symbols[i].riskContribution = 0.0;
|
||
|
portfolioStory.symbols[i].chaptersCount = 0;
|
||
|
portfolioStory.symbols[i].lastUpdate = TimeCurrent();
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void FetchPortfolioStory() {
|
||
|
string url = m_serverUrl + "/api/portfolio-story";
|
||
|
string headers = "Content-Type: application/json\r\n";
|
||
|
char result[];
|
||
|
string response = "";
|
||
|
|
||
|
string cookie = NULL, referer = NULL;
|
||
|
uchar data[];
|
||
|
int res = WebRequest("GET", url, cookie, referer, 5000, data, ArraySize(data), result, headers);
|
||
|
|
||
|
if(res == 200) {
|
||
|
response = CharArrayToString(result);
|
||
|
ParsePortfolioStoryResponse(response);
|
||
|
} else {
|
||
|
Print("❌ Failed to fetch portfolio story: HTTP ", res);
|
||
|
}
|
||
|
}
|
||
|
|
||
|
void ParsePortfolioStoryResponse(string response) {
|
||
|
CJAVal json;
|
||
|
if(!json.Deserialize(response)) {
|
||
|
Print("❌ Failed to parse portfolio story JSON");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
if(!json["success"].ToBool()) {
|
||
|
Print("❌ Portfolio story API returned error");
|
||
|
return;
|
||
|
}
|
||
|
|
||
|
// Update portfolio-level data
|
||
|
portfolioStory.globalNarrative = json["portfolioStory"].ToStr();
|
||
|
portfolioStory.activeExecutionPlans = (int)json["activeExecutionPlans"].ToInt();
|
||
|
portfolioStory.totalRiskExposure = json["totalRiskExposure"].ToDbl();
|
||
|
portfolioStory.lastUpdate = TimeCurrent();
|
||
|
|
||
|
// Update global context
|
||
|
if(json["globalContext"].m_type == jtOBJ) {
|
||
|
portfolioStory.marketRegime = json["globalContext"]["marketRegime"].ToStr();
|
||
|
portfolioStory.riskAppetite = json["globalContext"]["riskAppetite"].ToDbl();
|
||
|
portfolioStory.dominantTheme = json["globalContext"]["dominantTheme"].ToStr();
|
||
|
}
|
||
|
|
||
|
// Update individual symbol stories
|
||
|
if(json["instruments"].m_type == jtARRAY) {
|
||
|
int size = json["instruments"].Size();
|
||
|
for(int i = 0; i < MathMin(size, 5); i++) {
|
||
|
CJAVal instrument = json["instruments"][i];
|
||
|
if(i < 5) {
|
||
|
portfolioStory.symbols[i].symbol = instrument["symbol"].ToStr();
|
||
|
portfolioStory.symbols[i].narrative = instrument["narrative"].ToStr();
|
||
|
portfolioStory.symbols[i].hasExecutionPlan = instrument["hasExecutionPlan"].ToBool();
|
||
|
portfolioStory.symbols[i].chaptersCount = (int)instrument["chaptersCount"].ToInt();
|
||
|
portfolioStory.symbols[i].lastUpdate = TimeCurrent();
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
Print("✅ Portfolio story updated: ", portfolioStory.activeExecutionPlans, " active plans");
|
||
|
}
|
||
|
|
||
|
void DrawEnhancedDashboard() {
|
||
|
// Clear previous objects
|
||
|
ClearDashboard();
|
||
|
|
||
|
int y = layout.y;
|
||
|
|
||
|
// Draw main background
|
||
|
DrawMainBackground();
|
||
|
|
||
|
// Draw portfolio header
|
||
|
DrawPortfolioHeader(y);
|
||
|
y += layout.portfolioPanelHeight;
|
||
|
|
||
|
// Draw individual symbol panels
|
||
|
for(int i = 0; i < 5; i++) {
|
||
|
DrawSymbolPanel(portfolioStory.symbols[i], y, i);
|
||
|
y += layout.symbolPanelHeight;
|
||
|
}
|
||
|
|
||
|
// Draw portfolio summary footer
|
||
|
DrawPortfolioSummary(y);
|
||
|
}
|
||
|
|
||
|
void DrawMainBackground() {
|
||
|
string name = "dashboard_main_bg_" + m_symbol;
|
||
|
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||
|
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, layout.x - 5);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, layout.y - 5);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, layout.width + 10);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, layout.height + 10);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, C'20,20,25');
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, C'65,105,225');
|
||
|
ObjectSetInteger(0, name, OBJPROP_WIDTH, 2);
|
||
|
}
|
||
|
|
||
|
void DrawPortfolioHeader(int &y) {
|
||
|
// Portfolio title
|
||
|
DrawLabel("portfolio_title", "🚀 Multi-Symbol Trading Stories", layout.x + 10, y + 5, FONT_SIZE_LARGE, clrWhite);
|
||
|
y += layout.rowHeight + 5;
|
||
|
|
||
|
// Global narrative (word-wrapped)
|
||
|
string narrative = portfolioStory.globalNarrative;
|
||
|
if(StringLen(narrative) > 60) {
|
||
|
narrative = StringSubstr(narrative, 0, 57) + "...";
|
||
|
}
|
||
|
DrawLabel("portfolio_narrative", narrative, layout.x + 10, y, FONT_SIZE_NORMAL, C'200,200,200');
|
||
|
y += layout.rowHeight;
|
||
|
|
||
|
// Market regime and stats
|
||
|
string regimeText = "📊 " + portfolioStory.marketRegime + " | 🎯 " + IntegerToString(portfolioStory.activeExecutionPlans) + " Plans";
|
||
|
DrawLabel("portfolio_regime", regimeText, layout.x + 10, y, FONT_SIZE_NORMAL, COLOR_NEUTRAL);
|
||
|
y += layout.rowHeight;
|
||
|
|
||
|
// Risk appetite indicator
|
||
|
color riskColor = portfolioStory.riskAppetite > 0.7 ? COLOR_LOSS :
|
||
|
portfolioStory.riskAppetite > 0.3 ? COLOR_WARNING : COLOR_PROFIT;
|
||
|
string riskText = "🛡️ Risk: " + DoubleToString(portfolioStory.riskAppetite * 100, 1) + "%";
|
||
|
DrawLabel("portfolio_risk", riskText, layout.x + 10, y, FONT_SIZE_NORMAL, riskColor);
|
||
|
y += layout.rowHeight + 5;
|
||
|
}
|
||
|
|
||
|
void DrawSymbolPanel(SymbolStory &story, int y, int index) {
|
||
|
// Symbol panel background
|
||
|
string bgName = "symbol_bg_" + story.symbol;
|
||
|
color bgColor = GetSymbolColor(story.symbol, true);
|
||
|
ObjectCreate(0, bgName, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_XDISTANCE, layout.x);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_YDISTANCE, y);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_XSIZE, layout.width);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_YSIZE, layout.symbolPanelHeight - 5);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_BGCOLOR, bgColor);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||
|
ObjectSetInteger(0, bgName, OBJPROP_BORDER_COLOR, GetSymbolColor(story.symbol, false));
|
||
|
|
||
|
// Symbol name with icon
|
||
|
string symbolIcon = GetSymbolIcon(story.symbol);
|
||
|
string symbolText = symbolIcon + " " + story.symbol;
|
||
|
DrawLabel("symbol_name_" + story.symbol, symbolText, layout.x + 10, y + 5, FONT_SIZE_NORMAL, clrWhite);
|
||
|
|
||
|
// Execution plan indicator
|
||
|
if(story.hasExecutionPlan) {
|
||
|
DrawLabel("plan_indicator_" + story.symbol, "🎯 PLAN", layout.x + layout.width - 50, y + 5, FONT_SIZE_SMALL, COLOR_PROFIT);
|
||
|
} else {
|
||
|
DrawLabel("plan_indicator_" + story.symbol, "⏳ WAIT", layout.x + layout.width - 50, y + 5, FONT_SIZE_SMALL, COLOR_NEUTRAL);
|
||
|
}
|
||
|
|
||
|
// Signal and confidence
|
||
|
color signalColor = story.signal == "BUY" ? COLOR_PROFIT :
|
||
|
story.signal == "SELL" ? COLOR_LOSS : COLOR_NEUTRAL;
|
||
|
string signalText = story.signal + " (" + DoubleToString(story.confidence * 100, 1) + "%)";
|
||
|
DrawLabel("signal_" + story.symbol, signalText, layout.x + 10, y + 25, FONT_SIZE_NORMAL, signalColor);
|
||
|
|
||
|
// Chapters count
|
||
|
string chaptersText = "📚 " + IntegerToString(story.chaptersCount) + " chapters";
|
||
|
DrawLabel("chapters_" + story.symbol, chaptersText, layout.x + layout.width - 100, y + 25, FONT_SIZE_SMALL, COLOR_NEUTRAL);
|
||
|
|
||
|
// Narrative (truncated)
|
||
|
string narrative = story.narrative;
|
||
|
if(StringLen(narrative) > 50) {
|
||
|
narrative = StringSubstr(narrative, 0, 47) + "...";
|
||
|
}
|
||
|
DrawLabel("narrative_" + story.symbol, narrative, layout.x + 10, y + 45, FONT_SIZE_SMALL, C'180,180,180');
|
||
|
|
||
|
// Last update time
|
||
|
string timeText = "🕐 " + TimeToString(story.lastUpdate, TIME_MINUTES);
|
||
|
DrawLabel("time_" + story.symbol, timeText, layout.x + 10, y + 65, FONT_SIZE_SMALL, COLOR_NEUTRAL);
|
||
|
}
|
||
|
|
||
|
void DrawPortfolioSummary(int y) {
|
||
|
// Summary background
|
||
|
string name = "portfolio_summary_bg";
|
||
|
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||
|
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, layout.x);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, layout.width);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, 40);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, C'30,30,40');
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, C'65,105,225');
|
||
|
|
||
|
// Summary stats
|
||
|
string summaryText = "📊 " + IntegerToString(portfolioStory.activeExecutionPlans) + "/5 Active | " +
|
||
|
"🎯 Risk: " + DoubleToString(portfolioStory.totalRiskExposure, 1) + "% | " +
|
||
|
"🕐 " + TimeToString(portfolioStory.lastUpdate, TIME_MINUTES);
|
||
|
DrawLabel("portfolio_summary", summaryText, layout.x + 10, y + 10, FONT_SIZE_NORMAL, clrWhite);
|
||
|
|
||
|
// Status indicator
|
||
|
color statusColor = portfolioStory.activeExecutionPlans > 0 ? COLOR_PROFIT : COLOR_NEUTRAL;
|
||
|
DrawStatusDot("portfolio_status", layout.x + layout.width - 25, y + 15, statusColor);
|
||
|
}
|
||
|
|
||
|
void DrawLabel(string name, string text, int x, int y, int fontSize, color clr) {
|
||
|
ObjectCreate(0, name, OBJ_LABEL, 0, 0, 0);
|
||
|
ObjectSetString(0, name, OBJPROP_TEXT, text);
|
||
|
ObjectSetString(0, name, OBJPROP_FONT, m_fontName);
|
||
|
ObjectSetInteger(0, name, OBJPROP_FONTSIZE, fontSize);
|
||
|
ObjectSetInteger(0, name, OBJPROP_COLOR, clr);
|
||
|
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
||
|
}
|
||
|
|
||
|
void DrawStatusDot(string name, int x, int y, color clr) {
|
||
|
ObjectCreate(0, name, OBJ_RECTANGLE_LABEL, 0, 0, 0);
|
||
|
ObjectSetInteger(0, name, OBJPROP_CORNER, CORNER_LEFT_UPPER);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XDISTANCE, x);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YDISTANCE, y);
|
||
|
ObjectSetInteger(0, name, OBJPROP_XSIZE, 12);
|
||
|
ObjectSetInteger(0, name, OBJPROP_YSIZE, 12);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BGCOLOR, clr);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_TYPE, BORDER_FLAT);
|
||
|
ObjectSetInteger(0, name, OBJPROP_BORDER_COLOR, clrBlack);
|
||
|
}
|
||
|
|
||
|
color GetSymbolColor(string symbol, bool isBackground) {
|
||
|
if(StringFind(symbol, "BTC") >= 0) {
|
||
|
return isBackground ? C'25,25,35' : COLOR_CRYPTO;
|
||
|
}
|
||
|
if(StringFind(symbol, "XAU") >= 0) {
|
||
|
return isBackground ? C'35,30,25' : COLOR_GOLD;
|
||
|
}
|
||
|
if(StringFind(symbol, "USD") >= 0) {
|
||
|
return isBackground ? C'25,35,25' : COLOR_PROFIT;
|
||
|
}
|
||
|
return isBackground ? C'30,30,30' : COLOR_NEUTRAL;
|
||
|
}
|
||
|
|
||
|
string GetSymbolIcon(string symbol) {
|
||
|
if(StringFind(symbol, "BTC") >= 0) return "₿";
|
||
|
if(StringFind(symbol, "XAU") >= 0) return "🥇";
|
||
|
if(StringFind(symbol, "EUR") >= 0) return "€";
|
||
|
if(StringFind(symbol, "GBP") >= 0) return "£";
|
||
|
if(StringFind(symbol, "JPY") >= 0) return "¥";
|
||
|
if(StringFind(symbol, "AUD") >= 0) return "🇦🇺";
|
||
|
return "💱";
|
||
|
}
|
||
|
|
||
|
void ClearDashboard() {
|
||
|
// Remove all dashboard objects
|
||
|
ObjectsDeleteAll(0, "dashboard_", -1, OBJ_RECTANGLE_LABEL);
|
||
|
ObjectsDeleteAll(0, "portfolio_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "symbol_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "plan_indicator_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "signal_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "chapters_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "narrative_", -1, OBJ_LABEL);
|
||
|
ObjectsDeleteAll(0, "time_", -1, OBJ_LABEL);
|
||
|
}
|
||
|
};
|