//+------------------------------------------------------------------+ //| Enhanced Multi-Symbol Trading Story Dashboard | //| Displays comprehensive portfolio story on single chart | //+------------------------------------------------------------------+ #include #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); } };