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 let login = reader.read_i64();
78
79 let trade_mode = reader.read_i32() as i64;
81
82 let leverage = reader.read_i32() as i64;
84
85 let limit_orders = reader.read_i32() as i64;
87
88 let margin_so_mode = reader.read_i32() as i64;
90
91 let trade_allowed = reader.read_bool1();
93
94 let trade_expert = reader.read_bool1();
96
97 let margin_mode = reader.read_i32() as i64;
99
100 let currency_digits = reader.read_i32() as i64;
102
103 let fifo_close = reader.read_bool1();
105
106 let balance = reader.read_f64();
108
109 let credit = reader.read_f64();
111
112 let profit = reader.read_f64();
114
115 let equity = reader.read_f64();
117
118 let margin = reader.read_f64();
120
121 let free_margin = reader.read_f64();
123
124 let margin_level = reader.read_f64();
126
127 let margin_so_call = reader.read_f64();
129
130 let margin_so_so = reader.read_f64();
132
133 let margin_initial = reader.read_f64();
135
136 let margin_maintenance = reader.read_f64();
138
139 let assets = reader.read_f64();
141
142 let liabilities = reader.read_f64();
144
145 let commission_blocked = reader.read_f64();
147
148 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 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 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 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 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 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 pub fn order_calc_margin(&self, _action: i32, symbol: &str, volume: f64, price: f64) -> Result<f64> {
822 let symbol_info = self.symbol_info(symbol)?;
824
825 let margin_initial = symbol_info.unwrap().margin_initial;
828 let margin = volume * price * margin_initial / 4.0;
829
830 Ok(margin)
831 }
832
833 pub fn order_calc_profit(&self, _action: i32, symbol: &str, volume: f64, price_open: f64, price_close: f64) -> Result<f64> {
836 let symbol_info = self.symbol_info(symbol)?;
838
839 let profit = volume * (price_close - price_open) * symbol_info.unwrap().trade_contract_size;
841
842 Ok(profit)
843 }
844
845 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}