1use crate::api::{safe_cstr_to_string, to_cstring, CtpApi};
6use crate::encoding::GbkConverter;
7use crate::error::{CtpError, CtpResult};
8use crate::ffi::md_api::*;
9use crate::ffi::{CreateMdSpiBridge, MdSpiCallbacks};
10use crate::types::{ReqUserLoginField, RspInfoField, RspUserLoginField};
11use std::ffi::{c_void, CString};
12use std::os::raw::c_int;
13use std::ptr;
14use std::sync::{Arc, Mutex};
15
16#[allow(dead_code)]
18pub struct MdApi {
19 api_ptr: *mut c_void,
21 spi_ptr: *mut c_void,
23 initialized: bool,
25 request_id: Arc<Mutex<i32>>,
27 handler: Option<Box<dyn MdSpiHandler + Send + Sync>>,
29}
30
31#[allow(unused_variables)]
33pub trait MdSpiHandler {
34 fn on_front_connected(&mut self) {}
36
37 fn on_front_disconnected(&mut self, reason: i32) {}
42
43 fn on_heart_beat_warning(&mut self, time_lapse: i32) {}
48
49 fn on_rsp_user_login(
51 &mut self,
52 user_login: Option<RspUserLoginField>,
53 rsp_info: Option<RspInfoField>,
54 request_id: i32,
55 is_last: bool,
56 ) {
57 }
58
59 fn on_rsp_user_logout(
61 &mut self,
62 user_logout: Option<()>,
63 rsp_info: Option<RspInfoField>,
64 request_id: i32,
65 is_last: bool,
66 ) {
67 }
68
69 fn on_rsp_error(&mut self, rsp_info: Option<RspInfoField>, request_id: i32, is_last: bool) {}
71
72 fn on_rsp_sub_market_data(
74 &mut self,
75 specific_instrument: Option<SpecificInstrumentField>,
76 rsp_info: Option<RspInfoField>,
77 request_id: i32,
78 is_last: bool,
79 ) {
80 }
81
82 fn on_rsp_unsub_market_data(
84 &mut self,
85 specific_instrument: Option<SpecificInstrumentField>,
86 rsp_info: Option<RspInfoField>,
87 request_id: i32,
88 is_last: bool,
89 ) {
90 }
91
92 fn on_rtn_depth_market_data(&mut self, market_data: DepthMarketDataField) {}
94
95 fn on_rtn_for_quote_rsp(&mut self, for_quote_rsp: ForQuoteRspField) {}
97}
98
99#[repr(C)]
101#[derive(Debug, Clone)]
102pub struct DepthMarketDataField {
103 pub trading_day: [u8; 9],
105 pub reserve1: [u8; 31],
107 pub exchange_id: [u8; 9],
109 pub reserve2: [u8; 31],
111 pub last_price: f64,
113 pub pre_settlement_price: f64,
115 pub pre_close_price: f64,
117 pub pre_open_interest: f64,
119 pub open_price: f64,
121 pub highest_price: f64,
123 pub lowest_price: f64,
125 pub volume: i32,
127 pub turnover: f64,
129 pub open_interest: f64,
131 pub close_price: f64,
133 pub settlement_price: f64,
135 pub upper_limit_price: f64,
137 pub lower_limit_price: f64,
139 pub pre_delta: f64,
141 pub curr_delta: f64,
143 pub update_time: [u8; 9],
145 pub update_millisec: i32,
147 pub bid_price1: f64,
149 pub bid_volume1: i32,
151 pub ask_price1: f64,
153 pub ask_volume1: i32,
155 pub bid_price2: f64,
157 pub bid_volume2: i32,
159 pub ask_price2: f64,
161 pub ask_volume2: i32,
163 pub bid_price3: f64,
165 pub bid_volume3: i32,
167 pub ask_price3: f64,
169 pub ask_volume3: i32,
171 pub bid_price4: f64,
173 pub bid_volume4: i32,
175 pub ask_price4: f64,
177 pub ask_volume4: i32,
179 pub bid_price5: f64,
181 pub bid_volume5: i32,
183 pub ask_price5: f64,
185 pub ask_volume5: i32,
187 pub average_price: f64,
189 pub action_day: [u8; 9],
191 pub instrument_id: [u8; 81],
193 pub exchange_inst_id: [u8; 81],
195 pub banding_upper_price: f64,
197 pub banding_lower_price: f64,
199}
200
201impl Default for DepthMarketDataField {
202 fn default() -> Self {
203 unsafe { std::mem::zeroed() }
204 }
205}
206
207impl DepthMarketDataField {
208 pub fn get_instrument_id(&self) -> CtpResult<String> {
210 GbkConverter::fixed_bytes_to_utf8(&self.instrument_id)
211 }
212
213 pub fn get_exchange_id(&self) -> CtpResult<String> {
215 GbkConverter::fixed_bytes_to_utf8(&self.exchange_id)
216 }
217}
218
219#[repr(C)]
221#[derive(Debug, Clone)]
222pub struct SpecificInstrumentField {
223 pub reserve1: [u8; 31],
225 pub instrument_id: [u8; 81],
227}
228
229impl Default for SpecificInstrumentField {
230 fn default() -> Self {
231 unsafe { std::mem::zeroed() }
232 }
233}
234
235impl SpecificInstrumentField {
236 pub fn get_instrument_id(&self) -> CtpResult<String> {
238 GbkConverter::fixed_bytes_to_utf8(&self.instrument_id)
239 }
240}
241
242#[repr(C)]
244#[derive(Debug, Clone)]
245pub struct ForQuoteRspField {
246 pub trading_day: [u8; 9],
248 pub instrument_id: [u8; 31],
250 pub for_quote_ref: [u8; 13],
252 pub user_id: [u8; 16],
254 pub for_quote_local_id: [u8; 13],
256 pub exchange_id: [u8; 9],
258 pub participant_id: [u8; 11],
260 pub client_id: [u8; 11],
262 pub exchange_inst_id: [u8; 31],
264 pub trader_id: [u8; 21],
266 pub install_id: i32,
268 pub insert_time: [u8; 9],
270 pub for_quote_local_id2: [u8; 13],
272 pub action_day: [u8; 9],
274}
275
276impl Default for ForQuoteRspField {
277 fn default() -> Self {
278 unsafe { std::mem::zeroed() }
279 }
280}
281
282unsafe impl Send for MdApi {}
283unsafe impl Sync for MdApi {}
284
285impl MdApi {
286 pub fn new(
294 flow_path: Option<&str>,
295 is_using_udp: bool,
296 is_multicast: bool,
297 is_production_mode: bool,
298 ) -> CtpResult<Self> {
299 let flow_path_cstr = match flow_path {
300 Some(path) => Some(to_cstring(path)?),
301 None => None,
302 };
303
304 let flow_path_ptr = flow_path_cstr
305 .as_ref()
306 .map(|s| s.as_ptr())
307 .unwrap_or(ptr::null());
308
309 let api_ptr = unsafe {
310 CThostFtdcMdApi_CreateFtdcMdApi(
311 flow_path_ptr,
312 is_using_udp,
313 is_multicast,
314 is_production_mode,
315 )
316 };
317
318 if api_ptr.is_null() {
319 return Err(CtpError::InitializationError("创建行情API失败".to_string()));
320 }
321
322 Ok(MdApi {
323 api_ptr,
324 spi_ptr: ptr::null_mut(),
325 initialized: false,
326 request_id: Arc::new(Mutex::new(1)),
327 handler: None,
328 })
329 }
330
331 pub fn register_spi<T>(&mut self, handler: T) -> CtpResult<()>
333 where
334 T: MdSpiHandler + Send + Sync + 'static,
335 {
336 self.handler = Some(Box::new(handler));
337
338 let callbacks = MdSpiCallbacks {
340 user_data: self as *mut _ as *mut c_void,
341 on_front_connected: Some(on_front_connected_callback),
342 on_front_disconnected: Some(on_front_disconnected_callback),
343 on_heart_beat_warning: Some(on_heart_beat_warning_callback),
344 on_rsp_user_login: Some(on_rsp_user_login_callback),
345 on_rsp_user_logout: Some(on_rsp_user_logout_callback),
346 on_rsp_error: Some(on_rsp_error_callback),
347 on_rsp_sub_market_data: Some(on_rsp_sub_market_data_callback),
348 on_rsp_unsub_market_data: Some(on_rsp_unsub_market_data_callback),
349 on_rtn_depth_market_data: Some(on_rtn_depth_market_data_callback),
350 on_rtn_for_quote_rsp: Some(on_rtn_for_quote_rsp_callback),
351 };
352
353 self.spi_ptr = unsafe { CreateMdSpiBridge(&callbacks) };
355
356 if self.spi_ptr.is_null() {
357 return Err(CtpError::InitializationError(
358 "创建SPI桥接器失败".to_string(),
359 ));
360 }
361
362 unsafe {
364 CThostFtdcMdApi_RegisterSpi(self.api_ptr, self.spi_ptr);
365 }
366
367 Ok(())
368 }
369
370 pub fn req_user_login(&mut self, req: &ReqUserLoginField) -> CtpResult<i32> {
372 if self.api_ptr.is_null() {
373 return Err(CtpError::InitializationError("API未初始化".to_string()));
374 }
375 let request_id = self.next_request_id();
376
377 let result = unsafe {
378 CThostFtdcMdApi_ReqUserLogin(self.api_ptr, req as *const _ as *const c_void, request_id)
379 };
380 if result != 0 {
381 return Err(CtpError::FfiError(format!("登录请求失败: {}", result)));
382 }
383
384 Ok(request_id)
385 }
386
387 pub fn req_user_logout(&mut self) -> CtpResult<i32> {
389 if self.api_ptr.is_null() {
390 return Err(CtpError::InitializationError("API未初始化".to_string()));
391 }
392
393 let request_id = self.next_request_id();
394
395 let result =
396 unsafe { CThostFtdcMdApi_ReqUserLogout(self.api_ptr, ptr::null(), request_id) };
397
398 if result != 0 {
399 return Err(CtpError::FfiError(format!("登出请求失败: {}", result)));
400 }
401
402 Ok(request_id)
403 }
404
405 pub fn subscribe_market_data(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
410 if self.api_ptr.is_null() {
411 return Err(CtpError::InitializationError("API未初始化".to_string()));
412 }
413
414 let c_strings: Result<Vec<CString>, _> = instrument_ids
416 .iter()
417 .map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
418 .collect();
419
420 let c_strings = c_strings?;
421 let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
422
423 let result = unsafe {
424 CThostFtdcMdApi_SubscribeMarketData(self.api_ptr, c_ptrs.as_ptr(), c_ptrs.len() as i32)
425 };
426
427 if result != 0 {
428 return Err(CtpError::FfiError(format!("订阅行情失败: {}", result)));
429 }
430
431 Ok(())
432 }
433
434 pub fn unsubscribe_market_data(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
439 if self.api_ptr.is_null() {
440 return Err(CtpError::InitializationError("API未初始化".to_string()));
441 }
442
443 let c_strings: Result<Vec<CString>, _> = instrument_ids
445 .iter()
446 .map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
447 .collect();
448
449 let c_strings = c_strings?;
450 let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
451
452 let result = unsafe {
453 CThostFtdcMdApi_UnSubscribeMarketData(
454 self.api_ptr,
455 c_ptrs.as_ptr(),
456 c_ptrs.len() as i32,
457 )
458 };
459
460 if result != 0 {
461 return Err(CtpError::FfiError(format!("退订行情失败: {}", result)));
462 }
463
464 Ok(())
465 }
466
467 pub fn subscribe_for_quote_rsp(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
469 if self.api_ptr.is_null() {
470 return Err(CtpError::InitializationError("API未初始化".to_string()));
471 }
472
473 let c_strings: Result<Vec<CString>, _> = instrument_ids
474 .iter()
475 .map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
476 .collect();
477
478 let c_strings = c_strings?;
479 let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
480
481 let result = unsafe {
482 CThostFtdcMdApi_SubscribeForQuoteRsp(self.api_ptr, c_ptrs.as_ptr(), c_ptrs.len() as i32)
483 };
484
485 if result != 0 {
486 return Err(CtpError::FfiError(format!("订阅询价失败: {}", result)));
487 }
488
489 Ok(())
490 }
491
492 pub fn unsubscribe_for_quote_rsp(&mut self, instrument_ids: &[&str]) -> CtpResult<()> {
494 if self.api_ptr.is_null() {
495 return Err(CtpError::InitializationError("API未初始化".to_string()));
496 }
497
498 let c_strings: Result<Vec<CString>, _> = instrument_ids
499 .iter()
500 .map(|&id| GbkConverter::utf8_to_gb18030_cstring(id))
501 .collect();
502
503 let c_strings = c_strings?;
504 let c_ptrs: Vec<*const i8> = c_strings.iter().map(|s| s.as_ptr()).collect();
505
506 let result = unsafe {
507 CThostFtdcMdApi_UnSubscribeForQuoteRsp(
508 self.api_ptr,
509 c_ptrs.as_ptr(),
510 c_ptrs.len() as i32,
511 )
512 };
513
514 if result != 0 {
515 return Err(CtpError::FfiError(format!("退订询价失败: {}", result)));
516 }
517
518 Ok(())
519 }
520
521 fn next_request_id(&self) -> i32 {
523 let mut id = self.request_id.lock().unwrap();
524 let current = *id;
525 *id += 1;
526 current
527 }
528}
529
530impl CtpApi for MdApi {
531 fn get_version() -> CtpResult<String> {
532 let version_ptr = unsafe { CThostFtdcMdApi_GetApiVersion() };
533 safe_cstr_to_string(version_ptr)
534 }
535
536 fn init(&mut self) -> CtpResult<()> {
537 if self.api_ptr.is_null() {
538 return Err(CtpError::InitializationError("API指针为空".to_string()));
539 }
540
541 unsafe {
542 CThostFtdcMdApi_Init(self.api_ptr);
543 }
544
545 self.initialized = true;
546 Ok(())
547 }
548
549 fn release(&mut self) {
550 if !self.api_ptr.is_null() {
551 unsafe {
552 CThostFtdcMdApi_Release(self.api_ptr);
553 }
554 self.api_ptr = ptr::null_mut();
555 }
556 self.initialized = false;
557 }
558
559 fn get_trading_day(&self) -> CtpResult<String> {
560 if self.api_ptr.is_null() {
561 return Err(CtpError::InitializationError("API未初始化".to_string()));
562 }
563
564 let trading_day_ptr = unsafe { CThostFtdcMdApi_GetTradingDay(self.api_ptr) };
565
566 safe_cstr_to_string(trading_day_ptr)
567 }
568
569 fn register_front(&mut self, front_address: &str) -> CtpResult<()> {
570 if self.api_ptr.is_null() {
571 return Err(CtpError::InitializationError("API未初始化".to_string()));
572 }
573
574 let front_address_cstr = to_cstring(front_address)?;
575
576 unsafe {
577 CThostFtdcMdApi_RegisterFront(self.api_ptr, front_address_cstr.as_ptr());
578 }
579
580 Ok(())
581 }
582
583 fn join(&self) -> CtpResult<i32> {
584 if self.api_ptr.is_null() {
585 return Err(CtpError::InitializationError("API未初始化".to_string()));
586 }
587
588 let result = unsafe { CThostFtdcMdApi_Join(self.api_ptr) };
589
590 Ok(result)
591 }
592}
593
594impl Drop for MdApi {
595 fn drop(&mut self) {
596 self.release();
597 }
598}
599
600extern "C" fn on_front_connected_callback(user_data: *mut c_void) {
602 unsafe {
603 if let Some(api) = (user_data as *mut MdApi).as_mut() {
604 if let Some(ref mut handler) = api.handler {
605 handler.on_front_connected();
606 }
607 }
608 }
609}
610
611extern "C" fn on_front_disconnected_callback(user_data: *mut c_void, reason: c_int) {
612 unsafe {
613 if let Some(api) = (user_data as *mut MdApi).as_mut() {
614 if let Some(ref mut handler) = api.handler {
615 handler.on_front_disconnected(reason);
616 }
617 }
618 }
619}
620
621extern "C" fn on_heart_beat_warning_callback(user_data: *mut c_void, time_lapse: c_int) {
622 unsafe {
623 if let Some(api) = (user_data as *mut MdApi).as_mut() {
624 if let Some(ref mut handler) = api.handler {
625 handler.on_heart_beat_warning(time_lapse);
626 }
627 }
628 }
629}
630
631extern "C" fn on_rsp_user_login_callback(
632 user_data: *mut c_void,
633 user_login: *mut c_void,
634 rsp_info: *mut c_void,
635 request_id: c_int,
636 is_last: c_int,
637) {
638 unsafe {
639 if let Some(api) = (user_data as *mut MdApi).as_mut() {
640 if let Some(ref mut handler) = api.handler {
641 let parsed_user_login = if !user_login.is_null() {
643 let login_ptr = user_login as *const RspUserLoginField;
644 Some((*login_ptr).clone())
645 } else {
646 None
647 };
648
649 let parsed_rsp_info = if !rsp_info.is_null() {
651 let rsp_ptr = rsp_info as *const RspInfoField;
652 Some((*rsp_ptr).clone())
653 } else {
654 None
655 };
656
657 handler.on_rsp_user_login(
658 parsed_user_login,
659 parsed_rsp_info,
660 request_id,
661 is_last != 0,
662 );
663 }
664 }
665 }
666}
667
668extern "C" fn on_rsp_user_logout_callback(
669 user_data: *mut c_void,
670 _user_logout: *mut c_void,
671 _rsp_info: *mut c_void,
672 request_id: c_int,
673 is_last: c_int,
674) {
675 unsafe {
676 if let Some(api) = (user_data as *mut MdApi).as_mut() {
677 if let Some(ref mut handler) = api.handler {
678 handler.on_rsp_user_logout(None, None, request_id, is_last != 0);
680 }
681 }
682 }
683}
684
685extern "C" fn on_rsp_error_callback(
686 user_data: *mut c_void,
687 _rsp_info: *mut c_void,
688 request_id: c_int,
689 is_last: c_int,
690) {
691 unsafe {
692 if let Some(api) = (user_data as *mut MdApi).as_mut() {
693 if let Some(ref mut handler) = api.handler {
694 handler.on_rsp_error(None, request_id, is_last != 0);
696 }
697 }
698 }
699}
700
701extern "C" fn on_rsp_sub_market_data_callback(
702 user_data: *mut c_void,
703 specific_instrument: *mut c_void,
704 rsp_info: *mut c_void,
705 request_id: c_int,
706 is_last: c_int,
707) {
708 unsafe {
709 if let Some(api) = (user_data as *mut MdApi).as_mut() {
710 if let Some(ref mut handler) = api.handler {
711 let parsed_specific_instrument = if !specific_instrument.is_null() {
713 let instrument_ptr = specific_instrument as *const SpecificInstrumentField;
714 Some((*instrument_ptr).clone())
715 } else {
716 None
717 };
718
719 let parsed_rsp_info = if !rsp_info.is_null() {
721 let rsp_ptr = rsp_info as *const RspInfoField;
722 Some((*rsp_ptr).clone())
723 } else {
724 None
725 };
726
727 handler.on_rsp_sub_market_data(
728 parsed_specific_instrument,
729 parsed_rsp_info,
730 request_id,
731 is_last != 0,
732 );
733 }
734 }
735 }
736}
737
738extern "C" fn on_rsp_unsub_market_data_callback(
739 user_data: *mut c_void,
740 specific_instrument: *mut c_void,
741 rsp_info: *mut c_void,
742 request_id: c_int,
743 is_last: c_int,
744) {
745 unsafe {
746 if let Some(api) = (user_data as *mut MdApi).as_mut() {
747 if let Some(ref mut handler) = api.handler {
748 let parsed_specific_instrument = if !specific_instrument.is_null() {
750 let instrument_ptr = specific_instrument as *const SpecificInstrumentField;
751 Some((*instrument_ptr).clone())
752 } else {
753 None
754 };
755
756 let parsed_rsp_info = if !rsp_info.is_null() {
758 let rsp_ptr = rsp_info as *const RspInfoField;
759 Some((*rsp_ptr).clone())
760 } else {
761 None
762 };
763
764 handler.on_rsp_unsub_market_data(
765 parsed_specific_instrument,
766 parsed_rsp_info,
767 request_id,
768 is_last != 0,
769 );
770 }
771 }
772 }
773}
774
775extern "C" fn on_rtn_depth_market_data_callback(user_data: *mut c_void, market_data: *mut c_void) {
776 unsafe {
777 if let Some(api) = (user_data as *mut MdApi).as_mut() {
778 if let Some(ref mut handler) = api.handler {
779 if !market_data.is_null() {
781 let data_ptr = market_data as *const DepthMarketDataField;
782 let parsed_data = (*data_ptr).clone();
783 handler.on_rtn_depth_market_data(parsed_data);
784 }
785 }
786 }
787 }
788}
789
790extern "C" fn on_rtn_for_quote_rsp_callback(user_data: *mut c_void, _for_quote_rsp: *mut c_void) {
791 unsafe {
792 if let Some(api) = (user_data as *mut MdApi).as_mut() {
793 if let Some(ref mut handler) = api.handler {
794 let temp_data = ForQuoteRspField::default();
797 handler.on_rtn_for_quote_rsp(temp_data);
798 }
799 }
800 }
801}
802
803#[cfg(test)]
804mod tests {
805 use super::*;
806
807 #[test]
808 fn test_version() {
809 match MdApi::get_version() {
811 Ok(version) => eprintln!("版本: {}", version),
812 Err(e) => eprintln!("获取版本失败: {}", e),
813 }
814 }
815}