Skip to main content

mt5_rs/
client.rs

1use crate::error::{Mt5Error, Result};
2use crate::protocol::NamedPipeClient;
3use crate::types::*;
4
5pub struct Mt5Client {
6    pipe: Option<NamedPipeClient>,
7    build: i32,
8}
9
10impl Mt5Client {
11    pub fn new() -> Self {
12        Self { pipe: None, build: 0 }
13    }
14
15    pub fn initialize(&mut self, pipe_name: Option<&str>) -> Result<()> {
16        self.pipe = Some(NamedPipeClient::new(pipe_name)?);
17        
18        let pipe = self.pipe()?;
19        let mut data = Vec::new();
20        data.extend_from_slice(&3u32.to_le_bytes());
21        data.extend_from_slice(&encode_string("Go"));
22        
23        let resp = pipe.send(4, &data)?;
24        if resp.len() >= 4 {
25            let build = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
26            self.build = build as i32;
27        }
28        
29        Ok(())
30    }
31
32    pub fn shutdown(&mut self) {
33        self.pipe = None;
34    }
35
36    fn pipe(&self) -> Result<&NamedPipeClient> {
37        self.pipe
38            .as_ref()
39            .ok_or(Mt5Error::NotInitialized)
40    }
41
42    pub fn login(&self, login: i64, password: &str, server: &str) -> Result<()> {
43        let pipe = self.pipe()?;
44        let mut data = Vec::new();
45        data.extend_from_slice(&login.to_le_bytes());
46        data.extend_from_slice(&encode_string(password));
47        data.extend_from_slice(&encode_string(server));
48
49        let resp = pipe.send(4, &data)?;
50        if resp.len() < 4 {
51            return Err(Mt5Error::InvalidResponse("Response too short".into()));
52        }
53
54        let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
55        if status != 0 {
56            return Err(Mt5Error::CommandFailed {
57                cmd: 4,
58                error: format!("Login failed with status: {}", status),
59            });
60        }
61
62        Ok(())
63    }
64
65    pub fn account_info(&self) -> Result<AccountInfo> {
66        let pipe = self.pipe()?;
67        let resp = pipe.send(190, &[])?;
68
69        if resp.len() < 8 {
70            return Err(Mt5Error::InvalidResponse("Response too short".into()));
71        }
72
73        let mut reader = Reader::new(&resp);
74
75        // 按照 Python 输出和二进制数据验证的精确位置解析
76        // Pos 0-7: login (i64)
77        let login = reader.read_i64();
78        
79        // Pos 8-11: trade_mode (i32)
80        let trade_mode = reader.read_i32() as i64;
81        
82        // Pos 12-15: leverage (i32)
83        let leverage = reader.read_i32() as i64;
84        
85        // Pos 16-19: limit_orders (i32)
86        let limit_orders = reader.read_i32() as i64;
87        
88        // Pos 20-23: margin_so_mode (i32)
89        let margin_so_mode = reader.read_i32() as i64;
90        
91        // Pos 24: trade_allowed (bool, 1字节)
92        let trade_allowed = reader.read_bool1();
93        
94        // Pos 25: trade_expert (bool, 1字节)
95        let trade_expert = reader.read_bool1();
96        
97        // Pos 26-29: margin_mode (i32)
98        let margin_mode = reader.read_i32() as i64;
99        
100        // Pos 30-33: currency_digits (i32)
101        let currency_digits = reader.read_i32() as i64;
102        
103        // Pos 34: fifo_close (bool, 1字节)
104        let fifo_close = reader.read_bool1();
105
106        // Pos 35-42: balance (f64)
107        let balance = reader.read_f64();
108        
109        // Pos 43-50: credit (f64)
110        let credit = reader.read_f64();
111        
112        // Pos 51-58: profit (f64)
113        let profit = reader.read_f64();
114        
115        // Pos 59-66: equity (f64)
116        let equity = reader.read_f64();
117        
118        // Pos 67-74: margin (f64)
119        let margin = reader.read_f64();
120        
121        // Pos 75-82: margin_free (f64)
122        let free_margin = reader.read_f64();
123        
124        // Pos 83-90: margin_level (f64)
125        let margin_level = reader.read_f64();
126        
127        // Pos 91-98: margin_so_call (f64)
128        let margin_so_call = reader.read_f64();
129        
130        // Pos 99-106: margin_so_so (f64)
131        let margin_so_so = reader.read_f64();
132        
133        // Pos 107-114: margin_initial (f64)
134        let margin_initial = reader.read_f64();
135        
136        // Pos 115-122: margin_maintenance (f64)
137        let margin_maintenance = reader.read_f64();
138        
139        // Pos 123-130: assets (f64)
140        let assets = reader.read_f64();
141        
142        // Pos 131-138: liabilities (f64)
143        let liabilities = reader.read_f64();
144        
145        // Pos 139-146: commission_blocked (f64)
146        let commission_blocked = reader.read_f64();
147
148        // 读取字符串字段 (从 pos 147 开始)
149        let strings_offset = 147;
150        if strings_offset >= resp.len() {
151            return Err(Mt5Error::InvalidResponse(format!(
152                "Response too short for strings: {} < {}",
153                resp.len(),
154                strings_offset
155            )));
156        }
157
158        let mut sr = Reader::new(&resp[strings_offset..]);
159        let name = sr.read_fixed_string(256);
160        let server = sr.read_fixed_string(128);
161        let currency = sr.read_fixed_string(64);
162        let company = sr.read_fixed_string(256);
163
164        if sr.has_error() {
165            return Err(Mt5Error::InvalidResponse("Failed to read strings".into()));
166        }
167
168        Ok(AccountInfo {
169            login,
170            trade_mode,
171            leverage,
172            limit_orders,
173            margin_so_mode,
174            trade_allowed,
175            trade_expert,
176            margin_mode,
177            currency_digits,
178            fifo_close,
179            balance,
180            credit,
181            profit,
182            equity,
183            margin,
184            free_margin,
185            margin_level,
186            margin_so_call,
187            margin_so_so,
188            margin_initial,
189            margin_maintenance,
190            assets,
191            liabilities,
192            commission_blocked,
193            name,
194            server,
195            currency,
196            company,
197        })
198    }
199
200    pub fn terminal_info(&self) -> Result<TerminalInfo> {
201        let pipe = self.pipe()?;
202        let resp = pipe.send(180, &[])?;
203
204        if resp.len() < 40 {
205            return Err(Mt5Error::InvalidResponse("Response too short".into()));
206        }
207
208        let community_account = resp[2] != 0;
209        let community_connection = resp[3] != 0;
210        let connected = resp[6] != 0;
211        let dlls_allowed = resp[7] != 0;
212        let trade_allowed = resp[8] != 0;
213        let trade_api_disabled = resp[9] != 0;
214        let email_enabled = resp[10] != 0;
215        let ftp_enabled = resp[11] != 0;
216        let notifications_enabled = resp[4] != 0;
217        let mqid = resp[5] != 0;
218
219        let build = u16::from_le_bytes([resp[0], resp[1]]) as i64;
220        let max_bars = u32::from_le_bytes([resp[12], resp[13], resp[14], resp[15]]) as i64;
221        let code_page = u16::from_le_bytes([resp[17], resp[18]]) as i64;
222        let ping_last = u16::from_le_bytes([resp[21], resp[22]]) as i64;
223        let community_balance = f64::from_le_bytes([
224            resp[24], resp[25], resp[26], resp[27], resp[28], resp[29], resp[30], resp[31],
225        ]);
226        let retransmission = f64::from_le_bytes([
227            resp[32], resp[33], resp[34], resp[35], resp[36], resp[37], resp[38], resp[39],
228        ]);
229
230        let company = read_string_at_offset(&resp, 41);
231        let name = read_string_at_offset(&resp, 561);
232        let language = read_string_at_offset(&resp, 1081);
233        let path = read_string_at_offset(&resp, 1601);
234        let data_path = read_string_at_offset(&resp, 2121);
235        let common_data_path = read_string_at_offset(&resp, 2641);
236
237        Ok(TerminalInfo {
238            community_account,
239            community_connection,
240            connected,
241            dlls_allowed,
242            trade_allowed,
243            trade_api_disabled,
244            email_enabled,
245            ftp_enabled,
246            notifications_enabled,
247            mqid,
248            build,
249            max_bars,
250            code_page,
251            ping_last,
252            community_balance,
253            retransmission,
254            company,
255            name,
256            language,
257            path,
258            data_path,
259            common_data_path,
260        })
261    }
262
263    pub fn version(&self) -> Result<VersionInfo> {
264        let info = self.terminal_info()?;
265        Ok(VersionInfo {
266            version: info.build as i32,
267            build: info.build as i32,
268            build_date: format!("{} ({})", info.company, info.name),
269        })
270    }
271
272    pub fn symbols_total(&self) -> Result<i64> {
273        let pipe = self.pipe()?;
274        let resp = pipe.send(173, &[])?;
275
276        if resp.len() < 4 {
277            return Err(Mt5Error::InvalidResponse("Response too short".into()));
278        }
279
280        let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
281        Ok(total as i64)
282    }
283
284    pub fn symbols_get(&self) -> Result<Vec<SymbolInfo>> {
285        let pipe = self.pipe()?;
286        let resp = pipe.send(174, &[])?;
287
288        if resp.len() < 4 {
289            return Err(Mt5Error::InvalidResponse("Response too short".into()));
290        }
291
292        let mut reader = Reader::new(&resp);
293        let count = reader.read_u32() as usize;
294
295        let mut symbols = Vec::with_capacity(count);
296
297        for _ in 0..count {
298            let sym = Self::decode_symbol_info(&mut reader)?;
299            symbols.push(sym);
300        }
301
302        Ok(symbols)
303    }
304
305    fn decode_symbol_info(reader: &mut Reader) -> Result<SymbolInfo> {
306    // 严格按照 go-mt5 decodeSymbolInfo 的字段顺序和类型解析
307    // 参考:https://github.com/Mukbeast4/go-mt5/blob/main/symbols.go
308    let custom = reader.read_bool1();
309    let chart_mode = reader.read_u32() as i64;
310    let select = reader.read_bool1();
311    let visible = reader.read_bool1();
312    let session_deals = reader.read_i64();
313    let session_buy_orders = reader.read_i64();
314    let session_sell_orders = reader.read_i64();
315    let volume = reader.read_i64();
316    let volume_high = reader.read_i64();
317    let volume_low = reader.read_i64();
318    let time = reader.read_i64();
319    let digits = reader.read_u32() as i64;
320    let spread = reader.read_u32() as i64;
321    let spread_float = reader.read_bool1();
322    let ticks_book_depth = reader.read_u32() as i64;
323    let trade_calc_mode = reader.read_u32() as i64;
324    let trade_mode = reader.read_u32() as i64;
325    let start_time = reader.read_i64();
326    let expiration_time = reader.read_i64();
327    let trade_stops_level = reader.read_u32() as i64;
328    let trade_freeze_level = reader.read_u32() as i64;
329    let trade_exe_mode = reader.read_u32() as i64;
330    let swap_mode = reader.read_u32() as i64;
331    let swap_rollover3days = reader.read_u32() as i64;
332    let margin_hedged_use_leg = reader.read_bool1();
333    let expiration_mode = reader.read_u32() as i64;
334    let filling_mode = reader.read_u32() as i64;
335    let order_mode = reader.read_u32() as i64;
336    let order_gtc_mode = reader.read_u32() as i64;
337    let option_mode = reader.read_u32() as i64;
338    let option_right = reader.read_u32() as i64;
339    let bid = reader.read_f64();
340    let bid_high = reader.read_f64();
341    let bid_low = reader.read_f64();
342    let ask = reader.read_f64();
343    let ask_high = reader.read_f64();
344    let ask_low = reader.read_f64();
345    let last = reader.read_f64();
346    let last_high = reader.read_f64();
347    let last_low = reader.read_f64();
348    let volume_real = reader.read_f64();
349    let volume_high_real = reader.read_f64();
350    let volume_low_real = reader.read_f64();
351    let option_strike = reader.read_f64();
352    let point = reader.read_f64();
353    let trade_tick_value = reader.read_f64();
354    let trade_tick_value_profit = reader.read_f64();
355    let trade_tick_value_loss = reader.read_f64();
356    let trade_tick_size = reader.read_f64();
357    let trade_contract_size = reader.read_f64();
358    let trade_accrued_interest = reader.read_f64();
359    let trade_face_value = reader.read_f64();
360    let trade_liquidity_rate = reader.read_f64();
361    let volume_min = reader.read_f64();
362    let volume_max = reader.read_f64();
363    let volume_step = reader.read_f64();
364    let volume_limit = reader.read_f64();
365    let swap_long = reader.read_f64();
366    let swap_short = reader.read_f64();
367    let margin_initial = reader.read_f64();
368    let margin_maintenance = reader.read_f64();
369    let session_volume = reader.read_f64();
370    let session_turnover = reader.read_f64();
371    let session_interest = reader.read_f64();
372    let session_buy_orders_volume = reader.read_f64();
373    let session_sell_orders_volume = reader.read_f64();
374    let session_open = reader.read_f64();
375    let session_close = reader.read_f64();
376    let session_aw = reader.read_f64();
377    let session_price_settlement = reader.read_f64();
378    let session_price_limit_min = reader.read_f64();
379    let session_price_limit_max = reader.read_f64();
380    let margin_hedged = reader.read_f64();
381    let price_change = reader.read_f64();
382    let price_volatility = reader.read_f64();
383    let price_theoretical = reader.read_f64();
384    let price_greeks_delta = reader.read_f64();
385    let price_greeks_theta = reader.read_f64();
386    let price_greeks_gamma = reader.read_f64();
387    let price_greeks_vega = reader.read_f64();
388    let price_greeks_rho = reader.read_f64();
389    let price_greeks_omega = reader.read_f64();
390    let price_sensitivity = reader.read_f64();
391
392    // 字符串字段:固定宽度 UTF-16LE 槽(go-mt5 PR#3 验证)
393    // 总字符串区域 = 2432 字节
394    let basis = reader.read_fixed_string(64);
395    let category = reader.read_fixed_string(128);
396    let currency_base = reader.read_fixed_string(32);
397    let currency_profit = reader.read_fixed_string(32);
398    let currency_margin = reader.read_fixed_string(32);
399    let bank = reader.read_fixed_string(512);
400    let description = reader.read_fixed_string(64);
401    let exchange = reader.read_fixed_string(64);
402    let formula = reader.read_fixed_string(1024);
403    let isin = reader.read_fixed_string(32);
404    let page = reader.read_fixed_string(128);
405    let path = reader.read_fixed_string(256);
406    let symbol_name = reader.read_fixed_string(64);
407
408    if reader.has_error() {
409        return Err(Mt5Error::InvalidResponse("Failed to read symbol info".into()));
410    }
411
412    Ok(SymbolInfo {
413        custom,
414        chart_mode,
415        select,
416        visible,
417        session_deals,
418        session_buy_orders,
419        session_sell_orders,
420        volume,
421        volume_high,
422        volume_low,
423        time,
424        digits,
425        spread,
426        spread_float,
427        ticks_book_depth,
428        trade_calc_mode,
429        trade_mode,
430        start_time,
431        expiration_time,
432        trade_stops_level,
433        trade_freeze_level,
434        trade_exe_mode,
435        swap_mode,
436        swap_rollover3days,
437        margin_hedged_use_leg,
438        expiration_mode,
439        filling_mode,
440        order_mode,
441        order_gtc_mode,
442        option_mode,
443        option_right,
444        bid,
445        bidhigh: bid_high,
446        bidlow: bid_low,
447        ask,
448        askhigh: ask_high,
449        asklow: ask_low,
450        last,
451        lasthigh: last_high,
452        lastlow: last_low,
453        volume_real,
454        volumehigh_real: volume_high_real,
455        volumelow_real: volume_low_real,
456        option_strike,
457        point,
458        trade_tick_value,
459        trade_tick_value_profit,
460        trade_tick_value_loss,
461        trade_tick_size,
462        trade_contract_size,
463        trade_accrued_interest,
464        trade_face_value,
465        trade_liquidity_rate,
466        volume_min,
467        volume_max,
468        volume_step,
469        volume_limit,
470        swap_long,
471        swap_short,
472        margin_initial,
473        margin_maintenance,
474        session_volume,
475        session_turnover,
476        session_interest,
477        session_buy_orders_volume,
478        session_sell_orders_volume,
479        session_open,
480        session_close,
481        session_aw,
482        session_price_settlement,
483        session_price_limit_min,
484        session_price_limit_max,
485        margin_hedged,
486        price_change,
487        price_volatility,
488        price_theoretical,
489        price_greeks_delta,
490        price_greeks_theta,
491        price_greeks_gamma,
492        price_greeks_vega,
493        price_greeks_rho,
494        price_greeks_omega,
495        price_sensitivity,
496        basis,
497        category,
498        currency_base,
499        currency_profit,
500        currency_margin,
501        bank,
502        description,
503        exchange,
504        formula,
505        isin,
506        name: symbol_name,
507        page,
508        path,
509    })
510}
511
512    pub fn symbol_info(&self, symbol: &str) -> Result<Option<SymbolInfo>> {
513        let pipe = self.pipe()?;
514        let mut data = Vec::new();
515        data.extend_from_slice(&encode_string(symbol));
516        
517        let resp = pipe.send(170, &data)?;
518        
519        if resp.is_empty() {
520            return Ok(None);
521        }
522
523        let mut reader = Reader::new(&resp);
524        let info = Self::decode_symbol_info(&mut reader)?;
525        Ok(Some(info))
526    }
527
528    pub fn symbol_info_tick(&self, symbol: &str) -> Result<Option<Tick>> {
529        let pipe = self.pipe()?;
530        let mut data = Vec::new();
531        data.extend_from_slice(&encode_string(symbol));
532        
533        let resp = pipe.send(172, &data)?;
534        
535        if resp.is_empty() {
536            return Ok(None);
537        }
538
539        let mut reader = Reader::new(&resp);
540
541        // 严格按照 go-mt5 decodeTick 的字段顺序和类型解析
542        let time = reader.read_i64();
543        let bid = reader.read_f64();
544        let ask = reader.read_f64();
545        let last = reader.read_f64();
546        let volume = reader.read_u64();
547        let time_msc = reader.read_i64();
548        let flags = reader.read_u32();
549        let volume_real = reader.read_f64();
550
551        if reader.has_error() {
552            return Err(Mt5Error::InvalidResponse("Failed to read tick info".into()));
553        }
554
555        Ok(Some(Tick {
556            time,
557            bid,
558            ask,
559            last,
560            volume,
561            time_msc,
562            flags,
563            volume_real,
564        }))
565    }
566
567    pub fn symbol_select(&self, symbol: &str, enable: bool) -> Result<bool> {
568        let pipe = self.pipe()?;
569        let mut data = Vec::new();
570        data.extend_from_slice(&encode_string(symbol));
571        data.push(if enable { 1u8 } else { 0u8 });
572        
573        let resp = pipe.send(171, &data)?;
574        
575        // 空响应表示成功(MT5只返回8字节的头部,没有额外数据)
576        if resp.is_empty() {
577            return Ok(true);
578        }
579        
580        if resp.len() < 4 {
581            return Err(Mt5Error::InvalidResponse("Response too short".into()));
582        }
583
584        let status = i32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
585        Ok(status != 0)
586    }
587
588    pub fn positions_total(&self) -> Result<i64> {
589        let pipe = self.pipe()?;
590        let resp = pipe.send(120, &[])?;
591
592        if resp.len() < 4 {
593            return Err(Mt5Error::InvalidResponse("Response too short".into()));
594        }
595
596        let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
597        Ok(total as i64)
598    }
599
600    pub fn orders_total(&self) -> Result<i64> {
601        let pipe = self.pipe()?;
602        let resp = pipe.send(130, &[])?;
603
604        if resp.len() < 4 {
605            return Err(Mt5Error::InvalidResponse("Response too short".into()));
606        }
607
608        let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
609        Ok(total as i64)
610    }
611
612    pub fn positions_get(&self, symbol: Option<&str>) -> Result<Vec<TradePosition>> {
613        let pipe = self.pipe()?;
614        let cmd = 121;
615
616        let mut data = Vec::new();
617        if let Some(sym) = symbol {
618            data.extend_from_slice(&encode_string(sym));
619        }
620
621        let resp = pipe.send(cmd, &data)?;
622        parse_positions_response(&resp)
623    }
624
625    pub fn orders_get(&self, symbol: Option<&str>) -> Result<Vec<TradeOrder>> {
626        let pipe = self.pipe()?;
627        let cmd = 131;
628
629        let mut data = Vec::new();
630        if let Some(sym) = symbol {
631            data.extend_from_slice(&encode_string(sym));
632        }
633
634        let resp = pipe.send(cmd, &data)?;
635        parse_orders_response(&resp)
636    }
637
638    pub fn send_raw_command(&self, cmd: u32, data: &[u8]) -> Result<Vec<u8>> {
639        let pipe = self.pipe()?;
640        pipe.send(cmd, data)
641    }
642
643    pub fn copy_rates_from_pos(&self, symbol: &str, timeframe: i32, start_pos: i64, count: i32) -> Result<Vec<Rate>> {
644        let pipe = self.pipe()?;
645        // 根据go-mt5源码,命令代码108,参数使用u32编码
646        let cmd = 108;
647
648        let mut data = Vec::new();
649        data.extend_from_slice(&encode_string(symbol));
650        data.extend_from_slice(&(timeframe as u32).to_le_bytes());
651        data.extend_from_slice(&(start_pos as u32).to_le_bytes());
652        data.extend_from_slice(&(count as u32).to_le_bytes());
653
654        let resp = pipe.send(cmd, &data)?;
655        parse_rates_response(&resp)
656    }
657
658    pub fn copy_rates_from(&self, symbol: &str, timeframe: i32, date_from: i64, count: i32) -> Result<Vec<Rate>> {
659        let pipe = self.pipe()?;
660        let cmd = 106;
661
662        let mut data = Vec::new();
663        data.extend_from_slice(&encode_string(symbol));
664        data.extend_from_slice(&(timeframe as u32).to_le_bytes());
665        data.extend_from_slice(&date_from.to_le_bytes());
666        data.extend_from_slice(&(count as u32).to_le_bytes());
667
668        let resp = pipe.send(cmd, &data)?;
669        parse_rates_response(&resp)
670    }
671
672    pub fn copy_rates_range(&self, symbol: &str, timeframe: i32, date_from: i64, date_to: i64) -> Result<Vec<Rate>> {
673        let pipe = self.pipe()?;
674        let cmd = 107;
675
676        let mut data = Vec::new();
677        data.extend_from_slice(&encode_string(symbol));
678        data.extend_from_slice(&(timeframe as u32).to_le_bytes());
679        data.extend_from_slice(&date_from.to_le_bytes());
680        data.extend_from_slice(&date_to.to_le_bytes());
681
682        let resp = pipe.send(cmd, &data)?;
683        parse_rates_response(&resp)
684    }
685
686    pub fn copy_ticks_from(&self, symbol: &str, from: i64, count: i32, flags: i32) -> Result<Vec<Tick>> {
687        let pipe = self.pipe()?;
688        let cmd = 104;
689
690        let mut data = Vec::new();
691        data.extend_from_slice(&encode_string(symbol));
692        data.extend_from_slice(&from.to_le_bytes());
693        data.extend_from_slice(&(count as u32).to_le_bytes());
694        data.extend_from_slice(&(flags as u32).to_le_bytes());
695
696        let resp = pipe.send(cmd, &data)?;
697        parse_ticks_response(&resp)
698    }
699
700    pub fn copy_ticks_range(&self, symbol: &str, from: i64, to: i64, flags: i32) -> Result<Vec<Tick>> {
701        let pipe = self.pipe()?;
702        let cmd = 105;
703
704        let mut data = Vec::new();
705        data.extend_from_slice(&encode_string(symbol));
706        data.extend_from_slice(&from.to_le_bytes());
707        data.extend_from_slice(&to.to_le_bytes());
708        data.extend_from_slice(&(flags as u32).to_le_bytes());
709
710        let resp = pipe.send(cmd, &data)?;
711        parse_ticks_response(&resp)
712    }
713
714    pub fn history_deals_total(&self, from: i64, to: i64) -> Result<i64> {
715        let pipe = self.pipe()?;
716        let cmd = 150;
717
718        let mut data = Vec::new();
719        data.extend_from_slice(&from.to_le_bytes());
720        data.extend_from_slice(&to.to_le_bytes());
721
722        let resp = pipe.send(cmd, &data)?;
723        if resp.len() < 4 {
724            return Err(Mt5Error::InvalidResponse("Response too short".into()));
725        }
726
727        let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
728        Ok(total as i64)
729    }
730
731    pub fn history_deals_get(&self, from: i64, to: i64) -> Result<Vec<TradeDeal>> {
732        let pipe = self.pipe()?;
733        let cmd = 151;
734
735        let mut data = Vec::new();
736        data.extend_from_slice(&from.to_le_bytes());
737        data.extend_from_slice(&to.to_le_bytes());
738
739        let resp = pipe.send(cmd, &data)?;
740        parse_deals_response(&resp)
741    }
742
743    pub fn history_orders_total(&self, from: i64, to: i64) -> Result<i64> {
744        let pipe = self.pipe()?;
745        let cmd = 140;
746
747        let mut data = Vec::new();
748        data.extend_from_slice(&from.to_le_bytes());
749        data.extend_from_slice(&to.to_le_bytes());
750
751        let resp = pipe.send(cmd, &data)?;
752        if resp.len() < 4 {
753            return Err(Mt5Error::InvalidResponse("Response too short".into()));
754        }
755
756        let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
757        Ok(total as i64)
758    }
759
760    pub fn history_orders_get(&self, from: i64, to: i64) -> Result<Vec<TradeOrder>> {
761        let pipe = self.pipe()?;
762        let cmd = 141;
763
764        let mut data = Vec::new();
765        data.extend_from_slice(&from.to_le_bytes());
766        data.extend_from_slice(&to.to_le_bytes());
767
768        let resp = pipe.send(cmd, &data)?;
769        parse_orders_response(&resp)
770    }
771
772    pub fn market_book_add(&self, symbol: &str) -> Result<bool> {
773        let pipe = self.pipe()?;
774        let cmd = 191;
775
776        let data = encode_string(symbol);
777        let resp = pipe.send(cmd, &data)?;
778        
779        if resp.is_empty() {
780            return Ok(true);
781        }
782        
783        if resp.len() < 4 {
784            return Err(Mt5Error::InvalidResponse("Response too short".into()));
785        }
786
787        let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
788        Ok(status == 0)
789    }
790
791    pub fn market_book_get(&self, symbol: &str) -> Result<Vec<BookInfo>> {
792        let pipe = self.pipe()?;
793        let cmd = 193;
794
795        let data = encode_string(symbol);
796        let resp = pipe.send(cmd, &data)?;
797        parse_book_response(&resp)
798    }
799
800    pub fn market_book_release(&self, symbol: &str) -> Result<bool> {
801        let pipe = self.pipe()?;
802        let cmd = 192;
803
804        let data = encode_string(symbol);
805        let resp = pipe.send(cmd, &data)?;
806        
807        if resp.is_empty() {
808            return Ok(true);
809        }
810        
811        if resp.len() < 4 {
812            return Err(Mt5Error::InvalidResponse("Response too short".into()));
813        }
814
815        let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
816        Ok(status == 0)
817    }
818
819    /// 计算订单所需保证金(本地计算,不使用IPC)
820    /// 根据Python测试验证的公式:margin = volume × price × margin_initial / 4
821    pub fn order_calc_margin(&self, _action: i32, symbol: &str, volume: f64, price: f64) -> Result<f64> {
822        // 获取symbol info以获取margin_initial
823        let symbol_info = self.symbol_info(symbol)?;
824        
825        // 根据Python测试验证的公式计算
826        // margin = volume × price × margin_initial / 4
827        let margin_initial = symbol_info.unwrap().margin_initial;
828        let margin = volume * price * margin_initial / 4.0;
829        
830        Ok(margin)
831    }
832
833    /// 计算订单预期利润(本地计算,不使用IPC)
834    /// 公式:profit = volume × (price_close - price_open) × contract_size
835    pub fn order_calc_profit(&self, _action: i32, symbol: &str, volume: f64, price_open: f64, price_close: f64) -> Result<f64> {
836        // 获取symbol info以获取contract_size
837        let symbol_info = self.symbol_info(symbol)?;
838        
839        // 计算利润
840        let profit = volume * (price_close - price_open) * symbol_info.unwrap().trade_contract_size;
841        
842        Ok(profit)
843    }
844
845    // TODO: 未实现 - order_check
846    // Python: mt5.order_check(request)
847    // 功能:检查交易订单是否有效,返回检查结果
848    // 需要实现:构建TradeRequest结构,发送cmd到MT5,解析TradeCheckResult响应
849    // pub fn order_check(&self, request: &TradeRequest) -> Result<TradeCheckResult> {
850    //     unimplemented!()
851    // }
852
853    // TODO: 未实现 - order_send
854    // Python: mt5.order_send(request)
855    // 功能:发送交易订单到MT5终端执行
856    // 需要实现:构建TradeRequest结构,发送cmd到MT5,解析TradeResult响应
857    // pub fn order_send(&self, request: &TradeRequest) -> Result<TradeResult> {
858    //     unimplemented!()
859    // }
860
861    pub fn last_error(&self) -> Result<(i32, String)> {
862        let pipe = self.pipe()?;
863        let cmd = 3;
864
865        let resp = pipe.send(cmd, &[])?;
866        if resp.len() < 4 {
867            return Err(Mt5Error::InvalidResponse("Response too short".into()));
868        }
869
870        let mut reader = Reader::new(&resp);
871        let code = reader.read_i32();
872        let message = reader.read_string();
873
874        Ok((code, message))
875    }
876}
877
878fn parse_positions_response(data: &[u8]) -> Result<Vec<TradePosition>> {
879    if data.len() < 4 {
880        return Err(Mt5Error::InvalidResponse("Response too short".into()));
881    }
882
883    let mut reader = Reader::new(data);
884    let count = reader.read_u32() as usize;
885
886    let mut positions = Vec::with_capacity(count);
887
888    for _ in 0..count {
889        let ticket = reader.read_i64();
890        let time = reader.read_i64();
891        let time_msc = reader.read_i64();
892        let time_update = reader.read_i64();
893        let time_update_msc = reader.read_i64();
894        let r#type = reader.read_u32() as i32;
895        let magic = reader.read_i64();
896        let identifier = reader.read_i64();
897        let reason = reader.read_u32() as i32;
898        let volume = reader.read_f64();
899        let price_open = reader.read_f64();
900        let price_current = reader.read_f64();
901        let price_sl = reader.read_f64();
902        let price_tp = reader.read_f64();
903        let swap = reader.read_f64();
904        let profit = reader.read_f64();
905        let symbol = reader.read_fixed_string(64);
906        let comment = reader.read_fixed_string(64);
907        let external_id = reader.read_fixed_string(64);
908
909        if reader.has_error() {
910            break;
911        }
912
913        positions.push(TradePosition {
914            ticket,
915            time,
916            time_msc,
917            time_update,
918            time_update_msc,
919            r#type,
920            magic,
921            identifier,
922            reason,
923            volume,
924            price_open,
925            price_current,
926            price_sl,
927            price_tp,
928            swap,
929            profit,
930            symbol,
931            comment,
932            external_id,
933        });
934    }
935
936    Ok(positions)
937}
938
939fn parse_orders_response(data: &[u8]) -> Result<Vec<TradeOrder>> {
940    if data.len() < 4 {
941        return Err(Mt5Error::InvalidResponse("Response too short".into()));
942    }
943
944    let mut reader = Reader::new(data);
945    let count = reader.read_u32() as usize;
946
947    let mut orders = Vec::with_capacity(count);
948
949    for _ in 0..count {
950        let ticket = reader.read_i64();
951        let time_setup = reader.read_i64();
952        let time_setup_msc = reader.read_i64();
953        let time_done = reader.read_i64();
954        let time_done_msc = reader.read_i64();
955        let time_expiration = reader.read_i64();
956        let r#type = reader.read_u32() as i32;
957        let type_time = reader.read_u32() as i32;
958        let type_filling = reader.read_u32() as i32;
959        let state = reader.read_u32() as i32;
960        let magic = reader.read_i64();
961        let position_id = reader.read_i64();
962        let position_by_id = reader.read_i64();
963        let reason = reader.read_u32() as i32;
964        let volume_initial = reader.read_f64();
965        let volume_current = reader.read_f64();
966        let price_open = reader.read_f64();
967        let price_current = reader.read_f64();
968        let price_sl = reader.read_f64();
969        let price_tp = reader.read_f64();
970        let price_stoplimit = reader.read_f64();
971        let symbol = reader.read_fixed_string(64);
972        let comment = reader.read_fixed_string(64);
973        let external_id = reader.read_fixed_string(64);
974
975        if reader.has_error() {
976            break;
977        }
978
979        orders.push(TradeOrder {
980            ticket,
981            time_setup,
982            time_setup_msc,
983            time_done,
984            time_done_msc,
985            time_expiration,
986            r#type,
987            type_time,
988            type_filling,
989            state,
990            magic,
991            position_id,
992            position_by_id,
993            reason,
994            volume_initial,
995            volume_current,
996            price_open,
997            price_current,
998            price_sl,
999            price_tp,
1000            price_stoplimit,
1001            symbol,
1002            comment,
1003            external_id,
1004        });
1005    }
1006
1007    Ok(orders)
1008}
1009
1010fn parse_deals_response(data: &[u8]) -> Result<Vec<TradeDeal>> {
1011    if data.len() < 4 {
1012        return Err(Mt5Error::InvalidResponse("Response too short".into()));
1013    }
1014
1015    let mut reader = Reader::new(data);
1016    let count = reader.read_u32() as usize;
1017
1018    let mut deals = Vec::with_capacity(count);
1019
1020    for _ in 0..count {
1021        let ticket = reader.read_i64();
1022        let order = reader.read_i64();
1023        let time = reader.read_i64();
1024        let time_msc = reader.read_i64();
1025        let r#type = reader.read_u32() as i32;
1026        let entry = reader.read_u32() as i32;
1027        let magic = reader.read_i64();
1028        let position_id = reader.read_i64();
1029        let reason = reader.read_u32() as i32;
1030        let volume = reader.read_f64();
1031        let price = reader.read_f64();
1032        let commission = reader.read_f64();
1033        let swap = reader.read_f64();
1034        let profit = reader.read_f64();
1035        let fee = reader.read_f64();
1036        let symbol = reader.read_fixed_string(64);
1037        let comment = reader.read_fixed_string(64);
1038        let external_id = reader.read_fixed_string(64);
1039
1040        if reader.has_error() {
1041            break;
1042        }
1043
1044        deals.push(TradeDeal {
1045            ticket,
1046            order,
1047            time,
1048            time_msc,
1049            r#type,
1050            entry,
1051            magic,
1052            position_id,
1053            reason,
1054            volume,
1055            price,
1056            commission,
1057            swap,
1058            profit,
1059            fee,
1060            symbol,
1061            comment,
1062            external_id,
1063        });
1064    }
1065
1066    Ok(deals)
1067}
1068
1069fn parse_rates_response(data: &[u8]) -> Result<Vec<Rate>> {
1070    if data.len() < 4 {
1071        return Err(Mt5Error::InvalidResponse("Response too short".into()));
1072    }
1073
1074    let mut reader = Reader::new(data);
1075    let count = reader.read_u32() as usize;
1076
1077    let mut rates = Vec::with_capacity(count);
1078
1079    for _ in 0..count {
1080        let time = reader.read_i64();
1081        let open = reader.read_f64();
1082        let high = reader.read_f64();
1083        let low = reader.read_f64();
1084        let close = reader.read_f64();
1085        let tick_volume = reader.read_u64();
1086        let spread = reader.read_i32();
1087        let real_volume = reader.read_u64();
1088
1089        if reader.has_error() {
1090            break;
1091        }
1092
1093        rates.push(Rate {
1094            time,
1095            open,
1096            high,
1097            low,
1098            close,
1099            tick_volume,
1100            spread,
1101            real_volume,
1102        });
1103    }
1104
1105    Ok(rates)
1106}
1107
1108fn parse_ticks_response(data: &[u8]) -> Result<Vec<Tick>> {
1109    if data.len() < 4 {
1110        return Err(Mt5Error::InvalidResponse("Response too short".into()));
1111    }
1112
1113    let mut reader = Reader::new(data);
1114    let count = reader.read_u32() as usize;
1115
1116    let mut ticks = Vec::with_capacity(count);
1117
1118    for _ in 0..count {
1119        let time = reader.read_i64();
1120        let bid = reader.read_f64();
1121        let ask = reader.read_f64();
1122        let last = reader.read_f64();
1123        let volume = reader.read_u64();
1124        let time_msc = reader.read_i64();
1125        let flags = reader.read_u32();
1126        let volume_real = reader.read_f64();
1127
1128        if reader.has_error() {
1129            break;
1130        }
1131
1132        ticks.push(Tick {
1133            time,
1134            bid,
1135            ask,
1136            last,
1137            volume,
1138            time_msc,
1139            flags,
1140            volume_real,
1141        });
1142    }
1143
1144    Ok(ticks)
1145}
1146
1147fn parse_book_response(data: &[u8]) -> Result<Vec<BookInfo>> {
1148    if data.len() < 4 {
1149        return Err(Mt5Error::InvalidResponse("Response too short".into()));
1150    }
1151
1152    let mut reader = Reader::new(data);
1153    let count = reader.read_u32() as usize;
1154
1155    let mut books = Vec::with_capacity(count);
1156
1157    for _ in 0..count {
1158        let r#type = reader.read_i64();
1159        let price = reader.read_f64();
1160        let volume = reader.read_i64();
1161        let volume_real = reader.read_f64();
1162
1163        if reader.has_error() {
1164            break;
1165        }
1166
1167        books.push(BookInfo {
1168            r#type,
1169            price,
1170            volume,
1171            volume_real,
1172        });
1173    }
1174
1175    Ok(books)
1176}
1177
1178fn encode_string(s: &str) -> Vec<u8> {
1179    let chars: Vec<u16> = s.encode_utf16().collect();
1180    let mut data = Vec::with_capacity(4 + chars.len() * 2);
1181    data.extend_from_slice(&(chars.len() as u32).to_le_bytes());
1182    for c in chars {
1183        data.extend_from_slice(&c.to_le_bytes());
1184    }
1185    data
1186}
1187
1188struct Reader<'a> {
1189    data: &'a [u8],
1190    pos: usize,
1191    error: bool,
1192}
1193
1194impl<'a> Reader<'a> {
1195    fn new(data: &'a [u8]) -> Self {
1196        Self {
1197            data,
1198            pos: 0,
1199            error: false,
1200        }
1201    }
1202
1203    fn has_error(&self) -> bool {
1204        self.error
1205    }
1206
1207    fn read_i64(&mut self) -> i64 {
1208        if self.error || self.pos + 8 > self.data.len() {
1209            self.error = true;
1210            return 0;
1211        }
1212        let bytes = [
1213            self.data[self.pos],
1214            self.data[self.pos + 1],
1215            self.data[self.pos + 2],
1216            self.data[self.pos + 3],
1217            self.data[self.pos + 4],
1218            self.data[self.pos + 5],
1219            self.data[self.pos + 6],
1220            self.data[self.pos + 7],
1221        ];
1222        self.pos += 8;
1223        i64::from_le_bytes(bytes)
1224    }
1225
1226    fn read_u64(&mut self) -> u64 {
1227        if self.error || self.pos + 8 > self.data.len() {
1228            self.error = true;
1229            return 0;
1230        }
1231        let bytes = [
1232            self.data[self.pos],
1233            self.data[self.pos + 1],
1234            self.data[self.pos + 2],
1235            self.data[self.pos + 3],
1236            self.data[self.pos + 4],
1237            self.data[self.pos + 5],
1238            self.data[self.pos + 6],
1239            self.data[self.pos + 7],
1240        ];
1241        self.pos += 8;
1242        u64::from_le_bytes(bytes)
1243    }
1244
1245    fn read_i32(&mut self) -> i32 {
1246        if self.error || self.pos + 4 > self.data.len() {
1247            self.error = true;
1248            return 0;
1249        }
1250        let bytes = [
1251            self.data[self.pos],
1252            self.data[self.pos + 1],
1253            self.data[self.pos + 2],
1254            self.data[self.pos + 3],
1255        ];
1256        self.pos += 4;
1257        i32::from_le_bytes(bytes)
1258    }
1259
1260    fn read_u32(&mut self) -> u32 {
1261        if self.error || self.pos + 4 > self.data.len() {
1262            self.error = true;
1263            return 0;
1264        }
1265        let bytes = [
1266            self.data[self.pos],
1267            self.data[self.pos + 1],
1268            self.data[self.pos + 2],
1269            self.data[self.pos + 3],
1270        ];
1271        self.pos += 4;
1272        u32::from_le_bytes(bytes)
1273    }
1274
1275    fn read_f64(&mut self) -> f64 {
1276        if self.error || self.pos + 8 > self.data.len() {
1277            self.error = true;
1278            return 0.0;
1279        }
1280        let bytes = [
1281            self.data[self.pos],
1282            self.data[self.pos + 1],
1283            self.data[self.pos + 2],
1284            self.data[self.pos + 3],
1285            self.data[self.pos + 4],
1286            self.data[self.pos + 5],
1287            self.data[self.pos + 6],
1288            self.data[self.pos + 7],
1289        ];
1290        self.pos += 8;
1291        f64::from_le_bytes(bytes)
1292    }
1293
1294    fn read_bool(&mut self) -> bool {
1295        self.read_i64() != 0
1296    }
1297
1298    fn read_bool1(&mut self) -> bool {
1299        if self.error || self.pos + 1 > self.data.len() {
1300            self.error = true;
1301            return false;
1302        }
1303        let b = self.data[self.pos];
1304        self.pos += 1;
1305        b != 0
1306    }
1307
1308    fn read_string(&mut self) -> String {
1309        if self.error || self.pos + 4 > self.data.len() {
1310            self.error = true;
1311            return String::new();
1312        }
1313        let char_count = self.read_i32() as usize;
1314        let byte_count = char_count * 2;
1315        if self.pos + byte_count > self.data.len() {
1316            self.error = true;
1317            return String::new();
1318        }
1319        let mut chars = Vec::with_capacity(char_count);
1320        for _ in 0..char_count {
1321            let c = u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]);
1322            self.pos += 2;
1323            chars.push(c);
1324        }
1325        String::from_utf16_lossy(&chars)
1326    }
1327
1328    fn read_fixed_string(&mut self, slot_bytes: usize) -> String {
1329        if self.error || self.pos + slot_bytes > self.data.len() {
1330            self.error = true;
1331            return String::new();
1332        }
1333        let end = self.pos + slot_bytes;
1334        let buf = &self.data[self.pos..end];
1335
1336        let mut chars = Vec::with_capacity(slot_bytes / 2);
1337        let mut i = 0;
1338        while i + 1 < buf.len() {
1339            let c = u16::from_le_bytes([buf[i], buf[i + 1]]);
1340            if c == 0 {
1341                break;
1342            }
1343            chars.push(c);
1344            i += 2;
1345        }
1346        self.pos = end;
1347        String::from_utf16_lossy(&chars)
1348    }
1349}
1350
1351fn read_string_at_offset(data: &[u8], offset: usize) -> String {
1352    if offset >= data.len() {
1353        return String::new();
1354    }
1355    
1356    let mut chars = Vec::new();
1357    let mut pos = offset;
1358    while pos + 1 < data.len() {
1359        let c = u16::from_le_bytes([data[pos], data[pos + 1]]);
1360        pos += 2;
1361        if c == 0 {
1362            break;
1363        }
1364        chars.push(c);
1365    }
1366    String::from_utf16_lossy(&chars)
1367}