use crate::error::{Mt5Error, Result};
use crate::protocol::NamedPipeClient;
use crate::types::*;
pub struct Mt5Client {
pipe: Option<NamedPipeClient>,
build: i32,
}
impl Mt5Client {
pub fn new() -> Self {
Self { pipe: None, build: 0 }
}
pub fn initialize(&mut self, pipe_name: Option<&str>) -> Result<()> {
self.pipe = Some(NamedPipeClient::new(pipe_name)?);
let pipe = self.pipe()?;
let mut data = Vec::new();
data.extend_from_slice(&3u32.to_le_bytes());
data.extend_from_slice(&encode_string("Go"));
let resp = pipe.send(4, &data)?;
if resp.len() >= 4 {
let build = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
self.build = build as i32;
}
Ok(())
}
pub fn shutdown(&mut self) {
self.pipe = None;
}
fn pipe(&self) -> Result<&NamedPipeClient> {
self.pipe
.as_ref()
.ok_or(Mt5Error::NotInitialized)
}
pub fn login(&self, login: i64, password: &str, server: &str) -> Result<()> {
let pipe = self.pipe()?;
let mut data = Vec::new();
data.extend_from_slice(&login.to_le_bytes());
data.extend_from_slice(&encode_string(password));
data.extend_from_slice(&encode_string(server));
let resp = pipe.send(4, &data)?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
if status != 0 {
return Err(Mt5Error::CommandFailed {
cmd: 4,
error: format!("Login failed with status: {}", status),
});
}
Ok(())
}
pub fn account_info(&self) -> Result<AccountInfo> {
let pipe = self.pipe()?;
let resp = pipe.send(190, &[])?;
if resp.len() < 8 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(&resp);
let login = reader.read_i64();
let trade_mode = reader.read_i32() as i64;
let leverage = reader.read_i32() as i64;
let limit_orders = reader.read_i32() as i64;
let margin_so_mode = reader.read_i32() as i64;
let trade_allowed = reader.read_bool1();
let trade_expert = reader.read_bool1();
let margin_mode = reader.read_i32() as i64;
let currency_digits = reader.read_i32() as i64;
let fifo_close = reader.read_bool1();
let balance = reader.read_f64();
let credit = reader.read_f64();
let profit = reader.read_f64();
let equity = reader.read_f64();
let margin = reader.read_f64();
let free_margin = reader.read_f64();
let margin_level = reader.read_f64();
let margin_so_call = reader.read_f64();
let margin_so_so = reader.read_f64();
let margin_initial = reader.read_f64();
let margin_maintenance = reader.read_f64();
let assets = reader.read_f64();
let liabilities = reader.read_f64();
let commission_blocked = reader.read_f64();
let strings_offset = 147;
if strings_offset >= resp.len() {
return Err(Mt5Error::InvalidResponse(format!(
"Response too short for strings: {} < {}",
resp.len(),
strings_offset
)));
}
let mut sr = Reader::new(&resp[strings_offset..]);
let name = sr.read_fixed_string(256);
let server = sr.read_fixed_string(128);
let currency = sr.read_fixed_string(64);
let company = sr.read_fixed_string(256);
if sr.has_error() {
return Err(Mt5Error::InvalidResponse("Failed to read strings".into()));
}
Ok(AccountInfo {
login,
trade_mode,
leverage,
limit_orders,
margin_so_mode,
trade_allowed,
trade_expert,
margin_mode,
currency_digits,
fifo_close,
balance,
credit,
profit,
equity,
margin,
free_margin,
margin_level,
margin_so_call,
margin_so_so,
margin_initial,
margin_maintenance,
assets,
liabilities,
commission_blocked,
name,
server,
currency,
company,
})
}
pub fn terminal_info(&self) -> Result<TerminalInfo> {
let pipe = self.pipe()?;
let resp = pipe.send(180, &[])?;
if resp.len() < 40 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let community_account = resp[2] != 0;
let community_connection = resp[3] != 0;
let connected = resp[6] != 0;
let dlls_allowed = resp[7] != 0;
let trade_allowed = resp[8] != 0;
let trade_api_disabled = resp[9] != 0;
let email_enabled = resp[10] != 0;
let ftp_enabled = resp[11] != 0;
let notifications_enabled = resp[4] != 0;
let mqid = resp[5] != 0;
let build = u16::from_le_bytes([resp[0], resp[1]]) as i64;
let max_bars = u32::from_le_bytes([resp[12], resp[13], resp[14], resp[15]]) as i64;
let code_page = u16::from_le_bytes([resp[17], resp[18]]) as i64;
let ping_last = u16::from_le_bytes([resp[21], resp[22]]) as i64;
let community_balance = f64::from_le_bytes([
resp[24], resp[25], resp[26], resp[27], resp[28], resp[29], resp[30], resp[31],
]);
let retransmission = f64::from_le_bytes([
resp[32], resp[33], resp[34], resp[35], resp[36], resp[37], resp[38], resp[39],
]);
let company = read_string_at_offset(&resp, 41);
let name = read_string_at_offset(&resp, 561);
let language = read_string_at_offset(&resp, 1081);
let path = read_string_at_offset(&resp, 1601);
let data_path = read_string_at_offset(&resp, 2121);
let common_data_path = read_string_at_offset(&resp, 2641);
Ok(TerminalInfo {
community_account,
community_connection,
connected,
dlls_allowed,
trade_allowed,
trade_api_disabled,
email_enabled,
ftp_enabled,
notifications_enabled,
mqid,
build,
max_bars,
code_page,
ping_last,
community_balance,
retransmission,
company,
name,
language,
path,
data_path,
common_data_path,
})
}
pub fn version(&self) -> Result<VersionInfo> {
let info = self.terminal_info()?;
Ok(VersionInfo {
version: info.build as i32,
build: info.build as i32,
build_date: format!("{} ({})", info.company, info.name),
})
}
pub fn symbols_total(&self) -> Result<i64> {
let pipe = self.pipe()?;
let resp = pipe.send(173, &[])?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(total as i64)
}
pub fn symbols_get(&self) -> Result<Vec<SymbolInfo>> {
let pipe = self.pipe()?;
let resp = pipe.send(174, &[])?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(&resp);
let count = reader.read_u32() as usize;
let mut symbols = Vec::with_capacity(count);
for _ in 0..count {
let sym = Self::decode_symbol_info(&mut reader)?;
symbols.push(sym);
}
Ok(symbols)
}
fn decode_symbol_info(reader: &mut Reader) -> Result<SymbolInfo> {
let custom = reader.read_bool1();
let chart_mode = reader.read_u32() as i64;
let select = reader.read_bool1();
let visible = reader.read_bool1();
let session_deals = reader.read_i64();
let session_buy_orders = reader.read_i64();
let session_sell_orders = reader.read_i64();
let volume = reader.read_i64();
let volume_high = reader.read_i64();
let volume_low = reader.read_i64();
let time = reader.read_i64();
let digits = reader.read_u32() as i64;
let spread = reader.read_u32() as i64;
let spread_float = reader.read_bool1();
let ticks_book_depth = reader.read_u32() as i64;
let trade_calc_mode = reader.read_u32() as i64;
let trade_mode = reader.read_u32() as i64;
let start_time = reader.read_i64();
let expiration_time = reader.read_i64();
let trade_stops_level = reader.read_u32() as i64;
let trade_freeze_level = reader.read_u32() as i64;
let trade_exe_mode = reader.read_u32() as i64;
let swap_mode = reader.read_u32() as i64;
let swap_rollover3days = reader.read_u32() as i64;
let margin_hedged_use_leg = reader.read_bool1();
let expiration_mode = reader.read_u32() as i64;
let filling_mode = reader.read_u32() as i64;
let order_mode = reader.read_u32() as i64;
let order_gtc_mode = reader.read_u32() as i64;
let option_mode = reader.read_u32() as i64;
let option_right = reader.read_u32() as i64;
let bid = reader.read_f64();
let bid_high = reader.read_f64();
let bid_low = reader.read_f64();
let ask = reader.read_f64();
let ask_high = reader.read_f64();
let ask_low = reader.read_f64();
let last = reader.read_f64();
let last_high = reader.read_f64();
let last_low = reader.read_f64();
let volume_real = reader.read_f64();
let volume_high_real = reader.read_f64();
let volume_low_real = reader.read_f64();
let option_strike = reader.read_f64();
let point = reader.read_f64();
let trade_tick_value = reader.read_f64();
let trade_tick_value_profit = reader.read_f64();
let trade_tick_value_loss = reader.read_f64();
let trade_tick_size = reader.read_f64();
let trade_contract_size = reader.read_f64();
let trade_accrued_interest = reader.read_f64();
let trade_face_value = reader.read_f64();
let trade_liquidity_rate = reader.read_f64();
let volume_min = reader.read_f64();
let volume_max = reader.read_f64();
let volume_step = reader.read_f64();
let volume_limit = reader.read_f64();
let swap_long = reader.read_f64();
let swap_short = reader.read_f64();
let margin_initial = reader.read_f64();
let margin_maintenance = reader.read_f64();
let session_volume = reader.read_f64();
let session_turnover = reader.read_f64();
let session_interest = reader.read_f64();
let session_buy_orders_volume = reader.read_f64();
let session_sell_orders_volume = reader.read_f64();
let session_open = reader.read_f64();
let session_close = reader.read_f64();
let session_aw = reader.read_f64();
let session_price_settlement = reader.read_f64();
let session_price_limit_min = reader.read_f64();
let session_price_limit_max = reader.read_f64();
let margin_hedged = reader.read_f64();
let price_change = reader.read_f64();
let price_volatility = reader.read_f64();
let price_theoretical = reader.read_f64();
let price_greeks_delta = reader.read_f64();
let price_greeks_theta = reader.read_f64();
let price_greeks_gamma = reader.read_f64();
let price_greeks_vega = reader.read_f64();
let price_greeks_rho = reader.read_f64();
let price_greeks_omega = reader.read_f64();
let price_sensitivity = reader.read_f64();
let basis = reader.read_fixed_string(64);
let category = reader.read_fixed_string(128);
let currency_base = reader.read_fixed_string(32);
let currency_profit = reader.read_fixed_string(32);
let currency_margin = reader.read_fixed_string(32);
let bank = reader.read_fixed_string(512);
let description = reader.read_fixed_string(64);
let exchange = reader.read_fixed_string(64);
let formula = reader.read_fixed_string(1024);
let isin = reader.read_fixed_string(32);
let page = reader.read_fixed_string(128);
let path = reader.read_fixed_string(256);
let symbol_name = reader.read_fixed_string(64);
if reader.has_error() {
return Err(Mt5Error::InvalidResponse("Failed to read symbol info".into()));
}
Ok(SymbolInfo {
custom,
chart_mode,
select,
visible,
session_deals,
session_buy_orders,
session_sell_orders,
volume,
volume_high,
volume_low,
time,
digits,
spread,
spread_float,
ticks_book_depth,
trade_calc_mode,
trade_mode,
start_time,
expiration_time,
trade_stops_level,
trade_freeze_level,
trade_exe_mode,
swap_mode,
swap_rollover3days,
margin_hedged_use_leg,
expiration_mode,
filling_mode,
order_mode,
order_gtc_mode,
option_mode,
option_right,
bid,
bidhigh: bid_high,
bidlow: bid_low,
ask,
askhigh: ask_high,
asklow: ask_low,
last,
lasthigh: last_high,
lastlow: last_low,
volume_real,
volumehigh_real: volume_high_real,
volumelow_real: volume_low_real,
option_strike,
point,
trade_tick_value,
trade_tick_value_profit,
trade_tick_value_loss,
trade_tick_size,
trade_contract_size,
trade_accrued_interest,
trade_face_value,
trade_liquidity_rate,
volume_min,
volume_max,
volume_step,
volume_limit,
swap_long,
swap_short,
margin_initial,
margin_maintenance,
session_volume,
session_turnover,
session_interest,
session_buy_orders_volume,
session_sell_orders_volume,
session_open,
session_close,
session_aw,
session_price_settlement,
session_price_limit_min,
session_price_limit_max,
margin_hedged,
price_change,
price_volatility,
price_theoretical,
price_greeks_delta,
price_greeks_theta,
price_greeks_gamma,
price_greeks_vega,
price_greeks_rho,
price_greeks_omega,
price_sensitivity,
basis,
category,
currency_base,
currency_profit,
currency_margin,
bank,
description,
exchange,
formula,
isin,
name: symbol_name,
page,
path,
})
}
pub fn symbol_info(&self, symbol: &str) -> Result<Option<SymbolInfo>> {
let pipe = self.pipe()?;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
let resp = pipe.send(170, &data)?;
if resp.is_empty() {
return Ok(None);
}
let mut reader = Reader::new(&resp);
let info = Self::decode_symbol_info(&mut reader)?;
Ok(Some(info))
}
pub fn symbol_info_tick(&self, symbol: &str) -> Result<Option<Tick>> {
let pipe = self.pipe()?;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
let resp = pipe.send(172, &data)?;
if resp.is_empty() {
return Ok(None);
}
let mut reader = Reader::new(&resp);
let time = reader.read_i64();
let bid = reader.read_f64();
let ask = reader.read_f64();
let last = reader.read_f64();
let volume = reader.read_u64();
let time_msc = reader.read_i64();
let flags = reader.read_u32();
let volume_real = reader.read_f64();
if reader.has_error() {
return Err(Mt5Error::InvalidResponse("Failed to read tick info".into()));
}
Ok(Some(Tick {
time,
bid,
ask,
last,
volume,
time_msc,
flags,
volume_real,
}))
}
pub fn symbol_select(&self, symbol: &str, enable: bool) -> Result<bool> {
let pipe = self.pipe()?;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.push(if enable { 1u8 } else { 0u8 });
let resp = pipe.send(171, &data)?;
if resp.is_empty() {
return Ok(true);
}
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let status = i32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(status != 0)
}
pub fn positions_total(&self) -> Result<i64> {
let pipe = self.pipe()?;
let resp = pipe.send(120, &[])?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(total as i64)
}
pub fn orders_total(&self) -> Result<i64> {
let pipe = self.pipe()?;
let resp = pipe.send(130, &[])?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(total as i64)
}
pub fn positions_get(&self, symbol: Option<&str>) -> Result<Vec<TradePosition>> {
let pipe = self.pipe()?;
let cmd = 121;
let mut data = Vec::new();
if let Some(sym) = symbol {
data.extend_from_slice(&encode_string(sym));
}
let resp = pipe.send(cmd, &data)?;
parse_positions_response(&resp)
}
pub fn orders_get(&self, symbol: Option<&str>) -> Result<Vec<TradeOrder>> {
let pipe = self.pipe()?;
let cmd = 131;
let mut data = Vec::new();
if let Some(sym) = symbol {
data.extend_from_slice(&encode_string(sym));
}
let resp = pipe.send(cmd, &data)?;
parse_orders_response(&resp)
}
pub fn send_raw_command(&self, cmd: u32, data: &[u8]) -> Result<Vec<u8>> {
let pipe = self.pipe()?;
pipe.send(cmd, data)
}
pub fn copy_rates_from_pos(&self, symbol: &str, timeframe: i32, start_pos: i64, count: i32) -> Result<Vec<Rate>> {
let pipe = self.pipe()?;
let cmd = 108;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.extend_from_slice(&(timeframe as u32).to_le_bytes());
data.extend_from_slice(&(start_pos as u32).to_le_bytes());
data.extend_from_slice(&(count as u32).to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_rates_response(&resp)
}
pub fn copy_rates_from(&self, symbol: &str, timeframe: i32, date_from: i64, count: i32) -> Result<Vec<Rate>> {
let pipe = self.pipe()?;
let cmd = 106;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.extend_from_slice(&(timeframe as u32).to_le_bytes());
data.extend_from_slice(&date_from.to_le_bytes());
data.extend_from_slice(&(count as u32).to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_rates_response(&resp)
}
pub fn copy_rates_range(&self, symbol: &str, timeframe: i32, date_from: i64, date_to: i64) -> Result<Vec<Rate>> {
let pipe = self.pipe()?;
let cmd = 107;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.extend_from_slice(&(timeframe as u32).to_le_bytes());
data.extend_from_slice(&date_from.to_le_bytes());
data.extend_from_slice(&date_to.to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_rates_response(&resp)
}
pub fn copy_ticks_from(&self, symbol: &str, from: i64, count: i32, flags: i32) -> Result<Vec<Tick>> {
let pipe = self.pipe()?;
let cmd = 104;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&(count as u32).to_le_bytes());
data.extend_from_slice(&(flags as u32).to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_ticks_response(&resp)
}
pub fn copy_ticks_range(&self, symbol: &str, from: i64, to: i64, flags: i32) -> Result<Vec<Tick>> {
let pipe = self.pipe()?;
let cmd = 105;
let mut data = Vec::new();
data.extend_from_slice(&encode_string(symbol));
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&to.to_le_bytes());
data.extend_from_slice(&(flags as u32).to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_ticks_response(&resp)
}
pub fn history_deals_total(&self, from: i64, to: i64) -> Result<i64> {
let pipe = self.pipe()?;
let cmd = 150;
let mut data = Vec::new();
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&to.to_le_bytes());
let resp = pipe.send(cmd, &data)?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(total as i64)
}
pub fn history_deals_get(&self, from: i64, to: i64) -> Result<Vec<TradeDeal>> {
let pipe = self.pipe()?;
let cmd = 151;
let mut data = Vec::new();
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&to.to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_deals_response(&resp)
}
pub fn history_orders_total(&self, from: i64, to: i64) -> Result<i64> {
let pipe = self.pipe()?;
let cmd = 140;
let mut data = Vec::new();
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&to.to_le_bytes());
let resp = pipe.send(cmd, &data)?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let total = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(total as i64)
}
pub fn history_orders_get(&self, from: i64, to: i64) -> Result<Vec<TradeOrder>> {
let pipe = self.pipe()?;
let cmd = 141;
let mut data = Vec::new();
data.extend_from_slice(&from.to_le_bytes());
data.extend_from_slice(&to.to_le_bytes());
let resp = pipe.send(cmd, &data)?;
parse_orders_response(&resp)
}
pub fn market_book_add(&self, symbol: &str) -> Result<bool> {
let pipe = self.pipe()?;
let cmd = 191;
let data = encode_string(symbol);
let resp = pipe.send(cmd, &data)?;
if resp.is_empty() {
return Ok(true);
}
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(status == 0)
}
pub fn market_book_get(&self, symbol: &str) -> Result<Vec<BookInfo>> {
let pipe = self.pipe()?;
let cmd = 193;
let data = encode_string(symbol);
let resp = pipe.send(cmd, &data)?;
parse_book_response(&resp)
}
pub fn market_book_release(&self, symbol: &str) -> Result<bool> {
let pipe = self.pipe()?;
let cmd = 192;
let data = encode_string(symbol);
let resp = pipe.send(cmd, &data)?;
if resp.is_empty() {
return Ok(true);
}
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let status = u32::from_le_bytes([resp[0], resp[1], resp[2], resp[3]]);
Ok(status == 0)
}
pub fn order_calc_margin(&self, _action: i32, symbol: &str, volume: f64, price: f64) -> Result<f64> {
let symbol_info = self.symbol_info(symbol)?;
let margin_initial = symbol_info.unwrap().margin_initial;
let margin = volume * price * margin_initial / 4.0;
Ok(margin)
}
pub fn order_calc_profit(&self, _action: i32, symbol: &str, volume: f64, price_open: f64, price_close: f64) -> Result<f64> {
let symbol_info = self.symbol_info(symbol)?;
let profit = volume * (price_close - price_open) * symbol_info.unwrap().trade_contract_size;
Ok(profit)
}
pub fn last_error(&self) -> Result<(i32, String)> {
let pipe = self.pipe()?;
let cmd = 3;
let resp = pipe.send(cmd, &[])?;
if resp.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(&resp);
let code = reader.read_i32();
let message = reader.read_string();
Ok((code, message))
}
}
fn parse_positions_response(data: &[u8]) -> Result<Vec<TradePosition>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut positions = Vec::with_capacity(count);
for _ in 0..count {
let ticket = reader.read_i64();
let time = reader.read_i64();
let time_msc = reader.read_i64();
let time_update = reader.read_i64();
let time_update_msc = reader.read_i64();
let r#type = reader.read_u32() as i32;
let magic = reader.read_i64();
let identifier = reader.read_i64();
let reason = reader.read_u32() as i32;
let volume = reader.read_f64();
let price_open = reader.read_f64();
let price_current = reader.read_f64();
let price_sl = reader.read_f64();
let price_tp = reader.read_f64();
let swap = reader.read_f64();
let profit = reader.read_f64();
let symbol = reader.read_fixed_string(64);
let comment = reader.read_fixed_string(64);
let external_id = reader.read_fixed_string(64);
if reader.has_error() {
break;
}
positions.push(TradePosition {
ticket,
time,
time_msc,
time_update,
time_update_msc,
r#type,
magic,
identifier,
reason,
volume,
price_open,
price_current,
price_sl,
price_tp,
swap,
profit,
symbol,
comment,
external_id,
});
}
Ok(positions)
}
fn parse_orders_response(data: &[u8]) -> Result<Vec<TradeOrder>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut orders = Vec::with_capacity(count);
for _ in 0..count {
let ticket = reader.read_i64();
let time_setup = reader.read_i64();
let time_setup_msc = reader.read_i64();
let time_done = reader.read_i64();
let time_done_msc = reader.read_i64();
let time_expiration = reader.read_i64();
let r#type = reader.read_u32() as i32;
let type_time = reader.read_u32() as i32;
let type_filling = reader.read_u32() as i32;
let state = reader.read_u32() as i32;
let magic = reader.read_i64();
let position_id = reader.read_i64();
let position_by_id = reader.read_i64();
let reason = reader.read_u32() as i32;
let volume_initial = reader.read_f64();
let volume_current = reader.read_f64();
let price_open = reader.read_f64();
let price_current = reader.read_f64();
let price_sl = reader.read_f64();
let price_tp = reader.read_f64();
let price_stoplimit = reader.read_f64();
let symbol = reader.read_fixed_string(64);
let comment = reader.read_fixed_string(64);
let external_id = reader.read_fixed_string(64);
if reader.has_error() {
break;
}
orders.push(TradeOrder {
ticket,
time_setup,
time_setup_msc,
time_done,
time_done_msc,
time_expiration,
r#type,
type_time,
type_filling,
state,
magic,
position_id,
position_by_id,
reason,
volume_initial,
volume_current,
price_open,
price_current,
price_sl,
price_tp,
price_stoplimit,
symbol,
comment,
external_id,
});
}
Ok(orders)
}
fn parse_deals_response(data: &[u8]) -> Result<Vec<TradeDeal>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut deals = Vec::with_capacity(count);
for _ in 0..count {
let ticket = reader.read_i64();
let order = reader.read_i64();
let time = reader.read_i64();
let time_msc = reader.read_i64();
let r#type = reader.read_u32() as i32;
let entry = reader.read_u32() as i32;
let magic = reader.read_i64();
let position_id = reader.read_i64();
let reason = reader.read_u32() as i32;
let volume = reader.read_f64();
let price = reader.read_f64();
let commission = reader.read_f64();
let swap = reader.read_f64();
let profit = reader.read_f64();
let fee = reader.read_f64();
let symbol = reader.read_fixed_string(64);
let comment = reader.read_fixed_string(64);
let external_id = reader.read_fixed_string(64);
if reader.has_error() {
break;
}
deals.push(TradeDeal {
ticket,
order,
time,
time_msc,
r#type,
entry,
magic,
position_id,
reason,
volume,
price,
commission,
swap,
profit,
fee,
symbol,
comment,
external_id,
});
}
Ok(deals)
}
fn parse_rates_response(data: &[u8]) -> Result<Vec<Rate>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut rates = Vec::with_capacity(count);
for _ in 0..count {
let time = reader.read_i64();
let open = reader.read_f64();
let high = reader.read_f64();
let low = reader.read_f64();
let close = reader.read_f64();
let tick_volume = reader.read_u64();
let spread = reader.read_i32();
let real_volume = reader.read_u64();
if reader.has_error() {
break;
}
rates.push(Rate {
time,
open,
high,
low,
close,
tick_volume,
spread,
real_volume,
});
}
Ok(rates)
}
fn parse_ticks_response(data: &[u8]) -> Result<Vec<Tick>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut ticks = Vec::with_capacity(count);
for _ in 0..count {
let time = reader.read_i64();
let bid = reader.read_f64();
let ask = reader.read_f64();
let last = reader.read_f64();
let volume = reader.read_u64();
let time_msc = reader.read_i64();
let flags = reader.read_u32();
let volume_real = reader.read_f64();
if reader.has_error() {
break;
}
ticks.push(Tick {
time,
bid,
ask,
last,
volume,
time_msc,
flags,
volume_real,
});
}
Ok(ticks)
}
fn parse_book_response(data: &[u8]) -> Result<Vec<BookInfo>> {
if data.len() < 4 {
return Err(Mt5Error::InvalidResponse("Response too short".into()));
}
let mut reader = Reader::new(data);
let count = reader.read_u32() as usize;
let mut books = Vec::with_capacity(count);
for _ in 0..count {
let r#type = reader.read_i64();
let price = reader.read_f64();
let volume = reader.read_i64();
let volume_real = reader.read_f64();
if reader.has_error() {
break;
}
books.push(BookInfo {
r#type,
price,
volume,
volume_real,
});
}
Ok(books)
}
fn encode_string(s: &str) -> Vec<u8> {
let chars: Vec<u16> = s.encode_utf16().collect();
let mut data = Vec::with_capacity(4 + chars.len() * 2);
data.extend_from_slice(&(chars.len() as u32).to_le_bytes());
for c in chars {
data.extend_from_slice(&c.to_le_bytes());
}
data
}
struct Reader<'a> {
data: &'a [u8],
pos: usize,
error: bool,
}
impl<'a> Reader<'a> {
fn new(data: &'a [u8]) -> Self {
Self {
data,
pos: 0,
error: false,
}
}
fn has_error(&self) -> bool {
self.error
}
fn read_i64(&mut self) -> i64 {
if self.error || self.pos + 8 > self.data.len() {
self.error = true;
return 0;
}
let bytes = [
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
self.data[self.pos + 4],
self.data[self.pos + 5],
self.data[self.pos + 6],
self.data[self.pos + 7],
];
self.pos += 8;
i64::from_le_bytes(bytes)
}
fn read_u64(&mut self) -> u64 {
if self.error || self.pos + 8 > self.data.len() {
self.error = true;
return 0;
}
let bytes = [
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
self.data[self.pos + 4],
self.data[self.pos + 5],
self.data[self.pos + 6],
self.data[self.pos + 7],
];
self.pos += 8;
u64::from_le_bytes(bytes)
}
fn read_i32(&mut self) -> i32 {
if self.error || self.pos + 4 > self.data.len() {
self.error = true;
return 0;
}
let bytes = [
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
];
self.pos += 4;
i32::from_le_bytes(bytes)
}
fn read_u32(&mut self) -> u32 {
if self.error || self.pos + 4 > self.data.len() {
self.error = true;
return 0;
}
let bytes = [
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
];
self.pos += 4;
u32::from_le_bytes(bytes)
}
fn read_f64(&mut self) -> f64 {
if self.error || self.pos + 8 > self.data.len() {
self.error = true;
return 0.0;
}
let bytes = [
self.data[self.pos],
self.data[self.pos + 1],
self.data[self.pos + 2],
self.data[self.pos + 3],
self.data[self.pos + 4],
self.data[self.pos + 5],
self.data[self.pos + 6],
self.data[self.pos + 7],
];
self.pos += 8;
f64::from_le_bytes(bytes)
}
fn read_bool(&mut self) -> bool {
self.read_i64() != 0
}
fn read_bool1(&mut self) -> bool {
if self.error || self.pos + 1 > self.data.len() {
self.error = true;
return false;
}
let b = self.data[self.pos];
self.pos += 1;
b != 0
}
fn read_string(&mut self) -> String {
if self.error || self.pos + 4 > self.data.len() {
self.error = true;
return String::new();
}
let char_count = self.read_i32() as usize;
let byte_count = char_count * 2;
if self.pos + byte_count > self.data.len() {
self.error = true;
return String::new();
}
let mut chars = Vec::with_capacity(char_count);
for _ in 0..char_count {
let c = u16::from_le_bytes([self.data[self.pos], self.data[self.pos + 1]]);
self.pos += 2;
chars.push(c);
}
String::from_utf16_lossy(&chars)
}
fn read_fixed_string(&mut self, slot_bytes: usize) -> String {
if self.error || self.pos + slot_bytes > self.data.len() {
self.error = true;
return String::new();
}
let end = self.pos + slot_bytes;
let buf = &self.data[self.pos..end];
let mut chars = Vec::with_capacity(slot_bytes / 2);
let mut i = 0;
while i + 1 < buf.len() {
let c = u16::from_le_bytes([buf[i], buf[i + 1]]);
if c == 0 {
break;
}
chars.push(c);
i += 2;
}
self.pos = end;
String::from_utf16_lossy(&chars)
}
}
fn read_string_at_offset(data: &[u8], offset: usize) -> String {
if offset >= data.len() {
return String::new();
}
let mut chars = Vec::new();
let mut pos = offset;
while pos + 1 < data.len() {
let c = u16::from_le_bytes([data[pos], data[pos + 1]]);
pos += 2;
if c == 0 {
break;
}
chars.push(c);
}
String::from_utf16_lossy(&chars)
}