use crate::api::{safe_cstr_to_string, to_cstring, CtpApi};
use crate::encoding::GbkConverter;
use crate::error::{CtpError, CtpResult};
use crate::ffi::md_api::*;
use crate::ffi::{CreateMdSpiBridge, MdSpiCallbacks};
use crate::types::{ReqUserLoginField, RspInfoField, RspUserLoginField};
use std::ffi::{c_void, CString};
use std::os::raw::c_int;
use std::ptr;
use std::sync::{Arc, Mutex};
#[allow(dead_code)]
pub struct MdApi {
api_ptr: *mut c_void,
spi_ptr: *mut c_void,
initialized: bool,
request_id: Arc<Mutex<i32>>,
handler: Option<Box<dyn MdSpiHandler + Send + Sync>>,
}
#[allow(unused_variables)]
pub trait MdSpiHandler {
fn on_front_connected(&mut self) {}
fn on_front_disconnected(&mut self, reason: i32) {}
fn on_heart_beat_warning(&mut self, time_lapse: i32) {}
fn on_rsp_user_login(
&mut self,
user_login: Option<RspUserLoginField>,
rsp_info: Option<RspInfoField>,
request_id: i32,
is_last: bool,
) {
}
fn on_rsp_user_logout(
&mut self,
user_logout: Option<()>,
rsp_info: Option<RspInfoField>,
request_id: i32,
is_last: bool,
) {
}
fn on_rsp_error(&mut self, rsp_info: Option<RspInfoField>, request_id: i32, is_last: bool) {}
fn on_rsp_sub_market_data(
&mut self,
specific_instrument: Option<SpecificInstrumentField>,
rsp_info: Option<RspInfoField>,
request_id: i32,
is_last: bool,
) {
}
fn on_rsp_unsub_market_data(
&mut self,
specific_instrument: Option<SpecificInstrumentField>,
rsp_info: Option<RspInfoField>,
request_id: i32,
is_last: bool,
) {
}
fn on_rtn_depth_market_data(&mut self, market_data: DepthMarketDataField) {}
fn on_rtn_for_quote_rsp(&mut self, for_quote_rsp: ForQuoteRspField) {}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct DepthMarketDataField {
pub trading_day: [u8; 9],
pub reserve1: [u8; 31],
pub exchange_id: [u8; 9],
pub reserve2: [u8; 31],
pub last_price: f64,
pub pre_settlement_price: f64,
pub pre_close_price: f64,
pub pre_open_interest: f64,
pub open_price: f64,
pub highest_price: f64,
pub lowest_price: f64,
pub volume: i32,
pub turnover: f64,
pub open_interest: f64,
pub close_price: f64,
pub settlement_price: f64,
pub upper_limit_price: f64,
pub lower_limit_price: f64,
pub pre_delta: f64,
pub curr_delta: f64,
pub update_time: [u8; 9],
pub update_millisec: i32,
pub bid_price1: f64,
pub bid_volume1: i32,
pub ask_price1: f64,
pub ask_volume1: i32,
pub bid_price2: f64,
pub bid_volume2: i32,
pub ask_price2: f64,
pub ask_volume2: i32,
pub bid_price3: f64,
pub bid_volume3: i32,
pub ask_price3: f64,
pub ask_volume3: i32,
pub bid_price4: f64,
pub bid_volume4: i32,
pub ask_price4: f64,
pub ask_volume4: i32,
pub bid_price5: f64,
pub bid_volume5: i32,
pub ask_price5: f64,
pub ask_volume5: i32,
pub average_price: f64,
pub action_day: [u8; 9],
pub instrument_id: [u8; 81],
pub exchange_inst_id: [u8; 81],
pub banding_upper_price: f64,
pub banding_lower_price: f64,
}
impl Default for DepthMarketDataField {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl DepthMarketDataField {
pub fn get_instrument_id(&self) -> CtpResult<String> {
GbkConverter::fixed_bytes_to_utf8(&self.instrument_id)
}
pub fn get_exchange_id(&self) -> CtpResult<String> {
GbkConverter::fixed_bytes_to_utf8(&self.exchange_id)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct SpecificInstrumentField {
pub reserve1: [u8; 31],
pub instrument_id: [u8; 81],
}
impl Default for SpecificInstrumentField {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
impl SpecificInstrumentField {
pub fn get_instrument_id(&self) -> CtpResult<String> {
GbkConverter::fixed_bytes_to_utf8(&self.instrument_id)
}
}
#[repr(C)]
#[derive(Debug, Clone)]
pub struct ForQuoteRspField {
pub trading_day: [u8; 9],
pub instrument_id: [u8; 31],
pub for_quote_ref: [u8; 13],
pub user_id: [u8; 16],
pub for_quote_local_id: [u8; 13],
pub exchange_id: [u8; 9],
pub participant_id: [u8; 11],
pub client_id: [u8; 11],
pub exchange_inst_id: [u8; 31],
pub trader_id: [u8; 21],
pub install_id: i32,
pub insert_time: [u8; 9],
pub for_quote_local_id2: [u8; 13],
pub action_day: [u8; 9],
}
impl Default for ForQuoteRspField {
fn default() -> Self {
unsafe { std::mem::zeroed() }
}
}
unsafe impl Send for MdApi {}
unsafe impl Sync for MdApi {}
impl MdApi {
pub fn new(
flow_path: Option<&str>,
is_using_udp: bool,
is_multicast: bool,
is_production_mode: bool,
) -> CtpResult<Self> {
let flow_path_cstr = match flow_path {
Some(path) => Some(to_cstring(path)?),
None => None,
};
let flow_path_ptr = flow_path_cstr
.as_ref()
.map(|s| s.as_ptr())
.unwrap_or(ptr::null());
let api_ptr = unsafe {
CThostFtdcMdApi_CreateFtdcMdApi(
flow_path_ptr,
is_using_udp,
is_multicast,
is_production_mode,
)
};
if api_ptr.is_null() {
return Err(CtpError::InitializationError("创建行情API失败".to_string()));
}
Ok(MdApi {
api_ptr,
spi_ptr: ptr::null_mut(),
initialized: false,
request_id: Arc::new(Mutex::new(1)),
handler: None,
})
}
pub fn register_spi<T>(&mut self, handler: T) -> CtpResult<()>
where
T: MdSpiHandler + Send + Sync + 'static,
{
self.handler = Some(Box::new(handler));
let callbacks = MdSpiCallbacks {
user_data: self as *mut _ as *mut c_void,
on_front_connected: Some(on_front_connected_callback),
on_front_disconnected: Some(on_front_disconnected_callback),
on_heart_beat_warning: Some(on_heart_beat_warning_callback),
on_rsp_user_login: Some(on_rsp_user_login_callback),
on_rsp_user_logout: Some(on_rsp_user_logout_callback),
on_rsp_error: Some(on_rsp_error_callback),
on_rsp_sub_market_data: Some(on_rsp_sub_market_data_callback),
on_rsp_unsub_market_data: Some(on_rsp_unsub_market_data_callback),
on_rtn_depth_market_data: Some(on_rtn_depth_market_data_callback),
on_rtn_for_quote_rsp: Some(on_rtn_for_quote_rsp_callback),
};
self.spi_ptr = unsafe { CreateMdSpiBridge(&callbacks) };
if self.spi_ptr.is_null() {
return Err(CtpError::InitializationError(
"创建SPI桥接器失败".to_string(),
));
}
unsafe {
CThostFtdcMdApi_RegisterSpi(self.api_ptr, self.spi_ptr);
}
Ok(())
}
pub fn req_user_login(&mut self, req: &ReqUserLoginField) -> CtpResult<i32> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let request_id = self.next_request_id();
let result = unsafe {
CThostFtdcMdApi_ReqUserLogin(self.api_ptr, req as *const _ as *const c_void, request_id)
};
if result != 0 {
return Err(CtpError::FfiError(format!("登录请求失败: {}", result)));
}
Ok(request_id)
}
pub fn req_user_logout(&mut self) -> CtpResult<i32> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let request_id = self.next_request_id();
let result =
unsafe { CThostFtdcMdApi_ReqUserLogout(self.api_ptr, ptr::null(), request_id) };
if result != 0 {
return Err(CtpError::FfiError(format!("登出请求失败: {}", result)));
}
Ok(request_id)
}
pub fn subscribe_market_data(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let c_strings: Result<Vec<CString>, _> = instrument_ids
.iter()
.map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
.collect();
let c_strings = c_strings?;
let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
let result = unsafe {
CThostFtdcMdApi_SubscribeMarketData(self.api_ptr, c_ptrs.as_ptr(), c_ptrs.len() as i32)
};
if result != 0 {
return Err(CtpError::FfiError(format!("订阅行情失败: {}", result)));
}
Ok(())
}
pub fn unsubscribe_market_data(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let c_strings: Result<Vec<CString>, _> = instrument_ids
.iter()
.map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
.collect();
let c_strings = c_strings?;
let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
let result = unsafe {
CThostFtdcMdApi_UnSubscribeMarketData(
self.api_ptr,
c_ptrs.as_ptr(),
c_ptrs.len() as i32,
)
};
if result != 0 {
return Err(CtpError::FfiError(format!("退订行情失败: {}", result)));
}
Ok(())
}
pub fn subscribe_for_quote_rsp(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let c_strings: Result<Vec<CString>, _> = instrument_ids
.iter()
.map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
.collect();
let c_strings = c_strings?;
let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
let result = unsafe {
CThostFtdcMdApi_SubscribeForQuoteRsp(self.api_ptr, c_ptrs.as_ptr(), c_ptrs.len() as i32)
};
if result != 0 {
return Err(CtpError::FfiError(format!("订阅询价失败: {}", result)));
}
Ok(())
}
pub fn unsubscribe_for_quote_rsp(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let c_strings: Result<Vec<CString>, _> = instrument_ids
.iter()
.map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
.collect();
let c_strings = c_strings?;
let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
let result = unsafe {
CThostFtdcMdApi_UnSubscribeForQuoteRsp(
self.api_ptr,
c_ptrs.as_ptr(),
c_ptrs.len() as i32,
)
};
if result != 0 {
return Err(CtpError::FfiError(format!("退订询价失败: {}", result)));
}
Ok(())
}
fn next_request_id(&self) -> i32 {
let mut id = self.request_id.lock().unwrap();
let current = *id;
*id += 1;
current
}
}
impl CtpApi for MdApi {
fn get_version() -> CtpResult<String> {
let version_ptr = unsafe { CThostFtdcMdApi_GetApiVersion() };
safe_cstr_to_string(version_ptr)
}
fn init(&mut self) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API指针为空".to_string()));
}
unsafe {
CThostFtdcMdApi_Init(self.api_ptr);
}
self.initialized = true;
Ok(())
}
fn release(&mut self) {
if !self.api_ptr.is_null() {
unsafe {
CThostFtdcMdApi_Release(self.api_ptr);
}
self.api_ptr = ptr::null_mut();
}
self.initialized = false;
}
fn get_trading_day(&self) -> CtpResult<String> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let trading_day_ptr = unsafe { CThostFtdcMdApi_GetTradingDay(self.api_ptr) };
safe_cstr_to_string(trading_day_ptr)
}
fn register_front(&mut self, front_address: &str) -> CtpResult<()> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let front_address_cstr = to_cstring(front_address)?;
unsafe {
CThostFtdcMdApi_RegisterFront(self.api_ptr, front_address_cstr.as_ptr());
}
Ok(())
}
fn join(&self) -> CtpResult<i32> {
if self.api_ptr.is_null() {
return Err(CtpError::InitializationError("API未初始化".to_string()));
}
let result = unsafe { CThostFtdcMdApi_Join(self.api_ptr) };
Ok(result)
}
}
impl Drop for MdApi {
fn drop(&mut self) {
self.release();
}
}
extern "C" fn on_front_connected_callback(user_data: *mut c_void) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
handler.on_front_connected();
}
}
}
}
extern "C" fn on_front_disconnected_callback(user_data: *mut c_void, reason: c_int) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
handler.on_front_disconnected(reason);
}
}
}
}
extern "C" fn on_heart_beat_warning_callback(user_data: *mut c_void, time_lapse: c_int) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
handler.on_heart_beat_warning(time_lapse);
}
}
}
}
extern "C" fn on_rsp_user_login_callback(
user_data: *mut c_void,
user_login: *mut c_void,
rsp_info: *mut c_void,
request_id: c_int,
is_last: c_int,
) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
let parsed_user_login = if !user_login.is_null() {
let login_ptr = user_login as *const RspUserLoginField;
Some((*login_ptr).clone())
} else {
None
};
let parsed_rsp_info = if !rsp_info.is_null() {
let rsp_ptr = rsp_info as *const RspInfoField;
Some((*rsp_ptr).clone())
} else {
None
};
handler.on_rsp_user_login(
parsed_user_login,
parsed_rsp_info,
request_id,
is_last != 0,
);
}
}
}
}
extern "C" fn on_rsp_user_logout_callback(
user_data: *mut c_void,
_user_logout: *mut c_void,
_rsp_info: *mut c_void,
request_id: c_int,
is_last: c_int,
) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
handler.on_rsp_user_logout(None, None, request_id, is_last != 0);
}
}
}
}
extern "C" fn on_rsp_error_callback(
user_data: *mut c_void,
_rsp_info: *mut c_void,
request_id: c_int,
is_last: c_int,
) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
handler.on_rsp_error(None, request_id, is_last != 0);
}
}
}
}
extern "C" fn on_rsp_sub_market_data_callback(
user_data: *mut c_void,
specific_instrument: *mut c_void,
rsp_info: *mut c_void,
request_id: c_int,
is_last: c_int,
) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
let parsed_specific_instrument = if !specific_instrument.is_null() {
let instrument_ptr = specific_instrument as *const SpecificInstrumentField;
Some((*instrument_ptr).clone())
} else {
None
};
let parsed_rsp_info = if !rsp_info.is_null() {
let rsp_ptr = rsp_info as *const RspInfoField;
Some((*rsp_ptr).clone())
} else {
None
};
handler.on_rsp_sub_market_data(
parsed_specific_instrument,
parsed_rsp_info,
request_id,
is_last != 0,
);
}
}
}
}
extern "C" fn on_rsp_unsub_market_data_callback(
user_data: *mut c_void,
specific_instrument: *mut c_void,
rsp_info: *mut c_void,
request_id: c_int,
is_last: c_int,
) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
let parsed_specific_instrument = if !specific_instrument.is_null() {
let instrument_ptr = specific_instrument as *const SpecificInstrumentField;
Some((*instrument_ptr).clone())
} else {
None
};
let parsed_rsp_info = if !rsp_info.is_null() {
let rsp_ptr = rsp_info as *const RspInfoField;
Some((*rsp_ptr).clone())
} else {
None
};
handler.on_rsp_unsub_market_data(
parsed_specific_instrument,
parsed_rsp_info,
request_id,
is_last != 0,
);
}
}
}
}
extern "C" fn on_rtn_depth_market_data_callback(user_data: *mut c_void, market_data: *mut c_void) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
if !market_data.is_null() {
let data_ptr = market_data as *const DepthMarketDataField;
let parsed_data = (*data_ptr).clone();
handler.on_rtn_depth_market_data(parsed_data);
}
}
}
}
}
extern "C" fn on_rtn_for_quote_rsp_callback(user_data: *mut c_void, _for_quote_rsp: *mut c_void) {
unsafe {
if let Some(api) = (user_data as *mut MdApi).as_mut() {
if let Some(ref mut handler) = api.handler {
let temp_data = ForQuoteRspField::default();
handler.on_rtn_for_quote_rsp(temp_data);
}
}
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_version() {
match MdApi::get_version() {
Ok(version) => eprintln!("版本: {}", version),
Err(e) => eprintln!("获取版本失败: {}", e),
}
}
}