2026-01-20 09:55:52 +00:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| OpenAI.mqh |
|
|
|
|
|
//| OpenAI API Integration Module |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
#property copyright "QuarterTheory x VIZION"
|
|
|
|
|
#property strict
|
|
|
|
|
|
|
|
|
|
#include "GlobalVariables.mqh"
|
|
|
|
|
#include "InputParams.mqh"
|
|
|
|
|
#include "Utilities.mqh"
|
2026-01-22 04:48:51 -06:00
|
|
|
#include "Tradeexecution.mqh"
|
|
|
|
|
#include "Openai.mqh"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
2026-01-20 09:55:52 +00:00
|
|
|
|
2026-01-20 10:24:54 +00:00
|
|
|
//================ FORWARD DECLARATIONS ==================//
|
2026-01-22 04:48:51 -06:00
|
|
|
//bool IsAIEnabled();
|
2026-01-20 10:24:54 +00:00
|
|
|
|
2026-01-20 09:55:52 +00:00
|
|
|
//================ OPENAI GLOBALS ==================//
|
2026-01-20 10:28:08 +00:00
|
|
|
string OpenAI_API_Key_Internal = "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"; // Internal storage
|
|
|
|
|
string OpenAI_Model = "gpt-4o-mini"; // or "gpt-4o-mini" for faster/cheaper
|
2026-01-20 09:55:52 +00:00
|
|
|
string OpenAI_Endpoint = "https://api.openai.com/v1/chat/completions";
|
|
|
|
|
|
|
|
|
|
// Response cache to avoid duplicate API calls
|
|
|
|
|
struct OpenAI_Cache
|
|
|
|
|
{
|
|
|
|
|
string last_question;
|
|
|
|
|
string last_response;
|
|
|
|
|
datetime last_time;
|
|
|
|
|
};
|
|
|
|
|
OpenAI_Cache AI_Cache;
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Initialize OpenAI API |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool InitializeOpenAI(string api_key)
|
|
|
|
|
{
|
|
|
|
|
if(StringLen(api_key) < 20)
|
|
|
|
|
{
|
|
|
|
|
Print("❌ OpenAI API key invalid or too short");
|
|
|
|
|
return false;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 10:24:54 +00:00
|
|
|
OpenAI_API_Key_Internal = api_key; // Store internally
|
2026-01-20 09:55:52 +00:00
|
|
|
AI_Cache.last_question = "";
|
|
|
|
|
AI_Cache.last_response = "";
|
|
|
|
|
AI_Cache.last_time = 0;
|
|
|
|
|
|
|
|
|
|
Print("✅ OpenAI API initialized with model: ", OpenAI_Model);
|
|
|
|
|
return true;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-22 04:48:51 -06:00
|
|
|
|
2026-01-20 09:55:52 +00:00
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Build market context for AI analysis |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string BuildMarketContext()
|
|
|
|
|
{
|
|
|
|
|
string context = "Current Market Context:\n";
|
|
|
|
|
context += "=======================\n\n";
|
|
|
|
|
|
|
|
|
|
// Market Mode
|
|
|
|
|
context += "MARKET MODE:\n";
|
|
|
|
|
context += "- Family: " + FamilyToText(Current_Family) + "\n";
|
|
|
|
|
context += "- Bias: " + BiasToText(Current_Bias) + "\n";
|
|
|
|
|
context += "- Strength: " + StrengthToText(Current_Strength) + "\n";
|
|
|
|
|
context += "- State: " + StateToText(Current_State) + "\n";
|
|
|
|
|
context += "- MFIB Bias: " + BiasToText(MFIB_Bias) + "\n\n";
|
|
|
|
|
|
|
|
|
|
// Technical Indicators
|
|
|
|
|
context += "INDICATORS:\n";
|
|
|
|
|
context += "- ADX: " + DoubleToString(Current_ADX, 1) + "\n";
|
|
|
|
|
context += "- ATR: " + DoubleToString(Current_ATR, 5) + "\n";
|
|
|
|
|
context += "- Stoch K: " + DoubleToString(Stoch_K_Current, 1) + "\n";
|
|
|
|
|
context += "- Stoch D: " + DoubleToString(Stoch_D_Current, 1) + "\n\n";
|
|
|
|
|
|
|
|
|
|
// MA Positions
|
|
|
|
|
context += "MOVING AVERAGES:\n";
|
|
|
|
|
context += "- MA7: " + DoubleToString(MA_Current[0], 5) + "\n";
|
|
|
|
|
context += "- MA14: " + DoubleToString(MA_Current[1], 5) + "\n";
|
|
|
|
|
context += "- MA21: " + DoubleToString(MA_Current[2], 5) + "\n";
|
|
|
|
|
context += "- MA50: " + DoubleToString(MA_Current[3], 5) + "\n";
|
|
|
|
|
context += "- MA140: " + DoubleToString(MA_Current[4], 5) + "\n\n";
|
|
|
|
|
|
|
|
|
|
// Price Levels
|
|
|
|
|
double current = MidPrice();
|
|
|
|
|
context += "PRICE LEVELS:\n";
|
|
|
|
|
context += "- Current: " + DoubleToString(current, 5) + "\n";
|
|
|
|
|
context += "- MFIB .382: " + DoubleToString(MFIB_Level_382, 5) + "\n";
|
|
|
|
|
context += "- MFIB .618: " + DoubleToString(MFIB_Level_618, 5) + "\n\n";
|
|
|
|
|
|
|
|
|
|
// Signals
|
|
|
|
|
context += "SIGNALS:\n";
|
|
|
|
|
context += "- Praise Count: " + IntegerToString(Praise_Count) + "/8\n";
|
|
|
|
|
context += "- Warning Count: " + IntegerToString(Warning_Confluence_Count) + "\n";
|
|
|
|
|
|
|
|
|
|
if(Praise_Count > 0)
|
|
|
|
|
{
|
|
|
|
|
context += "- Active Praise: ";
|
|
|
|
|
if(Praise_Triple_Magnet) context += "TripleMagnet ";
|
|
|
|
|
if(Praise_Power_Couple) context += "PowerCouple ";
|
|
|
|
|
if(Praise_MFIB_Staircase) context += "MFIB-Staircase ";
|
|
|
|
|
if(Praise_MFIB_Express) context += "MFIB-Express ";
|
|
|
|
|
if(Praise_MFIB_Breakout) context += "MFIB-Breakout ";
|
|
|
|
|
if(Praise_MA_Stack) context += "MA-Stack ";
|
|
|
|
|
if(Praise_Clean_Reclaim) context += "CleanReclaim ";
|
|
|
|
|
if(Praise_Multi_Breakout) context += "MultiBreakout ";
|
|
|
|
|
context += "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if(Warning_Confluence_Count > 0)
|
|
|
|
|
{
|
|
|
|
|
context += "- Active Warnings: ";
|
|
|
|
|
if(MA7_Cross_14_Warning) context += "MA7x14 ";
|
|
|
|
|
if(MA7_Cross_21_Warning) context += "MA7x21 ";
|
|
|
|
|
if(Stoch_Extreme_Warning) context += "StochExtreme ";
|
|
|
|
|
if(MFIB_Reject_Warning) context += "MFIB-Reject ";
|
|
|
|
|
if(Band_Snap_Warning) context += "BandSnap ";
|
|
|
|
|
context += "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context += "\n";
|
|
|
|
|
|
|
|
|
|
// Open Positions
|
|
|
|
|
int total_positions = ArraySize(OpenPositions);
|
|
|
|
|
context += "POSITIONS:\n";
|
|
|
|
|
context += "- Total Open: " + IntegerToString(total_positions) + "\n";
|
|
|
|
|
|
|
|
|
|
int buy_count = 0, sell_count = 0;
|
|
|
|
|
double total_profit = 0;
|
|
|
|
|
|
|
|
|
|
for(int i=0; i<total_positions; i++)
|
|
|
|
|
{
|
|
|
|
|
if(OpenPositions[i].is_buy) buy_count++;
|
|
|
|
|
else sell_count++;
|
|
|
|
|
|
|
|
|
|
// Calculate floating P&L
|
|
|
|
|
if(PositionSelectByTicket(OpenPositions[i].ticket))
|
|
|
|
|
{
|
|
|
|
|
total_profit += PositionGetDouble(POSITION_PROFIT);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
context += "- Buys: " + IntegerToString(buy_count) + " | Sells: " + IntegerToString(sell_count) + "\n";
|
|
|
|
|
context += "- Floating P&L: $" + DoubleToString(total_profit, 2) + "\n";
|
|
|
|
|
|
|
|
|
|
return context;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Call OpenAI API |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string CallOpenAI(string prompt, string system_message = "")
|
|
|
|
|
{
|
|
|
|
|
// Check cache (avoid duplicate calls within 60 seconds)
|
|
|
|
|
if(prompt == AI_Cache.last_question &&
|
|
|
|
|
TimeCurrent() - AI_Cache.last_time < 60)
|
|
|
|
|
{
|
|
|
|
|
Print("📦 Using cached AI response");
|
|
|
|
|
return AI_Cache.last_response;
|
|
|
|
|
}
|
|
|
|
|
|
2026-01-20 10:24:54 +00:00
|
|
|
if(StringLen(OpenAI_API_Key_Internal) < 20)
|
2026-01-20 09:55:52 +00:00
|
|
|
{
|
|
|
|
|
Print("❌ OpenAI API key not configured");
|
|
|
|
|
return "ERROR: API key not set";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Default system message
|
|
|
|
|
if(system_message == "")
|
|
|
|
|
{
|
|
|
|
|
system_message = "You are an expert forex trading analyst integrated into the QuarterTheory trading system. ";
|
|
|
|
|
system_message += "Provide concise, actionable analysis. Focus on key insights. ";
|
|
|
|
|
system_message += "Use clear language. Keep responses under 200 words unless asked for detail.";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Build JSON request
|
|
|
|
|
string json_request = "{";
|
|
|
|
|
json_request += "\"model\":\"" + OpenAI_Model + "\",";
|
|
|
|
|
json_request += "\"messages\":[";
|
|
|
|
|
json_request += "{\"role\":\"system\",\"content\":\"" + EscapeJSON(system_message) + "\"},";
|
|
|
|
|
json_request += "{\"role\":\"user\",\"content\":\"" + EscapeJSON(prompt) + "\"}";
|
|
|
|
|
json_request += "],";
|
|
|
|
|
json_request += "\"temperature\":0.7,";
|
|
|
|
|
json_request += "\"max_tokens\":500";
|
|
|
|
|
json_request += "}";
|
|
|
|
|
|
|
|
|
|
// Prepare HTTP request
|
|
|
|
|
char post_data[];
|
|
|
|
|
char result_data[];
|
|
|
|
|
string result_headers;
|
|
|
|
|
|
|
|
|
|
StringToCharArray(json_request, post_data, 0, StringLen(json_request));
|
|
|
|
|
|
|
|
|
|
// Set headers
|
|
|
|
|
string headers = "Content-Type: application/json\r\n";
|
2026-01-20 10:24:54 +00:00
|
|
|
headers += "Authorization: Bearer " + OpenAI_API_Key_Internal + "\r\n";
|
2026-01-20 09:55:52 +00:00
|
|
|
|
|
|
|
|
// Make API call
|
|
|
|
|
Print("🤖 Calling OpenAI API...");
|
|
|
|
|
|
|
|
|
|
int timeout = 10000; // 10 seconds
|
|
|
|
|
int res = WebRequest(
|
|
|
|
|
"POST",
|
|
|
|
|
OpenAI_Endpoint,
|
|
|
|
|
headers,
|
|
|
|
|
timeout,
|
|
|
|
|
post_data,
|
|
|
|
|
result_data,
|
|
|
|
|
result_headers
|
|
|
|
|
);
|
|
|
|
|
|
|
|
|
|
if(res == -1)
|
|
|
|
|
{
|
|
|
|
|
int error = GetLastError();
|
|
|
|
|
Print("❌ WebRequest error: ", error);
|
|
|
|
|
Print("Make sure ", OpenAI_Endpoint, " is in allowed URLs");
|
|
|
|
|
Print("Tools → Options → Expert Advisors → Allow WebRequest for listed URL");
|
|
|
|
|
return "ERROR: WebRequest failed (" + IntegerToString(error) + ")";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// Parse response
|
|
|
|
|
string response = CharArrayToString(result_data);
|
|
|
|
|
|
|
|
|
|
// Extract content from JSON
|
|
|
|
|
string ai_response = ExtractAIResponse(response);
|
|
|
|
|
|
|
|
|
|
// Cache the response
|
|
|
|
|
AI_Cache.last_question = prompt;
|
|
|
|
|
AI_Cache.last_response = ai_response;
|
|
|
|
|
AI_Cache.last_time = TimeCurrent();
|
|
|
|
|
|
|
|
|
|
Print("✅ OpenAI response received");
|
|
|
|
|
|
|
|
|
|
return ai_response;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Escape JSON special characters |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string EscapeJSON(string text)
|
|
|
|
|
{
|
|
|
|
|
string result = text;
|
|
|
|
|
|
|
|
|
|
StringReplace(result, "\\", "\\\\");
|
|
|
|
|
StringReplace(result, "\"", "\\\"");
|
|
|
|
|
StringReplace(result, "\n", "\\n");
|
|
|
|
|
StringReplace(result, "\r", "\\r");
|
|
|
|
|
StringReplace(result, "\t", "\\t");
|
|
|
|
|
|
|
|
|
|
return result;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Extract AI response from JSON |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string ExtractAIResponse(string json)
|
|
|
|
|
{
|
|
|
|
|
// Simple JSON parsing - find "content":"..."
|
|
|
|
|
int start_pos = StringFind(json, "\"content\":\"");
|
|
|
|
|
if(start_pos == -1)
|
|
|
|
|
{
|
|
|
|
|
Print("⚠️ Could not parse AI response");
|
|
|
|
|
return "ERROR: Invalid response format";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
start_pos += 11; // Length of "content":"
|
|
|
|
|
|
|
|
|
|
int end_pos = StringFind(json, "\"", start_pos);
|
|
|
|
|
if(end_pos == -1)
|
|
|
|
|
{
|
|
|
|
|
return "ERROR: Malformed response";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string content = StringSubstr(json, start_pos, end_pos - start_pos);
|
|
|
|
|
|
|
|
|
|
// Unescape JSON
|
|
|
|
|
StringReplace(content, "\\n", "\n");
|
|
|
|
|
StringReplace(content, "\\r", "\r");
|
|
|
|
|
StringReplace(content, "\\t", "\t");
|
|
|
|
|
StringReplace(content, "\\\"", "\"");
|
|
|
|
|
StringReplace(content, "\\\\", "\\");
|
|
|
|
|
|
|
|
|
|
return content;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| AI-powered market analysis |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string GetAIMarketAnalysis()
|
|
|
|
|
{
|
|
|
|
|
string context = BuildMarketContext();
|
|
|
|
|
|
|
|
|
|
string prompt = context + "\n\nBased on this market data, provide:\n";
|
|
|
|
|
prompt += "1. Current market sentiment (1-2 sentences)\n";
|
|
|
|
|
prompt += "2. Key risk factors (2-3 points)\n";
|
|
|
|
|
prompt += "3. Trading recommendation (aggressive/moderate/defensive)\n";
|
|
|
|
|
prompt += "\nBe concise and actionable.";
|
|
|
|
|
|
|
|
|
|
return CallOpenAI(prompt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| AI trade validation |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string ValidateTradeWithAI(string setup_name, bool is_buy, TRADE_CATEGORY category)
|
|
|
|
|
{
|
|
|
|
|
string context = BuildMarketContext();
|
|
|
|
|
|
|
|
|
|
string direction = is_buy ? "BUY" : "SELL";
|
|
|
|
|
string cat_text = (category == CATEGORY_CONTINUATION) ? "CONTINUATION" : "COUNTER-TREND";
|
|
|
|
|
|
|
|
|
|
string prompt = context + "\n\nI'm about to take a " + direction + " trade:\n";
|
|
|
|
|
prompt += "- Setup: " + setup_name + "\n";
|
|
|
|
|
prompt += "- Category: " + cat_text + "\n";
|
|
|
|
|
prompt += "- Praise Signals: " + IntegerToString(Praise_Count) + "/8\n";
|
|
|
|
|
prompt += "- Warning Signals: " + IntegerToString(Warning_Confluence_Count) + "\n\n";
|
|
|
|
|
prompt += "Should I take this trade? Provide:\n";
|
|
|
|
|
prompt += "1. YES/NO/MAYBE\n";
|
|
|
|
|
prompt += "2. Brief reasoning (2-3 sentences)\n";
|
|
|
|
|
prompt += "3. Suggested position size adjustment (if any)";
|
|
|
|
|
|
|
|
|
|
return CallOpenAI(prompt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| AI position management advice |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string GetAIPositionAdvice(ulong ticket)
|
|
|
|
|
{
|
|
|
|
|
if(!PositionSelectByTicket(ticket))
|
|
|
|
|
return "ERROR: Position not found";
|
|
|
|
|
|
|
|
|
|
double entry = PositionGetDouble(POSITION_PRICE_OPEN);
|
|
|
|
|
double current_price = PositionGetDouble(POSITION_PRICE_CURRENT);
|
|
|
|
|
double profit = PositionGetDouble(POSITION_PROFIT);
|
|
|
|
|
bool is_buy = (PositionGetInteger(POSITION_TYPE) == POSITION_TYPE_BUY);
|
|
|
|
|
|
|
|
|
|
string context = BuildMarketContext();
|
|
|
|
|
|
|
|
|
|
string prompt = context + "\n\nI have an open position:\n";
|
|
|
|
|
prompt += "- Type: " + (is_buy ? "BUY" : "SELL") + "\n";
|
|
|
|
|
prompt += "- Entry: " + DoubleToString(entry, 5) + "\n";
|
|
|
|
|
prompt += "- Current: " + DoubleToString(current_price, 5) + "\n";
|
|
|
|
|
prompt += "- Profit: $" + DoubleToString(profit, 2) + "\n\n";
|
|
|
|
|
prompt += "Should I:\n";
|
|
|
|
|
prompt += "1. Hold\n";
|
|
|
|
|
prompt += "2. Take partial profit\n";
|
|
|
|
|
prompt += "3. Close completely\n";
|
|
|
|
|
prompt += "4. Add to position\n\n";
|
|
|
|
|
prompt += "Provide recommendation with brief reasoning.";
|
|
|
|
|
|
|
|
|
|
return CallOpenAI(prompt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| AI daily briefing |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string GetAIDailyBriefing()
|
|
|
|
|
{
|
|
|
|
|
string context = BuildMarketContext();
|
|
|
|
|
|
|
|
|
|
string prompt = context + "\n\nProvide a daily trading briefing:\n";
|
|
|
|
|
prompt += "1. Market environment summary\n";
|
|
|
|
|
prompt += "2. Best trading opportunities today\n";
|
|
|
|
|
prompt += "3. Key levels to watch\n";
|
|
|
|
|
prompt += "4. Risk management priorities\n\n";
|
|
|
|
|
prompt += "Format as a brief morning briefing (150 words max).";
|
|
|
|
|
|
|
|
|
|
string system_msg = "You are a professional forex market analyst providing a morning briefing. ";
|
|
|
|
|
system_msg += "Be precise, factual, and focused on actionable insights.";
|
|
|
|
|
|
|
|
|
|
return CallOpenAI(prompt, system_msg);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| AI pattern recognition |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
string IdentifyPatternsWithAI()
|
|
|
|
|
{
|
|
|
|
|
string context = BuildMarketContext();
|
|
|
|
|
|
|
|
|
|
// Get recent price action
|
|
|
|
|
string price_data = "\nRecent Price Action (last 10 bars):\n";
|
|
|
|
|
for(int i=0; i<10; i++)
|
|
|
|
|
{
|
|
|
|
|
double open = iOpen(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
|
double high = iHigh(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
|
double low = iLow(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
|
double close = iClose(_Symbol, PERIOD_CURRENT, i);
|
|
|
|
|
|
|
|
|
|
price_data += "Bar " + IntegerToString(i) + ": O=" + DoubleToString(open, 5);
|
|
|
|
|
price_data += " H=" + DoubleToString(high, 5);
|
|
|
|
|
price_data += " L=" + DoubleToString(low, 5);
|
|
|
|
|
price_data += " C=" + DoubleToString(close, 5) + "\n";
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
string prompt = context + price_data + "\n\nAnalyze this data and identify:\n";
|
|
|
|
|
prompt += "1. Any chart patterns forming (head & shoulders, triangles, wedges, etc.)\n";
|
|
|
|
|
prompt += "2. Trend characteristics (strong/weak, accelerating/decelerating)\n";
|
|
|
|
|
prompt += "3. Support/resistance levels\n";
|
|
|
|
|
prompt += "4. Potential reversal or continuation signals\n\n";
|
|
|
|
|
prompt += "Be specific and cite the evidence.";
|
|
|
|
|
|
|
|
|
|
return CallOpenAI(prompt);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
//| Check if AI features are enabled |
|
|
|
|
|
//+------------------------------------------------------------------+
|
|
|
|
|
bool IsAIEnabled()
|
|
|
|
|
{
|
2026-01-20 10:24:54 +00:00
|
|
|
return (StringLen(OpenAI_API_Key_Internal) >= 20);
|
2026-01-20 09:55:52 +00:00
|
|
|
}
|