diff --git a/API使用指南.md b/API使用指南.md index 2a462d5..56fb23c 100644 --- a/API使用指南.md +++ b/API使用指南.md @@ -305,6 +305,93 @@ for d in deals: --- +### 11. 全局变量(MQL5 信号桥) + +> 让 MQL5 指标/EA 把信号写出来,Python 通过 Bridge 读取。不用翻译 MQL5 代码。 + +#### 列出所有全局变量 + +``` +GET /gvar +``` + +```python +gvars = api("/gvar") +print(gvars) +# {"data": [{"name": "AT_Trend_XAUUSDc", "value": 1}, ...], "count": 5} +``` + +#### 读取指定变量 + +``` +GET /gvar/{name} +``` + +```python +trend = api("/gvar/AT_Trend_XAUUSDc")["value"] +print(f"趋势方向: {'多头' if trend == 1 else '空头'}") +``` + +#### 写入变量 + +``` +POST /gvar/{name} +``` + +Body: `{"value": 75.5}` + +```python +def set_gvar(name, value): + return api_post(f"/gvar/{name}", {"value": value}) + +set_gvar("MY_RSI", 75.5) +``` + +#### 删除变量 + +``` +DELETE /gvar/{name} +``` + +--- + +## MQL5 指标 → Python 完整流程 + +### 第一步:改指标源码,加一行输出 + +```mql5 +// 在 OnCalculate 末尾加 +int last = rates_total - 1; +GlobalVariableSet("MY_SIGNAL_" + _Symbol, signal_value); +``` + +### 第二步:Python 读取信号 + +```python +def read_signal(): + try: + return api(f"/gvar/MY_SIGNAL_XAUUSDc")["value"] + except: + return None + +signal = read_signal() +print(f"指标信号: {signal}") +``` + +### 第三步:根据信号做决策 + +```python +def on_tick(): + signal = read_signal() + if signal != 1: + return # 没信号,不动 + + if not bridge.has_position("XAUUSDc"): + bid, ask = bridge.tick("XAUUSDc") + bridge.buy("XAUUSDc", 0.01, ask, sl=ask - 50, tp=ask + 100) + print("指标发出买入信号,已开多") +``` + ## 完整策略模板 ```python diff --git a/Alpha Trend.ex5 b/Alpha Trend.ex5 new file mode 100644 index 0000000..4323303 Binary files /dev/null and b/Alpha Trend.ex5 differ diff --git a/Alpha Trend.mq5 b/Alpha Trend.mq5 new file mode 100644 index 0000000..3a41627 --- /dev/null +++ b/Alpha Trend.mq5 @@ -0,0 +1,231 @@ +//+-------------------------------------------------------------------------------------------------------+ +//| Alpha Trend.mq5 | +//| Copyright © 2022, Centaur | +//| https://www.mql5.com/en/users/centaur | +//| | +//|For screenshots and extra info: | +//| https://forex-station.com/viewtopic.php?p=1295492109&sid=16f4baa8018e53a3e71fb54b6b29977b#p1295492109 | +//| | +//+-------------------------------------------------------------------------------------------------------+ +#property copyright "Copyright © 2022, Centaur" +#property link "https://www.mql5.com/en/users/centaur" +#property version "1.00" +#property indicator_chart_window +#property indicator_buffers 16 +#property indicator_plots 3 +//--- plot alpha line and offset line +#property indicator_label1 "Alpha Line;Offset Line" +#property indicator_type1 DRAW_FILLING +#property indicator_color1 clrDodgerBlue,clrTomato +#property indicator_style1 STYLE_SOLID +#property indicator_width1 1 +//--- plot Buy Signal +#property indicator_label2 "Buy Signal" +#property indicator_type2 DRAW_ARROW +#property indicator_color2 clrDodgerBlue +#property indicator_style2 STYLE_SOLID +#property indicator_width2 1 +//--- plot Sell Signal +#property indicator_label3 "Sell Signal" +#property indicator_type3 DRAW_ARROW +#property indicator_color3 clrTomato +#property indicator_style3 STYLE_SOLID +#property indicator_width3 1 +//--- enumerations +enum ENUM_YES_NO + { + Yes, + No + }; +//--- input parameters +input int inp_length = 14; // Length +input double inp_atr_multiplier = 1.0; // ATR Multiplier +input ENUM_APPLIED_PRICE inp_price = PRICE_CLOSE; // Applied Price +input ENUM_YES_NO inp_change_calc = No; // Use volume data ? +input ENUM_YES_NO inp_signals = Yes; // Show Signals ? +//--- indicator plot buffers +double Alpha[]; +double Offset[]; +double Buy_Signal[]; +double Sell_Signal[]; +//--- indicator calculation buffers +double TR[]; +double ATR[]; +double RSI[]; +double MFI[]; +double UpTrend[]; +double DownTrend[]; +double Alpha_Calc[]; +double Buy_Signal_Calc[]; +double Sell_Signal_Calc[]; +double Buy_BarCount[]; +double Sell_BarCount[]; +double Arrows[]; +//--- indicator variables +int length; +double atr_multiplier; +int TR_Handle; +int RSI_Handle; +int MFI_Handle; +//+------------------------------------------------------------------+ +//| Custom indicator initialization function | +//+------------------------------------------------------------------+ +int OnInit() + { +//--- check input parameters + length = inp_length < 1 ? 1 : inp_length; + atr_multiplier = inp_atr_multiplier < 0.1 ? 0.1 : NormalizeDouble(inp_atr_multiplier, 1); +//--- indicator buffers mapping + SetIndexBuffer(0, Alpha, INDICATOR_DATA); + SetIndexBuffer(1, Offset, INDICATOR_DATA); + SetIndexBuffer(2, Buy_Signal, INDICATOR_DATA); + SetIndexBuffer(3, Sell_Signal, INDICATOR_DATA); + SetIndexBuffer(4, TR, INDICATOR_CALCULATIONS); + SetIndexBuffer(5, ATR, INDICATOR_CALCULATIONS); + SetIndexBuffer(6, RSI, INDICATOR_CALCULATIONS); + SetIndexBuffer(7, MFI, INDICATOR_CALCULATIONS); + SetIndexBuffer(8, UpTrend, INDICATOR_CALCULATIONS); + SetIndexBuffer(9, DownTrend, INDICATOR_CALCULATIONS); + SetIndexBuffer(10, Alpha_Calc, INDICATOR_CALCULATIONS); + SetIndexBuffer(11, Buy_Signal_Calc, INDICATOR_CALCULATIONS); + SetIndexBuffer(12, Sell_Signal_Calc, INDICATOR_CALCULATIONS); + SetIndexBuffer(13, Buy_BarCount, INDICATOR_CALCULATIONS); + SetIndexBuffer(14, Sell_BarCount, INDICATOR_CALCULATIONS); + SetIndexBuffer(15, Arrows, INDICATOR_CALCULATIONS); +//--- set indicator accuracy + IndicatorSetInteger(INDICATOR_DIGITS, _Digits); +//--- set indicator name display + string prices = inp_price == PRICE_OPEN ? "Open" : inp_price == PRICE_HIGH ? "High" : inp_price == PRICE_LOW ? "Low" : inp_price == PRICE_CLOSE ? "Close" : inp_price == PRICE_MEDIAN ? "Median" : inp_price == PRICE_TYPICAL ? "Typical" : inp_price == PRICE_WEIGHTED ? "Weighted" : " "; + string short_name = "Alpha Trend (" + IntegerToString(length) + ", " + DoubleToString(atr_multiplier, 1) + ", " + prices + ", " + EnumToString(inp_change_calc) + ", " + EnumToString(inp_signals) + ")"; + IndicatorSetString(INDICATOR_SHORTNAME, short_name); +//--- sets drawing lines to empty value + PlotIndexSetDouble(0, PLOT_EMPTY_VALUE, EMPTY_VALUE); + PlotIndexSetDouble(1, PLOT_EMPTY_VALUE, EMPTY_VALUE); + PlotIndexSetDouble(2, PLOT_EMPTY_VALUE, EMPTY_VALUE); + PlotIndexSetDouble(3, PLOT_EMPTY_VALUE, EMPTY_VALUE); +//--- initialize buffers + ArrayInitialize(Alpha, EMPTY_VALUE); + ArrayInitialize(Offset, EMPTY_VALUE); + ArrayInitialize(Buy_Signal, EMPTY_VALUE); + ArrayInitialize(Sell_Signal, EMPTY_VALUE); + ArrayInitialize(TR, 0.0); + ArrayInitialize(ATR, 0.0); + ArrayInitialize(RSI, 0.0); + ArrayInitialize(MFI, 0.0); + ArrayInitialize(UpTrend, 0.0); + ArrayInitialize(DownTrend, 0.0); + ArrayInitialize(Alpha_Calc, 0.0); + ArrayInitialize(Buy_Signal_Calc, 0.0); + ArrayInitialize(Sell_Signal_Calc, 0.0); + ArrayInitialize(Buy_BarCount, 0.0); + ArrayInitialize(Sell_BarCount, 0.0); + ArrayInitialize(Arrows, 0.0); +//--- setting a code from the Wingdings charset as the property of PLOT_ARROW + PlotIndexSetInteger(1, PLOT_ARROW, 225); + PlotIndexSetInteger(2, PLOT_ARROW, 226); +//--- Set the vertical shift of arrows in pixels + PlotIndexSetInteger(1, PLOT_ARROW_SHIFT, 20); + PlotIndexSetInteger(2, PLOT_ARROW_SHIFT, -20); +//--- create handles + TR_Handle = iATR(_Symbol, _Period, 1); + RSI_Handle = iRSI(_Symbol, _Period, length, inp_price); + MFI_Handle = iMFI(_Symbol, _Period, length, VOLUME_TICK); +//--- initialization succeeded + return(INIT_SUCCEEDED); + } +//+------------------------------------------------------------------+ +//| Custom indicator iteration function | +//+------------------------------------------------------------------+ +int OnCalculate(const int rates_total, + const int prev_calculated, + const datetime &time[], + const double &open[], + const double &high[], + const double &low[], + const double &close[], + const long &tick_volume[], + const long &volume[], + const int &spread[]) + { +//--- check period + if(length <= 1 || length > rates_total) + return(0); +//--- latest data copy + int copy; + if(prev_calculated > rates_total || prev_calculated <= 0) + copy = rates_total; + else + { + copy = rates_total - prev_calculated; + //--- last value is always copied + copy++; + } +//--- populate buffers + if(CopyBuffer(TR_Handle, 0, 0, copy, TR) <= 0) + return(0); + if(CopyBuffer(RSI_Handle, 0, 0, copy, RSI) <= 0) + return(0); + if(CopyBuffer(MFI_Handle, 0, 0, copy, MFI) <= 0) + return(0); +//--- calculate start position + int bar; + if(prev_calculated == 0) + bar = 0; + else + bar = prev_calculated - 1; +//--- main loop + for(int i = bar; i < rates_total && !_StopFlag; i++) + { + if(i > length) + { + //--- calculate alpha trend + ATR[i] = fSMA(i, length, TR); + UpTrend[i] = low[i] - ATR[i] * atr_multiplier; + DownTrend[i] = high[i] + ATR[i] * atr_multiplier; + Alpha_Calc[i] = (inp_change_calc == No ? RSI[i] >= 50 : MFI[i] >= 50) ? UpTrend[i] < Alpha_Calc[i - 1] ? Alpha_Calc[i - 1] : UpTrend[i] : DownTrend[i] > Alpha_Calc[i - 1] ? Alpha_Calc[i - 1] : DownTrend[i]; + //--- plot alpha trend + Alpha[i] = NormalizeDouble(Alpha_Calc[i], _Digits); + Offset[i] = NormalizeDouble(Alpha_Calc[i - 2], _Digits); + //--- plot buy and sell signals + Buy_Signal_Calc[i] = Alpha[i] > Offset[i] && Alpha[i - 1] <= Offset[i - 1] ? 1.0 : 0.0; + Sell_Signal_Calc[i] = Alpha[i] < Offset[i] && Alpha[i - 1] >= Offset[i - 1] ? 1.0 : 0.0; + Buy_BarCount[i] = fBarsSince(i, Buy_Signal_Calc); + Sell_BarCount[i] = fBarsSince(i, Sell_Signal_Calc); + Arrows[i] = Buy_BarCount[i] > Sell_BarCount[i] ? 1.0 : -1.0; + Buy_Signal[i] = inp_signals == No ? EMPTY_VALUE : Arrows[i] == -1.0 && Arrows[i - 1] == 1.0 ? Offset[i] : EMPTY_VALUE; + Sell_Signal[i] = inp_signals == No ? EMPTY_VALUE : Arrows[i] == 1.0 && Arrows[i - 1] == -1.0 ? Offset[i] : EMPTY_VALUE; + } + } +//--- write signals to GlobalVariable for Mt5Bridge + int last = rates_total - 1; + GlobalVariableSet("AT_Alpha_" + _Symbol, Alpha[last]); + GlobalVariableSet("AT_Offset_" + _Symbol, Offset[last]); + GlobalVariableSet("AT_Trend_" + _Symbol, Arrows[last]); + GlobalVariableSet("AT_Buy_" + _Symbol, Buy_Signal_Calc[last]); + GlobalVariableSet("AT_Sell_" + _Symbol, Sell_Signal_Calc[last]); +//--- return value of prev_calculated for next call + return(rates_total); + } +//+------------------------------------------------------------------+ +//| Function: Simple Moving Average (SMA) | +//+------------------------------------------------------------------+ +double fSMA(const int _position, const int _period, const double &_input[]) + { + double result = 0.0; + double sum = 0.0; + for(int k = 0; k < _period; k++) + sum += _input[_position - k]; + result = sum / _period; + return(result); + } +//+------------------------------------------------------------------+ +//| Function: Count bars since last event greater than zero | +//+------------------------------------------------------------------+ +double fBarsSince(const int _position, const double &_input[]) + { + int result = 0; + while(_input[_position - result] == 0.0) + result++; + return(result); + } +//+------------------------------------------------------------------+ \ No newline at end of file diff --git a/Program.cs b/Program.cs index 688919c..02890b4 100644 --- a/Program.cs +++ b/Program.cs @@ -1,4 +1,5 @@ using MtApi5; +using Microsoft.AspNetCore.Mvc; using System.Reflection; var builder = WebApplication.CreateBuilder(args); @@ -305,6 +306,47 @@ app.MapGet("/history/deals", (string date_from, string date_to, string? symbol, return new { data = dealList, count = dealList.Count, format = "json" }; }); +app.MapGet("/gvar", (HttpContext context) => +{ + var mt = context.RequestServices.GetRequiredService(); + var total = mt.GlobalVariablesTotal(); + var vars = new List(); + for (int i = 0; i < total; i++) + { + var name = mt.GlobalVariableName(i) ?? ""; + var value = mt.GlobalVariableGet(name); + vars.Add(new { name, value }); + } + return Results.Ok(new { data = vars, count = vars.Count, format = "json" }); +}); + +app.MapGet("/gvar/{name}", (HttpContext context, string name) => +{ + var mt = context.RequestServices.GetRequiredService(); + if (!mt.GlobalVariableCheck(name)) + return Results.NotFound(new { detail = $"GlobalVariable '{name}' not found" }); + var value = mt.GlobalVariableGet(name); + return Results.Ok(new { name, value }); +}); + +app.MapPost("/gvar/{name}", async (HttpContext context, string name) => +{ + var mt = context.RequestServices.GetRequiredService(); + var body = await context.Request.ReadFromJsonAsync(); + if (body == null) return Results.BadRequest("Invalid body"); + mt.GlobalVariableSet(name, body.value); + return Results.Ok(new { name, value = body.value }); +}); + +app.MapDelete("/gvar/{name}", (HttpContext context, string name) => +{ + var mt = context.RequestServices.GetRequiredService(); + if (!mt.GlobalVariableCheck(name)) + return Results.NotFound(new { detail = $"GlobalVariable '{name}' not found" }); + mt.GlobalVariableDel(name); + return Results.Ok(new { deleted = name }); +}); + app.Run(); DateTime MtTimeToDateTime(long mtTime) @@ -352,6 +394,11 @@ class TradeRequestBody public TradeRequestDto request { get; set; } = new(); } +class GvarSetDto +{ + public double value { get; set; } +} + class MtConnectionService : BackgroundService { private readonly MtApi5Client _client; diff --git a/README.md.md b/README.md.md index e7212c3..3e1d77e 100644 --- a/README.md.md +++ b/README.md.md @@ -431,6 +431,50 @@ GET /history/deals?date_from={from}&date_to={to}&symbol={symbol} **示例:** `/history/deals?date_from=2026-07-01&date_to=2026-07-03&symbol=XAUUSDc` +### 全局变量(MQL5 信号桥) + +让 MQL5 指标/EA 将信号写入 GlobalVariable,Python 通过 Bridge 读取。无需翻译 MQL5 代码。 + +``` +GET /gvar +GET /gvar/{name} +POST /gvar/{name} +DELETE /gvar/{name} +``` + +**列出所有全局变量:** +``` +GET /gvar +``` + +**读取指定变量:** +``` +GET /gvar/{name} +``` + +**写入变量:** +``` +POST /gvar/{name} +Body: {"value": 75.5} +``` + +**删除变量:** +``` +DELETE /gvar/{name} +``` + +**MQL5 侧(指标末尾加一行):** +```mql5 +int last = rates_total - 1; +GlobalVariableSet("MY_SIGNAL_" + _Symbol, Alpha[last]); +``` + +**Python 侧读取:** +```python +resp = requests.get(f"{BRIDGE}/gvar/MY_SIGNAL_XAUUSDc", headers=HEADERS) +signal = resp.json()["value"] +``` + --- ## 七、调用示例 diff --git a/bin/Debug/net8.0/Mt5Bridge.dll b/bin/Debug/net8.0/Mt5Bridge.dll index f79b3e1..b30033c 100644 Binary files a/bin/Debug/net8.0/Mt5Bridge.dll and b/bin/Debug/net8.0/Mt5Bridge.dll differ diff --git a/bin/Debug/net8.0/Mt5Bridge.exe b/bin/Debug/net8.0/Mt5Bridge.exe index eae3425..5ab86cb 100644 Binary files a/bin/Debug/net8.0/Mt5Bridge.exe and b/bin/Debug/net8.0/Mt5Bridge.exe differ diff --git a/bin/Debug/net8.0/Mt5Bridge.pdb b/bin/Debug/net8.0/Mt5Bridge.pdb index 25e0765..b6a0e8f 100644 Binary files a/bin/Debug/net8.0/Mt5Bridge.pdb and b/bin/Debug/net8.0/Mt5Bridge.pdb differ diff --git a/obj/Debug/net8.0/Mt5Bridge.AssemblyInfo.cs b/obj/Debug/net8.0/Mt5Bridge.AssemblyInfo.cs index bf2f7aa..241fb89 100644 --- a/obj/Debug/net8.0/Mt5Bridge.AssemblyInfo.cs +++ b/obj/Debug/net8.0/Mt5Bridge.AssemblyInfo.cs @@ -13,7 +13,7 @@ using System.Reflection; [assembly: System.Reflection.AssemblyCompanyAttribute("Mt5Bridge")] [assembly: System.Reflection.AssemblyConfigurationAttribute("Debug")] [assembly: System.Reflection.AssemblyFileVersionAttribute("1.0.0.0")] -[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+ac1153c0914bcde0cebee04271eb48f6047e7c85")] +[assembly: System.Reflection.AssemblyInformationalVersionAttribute("1.0.0+17ee7418db2cb5844ed378e50486c832fb71e825")] [assembly: System.Reflection.AssemblyProductAttribute("Mt5Bridge")] [assembly: System.Reflection.AssemblyTitleAttribute("Mt5Bridge")] [assembly: System.Reflection.AssemblyVersionAttribute("1.0.0.0")] diff --git a/obj/Debug/net8.0/Mt5Bridge.AssemblyInfoInputs.cache b/obj/Debug/net8.0/Mt5Bridge.AssemblyInfoInputs.cache index f6bfd86..3df2176 100644 --- a/obj/Debug/net8.0/Mt5Bridge.AssemblyInfoInputs.cache +++ b/obj/Debug/net8.0/Mt5Bridge.AssemblyInfoInputs.cache @@ -1 +1 @@ -3e51f6bbe9ff6ed0893f1911fc810b6691a001b8db244523b3de6ba0c5a11ab4 +5262a17ecfe612cf8968a576e60268f783ab6c2e11fe50fa49843137ec77f21c diff --git a/obj/Debug/net8.0/Mt5Bridge.dll b/obj/Debug/net8.0/Mt5Bridge.dll index f79b3e1..b30033c 100644 Binary files a/obj/Debug/net8.0/Mt5Bridge.dll and b/obj/Debug/net8.0/Mt5Bridge.dll differ diff --git a/obj/Debug/net8.0/Mt5Bridge.pdb b/obj/Debug/net8.0/Mt5Bridge.pdb index 25e0765..b6a0e8f 100644 Binary files a/obj/Debug/net8.0/Mt5Bridge.pdb and b/obj/Debug/net8.0/Mt5Bridge.pdb differ diff --git a/obj/Debug/net8.0/apphost.exe b/obj/Debug/net8.0/apphost.exe index eae3425..5ab86cb 100644 Binary files a/obj/Debug/net8.0/apphost.exe and b/obj/Debug/net8.0/apphost.exe differ diff --git a/obj/Debug/net8.0/ref/Mt5Bridge.dll b/obj/Debug/net8.0/ref/Mt5Bridge.dll index e7ef212..458234e 100644 Binary files a/obj/Debug/net8.0/ref/Mt5Bridge.dll and b/obj/Debug/net8.0/ref/Mt5Bridge.dll differ diff --git a/obj/Debug/net8.0/refint/Mt5Bridge.dll b/obj/Debug/net8.0/refint/Mt5Bridge.dll index e7ef212..458234e 100644 Binary files a/obj/Debug/net8.0/refint/Mt5Bridge.dll and b/obj/Debug/net8.0/refint/Mt5Bridge.dll differ diff --git a/延迟测试.py b/延迟测试.py new file mode 100644 index 0000000..077a5d3 --- /dev/null +++ b/延迟测试.py @@ -0,0 +1,53 @@ +import requests +import time +import statistics + +BRIDGE = "http://61.164.252.86:13485" +KEY = "UiHMqtaYLZzwBdcuS4RFmEGhgDO8N2eI" +HEADERS = {"X-API-Key": KEY} + +N = 20 # 测试次数 + +def test(name, func): + times = [] + for i in range(N): + t0 = time.perf_counter() + try: + func() + except Exception as e: + print(f" ❌ {name} 失败: {e}") + return + t1 = time.perf_counter() + times.append((t1 - t0) * 1000) + print(f" #{i+1:2d}: {times[-1]:.1f} ms", end="\r") + + print(f"\n{'='*50}") + print(f"{name}") + print(f" 最小: {min(times):.1f} ms") + print(f" 最大: {max(times):.1f} ms") + print(f" 平均: {statistics.mean(times):.1f} ms") + print(f" 中位: {statistics.median(times):.1f} ms") + +print(f"测试 {N} 次,请稍候...\n") + +# 1. 健康检查 +test("健康检查 /health", lambda: requests.get(f"{BRIDGE}/health", headers=HEADERS).raise_for_status()) + +# 2. 账户信息 +test("账户信息 /account", lambda: requests.get(f"{BRIDGE}/account", headers=HEADERS).raise_for_status()) + +# 3. 实时 Tick +test("实时 Tick /tick", lambda: requests.get(f"{BRIDGE}/symbols/XAUUSDc/tick", headers=HEADERS).raise_for_status()) + +# 4. 持仓查询 +test("持仓查询 /positions", lambda: requests.get(f"{BRIDGE}/positions", headers=HEADERS).raise_for_status()) + +# 5. 品种信息 +test("品种信息 /symbols", lambda: requests.get(f"{BRIDGE}/symbols/XAUUSDc", headers=HEADERS).raise_for_status()) + +# 6. K 线数据 +test("K 线 /rates (100根 H1)", lambda: requests.get(f"{BRIDGE}/rates/from-pos", headers=HEADERS, params={ + "symbol": "XAUUSDc", "timeframe": "TIMEFRAME_H1", "start_pos": 0, "count": 100 +}).raise_for_status()) + +print(f"\n总结: 你的网络 → MT5 云端 往返延迟") \ No newline at end of file