├── .github └── workflows │ └── rust.yml ├── .gitignore ├── .gitmodules ├── .travis.yml ├── CHANGELOG.md ├── Cargo.toml ├── README.md ├── ctp-common ├── Cargo.toml ├── generate.rs └── src │ ├── binding.rs │ ├── generated │ ├── error.rs.in │ └── struct.rs.in │ └── lib.rs ├── ctp-md ├── Cargo.toml ├── build.rs ├── examples │ └── api-md.rs ├── src │ ├── channel.rs │ └── lib.rs └── test_all.sh └── ctp-trader ├── Cargo.toml ├── build.rs ├── examples ├── api-trader.rs └── password_update.rs ├── src ├── channel.rs └── lib.rs └── test_all.sh /.github/workflows/rust.yml: -------------------------------------------------------------------------------- 1 | name: Rust 2 | 3 | on: [push] 4 | 5 | jobs: 6 | ubuntu1804: 7 | runs-on: ubuntu-18.04 8 | steps: 9 | - uses: actions/checkout@v2 10 | - name: Checkout submodules 11 | shell: bash 12 | run: | 13 | git submodule update --init 14 | - uses: actions-rs/toolchain@v1 15 | with: 16 | toolchain: 1.32.0 17 | - uses: actions-rs/cargo@v1 18 | with: 19 | command: test 20 | args: --manifest-path ctp-common/Cargo.toml 21 | - uses: actions-rs/cargo@v1 22 | with: 23 | command: test 24 | args: --manifest-path ctp-common/Cargo.toml 25 | - uses: actions-rs/cargo@v1 26 | with: 27 | command: test 28 | args: --manifest-path ctp-md/Cargo.toml 29 | - uses: actions-rs/cargo@v1 30 | with: 31 | command: test 32 | args: --manifest-path ctp-md/Cargo.toml --features channel 33 | - uses: actions-rs/cargo@v1 34 | with: 35 | command: test 36 | args: --manifest-path ctp-trader/Cargo.toml 37 | - uses: actions-rs/cargo@v1 38 | with: 39 | command: test 40 | args: --manifest-path ctp-trader/Cargo.toml --features channel 41 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | Cargo.lock 2 | target 3 | *.con 4 | config*.yml 5 | -------------------------------------------------------------------------------- /.gitmodules: -------------------------------------------------------------------------------- 1 | [submodule "api-ctp"] 2 | path = api-ctp 3 | url = https://gitlab.com/WiSaGaN/api-ctp.git 4 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | os: linux 2 | dist: trusty 3 | sudo: false 4 | language: rust 5 | # necessary for `travis-cargo coveralls --no-sudo` 6 | addons: 7 | apt: 8 | sources: 9 | - ubuntu-toolchain-r-test 10 | - llvm-toolchain-precise 11 | - llvm-toolchain-precise-3.6 12 | packages: 13 | - libclang-3.6-dev 14 | - llvm-3.6-dev 15 | 16 | # run builds for all the trains (and more) 17 | rust: 18 | - nightly 19 | - beta 20 | - stable 21 | - 1.32.0 22 | 23 | # the main build 24 | script: 25 | - cargo build --verbose --manifest-path ctp-common/Cargo.toml 26 | - cargo test --verbose --manifest-path ctp-common/Cargo.toml 27 | - cargo build --verbose --manifest-path ctp-md/Cargo.toml 28 | - cargo build --verbose --manifest-path ctp-md/Cargo.toml --features channel 29 | - cargo build --verbose --manifest-path ctp-trader/Cargo.toml 30 | - cargo build --verbose --manifest-path ctp-trader/Cargo.toml --features channel 31 | -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Changelog 2 | 3 | This project roughly adheres to [Semantic Versioning](http://semver.org/). For 0.x.y releases, `x` is the major version in semver, while `y` is the minor version. 4 | 5 | ## ctp-common 0.9.0, ctp-md 0.10.0, ctp-trader 0.10.0 - 2020-05-13 6 | 7 | * Fix memory leaks 8 | * Fix unsafe function signature 9 | * Fix clippy warnings 10 | 11 | ## ctp-md 0.9.0, ctp-trader 0.9.0 - 2020-01-23 12 | 13 | * Upgrade `crossbeam-channel` to 0.4.0 14 | 15 | ## ctp-common 0.8.2 - 2020-01-20 16 | 17 | * Fix clippy warnings 18 | 19 | ## ctp-common 0.8.1, ctp-md 0.8.1, ctp-trader 0.8.2 - 2020-01-12 20 | 21 | * Upgrade to 2018 edition 22 | 23 | ## ctp-trader 0.8.1 - 2019-06-13 24 | 25 | * Fix `crossbeam-channel` version 26 | 27 | ## ctp-common 0.8.0, ctp-md 0.8.0, ctp-trader 0.8.0 - 2019-06-13 28 | 29 | * Support transparent regulatory API for prod environment (api-ctp 6.3.15) 30 | 31 | ## ctp-common 0.7.0, ctp-md 0.8.0, ctp-trader 0.8.0 - 2019-06-13 32 | 33 | * Support transparent regulatory API for test environment (api-ctp 6.3.13) 34 | * Upgrade `crossbeam-channel` to 0.3.8 35 | * Upgrade `simple-error` to 0.2.0 36 | 37 | ## ctp-common 0.6.0, ctp-md 0.8.0, ctp-trader 0.8.0 - 2018-09-03 38 | 39 | * Support `crossbeam-channel 0.2.4` through feature `channel` 40 | -------------------------------------------------------------------------------- /Cargo.toml: -------------------------------------------------------------------------------- 1 | [workspace] 2 | members = ["ctp-common", "ctp-md", "ctp-trader"] 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # ctp-rs 2 | 3 | [![Build Status](https://travis-ci.org/WiSaGaN/ctp-rs.svg?branch=master)](https://travis-ci.org/WiSaGaN/ctp-rs) 4 | 5 | A Rust wrapper of CTP API. 6 | 7 | ## Background 8 | 9 | CTP is a popular trading system in Chinese futures market. Finding a CTP trading system to connect to is easy in Chinese futures market. As a result, large portions of individual investors implement their trading strategies against CTP. 10 | 11 | CTP API is an offical C++ API that manages connections to CTP trading system. Several language bindings for CTP API ranging from C# to Python have been created. Rust has native FFI support for C, but does not support C++ directly. This wrapper aims to provide an easy to use Rust binding of CTP API. 12 | 13 | In contrast to conventional Rust binding for C++ that uses intermediate C wrapper, this Rust wrapper includes a handcrafted C++ calling interface in Rust for virtual function calls and virtual tables in callbacks. This makes the Rust interface cleaner and faster than alternatives. 14 | 15 | This git repository contains 3 Rust crates: `ctp-common`, `ctp-md`, `ctp-trader`. `ctp-md` and `ctp-trader` both depend on `ctp-common`, but can be used separately. 16 | 17 | ### ctp-common 18 | 19 | Common datatypes including constants, structs in original C++ API, as well as some conversions to idiomatic Rust data types. 20 | 21 | ### ctp-md 22 | 23 | Wrapper for market data API. A run-time dependency of `thostmduserapi.so` is needed for Rust application that uses this crate. 24 | 25 | ### ctp-trader 26 | 27 | Wrapper for trader API. A run-time dependency of `thosttraderapi.so` is needed for Rust application that uses this crate. 28 | 29 | ## OS support 30 | 31 | Currently these 3 crates only support Linux x86_64 32 | 33 | ## Minimal Rust version 34 | 35 | 1.26.0 36 | -------------------------------------------------------------------------------- /ctp-common/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctp-common" 3 | version = "0.9.0" 4 | authors = ["Wangshan Lu "] 5 | edition = "2018" 6 | 7 | [dependencies] 8 | encoding = "0.2.33" 9 | memchr = "2.3.3" 10 | simple-error = "0.2.1" 11 | time = "0.1.43" 12 | -------------------------------------------------------------------------------- /ctp-common/generate.rs: -------------------------------------------------------------------------------- 1 | //! Rust script used for generate sources in `src/generated` 2 | //! Use `cargo script` to run it 3 | //! 4 | //! ```cargo 5 | //! [package] 6 | //! edition = "2018" 7 | //! 8 | //! [dependencies] 9 | //! bindgen = "0.30" 10 | //! encoding = "0.2" 11 | //! xmltree = "0.4" 12 | //! ``` 13 | use encoding::{ decode, DecoderTrap }; 14 | use encoding::all::GB18030; 15 | use std::io::{ Read, Write }; 16 | use std::path::Path; 17 | use xmltree as xml; 18 | 19 | pub fn gb18030_bytes_to_string(bytes: &[u8]) -> String { 20 | decode(bytes, DecoderTrap::Replace, GB18030).0.unwrap_or_else(|e| e.into_owned()) 21 | } 22 | 23 | fn generate_struct(input_h: &str, output_rs: &str) -> Result<(), String> { 24 | let binding = bindgen::builder() 25 | .header(input_h) 26 | .derive_debug(false) 27 | .derive_default(true) 28 | .generate() 29 | .map_err(|_| format!("failed to generate binding" ))?; 30 | let binding_output = binding.to_string().replace("c_char", "c_uchar"); 31 | let mut output_file = std::fs::File::create(output_rs).map_err(|e| format!("cannot create struct file, {}", e))?; 32 | output_file.write_all(binding_output.as_bytes()).map_err(|e| format!("cannot write struct file, {}", e)) 33 | } 34 | 35 | #[derive(Debug)] 36 | struct ErrorEntry { 37 | id: String, 38 | value: i64, 39 | prompt: String, 40 | } 41 | 42 | #[derive(Debug)] 43 | struct Errors { 44 | errors: Vec, 45 | } 46 | 47 | impl Errors { 48 | pub fn from_xml_element(element: xml::Element) -> Result { 49 | let mut errors = Vec::new(); 50 | for child in element.children { 51 | let id = child.attributes.get("id").ok_or(String::from("no id attribute in one of the child"))?.to_owned(); 52 | let value_string = child.attributes.get("value").ok_or(String::from("no value attribute in one of the child"))?; 53 | let value = value_string.parse().map_err(|e| format!("cannot parse value to integer, {}", e))?; 54 | let prompt = child.attributes.get("prompt").ok_or(String::from("no prompt attribute in one of the child"))?.to_owned(); 55 | errors.push(ErrorEntry { 56 | id: id, 57 | value: value, 58 | prompt: prompt, 59 | }); 60 | } 61 | Ok(Errors { 62 | errors: errors, 63 | }) 64 | } 65 | } 66 | 67 | fn generate_error(input_xml: &Path, output_rs: &Path) -> Result<(), String> { 68 | let mut file_bytes = vec!(); 69 | let mut input_file = std::fs::File::open(input_xml).map_err(|e| format!("failed to open data_type header, {}", e))?; 70 | input_file.read_to_end(&mut file_bytes).map_err(|e| format!("filed to read data_type header, {}", e))?; 71 | let file_string = gb18030_bytes_to_string(&file_bytes); 72 | let element = xml::Element::parse(file_string.as_bytes()).map_err(|e| format!("failed to parse input file as xml, {}", e))?; 73 | let errors = Errors::from_xml_element(element).map_err(|e| format!("cannot generate errors from parsed xml element, {}", e))?; 74 | let mut error_output = std::io::BufWriter::new(std::fs::File::create(output_rs).map_err(|e| format!("cannot create error file, {}", e))?); 75 | 76 | for error in errors.errors.iter() { 77 | error_output.write(format!("pub const ERROR_{}: TThostFtdcErrorIDType = {};\n", error.id, error.value).as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 78 | } 79 | error_output.write(format!("pub fn error_id_to_chinese_description(error_id: TThostFtdcErrorIDType) -> &'static str {{\n").as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 80 | error_output.write(format!(" match error_id {{\n").as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 81 | for error in errors.errors.iter() { 82 | error_output.write(format!(" ERROR_{} => \"{}\",\n", error.id, error.prompt).as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 83 | } 84 | error_output.write(format!(" _ => \"unknown error\",\n").as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 85 | error_output.write(format!(" }}\n}}\n").as_bytes()).map_err(|e| format!("cannot write error file, {}", e))?; 86 | Ok(()) 87 | } 88 | 89 | fn main() { 90 | let out_dir = "./src/generated"; 91 | let struct_header = "../api-ctp/include/ThostFtdcUserApiStruct.h"; 92 | let struct_out_path = format!("{}/struct.rs.in", out_dir); 93 | generate_struct(struct_header, &struct_out_path).unwrap(); 94 | let error_xml = "../api-ctp/misc/error.xml"; 95 | let error_out_path = format!("{}/error.rs.in", out_dir); 96 | generate_error(Path::new(error_xml), Path::new(&error_out_path)).unwrap(); 97 | } 98 | -------------------------------------------------------------------------------- /ctp-common/src/binding.rs: -------------------------------------------------------------------------------- 1 | #![allow(dead_code)] 2 | #![allow(non_camel_case_types)] 3 | #![allow(non_snake_case)] 4 | #![allow(non_upper_case_globals)] 5 | #![allow(clippy::unreadable_literal)] 6 | include!("generated/struct.rs.in"); 7 | include!("generated/error.rs.in"); 8 | 9 | pub const THOST_FTDC_BOOL_True: TThostFtdcBoolType = 1; 10 | pub const THOST_FTDC_BOOL_False: TThostFtdcBoolType = 0; 11 | 12 | pub const THOST_FTDC_COMB_FLAG_LENGTH: usize = 5; 13 | 14 | use std::fmt; 15 | use super::{ gb18030_cstr_to_str, normalize_double, reduce_comb_flags, maybe_char }; 16 | 17 | impl fmt::Debug for CThostFtdcRspAuthenticateField { 18 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 19 | fmt.debug_struct("CThostFtdcRspAuthenticateField") 20 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 21 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 22 | .field("UserProductInfo", &gb18030_cstr_to_str(&self.UserProductInfo)) 23 | .field("AppID", &gb18030_cstr_to_str(&self.AppID)) 24 | .field("AppType", &maybe_char(self.AppType)) 25 | .finish() 26 | } 27 | } 28 | 29 | impl fmt::Debug for CThostFtdcReqUserLoginField { 30 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 31 | fmt.debug_struct("CThostFtdcReqUserLoginField") 32 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 33 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 34 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 35 | .field("Password", &gb18030_cstr_to_str(&self.Password)) 36 | .field("UserProductInfo", &gb18030_cstr_to_str(&self.UserProductInfo)) 37 | .field("InterfaceProductInfo", &gb18030_cstr_to_str(&self.InterfaceProductInfo)) 38 | .field("ProtocolInfo", &gb18030_cstr_to_str(&self.ProtocolInfo)) 39 | .field("MacAddress", &gb18030_cstr_to_str(&self.MacAddress)) 40 | .field("OneTimePassword", &gb18030_cstr_to_str(&self.OneTimePassword)) 41 | .field("ClientIPAddress", &gb18030_cstr_to_str(&self.ClientIPAddress)) 42 | .finish() 43 | } 44 | } 45 | 46 | impl fmt::Debug for CThostFtdcRspUserLoginField { 47 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 48 | fmt.debug_struct("CThostFtdcRspUserLoginField") 49 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 50 | .field("LoginTime", &gb18030_cstr_to_str(&self.LoginTime)) 51 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 52 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 53 | .field("SystemName", &gb18030_cstr_to_str(&self.SystemName)) 54 | .field("FrontID", &self.FrontID) 55 | .field("SessionID", &self.SessionID) 56 | .field("MaxOrderRef", &gb18030_cstr_to_str(&self.MaxOrderRef)) 57 | .field("SHFETime", &gb18030_cstr_to_str(&self.SHFETime)) 58 | .field("DCETime", &gb18030_cstr_to_str(&self.DCETime)) 59 | .field("CZCETime", &gb18030_cstr_to_str(&self.CZCETime)) 60 | .field("FFEXTime", &gb18030_cstr_to_str(&self.FFEXTime)) 61 | .field("INETime", &gb18030_cstr_to_str(&self.INETime)) 62 | .finish() 63 | } 64 | } 65 | 66 | impl fmt::Debug for CThostFtdcUserLogoutField { 67 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 68 | fmt.debug_struct("CThostFtdcUserLogoutField") 69 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 70 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 71 | .finish() 72 | } 73 | } 74 | 75 | impl fmt::Debug for CThostFtdcUserPasswordUpdateField { 76 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 77 | fmt.debug_struct("CThostFtdcUserPasswordUpdateField") 78 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 79 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 80 | .field("OldPassword", &gb18030_cstr_to_str(&self.OldPassword)) 81 | .field("NewPassword", &gb18030_cstr_to_str(&self.NewPassword)) 82 | .finish() 83 | } 84 | } 85 | 86 | impl fmt::Debug for CThostFtdcSpecificInstrumentField { 87 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 88 | fmt.debug_struct("CThostFtdcSpecificInstrumentField") 89 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 90 | .finish() 91 | } 92 | } 93 | 94 | impl fmt::Debug for CThostFtdcInstrumentField { 95 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 96 | fmt.debug_struct("CThostFtdcInstrumentField") 97 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 98 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 99 | .field("InstrumentName", &gb18030_cstr_to_str(&self.InstrumentName)) 100 | .field("ExchangeInstID", &gb18030_cstr_to_str(&self.ExchangeInstID)) 101 | .field("ProductID", &gb18030_cstr_to_str(&self.ProductID)) 102 | .field("ProductClass", &char::from(self.ProductClass)) 103 | .field("DeliveryYear", &self.DeliveryYear) 104 | .field("DeliveryMonth", &self.DeliveryMonth) 105 | .field("MaxMarketOrderVolume", &self.MaxMarketOrderVolume) 106 | .field("MinMarketOrderVolume", &self.MinMarketOrderVolume) 107 | .field("MaxLimitOrderVolume", &self.MaxLimitOrderVolume) 108 | .field("MinLimitOrderVolume", &self.MinLimitOrderVolume) 109 | .field("VolumeMultiple", &self.VolumeMultiple) 110 | .field("PriceTick", &self.PriceTick) 111 | .field("CreateDate", &gb18030_cstr_to_str(&self.CreateDate)) 112 | .field("OpenDate", &gb18030_cstr_to_str(&self.OpenDate)) 113 | .field("ExpireDate", &gb18030_cstr_to_str(&self.ExpireDate)) 114 | .field("StartDelivDate", &gb18030_cstr_to_str(&self.StartDelivDate)) 115 | .field("EndDelivDate", &gb18030_cstr_to_str(&self.EndDelivDate)) 116 | .field("InstLifePhase", &char::from(self.InstLifePhase)) 117 | .field("IsTrading", &self.IsTrading) 118 | .field("PositionType", &char::from(self.PositionType)) 119 | .field("PositionDateType", &char::from(self.PositionDateType)) 120 | .field("LongMarginRatio", &normalize_double(self.LongMarginRatio)) 121 | .field("ShortMarginRatio", &normalize_double(self.ShortMarginRatio)) 122 | .field("MaxMarginSideAlgorithm", &char::from(self.MaxMarginSideAlgorithm)) 123 | .field("UnderlyingInstrID", &gb18030_cstr_to_str(&self.UnderlyingInstrID)) 124 | .field("StrikePrice", &normalize_double(self.StrikePrice)) 125 | .field("OptionsType", &maybe_char(self.OptionsType)) 126 | .field("UnderlyingMultiple", &normalize_double(self.UnderlyingMultiple)) 127 | .field("CombinationType", &maybe_char(self.CombinationType)) 128 | .finish() 129 | } 130 | } 131 | 132 | impl fmt::Debug for CThostFtdcInstrumentStatusField { 133 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 134 | fmt.debug_struct("CThostFtdcInstrumentStatusField ") 135 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 136 | .field("ExchangeInstID", &gb18030_cstr_to_str(&self.ExchangeInstID)) 137 | .field("SettlementGroupID", &gb18030_cstr_to_str(&self.SettlementGroupID)) 138 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 139 | .field("InstrumentStatus", &char::from(self.InstrumentStatus)) 140 | .field("TradingSegmentSN", &self.TradingSegmentSN) 141 | .field("EnterTime", &gb18030_cstr_to_str(&self.EnterTime)) 142 | .field("EnterReason", &char::from(self.EnterReason)) 143 | .finish() 144 | } 145 | } 146 | 147 | impl fmt::Debug for CThostFtdcOrderField { 148 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 149 | fmt.debug_struct("CThostFtdcOrderField") 150 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 151 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 152 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 153 | .field("OrderRef", &gb18030_cstr_to_str(&self.OrderRef)) 154 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 155 | .field("OrderPriceType", &char::from(self.OrderPriceType)) 156 | .field("Direction", &char::from(self.Direction)) 157 | .field("CombOffsetFlag", &reduce_comb_flags(&self.CombOffsetFlag)) 158 | .field("CombHedgeFlag", &reduce_comb_flags(&self.CombHedgeFlag)) 159 | .field("LimitPrice", &self.LimitPrice) 160 | .field("VolumeTotalOriginal", &self.VolumeTotalOriginal) 161 | .field("TimeCondition", &char::from(self.TimeCondition)) 162 | .field("GTDDate", &gb18030_cstr_to_str(&self.GTDDate)) 163 | .field("VolumeCondition", &char::from(self.VolumeCondition)) 164 | .field("MinVolume", &self.MinVolume) 165 | .field("ContingentCondition", &char::from(self.ContingentCondition)) 166 | .field("StopPrice", &self.StopPrice) 167 | .field("ForceCloseReason", &char::from(self.ForceCloseReason)) 168 | .field("IsAutoSuspend", &self.IsAutoSuspend) 169 | .field("BusinessUnit", &gb18030_cstr_to_str(&self.BusinessUnit)) 170 | .field("RequestID", &self.RequestID) 171 | .field("OrderLocalID", &gb18030_cstr_to_str(&self.OrderLocalID)) 172 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 173 | .field("ParticipantID", &gb18030_cstr_to_str(&self.ParticipantID)) 174 | .field("ClientID", &gb18030_cstr_to_str(&self.ClientID)) 175 | .field("ExchangeInstID", &gb18030_cstr_to_str(&self.ExchangeInstID)) 176 | .field("TraderID", &gb18030_cstr_to_str(&self.TraderID)) 177 | .field("InstallID", &self.InstallID) 178 | .field("OrderSubmitStatus", &char::from(self.OrderSubmitStatus)) 179 | .field("NotifySequence", &self.NotifySequence) 180 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 181 | .field("SettlementID", &self.SettlementID) 182 | .field("OrderSysID", &gb18030_cstr_to_str(&self.OrderSysID)) 183 | .field("OrderSource", &maybe_char(self.OrderSource)) 184 | .field("OrderStatus", &char::from(self.OrderStatus)) 185 | .field("OrderType", &maybe_char(self.OrderType)) 186 | .field("VolumeTraded", &self.VolumeTraded) 187 | .field("VolumeTotal", &self.VolumeTotal) 188 | .field("InsertDate", &gb18030_cstr_to_str(&self.InsertDate)) 189 | .field("InsertTime", &gb18030_cstr_to_str(&self.InsertTime)) 190 | .field("ActiveTime", &gb18030_cstr_to_str(&self.ActiveTime)) 191 | .field("SuspendTime", &gb18030_cstr_to_str(&self.SuspendTime)) 192 | .field("UpdateTime", &gb18030_cstr_to_str(&self.UpdateTime)) 193 | .field("CancelTime", &gb18030_cstr_to_str(&self.CancelTime)) 194 | .field("ActiveTraderID", &gb18030_cstr_to_str(&self.ActiveTraderID)) 195 | .field("ClearingPartID", &gb18030_cstr_to_str(&self.ClearingPartID)) 196 | .field("SequenceNo", &self.SequenceNo) 197 | .field("FrontID", &self.FrontID) 198 | .field("SessionID", &self.SessionID) 199 | .field("UserProductInfo", &gb18030_cstr_to_str(&self.UserProductInfo)) 200 | .field("StatusMsg", &gb18030_cstr_to_str(&self.StatusMsg)) 201 | .field("UserForceClose", &self.UserForceClose) 202 | .field("ActiveUserID", &gb18030_cstr_to_str(&self.ActiveUserID)) 203 | .field("BrokerOrderSeq", &self.BrokerOrderSeq) 204 | .field("RelativeOrderSysID", &gb18030_cstr_to_str(&self.RelativeOrderSysID)) 205 | .field("ZCETotalTradedVolume", &self.ZCETotalTradedVolume) 206 | .field("IsSwapOrder", &self.IsSwapOrder) 207 | .finish() 208 | } 209 | } 210 | 211 | impl fmt::Debug for CThostFtdcTradeField { 212 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 213 | fmt.debug_struct("CThostFtdcTradeField") 214 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 215 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 216 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 217 | .field("OrderRef", &gb18030_cstr_to_str(&self.OrderRef)) 218 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 219 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 220 | .field("TradeID", &gb18030_cstr_to_str(&self.TradeID)) 221 | .field("Direction", &char::from(self.Direction)) 222 | .field("OrderSysID", &gb18030_cstr_to_str(&self.OrderSysID)) 223 | .field("ParticipantID", &gb18030_cstr_to_str(&self.ParticipantID)) 224 | .field("ClientID", &gb18030_cstr_to_str(&self.ClientID)) 225 | .field("TradingRole", &self.TradingRole) 226 | .field("ExchangeInstID", &gb18030_cstr_to_str(&self.ExchangeInstID)) 227 | .field("OffsetFlag", &char::from(self.OffsetFlag)) 228 | .field("HedgeFlag", &char::from(self.HedgeFlag)) 229 | .field("Price", &self.Price) 230 | .field("Volume", &self.Volume) 231 | .field("TradeDate", &gb18030_cstr_to_str(&self.TradeDate)) 232 | .field("TradeTime", &gb18030_cstr_to_str(&self.TradeTime)) 233 | .field("TradeType", &maybe_char(self.TradeType)) 234 | .field("PriceSource", &maybe_char(self.PriceSource)) 235 | .field("TraderID", &gb18030_cstr_to_str(&self.TraderID)) 236 | .field("OrderLocalID", &gb18030_cstr_to_str(&self.OrderLocalID)) 237 | .field("ClearingPartID", &gb18030_cstr_to_str(&self.ClearingPartID)) 238 | .field("BusinessUnit", &gb18030_cstr_to_str(&self.BusinessUnit)) 239 | .field("SequenceNo", &self.SequenceNo) 240 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 241 | .field("SettlementID", &self.SettlementID) 242 | .field("BrokerOrderSeq", &self.BrokerOrderSeq) 243 | .field("TradeSource", &char::from(self.TradeSource)) 244 | .finish() 245 | } 246 | } 247 | 248 | impl fmt::Debug for CThostFtdcSettlementInfoField { 249 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 250 | fmt.debug_struct("CThostFtdcSettlementInfoField") 251 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 252 | .field("SettlementID", &self.SettlementID) 253 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 254 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 255 | .field("SequenceNo", &self.SequenceNo) 256 | .field("Content", &gb18030_cstr_to_str(&self.Content)) 257 | .finish() 258 | } 259 | } 260 | 261 | impl fmt::Debug for CThostFtdcExchangeMarginRateField { 262 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 263 | fmt.debug_struct("CThostFtdcExchangeMarginRateField") 264 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 265 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 266 | .field("HedgeFlag", &char::from(self.HedgeFlag)) 267 | .field("LongMarginRatioByMoney", &normalize_double(self.LongMarginRatioByMoney)) 268 | .field("LongMarginRatioByVolume", &normalize_double(self.LongMarginRatioByVolume)) 269 | .field("ShortMarginRatioByMoney", &normalize_double(self.ShortMarginRatioByMoney)) 270 | .field("ShortMarginRatioByVolume", &normalize_double(self.ShortMarginRatioByVolume)) 271 | .finish() 272 | } 273 | } 274 | 275 | impl fmt::Debug for CThostFtdcExchangeMarginRateAdjustField { 276 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 277 | fmt.debug_struct("CThostFtdcExchangeMarginRateAdjustField") 278 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 279 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 280 | .field("HedgeFlag", &char::from(self.HedgeFlag)) 281 | .field("LongMarginRatioByMoney", &normalize_double(self.LongMarginRatioByMoney)) 282 | .field("LongMarginRatioByVolume", &normalize_double(self.LongMarginRatioByVolume)) 283 | .field("ShortMarginRatioByMoney", &normalize_double(self.ShortMarginRatioByMoney)) 284 | .field("ShortMarginRatioByVolume", &normalize_double(self.ShortMarginRatioByVolume)) 285 | .field("ExchLongMarginRatioByMoney", &normalize_double(self.ExchLongMarginRatioByMoney)) 286 | .field("ExchLongMarginRatioByVolume", &normalize_double(self.ExchLongMarginRatioByVolume)) 287 | .field("ExchShortMarginRatioByMoney", &normalize_double(self.ExchShortMarginRatioByMoney)) 288 | .field("ExchShortMarginRatioByVolume", &normalize_double(self.ExchShortMarginRatioByVolume)) 289 | .field("NoLongMarginRatioByMoney", &normalize_double(self.NoLongMarginRatioByMoney)) 290 | .field("NoLongMarginRatioByVolume", &normalize_double(self.NoLongMarginRatioByVolume)) 291 | .field("NoShortMarginRatioByMoney", &normalize_double(self.NoShortMarginRatioByMoney)) 292 | .field("NoShortMarginRatioByVolume", &normalize_double(self.NoShortMarginRatioByVolume)) 293 | .finish() 294 | } 295 | } 296 | 297 | impl fmt::Debug for CThostFtdcExchangeRateField { 298 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 299 | fmt.debug_struct("CThostFtdcExchangeRateField") 300 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 301 | .field("FromCurrencyID", &gb18030_cstr_to_str(&self.FromCurrencyID)) 302 | .field("FromCurrencyUnit", &self.FromCurrencyUnit) 303 | .field("ToCurrencyID", &gb18030_cstr_to_str(&self.ToCurrencyID)) 304 | .field("ExchangeRate", &self.ExchangeRate) 305 | .finish() 306 | } 307 | } 308 | 309 | impl fmt::Debug for CThostFtdcSettlementInfoConfirmField { 310 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 311 | fmt.debug_struct("CThostFtdcSettlementInfoConfirmField") 312 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 313 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 314 | .field("ConfirmDate", &gb18030_cstr_to_str(&self.ConfirmDate)) 315 | .field("ConfirmTime", &gb18030_cstr_to_str(&self.ConfirmTime)) 316 | .finish() 317 | } 318 | } 319 | 320 | impl fmt::Debug for CThostFtdcInputOrderField { 321 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 322 | fmt.debug_struct("CThostFtdcInputOrderField") 323 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 324 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 325 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 326 | .field("OrderRef", &gb18030_cstr_to_str(&self.OrderRef)) 327 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 328 | .field("OrderPriceType", &char::from(self.OrderPriceType)) 329 | .field("Direction", &char::from(self.Direction)) 330 | .field("CombOffsetFlag", &reduce_comb_flags(&self.CombOffsetFlag)) 331 | .field("CombHedgeFlag", &reduce_comb_flags(&self.CombHedgeFlag)) 332 | .field("LimitPrice", &self.LimitPrice) 333 | .field("VolumeTotalOriginal", &self.VolumeTotalOriginal) 334 | .field("TimeCondition", &char::from(self.TimeCondition)) 335 | .field("GTDDate", &gb18030_cstr_to_str(&self.GTDDate)) 336 | .field("VolumeCondition", &char::from(self.VolumeCondition)) 337 | .field("MinVolume", &self.MinVolume) 338 | .field("ContingentCondition", &char::from(self.ContingentCondition)) 339 | .field("StopPrice", &self.StopPrice) 340 | .field("ForceCloseReason", &char::from(self.ForceCloseReason)) 341 | .field("IsAutoSuspend", &self.IsAutoSuspend) 342 | .field("BusinessUnit", &gb18030_cstr_to_str(&self.BusinessUnit)) 343 | .field("RequestID", &self.RequestID) 344 | .field("UserForceClose", &self.UserForceClose) 345 | .field("IsSwapOrder", &self.IsSwapOrder) 346 | .finish() 347 | } 348 | } 349 | 350 | impl fmt::Debug for CThostFtdcOrderActionField { 351 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 352 | fmt.debug_struct("CThostFtdcOrderActionField") 353 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 354 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 355 | .field("OrderActionRef", &self.OrderActionRef) 356 | .field("OrderRef", &gb18030_cstr_to_str(&self.OrderRef)) 357 | .field("RequestID", &self.RequestID) 358 | .field("FrontID", &self.FrontID) 359 | .field("SessionID", &self.SessionID) 360 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 361 | .field("OrderSysID", &gb18030_cstr_to_str(&self.OrderSysID)) 362 | .field("ActionFlag", &char::from(self.ActionFlag)) 363 | .field("LimitPrice", &self.LimitPrice) 364 | .field("VolumeChange", &self.VolumeChange) 365 | .field("ActionDate", &gb18030_cstr_to_str(&self.ActionDate)) 366 | .field("ActionTime", &gb18030_cstr_to_str(&self.ActionTime)) 367 | .field("TraderID", &gb18030_cstr_to_str(&self.TraderID)) 368 | .field("InstallID", &self.InstallID) 369 | .field("OrderLocalID", &gb18030_cstr_to_str(&self.OrderLocalID)) 370 | .field("ActionLocalID", &gb18030_cstr_to_str(&self.ActionLocalID)) 371 | .field("ParticipantID", &gb18030_cstr_to_str(&self.ParticipantID)) 372 | .field("ClientID", &gb18030_cstr_to_str(&self.ClientID)) 373 | .field("BusinessUnit", &gb18030_cstr_to_str(&self.BusinessUnit)) 374 | .field("OrderActionStatus", &self.OrderActionStatus) 375 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 376 | .field("StatusMsg", &gb18030_cstr_to_str(&self.StatusMsg)) 377 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 378 | .finish() 379 | } 380 | } 381 | 382 | impl fmt::Debug for CThostFtdcInputOrderActionField { 383 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 384 | fmt.debug_struct("CThostFtdcInputOrderActionField") 385 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 386 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 387 | .field("OrderActionRef", &self.OrderActionRef) 388 | .field("OrderRef", &gb18030_cstr_to_str(&self.OrderRef)) 389 | .field("RequestID", &self.RequestID) 390 | .field("FrontID", &self.FrontID) 391 | .field("SessionID", &self.SessionID) 392 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 393 | .field("OrderSysID", &gb18030_cstr_to_str(&self.OrderSysID)) 394 | .field("ActionFlag", &char::from(self.ActionFlag)) 395 | .field("LimitPrice", &self.LimitPrice) 396 | .field("VolumeChange", &self.VolumeChange) 397 | .field("UserID", &gb18030_cstr_to_str(&self.UserID)) 398 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 399 | .finish() 400 | } 401 | } 402 | 403 | impl fmt::Debug for CThostFtdcTradingNoticeInfoField { 404 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 405 | fmt.debug_struct("CThostFtdcTradingNoticeInfoField") 406 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 407 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 408 | .field("SendTime", &gb18030_cstr_to_str(&self.SendTime)) 409 | .field("FieldContent", &gb18030_cstr_to_str(&self.FieldContent)) 410 | .field("SequenceSeries", &self.SequenceSeries) 411 | .field("SequenceNo", &self.SequenceNo) 412 | .finish() 413 | } 414 | } 415 | 416 | impl fmt::Debug for CThostFtdcInvestorField { 417 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 418 | fmt.debug_struct("CThostFtdcInvestorField") 419 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 420 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 421 | .field("InvestorGroupID", &gb18030_cstr_to_str(&self.InvestorGroupID)) 422 | .field("InvestorName", &gb18030_cstr_to_str(&self.InvestorName)) 423 | .field("IdentifiedCardType", &char::from(self.IdentifiedCardType)) 424 | .field("IdentifiedCardNo", &gb18030_cstr_to_str(&self.IdentifiedCardNo)) 425 | .field("IsActive", &self.IsActive) 426 | .field("Telephone", &gb18030_cstr_to_str(&self.Telephone)) 427 | .field("Address", &gb18030_cstr_to_str(&self.Address)) 428 | .field("OpenDate", &gb18030_cstr_to_str(&self.OpenDate)) 429 | .field("Mobile", &gb18030_cstr_to_str(&self.Mobile)) 430 | .field("CommModelID", &gb18030_cstr_to_str(&self.CommModelID)) 431 | .field("MarginModelID", &gb18030_cstr_to_str(&self.MarginModelID)) 432 | .finish() 433 | } 434 | } 435 | 436 | impl fmt::Debug for CThostFtdcTradingCodeField { 437 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 438 | fmt.debug_struct("CThostFtdcTradingCodeField") 439 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 440 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 441 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 442 | .field("ClientID", &gb18030_cstr_to_str(&self.ClientID)) 443 | .field("IsActive", &self.IsActive) 444 | .field("ClientIDType", &char::from(self.ClientIDType)) 445 | .finish() 446 | } 447 | } 448 | 449 | 450 | impl fmt::Debug for CThostFtdcTradingAccountField { 451 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 452 | fmt.debug_struct("CThostFtdcTradingAccountField") 453 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 454 | .field("AccountID", &gb18030_cstr_to_str(&self.AccountID)) 455 | .field("PreMortgage", &self.PreMortgage) 456 | .field("PreCredit", &self.PreCredit) 457 | .field("PreDeposit", &self.PreDeposit) 458 | .field("PreBalance", &self.PreBalance) 459 | .field("PreMargin", &self.PreMargin) 460 | .field("InterestBase", &self.InterestBase) 461 | .field("Interest", &self.Interest) 462 | .field("Deposit", &self.Deposit) 463 | .field("Withdraw", &self.Withdraw) 464 | .field("FrozenMargin", &self.FrozenMargin) 465 | .field("FrozenCash", &self.FrozenCash) 466 | .field("FrozenCommission", &self.FrozenCommission) 467 | .field("CurrMargin", &self.CurrMargin) 468 | .field("CashIn", &self.CashIn) 469 | .field("Commission", &self.Commission) 470 | .field("CloseProfit", &self.CloseProfit) 471 | .field("PositionProfit", &self.PositionProfit) 472 | .field("Balance", &self.Balance) 473 | .field("Available", &self.Available) 474 | .field("WithdrawQuota", &self.WithdrawQuota) 475 | .field("Reserve", &self.Reserve) 476 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 477 | .field("SettlementID", &self.SettlementID) 478 | .field("Credit", &self.Credit) 479 | .field("Mortgage", &self.Mortgage) 480 | .field("ExchangeMargin", &self.ExchangeMargin) 481 | .field("DeliveryMargin", &self.DeliveryMargin) 482 | .field("ExchangeDeliveryMargin", &self.ExchangeDeliveryMargin) 483 | .field("ReserveBalance", &self.ReserveBalance) 484 | .field("CurrencyID", &gb18030_cstr_to_str(&self.CurrencyID)) 485 | .field("PreFundMortgageIn", &self.PreFundMortgageIn) 486 | .field("PreFundMortgageOut", &self.PreFundMortgageOut) 487 | .field("FundMortgageIn", &self.FundMortgageIn) 488 | .field("FundMortgageOut", &self.FundMortgageOut) 489 | .field("FundMortgageAvailable", &self.FundMortgageAvailable) 490 | .field("MortgageableFund", &self.MortgageableFund) 491 | .field("SpecProductMargin", &self.SpecProductMargin) 492 | .field("SpecProductFrozenMargin", &self.SpecProductFrozenMargin) 493 | .field("SpecProductCommission", &self.SpecProductCommission) 494 | .field("SpecProductFrozenCommission", &self.SpecProductFrozenCommission) 495 | .field("SpecProductPositionProfit", &self.SpecProductPositionProfit) 496 | .field("SpecProductCloseProfit", &self.SpecProductCloseProfit) 497 | .field("SpecProductPositionProfitByAlg", &self.SpecProductPositionProfitByAlg) 498 | .field("SpecProductExchangeMargin", &self.SpecProductExchangeMargin) 499 | .finish() 500 | } 501 | } 502 | 503 | 504 | impl fmt::Debug for CThostFtdcInvestorPositionField { 505 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 506 | fmt.debug_struct("CThostFtdcInvestorPositionField") 507 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 508 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 509 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 510 | .field("PosiDirection", &char::from(self.PosiDirection)) 511 | .field("HedgeFlag", &char::from(self.HedgeFlag)) 512 | .field("PositionDate", &char::from(self.PositionDate)) 513 | .field("YdPosition", &self.YdPosition) 514 | .field("Position", &self.Position) 515 | .field("LongFrozen", &self.LongFrozen) 516 | .field("ShortFrozen", &self.ShortFrozen) 517 | .field("LongFrozenAmount", &self.LongFrozenAmount) 518 | .field("ShortFrozenAmount", &self.ShortFrozenAmount) 519 | .field("OpenVolume", &self.OpenVolume) 520 | .field("CloseVolume", &self.CloseVolume) 521 | .field("OpenAmount", &self.OpenAmount) 522 | .field("CloseAmount", &self.CloseAmount) 523 | .field("PositionCost", &self.PositionCost) 524 | .field("PreMargin", &self.PreMargin) 525 | .field("UseMargin", &self.UseMargin) 526 | .field("FrozenMargin", &self.FrozenMargin) 527 | .field("FrozenCash", &self.FrozenCash) 528 | .field("FrozenCommission", &self.FrozenCommission) 529 | .field("CashIn", &self.CashIn) 530 | .field("Commission", &self.Commission) 531 | .field("CloseProfit", &self.CloseProfit) 532 | .field("PositionProfit", &self.PositionProfit) 533 | .field("PreSettlementPrice", &self.PreSettlementPrice) 534 | .field("SettlementPrice", &self.SettlementPrice) 535 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 536 | .field("SettlementID", &self.SettlementID) 537 | .field("OpenCost", &self.OpenCost) 538 | .field("ExchangeMargin", &self.ExchangeMargin) 539 | .field("CombPosition", &self.CombPosition) 540 | .field("CombLongFrozen", &self.CombLongFrozen) 541 | .field("CombShortFrozen", &self.CombShortFrozen) 542 | .field("CloseProfitByDate", &self.CloseProfitByDate) 543 | .field("CloseProfitByTrade", &self.CloseProfitByTrade) 544 | .field("TodayPosition", &self.TodayPosition) 545 | .field("MarginRateByMoney", &self.MarginRateByMoney) 546 | .field("MarginRateByVolume", &self.MarginRateByVolume) 547 | .field("StrikeFrozen", &self.StrikeFrozen) 548 | .field("StrikeFrozenAmount", &self.StrikeFrozenAmount) 549 | .field("AbandonFrozen", &self.AbandonFrozen) 550 | .finish() 551 | } 552 | } 553 | 554 | impl fmt::Debug for CThostFtdcInstrumentMarginRateField { 555 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 556 | fmt.debug_struct("CThostFtdcInstrumentMarginRateField") 557 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 558 | .field("InvestorRange", &self.InvestorRange) 559 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 560 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 561 | .field("HedgeFlag", &char::from(self.HedgeFlag)) 562 | .field("LongMarginRatioByMoney", &normalize_double(self.LongMarginRatioByMoney)) 563 | .field("LongMarginRatioByVolume", &normalize_double(self.LongMarginRatioByVolume)) 564 | .field("ShortMarginRatioByMoney", &normalize_double(self.ShortMarginRatioByMoney)) 565 | .field("ShortMarginRatioByVolume", &normalize_double(self.ShortMarginRatioByVolume)) 566 | .field("IsRelative", &self.IsRelative) 567 | .finish() 568 | } 569 | } 570 | 571 | impl fmt::Debug for CThostFtdcInstrumentCommissionRateField { 572 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 573 | fmt.debug_struct("CThostFtdcInstrumentCommissionRateField") 574 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 575 | .field("InvestorRange", &self.InvestorRange) 576 | .field("BrokerID", &gb18030_cstr_to_str(&self.BrokerID)) 577 | .field("InvestorID", &gb18030_cstr_to_str(&self.InvestorID)) 578 | .field("OpenRatioByMoney", &self.OpenRatioByMoney) 579 | .field("OpenRatioByVolume", &self.OpenRatioByVolume) 580 | .field("CloseRatioByMoney", &self.CloseRatioByMoney) 581 | .field("CloseRatioByVolume", &self.CloseRatioByVolume) 582 | .field("CloseTodayRatioByMoney", &self.CloseTodayRatioByMoney) 583 | .field("CloseTodayRatioByVolume", &self.CloseTodayRatioByVolume) 584 | .finish() 585 | } 586 | } 587 | 588 | 589 | impl fmt::Debug for CThostFtdcDepthMarketDataField { 590 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 591 | let mut debug = fmt.debug_struct("CThostFtdcDepthMarketDataField"); 592 | debug.field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 593 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 594 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 595 | .field("ExchangeInstID", &gb18030_cstr_to_str(&self.ExchangeInstID)) 596 | .field("LastPrice", &normalize_double(self.LastPrice)) 597 | .field("PreSettlementPrice", &normalize_double(self.PreSettlementPrice)) 598 | .field("PreClosePrice", &normalize_double(self.PreClosePrice)) 599 | .field("PreOpenInterest", &self.PreOpenInterest) 600 | .field("OpenPrice", &normalize_double(self.OpenPrice)) 601 | .field("HighestPrice", &normalize_double(self.HighestPrice)) 602 | .field("LowestPrice", &normalize_double(self.LowestPrice)) 603 | .field("Volume", &self.Volume) 604 | .field("Turnover", &self.Turnover) 605 | .field("OpenInterest", &self.OpenInterest) 606 | .field("ClosePrice", &normalize_double(self.ClosePrice)) 607 | .field("SettlementPrice", &normalize_double(self.SettlementPrice)) 608 | .field("UpperLimitPrice", &normalize_double(self.UpperLimitPrice)) 609 | .field("LowerLimitPrice", &normalize_double(self.LowerLimitPrice)) 610 | .field("PreDelta", &normalize_double(self.PreDelta)) 611 | .field("CurrDelta", &normalize_double(self.CurrDelta)) 612 | .field("UpdateTime", &gb18030_cstr_to_str(&self.UpdateTime)) 613 | .field("UpdateMillisec", &self.UpdateMillisec) 614 | .field("BidPrice1", &normalize_double(self.BidPrice1)) 615 | .field("BidVolume1", &self.BidVolume1) 616 | .field("AskPrice1", &normalize_double(self.AskPrice1)) 617 | .field("AskVolume1", &self.AskVolume1); 618 | if !(self.BidVolume2 == 0 && 619 | self.AskVolume2 == 0 && 620 | self.BidVolume3 == 0 && 621 | self.AskVolume3 == 0 && 622 | self.BidVolume4 == 0 && 623 | self.AskVolume4 == 0 && 624 | self.BidVolume5 == 0 && 625 | self.AskVolume5 == 0) { 626 | debug.field("BidPrice2", &normalize_double(self.BidPrice2)) 627 | .field("BidVolume2", &self.BidVolume2) 628 | .field("AskPrice2", &normalize_double(self.AskPrice2)) 629 | .field("AskVolume2", &self.AskVolume2) 630 | .field("BidPrice3", &normalize_double(self.BidPrice3)) 631 | .field("BidVolume3", &self.BidVolume3) 632 | .field("AskPrice3", &normalize_double(self.AskPrice3)) 633 | .field("AskVolume3", &self.AskVolume3) 634 | .field("BidPrice4", &normalize_double(self.BidPrice4)) 635 | .field("BidVolume4", &self.BidVolume4) 636 | .field("AskPrice4", &normalize_double(self.AskPrice4)) 637 | .field("AskVolume4", &self.AskVolume4) 638 | .field("BidPrice5", &normalize_double(self.BidPrice5)) 639 | .field("BidVolume5", &self.BidVolume5) 640 | .field("AskPrice5", &normalize_double(self.AskPrice5)) 641 | .field("AskVolume5", &self.AskVolume5); 642 | } 643 | debug.field("AveragePrice", &normalize_double(self.AveragePrice)) 644 | .field("ActionDay", &gb18030_cstr_to_str(&self.ActionDay)) 645 | .finish() 646 | } 647 | } 648 | 649 | impl fmt::Debug for CThostFtdcExchangeField { 650 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 651 | fmt.debug_struct("CThostFtdcExchangeField") 652 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 653 | .field("ExchangeName", &gb18030_cstr_to_str(&self.ExchangeName)) 654 | .field("ExchangeProperty", &char::from(self.ExchangeProperty)) 655 | .finish() 656 | } 657 | } 658 | 659 | impl fmt::Debug for CThostFtdcProductField { 660 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 661 | fmt.debug_struct("CThostFtdcProductField") 662 | .field("ProductID", &gb18030_cstr_to_str(&self.ProductID)) 663 | .field("ProductName", &gb18030_cstr_to_str(&self.ProductName)) 664 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 665 | .field("ProductClass", &char::from(self.ProductClass)) 666 | .field("VolumeMultiple", &self.VolumeMultiple) 667 | .field("PriceTick", &self.PriceTick) 668 | .field("MaxMarketOrderVolume", &self.MaxMarketOrderVolume) 669 | .field("MinMarketOrderVolume", &self.MinMarketOrderVolume) 670 | .field("MaxLimitOrderVolume", &self.MaxLimitOrderVolume) 671 | .field("MinLimitOrderVolume", &self.MinLimitOrderVolume) 672 | .field("PositionType", &char::from(self.PositionType)) 673 | .field("PositionDateType", &char::from(self.PositionDateType)) 674 | .field("CloseDealType", &char::from(self.CloseDealType)) 675 | .field("TradeCurrencyID", &gb18030_cstr_to_str(&self.TradeCurrencyID)) 676 | .field("MortgageFundUseRange", &char::from(self.MortgageFundUseRange)) 677 | .field("ExchangeProductID", &gb18030_cstr_to_str(&self.ExchangeProductID)) 678 | .field("UnderlyingMultiple", &normalize_double(self.UnderlyingMultiple)) 679 | .finish() 680 | } 681 | } 682 | 683 | impl fmt::Debug for CThostFtdcForQuoteRspField { 684 | fn fmt(&self, fmt: &mut fmt::Formatter) -> fmt::Result { 685 | fmt.debug_struct("CThostFtdcForQuoteRspField") 686 | .field("TradingDay", &gb18030_cstr_to_str(&self.TradingDay)) 687 | .field("InstrumentID", &gb18030_cstr_to_str(&self.InstrumentID)) 688 | .field("ForQuoteSysID", &gb18030_cstr_to_str(&self.ForQuoteSysID)) 689 | .field("ForQuoteTime", &gb18030_cstr_to_str(&self.ForQuoteTime)) 690 | .field("ActionDay", &gb18030_cstr_to_str(&self.ActionDay)) 691 | .field("ExchangeID", &gb18030_cstr_to_str(&self.ExchangeID)) 692 | .finish() 693 | } 694 | } 695 | -------------------------------------------------------------------------------- /ctp-common/src/generated/error.rs.in: -------------------------------------------------------------------------------- 1 | pub const ERROR_NONE: TThostFtdcErrorIDType = 0; 2 | pub const ERROR_INVALID_DATA_SYNC_STATUS: TThostFtdcErrorIDType = 1; 3 | pub const ERROR_INCONSISTENT_INFORMATION: TThostFtdcErrorIDType = 2; 4 | pub const ERROR_INVALID_LOGIN: TThostFtdcErrorIDType = 3; 5 | pub const ERROR_USER_NOT_ACTIVE: TThostFtdcErrorIDType = 4; 6 | pub const ERROR_DUPLICATE_LOGIN: TThostFtdcErrorIDType = 5; 7 | pub const ERROR_NOT_LOGIN_YET: TThostFtdcErrorIDType = 6; 8 | pub const ERROR_NOT_INITED: TThostFtdcErrorIDType = 7; 9 | pub const ERROR_FRONT_NOT_ACTIVE: TThostFtdcErrorIDType = 8; 10 | pub const ERROR_NO_PRIVILEGE: TThostFtdcErrorIDType = 9; 11 | pub const ERROR_CHANGE_OTHER_PASSWORD: TThostFtdcErrorIDType = 10; 12 | pub const ERROR_USER_NOT_FOUND: TThostFtdcErrorIDType = 11; 13 | pub const ERROR_BROKER_NOT_FOUND: TThostFtdcErrorIDType = 12; 14 | pub const ERROR_INVESTOR_NOT_FOUND: TThostFtdcErrorIDType = 13; 15 | pub const ERROR_OLD_PASSWORD_MISMATCH: TThostFtdcErrorIDType = 14; 16 | pub const ERROR_BAD_FIELD: TThostFtdcErrorIDType = 15; 17 | pub const ERROR_INSTRUMENT_NOT_FOUND: TThostFtdcErrorIDType = 16; 18 | pub const ERROR_INSTRUMENT_NOT_TRADING: TThostFtdcErrorIDType = 17; 19 | pub const ERROR_NOT_EXCHANGE_PARTICIPANT: TThostFtdcErrorIDType = 18; 20 | pub const ERROR_INVESTOR_NOT_ACTIVE: TThostFtdcErrorIDType = 19; 21 | pub const ERROR_NOT_EXCHANGE_CLIENT: TThostFtdcErrorIDType = 20; 22 | pub const ERROR_NO_VALID_TRADER_AVAILABLE: TThostFtdcErrorIDType = 21; 23 | pub const ERROR_DUPLICATE_ORDER_REF: TThostFtdcErrorIDType = 22; 24 | pub const ERROR_BAD_ORDER_ACTION_FIELD: TThostFtdcErrorIDType = 23; 25 | pub const ERROR_DUPLICATE_ORDER_ACTION_REF: TThostFtdcErrorIDType = 24; 26 | pub const ERROR_ORDER_NOT_FOUND: TThostFtdcErrorIDType = 25; 27 | pub const ERROR_INSUITABLE_ORDER_STATUS: TThostFtdcErrorIDType = 26; 28 | pub const ERROR_UNSUPPORTED_FUNCTION: TThostFtdcErrorIDType = 27; 29 | pub const ERROR_NO_TRADING_RIGHT: TThostFtdcErrorIDType = 28; 30 | pub const ERROR_CLOSE_ONLY: TThostFtdcErrorIDType = 29; 31 | pub const ERROR_OVER_CLOSE_POSITION: TThostFtdcErrorIDType = 30; 32 | pub const ERROR_INSUFFICIENT_MONEY: TThostFtdcErrorIDType = 31; 33 | pub const ERROR_DUPLICATE_PK: TThostFtdcErrorIDType = 32; 34 | pub const ERROR_CANNOT_FIND_PK: TThostFtdcErrorIDType = 33; 35 | pub const ERROR_CAN_NOT_INACTIVE_BROKER: TThostFtdcErrorIDType = 34; 36 | pub const ERROR_BROKER_SYNCHRONIZING: TThostFtdcErrorIDType = 35; 37 | pub const ERROR_BROKER_SYNCHRONIZED: TThostFtdcErrorIDType = 36; 38 | pub const ERROR_SHORT_SELL: TThostFtdcErrorIDType = 37; 39 | pub const ERROR_INVALID_SETTLEMENT_REF: TThostFtdcErrorIDType = 38; 40 | pub const ERROR_CFFEX_NETWORK_ERROR: TThostFtdcErrorIDType = 39; 41 | pub const ERROR_CFFEX_OVER_REQUEST: TThostFtdcErrorIDType = 40; 42 | pub const ERROR_CFFEX_OVER_REQUEST_PER_SECOND: TThostFtdcErrorIDType = 41; 43 | pub const ERROR_SETTLEMENT_INFO_NOT_CONFIRMED: TThostFtdcErrorIDType = 42; 44 | pub const ERROR_DEPOSIT_NOT_FOUND: TThostFtdcErrorIDType = 43; 45 | pub const ERROR_EXCHANG_TRADING: TThostFtdcErrorIDType = 44; 46 | pub const ERROR_PARKEDORDER_NOT_FOUND: TThostFtdcErrorIDType = 45; 47 | pub const ERROR_PARKEDORDER_HASSENDED: TThostFtdcErrorIDType = 46; 48 | pub const ERROR_PARKEDORDER_HASDELETE: TThostFtdcErrorIDType = 47; 49 | pub const ERROR_INVALID_INVESTORIDORPASSWORD: TThostFtdcErrorIDType = 48; 50 | pub const ERROR_INVALID_LOGIN_IPADDRESS: TThostFtdcErrorIDType = 49; 51 | pub const ERROR_OVER_CLOSETODAY_POSITION: TThostFtdcErrorIDType = 50; 52 | pub const ERROR_OVER_CLOSEYESTERDAY_POSITION: TThostFtdcErrorIDType = 51; 53 | pub const ERROR_BROKER_NOT_ENOUGH_CONDORDER: TThostFtdcErrorIDType = 52; 54 | pub const ERROR_INVESTOR_NOT_ENOUGH_CONDORDER: TThostFtdcErrorIDType = 53; 55 | pub const ERROR_BROKER_NOT_SUPPORT_CONDORDER: TThostFtdcErrorIDType = 54; 56 | pub const ERROR_RESEND_ORDER_BROKERINVESTOR_NOTMATCH: TThostFtdcErrorIDType = 55; 57 | pub const ERROR_SYC_OTP_FAILED: TThostFtdcErrorIDType = 56; 58 | pub const ERROR_OTP_MISMATCH: TThostFtdcErrorIDType = 57; 59 | pub const ERROR_OTPPARAM_NOT_FOUND: TThostFtdcErrorIDType = 58; 60 | pub const ERROR_UNSUPPORTED_OTPTYPE: TThostFtdcErrorIDType = 59; 61 | pub const ERROR_SINGLEUSERSESSION_EXCEED_LIMIT: TThostFtdcErrorIDType = 60; 62 | pub const ERROR_EXCHANGE_UNSUPPORTED_ARBITRAGE: TThostFtdcErrorIDType = 61; 63 | pub const ERROR_NO_CONDITIONAL_ORDER_RIGHT: TThostFtdcErrorIDType = 62; 64 | pub const ERROR_AUTH_FAILED: TThostFtdcErrorIDType = 63; 65 | pub const ERROR_NOT_AUTHENT: TThostFtdcErrorIDType = 64; 66 | pub const ERROR_SWAPORDER_UNSUPPORTED: TThostFtdcErrorIDType = 65; 67 | pub const ERROR_OPTIONS_ONLY_SUPPORT_SPEC: TThostFtdcErrorIDType = 66; 68 | pub const ERROR_DUPLICATE_EXECORDER_REF: TThostFtdcErrorIDType = 67; 69 | pub const ERROR_RESEND_EXECORDER_BROKERINVESTOR_NOTMATCH: TThostFtdcErrorIDType = 68; 70 | pub const ERROR_EXECORDER_NOTOPTIONS: TThostFtdcErrorIDType = 69; 71 | pub const ERROR_OPTIONS_NOT_SUPPORT_EXEC: TThostFtdcErrorIDType = 70; 72 | pub const ERROR_BAD_EXECORDER_ACTION_FIELD: TThostFtdcErrorIDType = 71; 73 | pub const ERROR_DUPLICATE_EXECORDER_ACTION_REF: TThostFtdcErrorIDType = 72; 74 | pub const ERROR_EXECORDER_NOT_FOUND: TThostFtdcErrorIDType = 73; 75 | pub const ERROR_OVER_EXECUTE_POSITION: TThostFtdcErrorIDType = 74; 76 | pub const ERROR_LOGIN_FORBIDDEN: TThostFtdcErrorIDType = 75; 77 | pub const ERROR_INVALID_TRANSFER_AGENT: TThostFtdcErrorIDType = 76; 78 | pub const ERROR_NO_FOUND_FUNCTION: TThostFtdcErrorIDType = 77; 79 | pub const ERROR_SEND_EXCHANGEORDER_FAILED: TThostFtdcErrorIDType = 78; 80 | pub const ERROR_SEND_EXCHANGEORDERACTION_FAILED: TThostFtdcErrorIDType = 79; 81 | pub const ERROR_PRICETYPE_NOTSUPPORT_BYEXCHANGE: TThostFtdcErrorIDType = 80; 82 | pub const ERROR_BAD_EXECUTE_TYPE: TThostFtdcErrorIDType = 81; 83 | pub const ERROR_BAD_OPTION_INSTR: TThostFtdcErrorIDType = 82; 84 | pub const ERROR_INSTR_NOTSUPPORT_FORQUOTE: TThostFtdcErrorIDType = 83; 85 | pub const ERROR_RESEND_QUOTE_BROKERINVESTOR_NOTMATCH: TThostFtdcErrorIDType = 84; 86 | pub const ERROR_INSTR_NOTSUPPORT_QUOTE: TThostFtdcErrorIDType = 85; 87 | pub const ERROR_QUOTE_NOT_FOUND: TThostFtdcErrorIDType = 86; 88 | pub const ERROR_OPTIONS_NOT_SUPPORT_ABANDON: TThostFtdcErrorIDType = 87; 89 | pub const ERROR_COMBOPTIONS_SUPPORT_IOC_ONLY: TThostFtdcErrorIDType = 88; 90 | pub const ERROR_OPEN_FILE_FAILED: TThostFtdcErrorIDType = 89; 91 | pub const ERROR_NEED_RETRY: TThostFtdcErrorIDType = 90; 92 | pub const ERROR_EXCHANGE_RTNERROR: TThostFtdcErrorIDType = 91; 93 | pub const ERROR_QUOTE_DERIVEDORDER_ACTIONERROR: TThostFtdcErrorIDType = 92; 94 | pub const ERROR_INSTRUMENTMAP_NOT_FOUND: TThostFtdcErrorIDType = 93; 95 | pub const ERROR_CANCELLATION_OF_OTC_DERIVED_ORDER_NOT_ALLOWED: TThostFtdcErrorIDType = 94; 96 | pub const ERROR_NO_TRADING_RIGHT_IN_SEPC_DR: TThostFtdcErrorIDType = 101; 97 | pub const ERROR_NO_DR_NO: TThostFtdcErrorIDType = 102; 98 | pub const ERROR_BATCHACTION_NOSUPPORT: TThostFtdcErrorIDType = 103; 99 | pub const ERROR_POSI_LIMIT: TThostFtdcErrorIDType = 106; 100 | pub const ERROR_OUT_OF_TIMEINTERVAL: TThostFtdcErrorIDType = 113; 101 | pub const ERROR_OUT_OF_PRICEINTERVAL: TThostFtdcErrorIDType = 114; 102 | pub const ERROR_ORDER_FREQ_LIMIT: TThostFtdcErrorIDType = 116; 103 | pub const ERROR_WEAK_PASSWORD: TThostFtdcErrorIDType = 131; 104 | pub const ERROR_EXEC_FORBIDDEN_TIME: TThostFtdcErrorIDType = 139; 105 | pub const ERROR_FIRST_LOGIN: TThostFtdcErrorIDType = 140; 106 | pub const ERROR_PWD_OUT_OF_DATE: TThostFtdcErrorIDType = 141; 107 | pub const ERROR_PWD_MUST_DIFF: TThostFtdcErrorIDType = 142; 108 | pub const ERROR_IP_FORBIDDEN: TThostFtdcErrorIDType = 143; 109 | pub const ERROR_IP_BLACK: TThostFtdcErrorIDType = 144; 110 | pub const ERROR_SEND_INSTITUTION_CODE_ERROR: TThostFtdcErrorIDType = 1000; 111 | pub const ERROR_NO_GET_PLATFORM_SN: TThostFtdcErrorIDType = 1001; 112 | pub const ERROR_ILLEGAL_TRANSFER_BANK: TThostFtdcErrorIDType = 1002; 113 | pub const ERROR_ALREADY_OPEN_ACCOUNT: TThostFtdcErrorIDType = 1003; 114 | pub const ERROR_NOT_OPEN_ACCOUNT: TThostFtdcErrorIDType = 1004; 115 | pub const ERROR_PROCESSING: TThostFtdcErrorIDType = 1005; 116 | pub const ERROR_OVERTIME: TThostFtdcErrorIDType = 1006; 117 | pub const ERROR_RECORD_NOT_FOUND: TThostFtdcErrorIDType = 1007; 118 | pub const ERROR_NO_FOUND_REVERSAL_ORIGINAL_TRANSACTION: TThostFtdcErrorIDType = 1008; 119 | pub const ERROR_CONNECT_HOST_FAILED: TThostFtdcErrorIDType = 1009; 120 | pub const ERROR_SEND_FAILED: TThostFtdcErrorIDType = 1010; 121 | pub const ERROR_LATE_RESPONSE: TThostFtdcErrorIDType = 1011; 122 | pub const ERROR_REVERSAL_BANKID_NOT_MATCH: TThostFtdcErrorIDType = 1012; 123 | pub const ERROR_REVERSAL_BANKACCOUNT_NOT_MATCH: TThostFtdcErrorIDType = 1013; 124 | pub const ERROR_REVERSAL_BROKERID_NOT_MATCH: TThostFtdcErrorIDType = 1014; 125 | pub const ERROR_REVERSAL_ACCOUNTID_NOT_MATCH: TThostFtdcErrorIDType = 1015; 126 | pub const ERROR_REVERSAL_AMOUNT_NOT_MATCH: TThostFtdcErrorIDType = 1016; 127 | pub const ERROR_DB_OPERATION_FAILED: TThostFtdcErrorIDType = 1017; 128 | pub const ERROR_SEND_ASP_FAILURE: TThostFtdcErrorIDType = 1018; 129 | pub const ERROR_NOT_SIGNIN: TThostFtdcErrorIDType = 1019; 130 | pub const ERROR_ALREADY_SIGNIN: TThostFtdcErrorIDType = 1020; 131 | pub const ERROR_AMOUNT_OR_TIMES_OVER: TThostFtdcErrorIDType = 1021; 132 | pub const ERROR_NOT_IN_TRANSFER_TIME: TThostFtdcErrorIDType = 1022; 133 | pub const ERROR_BANK_SERVER_ERROR: TThostFtdcErrorIDType = 1023; 134 | pub const ERROR_BANK_SERIAL_IS_REPEALED: TThostFtdcErrorIDType = 1024; 135 | pub const ERROR_BANK_SERIAL_NOT_EXIST: TThostFtdcErrorIDType = 1025; 136 | pub const ERROR_NOT_ORGAN_MAP: TThostFtdcErrorIDType = 1026; 137 | pub const ERROR_EXIST_TRANSFER: TThostFtdcErrorIDType = 1027; 138 | pub const ERROR_BANK_FORBID_REVERSAL: TThostFtdcErrorIDType = 1028; 139 | pub const ERROR_DUP_BANK_SERIAL: TThostFtdcErrorIDType = 1029; 140 | pub const ERROR_FBT_SYSTEM_BUSY: TThostFtdcErrorIDType = 1030; 141 | pub const ERROR_MACKEY_SYNCING: TThostFtdcErrorIDType = 1031; 142 | pub const ERROR_ACCOUNTID_ALREADY_REGISTER: TThostFtdcErrorIDType = 1032; 143 | pub const ERROR_BANKACCOUNT_ALREADY_REGISTER: TThostFtdcErrorIDType = 1033; 144 | pub const ERROR_DUP_BANK_SERIAL_REDO_OK: TThostFtdcErrorIDType = 1034; 145 | pub const ERROR_CURRENCYID_NOT_SUPPORTED: TThostFtdcErrorIDType = 1035; 146 | pub const ERROR_INVALID_MAC: TThostFtdcErrorIDType = 1036; 147 | pub const ERROR_NOT_SUPPORT_SECAGENT_BY_BANK: TThostFtdcErrorIDType = 1037; 148 | pub const ERROR_PINKEY_SYNCING: TThostFtdcErrorIDType = 1038; 149 | pub const ERROR_SECAGENT_QUERY_BY_CCB: TThostFtdcErrorIDType = 1039; 150 | pub const ERROR_BANKACCOUNT_NOT_EMPTY: TThostFtdcErrorIDType = 1040; 151 | pub const ERROR_INVALID_RESERVE_OPEN_ACCOUNT: TThostFtdcErrorIDType = 1041; 152 | pub const ERROR_OPEN_ACCOUNT_NOT_DEFAULT_ACCOUNT: TThostFtdcErrorIDType = 1042; 153 | pub const ERROR_BANK_SYSTEM_INTERNAL_ERROR: TThostFtdcErrorIDType = 1043; 154 | pub const ERROR_NO_VALID_BANKOFFER_AVAILABLE: TThostFtdcErrorIDType = 2000; 155 | pub const ERROR_PASSWORD_MISMATCH: TThostFtdcErrorIDType = 2001; 156 | pub const ERROR_DUPLATION_BANK_SERIAL: TThostFtdcErrorIDType = 2004; 157 | pub const ERROR_DUPLATION_OFFER_SERIAL: TThostFtdcErrorIDType = 2005; 158 | pub const ERROR_SERIAL_NOT_EXSIT: TThostFtdcErrorIDType = 2006; 159 | pub const ERROR_SERIAL_IS_REPEALED: TThostFtdcErrorIDType = 2007; 160 | pub const ERROR_SERIAL_MISMATCH: TThostFtdcErrorIDType = 2008; 161 | pub const ERROR_IdentifiedCardNo_MISMATCH: TThostFtdcErrorIDType = 2009; 162 | pub const ERROR_ACCOUNT_NOT_FUND: TThostFtdcErrorIDType = 2011; 163 | pub const ERROR_ACCOUNT_NOT_ACTIVE: TThostFtdcErrorIDType = 2012; 164 | pub const ERROR_NOT_ALLOW_REPEAL_BYMANUAL: TThostFtdcErrorIDType = 2013; 165 | pub const ERROR_AMOUNT_OUTOFTHEWAY: TThostFtdcErrorIDType = 2014; 166 | pub const ERROR_EXCHANGERATE_NOT_FOUND: TThostFtdcErrorIDType = 2015; 167 | pub const ERROR_RESERVE_OPEN_ACCOUNT_NOT_FUND: TThostFtdcErrorIDType = 2016; 168 | pub const ERROR_DUPLICATE_RESERVE_OPEN_ACCOUNT_NOT_FUND: TThostFtdcErrorIDType = 2017; 169 | pub const ERROR_WAITING_OFFER_RSP: TThostFtdcErrorIDType = 999999; 170 | pub const ERROR_FBE_NO_GET_PLATFORM_SN: TThostFtdcErrorIDType = 3001; 171 | pub const ERROR_FBE_ILLEGAL_TRANSFER_BANK: TThostFtdcErrorIDType = 3002; 172 | pub const ERROR_FBE_PROCESSING: TThostFtdcErrorIDType = 3005; 173 | pub const ERROR_FBE_OVERTIME: TThostFtdcErrorIDType = 3006; 174 | pub const ERROR_FBE_RECORD_NOT_FOUND: TThostFtdcErrorIDType = 3007; 175 | pub const ERROR_FBE_CONNECT_HOST_FAILED: TThostFtdcErrorIDType = 3009; 176 | pub const ERROR_FBE_SEND_FAILED: TThostFtdcErrorIDType = 3010; 177 | pub const ERROR_FBE_LATE_RESPONSE: TThostFtdcErrorIDType = 3011; 178 | pub const ERROR_FBE_DB_OPERATION_FAILED: TThostFtdcErrorIDType = 3017; 179 | pub const ERROR_FBE_NOT_SIGNIN: TThostFtdcErrorIDType = 3019; 180 | pub const ERROR_FBE_ALREADY_SIGNIN: TThostFtdcErrorIDType = 3020; 181 | pub const ERROR_FBE_AMOUNT_OR_TIMES_OVER: TThostFtdcErrorIDType = 3021; 182 | pub const ERROR_FBE_NOT_IN_TRANSFER_TIME: TThostFtdcErrorIDType = 3022; 183 | pub const ERROR_FBE_BANK_SERVER_ERROR: TThostFtdcErrorIDType = 3023; 184 | pub const ERROR_FBE_NOT_ORGAN_MAP: TThostFtdcErrorIDType = 3026; 185 | pub const ERROR_FBE_SYSTEM_BUSY: TThostFtdcErrorIDType = 3030; 186 | pub const ERROR_FBE_CURRENCYID_NOT_SUPPORTED: TThostFtdcErrorIDType = 3035; 187 | pub const ERROR_FBE_WRONG_BANK_ACCOUNT: TThostFtdcErrorIDType = 3036; 188 | pub const ERROR_FBE_BANK_ACCOUNT_NO_FUNDS: TThostFtdcErrorIDType = 3037; 189 | pub const ERROR_FBE_DUP_CERT_NO: TThostFtdcErrorIDType = 3038; 190 | pub const ERROR_API_UNSUPPORTED_VERSION: TThostFtdcErrorIDType = 3039; 191 | pub const ERROR_API_INVALID_KEY: TThostFtdcErrorIDType = 3040; 192 | pub const ERROR_OPTION_SELF_CLOSE_NOT_OPTION: TThostFtdcErrorIDType = 3041; 193 | pub const ERROR_OPTION_SELF_CLOSE_DUPLICATE_REF: TThostFtdcErrorIDType = 3042; 194 | pub const ERROR_OPTION_SELF_CLOSE_BAD_FIELD: TThostFtdcErrorIDType = 3043; 195 | pub const ERROR_OPTION_SELF_CLOSE_REC_NOT_FOUND: TThostFtdcErrorIDType = 3044; 196 | pub const ERROR_OPTION_SELF_CLOSE_STATUS_ERR: TThostFtdcErrorIDType = 3045; 197 | pub const ERROR_OPTION_SELF_CLOSE_DOUBLE_SET_ERR: TThostFtdcErrorIDType = 3046; 198 | pub const ERROR_QUOTE_WRONG_HEDGE_TYPE: TThostFtdcErrorIDType = 3047; 199 | pub fn error_id_to_chinese_description(error_id: TThostFtdcErrorIDType) -> &'static str { 200 | match error_id { 201 | ERROR_NONE => "CTP:正确", 202 | ERROR_INVALID_DATA_SYNC_STATUS => "CTP:不在已同步状态", 203 | ERROR_INCONSISTENT_INFORMATION => "CTP:会话信息不一致", 204 | ERROR_INVALID_LOGIN => "CTP:不合法的登录", 205 | ERROR_USER_NOT_ACTIVE => "CTP:用户不活跃", 206 | ERROR_DUPLICATE_LOGIN => "CTP:重复的登录", 207 | ERROR_NOT_LOGIN_YET => "CTP:还没有登录", 208 | ERROR_NOT_INITED => "CTP:还没有初始化", 209 | ERROR_FRONT_NOT_ACTIVE => "CTP:前置不活跃", 210 | ERROR_NO_PRIVILEGE => "CTP:无此权限", 211 | ERROR_CHANGE_OTHER_PASSWORD => "CTP:修改别人的口令", 212 | ERROR_USER_NOT_FOUND => "CTP:找不到该用户", 213 | ERROR_BROKER_NOT_FOUND => "CTP:找不到该经纪公司", 214 | ERROR_INVESTOR_NOT_FOUND => "CTP:找不到投资者", 215 | ERROR_OLD_PASSWORD_MISMATCH => "CTP:原口令不匹配", 216 | ERROR_BAD_FIELD => "CTP:报单字段有误", 217 | ERROR_INSTRUMENT_NOT_FOUND => "CTP:找不到合约", 218 | ERROR_INSTRUMENT_NOT_TRADING => "CTP:合约不能交易", 219 | ERROR_NOT_EXCHANGE_PARTICIPANT => "CTP:经纪公司不是交易所的会员", 220 | ERROR_INVESTOR_NOT_ACTIVE => "CTP:投资者不活跃", 221 | ERROR_NOT_EXCHANGE_CLIENT => "CTP:投资者未在交易所开户", 222 | ERROR_NO_VALID_TRADER_AVAILABLE => "CTP:该交易席位未连接到交易所", 223 | ERROR_DUPLICATE_ORDER_REF => "CTP:报单错误:不允许重复报单", 224 | ERROR_BAD_ORDER_ACTION_FIELD => "CTP:错误的报单操作字段", 225 | ERROR_DUPLICATE_ORDER_ACTION_REF => "CTP:撤单已报送,不允许重复撤单", 226 | ERROR_ORDER_NOT_FOUND => "CTP:撤单找不到相应报单", 227 | ERROR_INSUITABLE_ORDER_STATUS => "CTP:报单已全成交或已撤销,不能再撤", 228 | ERROR_UNSUPPORTED_FUNCTION => "CTP:不支持的功能", 229 | ERROR_NO_TRADING_RIGHT => "CTP:没有报单交易权限", 230 | ERROR_CLOSE_ONLY => "CTP:只能平仓", 231 | ERROR_OVER_CLOSE_POSITION => "CTP:平仓量超过持仓量", 232 | ERROR_INSUFFICIENT_MONEY => "CTP:资金不足", 233 | ERROR_DUPLICATE_PK => "CTP:主键重复", 234 | ERROR_CANNOT_FIND_PK => "CTP:找不到主键", 235 | ERROR_CAN_NOT_INACTIVE_BROKER => "CTP:设置经纪公司不活跃状态失败", 236 | ERROR_BROKER_SYNCHRONIZING => "CTP:经纪公司正在同步", 237 | ERROR_BROKER_SYNCHRONIZED => "CTP:经纪公司已同步", 238 | ERROR_SHORT_SELL => "CTP:现货交易不能卖空", 239 | ERROR_INVALID_SETTLEMENT_REF => "CTP:不合法的结算引用", 240 | ERROR_CFFEX_NETWORK_ERROR => "CTP:交易所网络连接失败", 241 | ERROR_CFFEX_OVER_REQUEST => "CTP:交易所未处理请求超过许可数", 242 | ERROR_CFFEX_OVER_REQUEST_PER_SECOND => "CTP:交易所每秒发送请求数超过许可数", 243 | ERROR_SETTLEMENT_INFO_NOT_CONFIRMED => "CTP:结算结果未确认", 244 | ERROR_DEPOSIT_NOT_FOUND => "CTP:没有对应的入金记录", 245 | ERROR_EXCHANG_TRADING => "CTP:交易所已经进入连续交易状态", 246 | ERROR_PARKEDORDER_NOT_FOUND => "CTP:找不到预埋(撤单)单", 247 | ERROR_PARKEDORDER_HASSENDED => "CTP:预埋(撤单)单已经发送", 248 | ERROR_PARKEDORDER_HASDELETE => "CTP:预埋(撤单)单已经删除", 249 | ERROR_INVALID_INVESTORIDORPASSWORD => "CTP:无效的投资者或者密码", 250 | ERROR_INVALID_LOGIN_IPADDRESS => "CTP:不合法的登录IP地址", 251 | ERROR_OVER_CLOSETODAY_POSITION => "CTP:平今仓位不足", 252 | ERROR_OVER_CLOSEYESTERDAY_POSITION => "CTP:平昨仓位不足", 253 | ERROR_BROKER_NOT_ENOUGH_CONDORDER => "CTP:经纪公司没有足够可用的条件单数量", 254 | ERROR_INVESTOR_NOT_ENOUGH_CONDORDER => "CTP:投资者没有足够可用的条件单数量", 255 | ERROR_BROKER_NOT_SUPPORT_CONDORDER => "CTP:经纪公司不支持条件单", 256 | ERROR_RESEND_ORDER_BROKERINVESTOR_NOTMATCH => "CTP:重发未知单经纪公司/投资者不匹配", 257 | ERROR_SYC_OTP_FAILED => "CTP:同步动态令牌失败", 258 | ERROR_OTP_MISMATCH => "CTP:动态令牌校验错误", 259 | ERROR_OTPPARAM_NOT_FOUND => "CTP:找不到动态令牌配置信息", 260 | ERROR_UNSUPPORTED_OTPTYPE => "CTP:不支持的动态令牌类型", 261 | ERROR_SINGLEUSERSESSION_EXCEED_LIMIT => "CTP:用户在线会话超出上限", 262 | ERROR_EXCHANGE_UNSUPPORTED_ARBITRAGE => "CTP:该交易所不支持套利/做市商类型报单", 263 | ERROR_NO_CONDITIONAL_ORDER_RIGHT => "CTP:没有条件单交易权限", 264 | ERROR_AUTH_FAILED => "CTP:客户端认证失败", 265 | ERROR_NOT_AUTHENT => "CTP:客户端未认证", 266 | ERROR_SWAPORDER_UNSUPPORTED => "CTP:该合约不支持互换类型报单", 267 | ERROR_OPTIONS_ONLY_SUPPORT_SPEC => "CTP:该期权合约只支持投机类型报单", 268 | ERROR_DUPLICATE_EXECORDER_REF => "CTP:执行宣告错误,不允许重复执行", 269 | ERROR_RESEND_EXECORDER_BROKERINVESTOR_NOTMATCH => "CTP:重发未知执行宣告经纪公司/投资者不匹配", 270 | ERROR_EXECORDER_NOTOPTIONS => "CTP:只有期权合约可执行", 271 | ERROR_OPTIONS_NOT_SUPPORT_EXEC => "CTP:该期权合约不支持执行", 272 | ERROR_BAD_EXECORDER_ACTION_FIELD => "CTP:执行宣告字段有误", 273 | ERROR_DUPLICATE_EXECORDER_ACTION_REF => "CTP:执行宣告撤单已报送,不允许重复撤单", 274 | ERROR_EXECORDER_NOT_FOUND => "CTP:执行宣告撤单找不到相应执行宣告", 275 | ERROR_OVER_EXECUTE_POSITION => "CTP:执行仓位不足", 276 | ERROR_LOGIN_FORBIDDEN => "CTP:连续登录失败次数超限,登录被禁止", 277 | ERROR_INVALID_TRANSFER_AGENT => "CTP:非法银期代理关系", 278 | ERROR_NO_FOUND_FUNCTION => "CTP:无此功能", 279 | ERROR_SEND_EXCHANGEORDER_FAILED => "CTP:发送报单失败", 280 | ERROR_SEND_EXCHANGEORDERACTION_FAILED => "CTP:发送报单操作失败", 281 | ERROR_PRICETYPE_NOTSUPPORT_BYEXCHANGE => "CTP:交易所不支持的价格类型", 282 | ERROR_BAD_EXECUTE_TYPE => "CTP:错误的执行类型", 283 | ERROR_BAD_OPTION_INSTR => "CTP:无效的组合合约", 284 | ERROR_INSTR_NOTSUPPORT_FORQUOTE => "CTP:该合约不支持询价", 285 | ERROR_RESEND_QUOTE_BROKERINVESTOR_NOTMATCH => "CTP:重发未知报价经纪公司/投资者不匹配", 286 | ERROR_INSTR_NOTSUPPORT_QUOTE => "CTP:该合约不支持报价", 287 | ERROR_QUOTE_NOT_FOUND => "CTP:报价撤单找不到相应报价", 288 | ERROR_OPTIONS_NOT_SUPPORT_ABANDON => "CTP:该期权合约不支持放弃执行", 289 | ERROR_COMBOPTIONS_SUPPORT_IOC_ONLY => "CTP:该组合期权合约只支持IOC", 290 | ERROR_OPEN_FILE_FAILED => "CTP:打开文件失败", 291 | ERROR_NEED_RETRY => "CTP:查询未就绪,请稍后重试", 292 | ERROR_EXCHANGE_RTNERROR => "CTP:交易所返回的错误", 293 | ERROR_QUOTE_DERIVEDORDER_ACTIONERROR => "CTP:报价衍生单要等待交易所返回才能撤单", 294 | ERROR_INSTRUMENTMAP_NOT_FOUND => "CTP:找不到组合合约映射", 295 | ERROR_CANCELLATION_OF_OTC_DERIVED_ORDER_NOT_ALLOWED => "CTP:不允许撤销OTC衍生报单", 296 | ERROR_NO_TRADING_RIGHT_IN_SEPC_DR => "CTP:用户在本系统没有报单权限", 297 | ERROR_NO_DR_NO => "CTP:系统缺少灾备标示号", 298 | ERROR_BATCHACTION_NOSUPPORT => "CTP:该交易所不支持批量撤单", 299 | ERROR_POSI_LIMIT => "CTP:投资者限仓", 300 | ERROR_OUT_OF_TIMEINTERVAL => "CTP:当前时间禁止询价", 301 | ERROR_OUT_OF_PRICEINTERVAL => "CTP:当前价差禁止询价", 302 | ERROR_ORDER_FREQ_LIMIT => "CTP:下单频率限制", 303 | ERROR_WEAK_PASSWORD => "CTP:您当前密码为弱密码,请修改成强密码后重新登录", 304 | ERROR_EXEC_FORBIDDEN_TIME => "CTP:当前时间禁止行权", 305 | ERROR_FIRST_LOGIN => "CTP:首次登录必须修改密码,请修改密码后重新登录", 306 | ERROR_PWD_OUT_OF_DATE => "CTP:您当前密码已过期,请修改后登录", 307 | ERROR_PWD_MUST_DIFF => "CTP:修改密码失败。新密码不允许与旧密码相同", 308 | ERROR_IP_FORBIDDEN => "CTP:您登录失败次数过多,IP被禁止登入CTP", 309 | ERROR_IP_BLACK => "CTP:您当前IP在黑名单中,被禁止登入CTP", 310 | ERROR_SEND_INSTITUTION_CODE_ERROR => "CTP:银期转账:发送机构代码错误", 311 | ERROR_NO_GET_PLATFORM_SN => "CTP:银期转账:取平台流水号错误", 312 | ERROR_ILLEGAL_TRANSFER_BANK => "CTP:银期转账:不合法的转账银行", 313 | ERROR_ALREADY_OPEN_ACCOUNT => "CTP:银期转账:已经开户", 314 | ERROR_NOT_OPEN_ACCOUNT => "CTP:银期转账:未开户", 315 | ERROR_PROCESSING => "CTP:银期转账:处理中", 316 | ERROR_OVERTIME => "CTP:银期转账:交易超时", 317 | ERROR_RECORD_NOT_FOUND => "CTP:银期转账:找不到记录", 318 | ERROR_NO_FOUND_REVERSAL_ORIGINAL_TRANSACTION => "CTP:银期转账:找不到被冲正的原始交易", 319 | ERROR_CONNECT_HOST_FAILED => "CTP:银期转账:连接主机失败", 320 | ERROR_SEND_FAILED => "CTP:银期转账:发送失败", 321 | ERROR_LATE_RESPONSE => "CTP:银期转账:迟到应答", 322 | ERROR_REVERSAL_BANKID_NOT_MATCH => "CTP:银期转账:冲正交易银行代码错误", 323 | ERROR_REVERSAL_BANKACCOUNT_NOT_MATCH => "CTP:银期转账:冲正交易银行账户错误", 324 | ERROR_REVERSAL_BROKERID_NOT_MATCH => "CTP:银期转账:冲正交易经纪公司代码错误", 325 | ERROR_REVERSAL_ACCOUNTID_NOT_MATCH => "CTP:银期转账:冲正交易资金账户错误", 326 | ERROR_REVERSAL_AMOUNT_NOT_MATCH => "CTP:银期转账:冲正交易交易金额错误", 327 | ERROR_DB_OPERATION_FAILED => "CTP:银期转账:数据库操作错误", 328 | ERROR_SEND_ASP_FAILURE => "CTP:银期转账:发送到交易系统失败", 329 | ERROR_NOT_SIGNIN => "CTP:银期转账:没有签到", 330 | ERROR_ALREADY_SIGNIN => "CTP:银期转账:已经签到", 331 | ERROR_AMOUNT_OR_TIMES_OVER => "CTP:银期转账:金额或次数超限", 332 | ERROR_NOT_IN_TRANSFER_TIME => "CTP:银期转账:这一时间段不能转账", 333 | ERROR_BANK_SERVER_ERROR => "银行主机错", 334 | ERROR_BANK_SERIAL_IS_REPEALED => "CTP:银期转账:银行已经冲正", 335 | ERROR_BANK_SERIAL_NOT_EXIST => "CTP:银期转账:银行流水不存在", 336 | ERROR_NOT_ORGAN_MAP => "CTP:银期转账:机构没有签约", 337 | ERROR_EXIST_TRANSFER => "CTP:银期转账:存在转账,不能销户", 338 | ERROR_BANK_FORBID_REVERSAL => "CTP:银期转账:银行不支持冲正", 339 | ERROR_DUP_BANK_SERIAL => "CTP:银期转账:重复的银行流水", 340 | ERROR_FBT_SYSTEM_BUSY => "CTP:银期转账:转账系统忙,稍后再试", 341 | ERROR_MACKEY_SYNCING => "CTP:银期转账:MAC密钥正在同步", 342 | ERROR_ACCOUNTID_ALREADY_REGISTER => "CTP:银期转账:资金账户已经登记", 343 | ERROR_BANKACCOUNT_ALREADY_REGISTER => "CTP:银期转账:银行账户已经登记", 344 | ERROR_DUP_BANK_SERIAL_REDO_OK => "CTP:银期转账:重复的银行流水,重发成功", 345 | ERROR_CURRENCYID_NOT_SUPPORTED => "CTP:银期转账:该币种代码不支持", 346 | ERROR_INVALID_MAC => "CTP:银期转账:MAC值验证失败", 347 | ERROR_NOT_SUPPORT_SECAGENT_BY_BANK => "CTP:银期转账:不支持银行端发起的二级代理商转账和查询", 348 | ERROR_PINKEY_SYNCING => "CTP:银期转账:PIN密钥正在同步", 349 | ERROR_SECAGENT_QUERY_BY_CCB => "CTP:银期转账:建行发起的二级代理商查询", 350 | ERROR_BANKACCOUNT_NOT_EMPTY => "CTP:银期转账:银行账户不能为空", 351 | ERROR_INVALID_RESERVE_OPEN_ACCOUNT => "CTP:银期转账:资金账户存在,预约开户失败", 352 | ERROR_OPEN_ACCOUNT_NOT_DEFAULT_ACCOUNT => "CTP:银期转账:开户请求的银行卡号和预留的账号不同", 353 | ERROR_BANK_SYSTEM_INTERNAL_ERROR => "银行系统内部错误", 354 | ERROR_NO_VALID_BANKOFFER_AVAILABLE => "CTP:该报盘未连接到银行", 355 | ERROR_PASSWORD_MISMATCH => "CTP:资金密码错误", 356 | ERROR_DUPLATION_BANK_SERIAL => "CTP:银行流水号重复", 357 | ERROR_DUPLATION_OFFER_SERIAL => "CTP:报盘流水号重复", 358 | ERROR_SERIAL_NOT_EXSIT => "CTP:被冲正流水不存在(冲正交易)", 359 | ERROR_SERIAL_IS_REPEALED => "CTP:原流水已冲正(冲正交易)", 360 | ERROR_SERIAL_MISMATCH => "CTP:与原流水信息不符(冲正交易)", 361 | ERROR_IdentifiedCardNo_MISMATCH => "CTP:证件号码或类型错误", 362 | ERROR_ACCOUNT_NOT_FUND => "CTP:资金账户不存在", 363 | ERROR_ACCOUNT_NOT_ACTIVE => "CTP:资金账户已经销户", 364 | ERROR_NOT_ALLOW_REPEAL_BYMANUAL => "CTP:该交易不能执行手工冲正", 365 | ERROR_AMOUNT_OUTOFTHEWAY => "CTP:转帐金额错误", 366 | ERROR_EXCHANGERATE_NOT_FOUND => "CTP:找不到汇率", 367 | ERROR_RESERVE_OPEN_ACCOUNT_NOT_FUND => "CTP:找不到预约开户请求", 368 | ERROR_DUPLICATE_RESERVE_OPEN_ACCOUNT_NOT_FUND => "CTP:重复的预约开户请求", 369 | ERROR_WAITING_OFFER_RSP => "CTP:等待银期报盘处理结果", 370 | ERROR_FBE_NO_GET_PLATFORM_SN => "CTP:银期换汇:取平台流水号错误", 371 | ERROR_FBE_ILLEGAL_TRANSFER_BANK => "CTP:银期换汇:不合法的转账银行", 372 | ERROR_FBE_PROCESSING => "CTP:银期换汇:处理中", 373 | ERROR_FBE_OVERTIME => "CTP:银期换汇:交易超时", 374 | ERROR_FBE_RECORD_NOT_FOUND => "CTP:银期换汇:找不到记录", 375 | ERROR_FBE_CONNECT_HOST_FAILED => "CTP:银期换汇:连接主机失败", 376 | ERROR_FBE_SEND_FAILED => "CTP:银期换汇:发送失败", 377 | ERROR_FBE_LATE_RESPONSE => "CTP:银期换汇:迟到应答", 378 | ERROR_FBE_DB_OPERATION_FAILED => "CTP:银期换汇:数据库操作错误", 379 | ERROR_FBE_NOT_SIGNIN => "CTP:银期换汇:没有签到", 380 | ERROR_FBE_ALREADY_SIGNIN => "CTP:银期换汇:已经签到", 381 | ERROR_FBE_AMOUNT_OR_TIMES_OVER => "CTP:银期换汇:金额或次数超限", 382 | ERROR_FBE_NOT_IN_TRANSFER_TIME => "CTP:银期换汇:这一时间段不能换汇", 383 | ERROR_FBE_BANK_SERVER_ERROR => "CTP:银期换汇:银行主机错", 384 | ERROR_FBE_NOT_ORGAN_MAP => "CTP:银期换汇:机构没有签约", 385 | ERROR_FBE_SYSTEM_BUSY => "CTP:银期换汇:换汇系统忙,稍后再试", 386 | ERROR_FBE_CURRENCYID_NOT_SUPPORTED => "CTP:银期换汇:该币种代码不支持", 387 | ERROR_FBE_WRONG_BANK_ACCOUNT => "CTP:银期换汇:银行帐号不正确", 388 | ERROR_FBE_BANK_ACCOUNT_NO_FUNDS => "CTP:银期换汇:银行帐户余额不足", 389 | ERROR_FBE_DUP_CERT_NO => "CTP:银期换汇:凭证号重复", 390 | ERROR_API_UNSUPPORTED_VERSION => "CTP: 不支持该API版本", 391 | ERROR_API_INVALID_KEY => "CTP: 无效的API KEY", 392 | ERROR_OPTION_SELF_CLOSE_NOT_OPTION => "CTP:期权对冲,履约对冲:非期权合约", 393 | ERROR_OPTION_SELF_CLOSE_DUPLICATE_REF => "CTP:期权对冲,履约对冲:请求引用不合法", 394 | ERROR_OPTION_SELF_CLOSE_BAD_FIELD => "CTP:期权对冲,履约对冲:非法字段 ", 395 | ERROR_OPTION_SELF_CLOSE_REC_NOT_FOUND => "CTP:期权对冲,履约对冲:撤销未找到记录", 396 | ERROR_OPTION_SELF_CLOSE_STATUS_ERR => "CTP:期权对冲,履约对冲:对冲状态不对,不能撤销", 397 | ERROR_OPTION_SELF_CLOSE_DOUBLE_SET_ERR => "CTP:期权对冲,履约对冲:不能重复设置,只能先撤销再设置", 398 | ERROR_QUOTE_WRONG_HEDGE_TYPE => "CTP:报价不支持改投机套保类型", 399 | _ => "unknown error", 400 | } 401 | } 402 | -------------------------------------------------------------------------------- /ctp-common/src/lib.rs: -------------------------------------------------------------------------------- 1 | mod binding; 2 | pub use binding::*; 3 | 4 | use encoding::{ DecoderTrap, Encoding }; 5 | use encoding::all::GB18030; 6 | use simple_error::SimpleError; 7 | use std::borrow::Cow; 8 | use std::fmt; 9 | use std::os::raw::c_int; 10 | use time::{ Timespec, Tm }; 11 | 12 | /// 交易接口中的查询操作的限制为: 13 | /// 每秒钟最多只能进行一次查询操作。 14 | /// 在途的查询操作最多只能有一个。 15 | /// 在途:查询操作从发送请求,到接收到响应为一个完整的过程。如果请求已经发送,但是未收到响应,则称 16 | /// 该查询操作在途。 17 | /// 上述限制只针对交易接口中的数据查询操作(ReqQryXXX),对报单,撤单,报价,询价等操作没有影响。 18 | pub const DEFAULT_MAX_NUM_QUERY_REQUEST_PER_SECOND: usize = 1; 19 | 20 | /// 报单流量限制是由期货公司通过在系统中配置相关参数实现限制的。 21 | /// 不进行配置的情况下,默认流量限制为: 22 | /// 在一个连接会话(Session)中,每个客户端每秒钟最多只能发送 6 笔交易相关的指令(报单,撤单等)。 23 | pub const DEFAULT_MAX_NUM_ORDER_REQUEST_PER_SECOND: usize = 6; 24 | /// 同一个账户同时最多只能建立 6 个会话(Session)。 25 | pub const DEFAULT_MAX_NUM_CONCURRENT_SESSION: usize = 6; 26 | 27 | pub fn ascii_cstr_to_str(s: &[u8]) -> Result<&str, SimpleError> { 28 | match s.last() { 29 | Some(&0u8) => { 30 | let len = memchr::memchr(0, s).unwrap(); 31 | let ascii_s = &s[0..len]; 32 | if ascii_s.is_ascii() { 33 | unsafe { 34 | Ok(std::str::from_utf8_unchecked(ascii_s)) 35 | } 36 | } else { 37 | Err(SimpleError::new("cstr is not ascii")) 38 | } 39 | }, 40 | Some(&c) => { 41 | Err(SimpleError::new(format!("cstr should terminate with null instead of {:#x}", c))) 42 | }, 43 | None => { 44 | Err(SimpleError::new("cstr cannot have 0 length")) 45 | } 46 | } 47 | } 48 | 49 | pub fn gb18030_cstr_to_str(v: &[u8]) -> Cow { 50 | let slice = v.split(|&c| c == 0u8).next().unwrap(); 51 | if slice.is_ascii() { 52 | unsafe { 53 | return Cow::Borrowed::(std::str::from_utf8_unchecked(slice)); 54 | } 55 | } 56 | match GB18030.decode(slice, DecoderTrap::Replace) { 57 | Ok(s) => Cow::Owned(s), 58 | Err(e) => e, 59 | } 60 | } 61 | 62 | pub fn reduce_comb_flags(flags: &[u8]) -> String { 63 | flags.iter().filter(|&&c| c != 0).map(|&c| char::from(c)).collect() 64 | } 65 | 66 | pub fn maybe_char(c: u8) -> Option { 67 | if c != 0u8 { 68 | Some(char::from(c)) 69 | } else { 70 | None 71 | } 72 | } 73 | 74 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 75 | pub struct OrderIdLocalTrio { 76 | pub front_id: TThostFtdcFrontIDType, 77 | pub session_id: TThostFtdcSessionIDType, 78 | pub order_ref: TThostFtdcOrderRefType, 79 | } 80 | 81 | #[derive(Clone, Copy, Debug, Eq, Hash, Ord, PartialEq, PartialOrd)] 82 | pub struct OrderIdExchangeDuo { 83 | pub exchange_id: TThostFtdcExchangeIDType, 84 | pub order_sys_id: TThostFtdcOrderSysIDType, 85 | } 86 | 87 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 88 | pub enum ResumeType { 89 | Restart = THOST_TE_RESUME_TYPE::THOST_TERT_RESTART as isize, 90 | Resume = THOST_TE_RESUME_TYPE::THOST_TERT_RESUME as isize, 91 | Quick = THOST_TE_RESUME_TYPE::THOST_TERT_QUICK as isize, 92 | } 93 | 94 | impl std::convert::Into for ResumeType { 95 | fn into(self) -> THOST_TE_RESUME_TYPE { 96 | match self { 97 | ResumeType::Restart => THOST_TE_RESUME_TYPE::THOST_TERT_RESTART, 98 | ResumeType::Resume => THOST_TE_RESUME_TYPE::THOST_TERT_RESUME, 99 | ResumeType::Quick => THOST_TE_RESUME_TYPE::THOST_TERT_QUICK, 100 | } 101 | } 102 | } 103 | 104 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 105 | pub enum DisconnectionReason { 106 | ReadError = 0x1001, 107 | WriteError = 0x1002, 108 | HeartbeatTimeout = 0x2001, 109 | HeartbeatSendError = 0x2002, 110 | ErrorMessageReceived = 0x2003, 111 | Unknown = 0x0000, 112 | } 113 | 114 | impl std::convert::From for DisconnectionReason { 115 | fn from(reason: c_int) -> DisconnectionReason { 116 | match reason { 117 | 0x1001 => DisconnectionReason::ReadError, 118 | 0x1002 => DisconnectionReason::WriteError, 119 | 0x2001 => DisconnectionReason::HeartbeatTimeout, 120 | 0x2002 => DisconnectionReason::HeartbeatSendError, 121 | 0x2003 => DisconnectionReason::ErrorMessageReceived, 122 | _ => DisconnectionReason::Unknown, 123 | } 124 | } 125 | } 126 | 127 | impl fmt::Display for DisconnectionReason { 128 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 129 | use DisconnectionReason::*; 130 | match *self { 131 | ReadError => f.write_str("read error"), 132 | WriteError => f.write_str("write error"), 133 | HeartbeatTimeout => f.write_str("heartbeat timeout"), 134 | HeartbeatSendError => f.write_str("heatbeat send error"), 135 | ErrorMessageReceived => f.write_str("error message received"), 136 | Unknown => f.write_str("unknown"), 137 | } 138 | } 139 | } 140 | 141 | #[derive(Clone, Copy, Debug, PartialEq, Eq)] 142 | pub enum ApiError { 143 | NetworkError = -1, 144 | QueueFull = -2, 145 | Throttled = -3, 146 | } 147 | 148 | impl std::error::Error for ApiError { 149 | } 150 | 151 | impl fmt::Display for ApiError { 152 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 153 | match *self { 154 | ApiError::NetworkError => f.write_str("network error"), 155 | ApiError::QueueFull => f.write_str("queue full"), 156 | ApiError::Throttled => f.write_str("throttled"), 157 | } 158 | } 159 | } 160 | 161 | #[must_use] 162 | pub type ApiResult = Result<(), ApiError>; 163 | 164 | // TODO: use std::convert::From after trait specialization? 165 | pub fn from_api_return_to_api_result(api_return: c_int) -> ApiResult { 166 | match api_return { 167 | 0 => Ok(()), 168 | -1 => Err(ApiError::NetworkError), 169 | -2 => Err(ApiError::QueueFull), 170 | -3 => Err(ApiError::Throttled), 171 | // no other values are possible in specification 172 | i => panic!("api return unspecified {}", i), 173 | } 174 | } 175 | 176 | #[derive(Clone, Debug, PartialEq)] 177 | pub struct RspError { 178 | pub id: TThostFtdcErrorIDType, 179 | pub msg: String, 180 | } 181 | 182 | impl std::error::Error for RspError { 183 | } 184 | 185 | impl fmt::Display for RspError { 186 | fn fmt(&self, f: &mut fmt::Formatter) -> fmt::Result { 187 | write!(f, "{} {}", self.id, self.msg) 188 | } 189 | } 190 | 191 | #[must_use] 192 | pub type RspResult = Result<(), RspError>; 193 | 194 | pub fn from_rsp_result_to_string(rsp_result: &RspResult) -> String { 195 | match rsp_result { 196 | Ok(()) => "Ok(())".to_string(), 197 | Err(err) => format!("Err(RspError{{ id: {}, msg: {} }})", err.id, err.msg), 198 | } 199 | } 200 | 201 | /// # Safety 202 | /// 203 | /// `rsp_info` needs to be either a null pointer or a valid pointer of type `CThostFtdcRspInfoField`. 204 | pub unsafe fn from_rsp_info_to_rsp_result(rsp_info: *const CThostFtdcRspInfoField) -> RspResult { 205 | #[allow(unused_unsafe)] // for future "unsafe blocks in unsafe fn" feature 206 | match unsafe { rsp_info.as_ref() } { 207 | Some(info) => match info { 208 | CThostFtdcRspInfoField { ErrorID: 0, .. } => { 209 | Ok(()) 210 | }, 211 | CThostFtdcRspInfoField { ErrorID: id, ErrorMsg: msg } => { 212 | Err(RspError{ id: *id, msg: gb18030_cstr_to_str(msg).into_owned() }) 213 | } 214 | }, 215 | None => { 216 | Ok(()) 217 | }, 218 | } 219 | } 220 | 221 | pub fn is_terminal_order_status(order_status: TThostFtdcOrderStatusType) -> bool { 222 | order_status == THOST_FTDC_OST_AllTraded || 223 | order_status == THOST_FTDC_OST_PartTradedNotQueueing || 224 | order_status == THOST_FTDC_OST_NoTradeNotQueueing || 225 | order_status == THOST_FTDC_OST_Canceled 226 | } 227 | 228 | pub fn is_valid_order_sys_id(order_sys_id: &TThostFtdcOrderSysIDType) -> bool { 229 | order_sys_id[0] != b'\0' 230 | } 231 | 232 | #[allow(clippy::trivially_copy_pass_by_ref)] // Will be removed 233 | #[deprecated(since = "0.9.0", note = "This will be removed in 0.10.0")] 234 | pub fn to_exchange_timestamp(trading_day: &TThostFtdcDateType, 235 | update_time: &TThostFtdcTimeType, 236 | update_millisec: &TThostFtdcMillisecType) -> Result { 237 | let year = match ::std::str::from_utf8(&trading_day[0..4]) { 238 | Ok(year_str) => { 239 | match year_str.parse::() { 240 | Ok(year) => year, 241 | Err(err) => { 242 | return Err(SimpleError::new(format!("invalid year string, {}", err))); 243 | }, 244 | } 245 | }, 246 | Err(err) => { 247 | return Err(SimpleError::new(format!("year not utf8, {}", err))); 248 | }, 249 | }; 250 | let month = match ::std::str::from_utf8(&trading_day[4..6]) { 251 | Ok(month_str) => { 252 | match month_str.parse::() { 253 | Ok(month) => month, 254 | Err(err) => { 255 | return Err(SimpleError::new(format!("invalid month string, {}", err))); 256 | }, 257 | } 258 | }, 259 | Err(err) => { 260 | return Err(SimpleError::new(format!("month not utf8, {}", err))); 261 | }, 262 | }; 263 | let day = match ::std::str::from_utf8(&trading_day[6..8]) { 264 | Ok(day_str) => { 265 | match day_str.parse::() { 266 | Ok(day) => day, 267 | Err(err) => { 268 | return Err(SimpleError::new(format!("invalid day string, {}", err))); 269 | }, 270 | } 271 | }, 272 | Err(err) => { 273 | return Err(SimpleError::new(format!("day not utf8, {}", err))); 274 | }, 275 | }; 276 | let hour = match ::std::str::from_utf8(&update_time[0..2]) { 277 | Ok(hour_str) => { 278 | match hour_str.parse::() { 279 | Ok(hour) => hour, 280 | Err(err) => { 281 | return Err(SimpleError::new(format!("invalid hour string, {}", err))); 282 | }, 283 | } 284 | }, 285 | Err(err) => { 286 | return Err(SimpleError::new(format!("hour not utf8, {}", err))); 287 | }, 288 | }; 289 | let minute = match ::std::str::from_utf8(&update_time[3..5]) { 290 | Ok(minute_str) => { 291 | match minute_str.parse::() { 292 | Ok(minute) => minute, 293 | Err(err) => { 294 | return Err(SimpleError::new(format!("invalid minute string, {}", err))); 295 | }, 296 | } 297 | }, 298 | Err(err) => { 299 | return Err(SimpleError::new(format!("minute not utf8, {}", err))); 300 | }, 301 | }; 302 | let second = match ::std::str::from_utf8(&update_time[6..8]) { 303 | Ok(second_str) => { 304 | match second_str.parse::() { 305 | Ok(second) => second, 306 | Err(err) => { 307 | return Err(SimpleError::new(format!("invalid second string, {}", err))); 308 | }, 309 | } 310 | }, 311 | Err(err) => { 312 | return Err(SimpleError::new(format!("second not utf8, {}", err))); 313 | }, 314 | }; 315 | let nanosec = *update_millisec as i32 * 1000 * 1000; 316 | let tm = Tm { tm_sec: second as i32, 317 | tm_min: minute as i32, 318 | tm_hour: hour as i32 - 8, // UTC+8 319 | tm_mday: day as i32, 320 | tm_mon: month as i32 - 1, 321 | tm_year: year as i32 - 1900, 322 | tm_wday: 0i32, 323 | tm_yday: 0i32, 324 | tm_isdst: 0i32, 325 | tm_utcoff: 0i32, 326 | tm_nsec: nanosec }; 327 | Ok(tm.to_timespec()) 328 | } 329 | 330 | pub fn set_cstr_from_str(buffer: &mut [u8], text: &str) -> Result<(), SimpleError> { 331 | if let Some(i) = memchr::memchr(0, text.as_bytes()) { 332 | return Err(SimpleError::new(format!("null found in str at offset {} when filling cstr", i))); 333 | } 334 | if text.len() + 1 > buffer.len() { 335 | return Err(SimpleError::new(format!("str len {} too long when filling cstr with buffer len {}", text.len(), buffer.len()))); 336 | } 337 | unsafe { 338 | std::ptr::copy_nonoverlapping(text.as_ptr(), buffer.as_mut_ptr(), text.len()); 339 | *buffer.get_unchecked_mut(text.len()) = 0u8; 340 | } 341 | Ok(()) 342 | } 343 | 344 | pub fn set_cstr_from_str_truncate(buffer: &mut [u8], text: &str) { 345 | for (place, data) in buffer.split_last_mut().expect("buffer len 0 in set_cstr_from_str_truncate").1.iter_mut().zip(text.as_bytes().iter()) { 346 | *place = *data; 347 | } 348 | unsafe { 349 | *buffer.get_unchecked_mut(text.len()) = 0u8; 350 | } 351 | } 352 | 353 | pub fn normalize_double(d: f64) -> Option { 354 | if d == std::f64::MAX { 355 | None 356 | } else { 357 | Some(d) 358 | } 359 | } 360 | 361 | #[cfg(test)] 362 | mod tests { 363 | use std::borrow::Cow; 364 | use time::Timespec; 365 | use super::ascii_cstr_to_str; 366 | use super::gb18030_cstr_to_str; 367 | use super::to_exchange_timestamp; 368 | use super::{ set_cstr_from_str, set_cstr_from_str_truncate }; 369 | use super::CThostFtdcDepthMarketDataField; 370 | 371 | #[test] 372 | fn len_0_ascii_cstr_to_str() { 373 | assert!(ascii_cstr_to_str(b"").is_err()); 374 | } 375 | 376 | #[test] 377 | fn ascii_cstr_to_str_trivial() { 378 | assert_eq!(ascii_cstr_to_str(b"hello\0"), Ok("hello")); 379 | } 380 | 381 | #[test] 382 | fn non_null_terminated_ascii_cstr_to_str() { 383 | assert!(ascii_cstr_to_str(b"hello").is_err()); 384 | } 385 | 386 | #[test] 387 | fn non_ascii_cstr_to_str() { 388 | assert!(ascii_cstr_to_str(b"\xd5\xfd\xc8\xb7\0").is_err()); 389 | } 390 | 391 | #[test] 392 | fn cstr_conversion_empty_str() { 393 | match gb18030_cstr_to_str(b"") { 394 | Cow::Borrowed::(s) => assert_eq!(s, ""), 395 | Cow::Owned::(_) => panic!("ascii str should not allocate"), 396 | }; 397 | match gb18030_cstr_to_str(b"\0") { 398 | Cow::Borrowed::(s) => assert_eq!(s, ""), 399 | Cow::Owned::(_) => panic!("ascii str should not allocate"), 400 | }; 401 | } 402 | 403 | #[test] 404 | fn cstr_conversion_ascii() { 405 | match gb18030_cstr_to_str(b"ascii") { 406 | Cow::Borrowed::(s) => assert_eq!(s, "ascii"), 407 | Cow::Owned::(_) => panic!("ascii str should not allocate"), 408 | }; 409 | } 410 | 411 | #[test] 412 | fn cstr_conversion_ascii_cstr() { 413 | match gb18030_cstr_to_str(b"ascii\0") { 414 | Cow::Borrowed::(s) => assert_eq!(s, "ascii"), 415 | Cow::Owned::(_) => panic!("ascii str should not allocate"), 416 | }; 417 | } 418 | 419 | #[test] 420 | fn cstr_conversion_gb2312() { 421 | assert_eq!(gb18030_cstr_to_str(b"\xd5\xfd\xc8\xb7"), "正确"); 422 | } 423 | 424 | #[test] 425 | fn cstr_conversion_gb2312_cstr() { 426 | assert_eq!(gb18030_cstr_to_str(b"\xd5\xfd\xc8\xb7\0"), "正确"); 427 | } 428 | 429 | #[test] 430 | fn fill_cstr_with_str() { 431 | let mut buffer: [u8; 8] = Default::default(); 432 | set_cstr_from_str(buffer.as_mut(), "hello").unwrap(); 433 | assert_eq!(buffer.as_ref(), b"hello\0\0\0"); 434 | } 435 | 436 | #[test] 437 | fn fill_cstr_with_long_str() { 438 | let mut buffer: [u8; 1] = Default::default(); 439 | assert!(set_cstr_from_str(buffer.as_mut(), "hello").is_err()); 440 | } 441 | 442 | #[test] 443 | fn fill_cstr_with_str_containing_null() { 444 | let mut buffer: [u8; 8] = Default::default(); 445 | assert!(set_cstr_from_str(buffer.as_mut(), "he\0llo").is_err()); 446 | } 447 | 448 | #[test] 449 | fn fill_cstr_with_str_truncate() { 450 | let mut buffer: [u8; 8] = Default::default(); 451 | set_cstr_from_str_truncate(buffer.as_mut(), "hello"); 452 | assert_eq!(buffer.as_ref(), b"hello\0\0\0"); 453 | } 454 | 455 | #[test] 456 | #[should_panic] 457 | fn fill_0_len_cstr_with_str_truncate_panic() { 458 | let mut buffer: [u8; 0] = Default::default(); 459 | set_cstr_from_str_truncate(buffer.as_mut(), "hello"); 460 | } 461 | 462 | #[test] 463 | fn fill_cstr_with_long_str_truncate() { 464 | let mut buffer: [u8; 6] = Default::default(); 465 | set_cstr_from_str_truncate(buffer.as_mut(), "hello world"); 466 | assert_eq!(buffer.as_ref(), b"hello\0"); 467 | } 468 | 469 | #[test] 470 | fn exchange_timestamp_conversion() { 471 | let mut md: CThostFtdcDepthMarketDataField = Default::default(); 472 | md.TradingDay = *b"19700101\0"; 473 | md.UpdateTime = *b"08:00:00\0"; 474 | md.UpdateMillisec = 0; 475 | let ts1 = to_exchange_timestamp(&md.TradingDay, &md.UpdateTime, &md.UpdateMillisec); 476 | assert_eq!(Ok(Timespec{ sec: 0, nsec: 0 }), ts1); 477 | md.TradingDay = *b"19700102\0"; 478 | md.UpdateTime = *b"00:00:00\0"; 479 | let ts2 = to_exchange_timestamp(&md.TradingDay, &md.UpdateTime, &md.UpdateMillisec); 480 | assert_eq!(Ok(Timespec{ sec: 57600, nsec: 0 }), ts2); 481 | } 482 | } 483 | -------------------------------------------------------------------------------- /ctp-md/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctp-md" 3 | version = "0.10.0" 4 | authors = ["Wangshan Lu "] 5 | edition = "2018" 6 | links = "ctp-md" 7 | 8 | [features] 9 | channel = ["crossbeam-channel"] 10 | 11 | [dependencies] 12 | crossbeam-channel = { version = "0.4.0", optional = true } 13 | 14 | [dependencies.ctp-common] 15 | version = "0.9.0" 16 | path = "../ctp-common" 17 | -------------------------------------------------------------------------------- /ctp-md/build.rs: -------------------------------------------------------------------------------- 1 | const SO_FILENAME: &str = "thostmduserapi_se.so"; 2 | 3 | fn main() { 4 | let out_dir = std::env::var("OUT_DIR").unwrap(); 5 | let current_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); 6 | let so_symlink_string = format!("{}/lib{}", out_dir, SO_FILENAME); 7 | let so_symlink = std::path::Path::new(&so_symlink_string); 8 | if so_symlink.exists() { 9 | std::fs::remove_file(so_symlink).expect("symlink exists, but failed to remove it"); 10 | } 11 | std::os::unix::fs::symlink(&format!("{}/../api-ctp/lib/{}", current_dir, SO_FILENAME), so_symlink).expect("failed to create new symlink"); 12 | println!("cargo:rustc-link-search=native={}", out_dir); 13 | let target_so = format!("{}/{}", out_dir, SO_FILENAME); 14 | std::fs::copy(&format!("{}/../api-ctp/lib/{}", current_dir, SO_FILENAME), &target_so).expect("failed to copy so to outdir"); 15 | println!("cargo:resource={}", target_so); 16 | } 17 | -------------------------------------------------------------------------------- /ctp-md/examples/api-md.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::CString; 2 | 3 | use ctp_md::*; 4 | 5 | struct Spi; 6 | impl MdSpi for Spi { 7 | } 8 | 9 | fn main() { 10 | let flow_path = ::std::ffi::CString::new("").unwrap(); 11 | let mut md_api = MdApi::new(flow_path, false, false); 12 | md_api.register_spi(Box::new(Spi)); 13 | md_api.register_front(std::ffi::CString::new("tcp://180.168.146.187:10031").unwrap()); 14 | md_api.init(); 15 | std::thread::sleep(std::time::Duration::from_secs(1)); 16 | match md_api.req_user_login(&Default::default(), 1) { 17 | Ok(()) => println!("req_user_login ok"), 18 | Err(err) => println!("req_user_login err: {:?}", err), 19 | }; 20 | std::thread::sleep(std::time::Duration::from_secs(1)); 21 | let instrument_ids = vec!(CString::new("IF1703").unwrap(), 22 | CString::new("au1712").unwrap(), 23 | CString::new("m1709").unwrap(), 24 | CString::new("CF709").unwrap()); 25 | match md_api.subscribe_market_data(&instrument_ids.clone()) { 26 | Ok(()) => println!("subscribe_market_data ok"), 27 | Err(err) => println!("subscribe_market_data err: {:?}", err), 28 | }; 29 | std::thread::sleep(std::time::Duration::from_secs(1)); 30 | match md_api.subscribe_for_quote_rsp(&instrument_ids) { 31 | Ok(()) => println!("subscribe_for_quote_rsp ok"), 32 | Err(err) => println!("subscribe_for_quote_rsp err: {:?}", err), 33 | }; 34 | std::thread::sleep(std::time::Duration::from_secs(5)); 35 | /* 36 | match md_api.req_user_logout(&Default::default(), 2) { 37 | Ok(()) => println!("req_user_logout ok"), 38 | Err(err) => println!("req_user_logout err: {:?}", err), 39 | }; 40 | std::thread::sleep(std::time::Duration::from_secs(1)); 41 | */ 42 | } 43 | -------------------------------------------------------------------------------- /ctp-md/src/channel.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel as channel; 2 | 3 | use std::time::SystemTime; 4 | 5 | use super::*; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct ChannelMdSpi { 9 | sender: channel::Sender<(MdSpiOutput, SystemTime)>, 10 | } 11 | 12 | impl ChannelMdSpi { 13 | pub fn new(sender: channel::Sender<(MdSpiOutput, SystemTime)>) -> Self { 14 | ChannelMdSpi { 15 | sender, 16 | } 17 | } 18 | } 19 | 20 | impl MdSpi for ChannelMdSpi { 21 | fn on_front_connected(&mut self) { 22 | self.sender.send((MdSpiOutput::FrontConnected(MdSpiOnFrontConnected{ }), SystemTime::now())).expect("cannot send md spi") 23 | } 24 | 25 | fn on_front_disconnected(&mut self, reason: DisconnectionReason) { 26 | self.sender.send((MdSpiOutput::FrontDisconnected(MdSpiOnFrontDisconnected{ reason }), SystemTime::now())).expect("cannot send md spi") 27 | } 28 | 29 | fn on_rsp_user_login(&mut self, rsp_user_login: Option<&CThostFtdcRspUserLoginField>, result: RspResult, request_id: i32, is_last: bool) { 30 | self.sender.send((MdSpiOutput::RspUserLogin(MdSpiOnRspUserLogin{ user_login: rsp_user_login.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 31 | } 32 | 33 | fn on_rsp_user_logout(&mut self, rsp_user_logout: Option<&CThostFtdcUserLogoutField>, result: RspResult, request_id: i32, is_last: bool) { 34 | self.sender.send((MdSpiOutput::RspUserLogout(MdSpiOnRspUserLogout{ user_logout: rsp_user_logout.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 35 | } 36 | 37 | fn on_rsp_error(&mut self, result: RspResult, request_id: i32, is_last: bool) { 38 | self.sender.send((MdSpiOutput::RspError(MdSpiOnRspError{ result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 39 | } 40 | 41 | fn on_rsp_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: i32, is_last: bool) { 42 | self.sender.send((MdSpiOutput::SubMarketData(MdSpiOnRspSubMarketData{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 43 | } 44 | 45 | fn on_rsp_un_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: i32, is_last: bool) { 46 | self.sender.send((MdSpiOutput::UnSubMarketData(MdSpiOnRspUnSubMarketData{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 47 | } 48 | 49 | fn on_rsp_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 50 | self.sender.send((MdSpiOutput::SubForQuoteRsp(MdSpiOnRspSubForQuoteRsp{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 51 | } 52 | 53 | fn on_rsp_un_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 54 | self.sender.send((MdSpiOutput::UnSubForQuoteRsp(MdSpiOnRspUnSubForQuoteRsp{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send md spi") 55 | } 56 | 57 | fn on_rtn_depth_market_data(&mut self, depth_market_data: Option<&CThostFtdcDepthMarketDataField>) { 58 | self.sender.send((MdSpiOutput::DepthMarketData(MdSpiOnRtnDepthMarketData{ depth_market_data: *depth_market_data.expect("depth_market_data is none") }), SystemTime::now())).expect("cannot send md spi") 59 | } 60 | 61 | fn on_rtn_for_quote_rsp(&mut self, for_quote_rsp: Option<&CThostFtdcForQuoteRspField>) { 62 | self.sender.send((MdSpiOutput::ForQuoteRsp(MdSpiOnRtnForQuoteRsp{ for_quote_rsp: *for_quote_rsp.expect("for_quote_rsp is none") }), SystemTime::now())).expect("cannot send md spi") 63 | } 64 | } 65 | -------------------------------------------------------------------------------- /ctp-md/src/lib.rs: -------------------------------------------------------------------------------- 1 | use std::ffi::{ CStr, CString }; 2 | use std::os::raw::{ c_void, c_char, c_int }; 3 | use std::sync::mpsc; 4 | 5 | #[allow(non_camel_case_types)] 6 | type c_bool = std::os::raw::c_uchar; 7 | 8 | #[cfg(feature = "channel")] 9 | mod channel; 10 | #[cfg(feature = "channel")] 11 | pub use channel::*; 12 | 13 | pub use ctp_common::*; 14 | 15 | #[allow(dead_code)] 16 | #[link(name = "thostmduserapi_se")] 17 | extern "C" { 18 | #[link_name = "_ZN15CThostFtdcMdApi15CreateFtdcMdApiEPKcbb"] 19 | fn CThostFtdcMdApiCreateFtdcMdApi(pszFlowPath: *const c_char, bIsUsingUdp: c_bool, bIsMulticast: c_bool) -> *mut c_void; 20 | #[link_name = "_ZN15CThostFtdcMdApi13GetApiVersionEv"] 21 | fn CThostFtdcMdApiGetApiVersion() -> *const c_char; 22 | #[link_name = "_ZN14CFtdcMdApiImpl7ReleaseEv"] 23 | fn CFtdcMdApiImplRelease(api: *mut c_void); 24 | #[link_name = "_ZN14CFtdcMdApiImpl4InitEv"] 25 | fn CFtdcMdApiImplInit(api: *mut c_void); 26 | #[link_name = "_ZN14CFtdcMdApiImpl4JoinEv"] 27 | fn CFtdcMdApiImplJoin(api: *mut c_void) -> c_int; 28 | #[link_name = "_ZN14CFtdcMdApiImpl13GetTradingDayEv"] 29 | fn CFtdcMdApiImplGetTradingDay(api: *mut c_void) -> *const c_char; 30 | #[link_name = "_ZN14CFtdcMdApiImpl13RegisterFrontEPc"] 31 | fn CFtdcMdApiImplRegisterFront(api: *mut c_void, pszFrontAddress: *const c_char); 32 | #[link_name = "_ZN14CFtdcMdApiImpl18RegisterNameServerEPc"] 33 | fn CFtdcMdApiImplRegisterNameServer(api: *mut c_void, pszNsAddress: *const c_char); 34 | #[link_name = "_ZN14CFtdcMdApiImpl20RegisterFensUserInfoEP27CThostFtdcFensUserInfoField"] 35 | fn CFtdcMdApiImplRegisterFensUserInfo(api: *mut c_void, pFensUserInfo: *const CThostFtdcFensUserInfoField); 36 | #[link_name = "_ZN14CFtdcMdApiImpl11RegisterSpiEP15CThostFtdcMdSpi"] 37 | fn CFtdcMdApiImplRegisterSpi(api: *mut c_void, pSpi: *mut c_void); 38 | #[link_name = "_ZN14CFtdcMdApiImpl19SubscribeMarketDataEPPci"] 39 | fn CFtdcMdApiImplSubscribeMarketData(api: *mut c_void, ppInstrumentID: *const *const c_char, nCount: c_int) -> c_int; 40 | #[link_name = "_ZN14CFtdcMdApiImpl21UnSubscribeMarketDataEPPci"] 41 | fn CFtdcMdApiImplUnSubscribeMarketData(api: *mut c_void, ppInstrumentID: *const *const c_char, nCount: c_int) -> c_int; 42 | #[link_name = "_ZN14CFtdcMdApiImpl20SubscribeForQuoteRspEPPci"] 43 | fn CFtdcMdApiImplSubscribeForQuoteRsp(api: *mut c_void, ppInstrumentID: *const *const c_char, nCount: c_int) -> c_int; 44 | #[link_name = "_ZN14CFtdcMdApiImpl22UnSubscribeForQuoteRspEPPci"] 45 | fn CFtdcMdApiImplUnSubscribeForQuoteRsp(api: *mut c_void, ppInstrumentID: *const *const c_char, nCount: c_int) -> c_int; 46 | #[link_name = "_ZN14CFtdcMdApiImpl12ReqUserLoginEP27CThostFtdcReqUserLoginFieldi"] 47 | fn CFtdcMdApiImplReqUserLogin(api: *mut c_void, pReqUserLoginField: *const CThostFtdcReqUserLoginField, nRequestID: c_int) -> c_int; 48 | #[link_name = "_ZN14CFtdcMdApiImpl13ReqUserLogoutEP25CThostFtdcUserLogoutFieldi"] 49 | fn CFtdcMdApiImplReqUserLogout(api: *mut c_void, pUserLogoutField: *const CThostFtdcUserLogoutField, nRequestID: c_int) -> c_int; 50 | } 51 | 52 | pub trait GenericMdApi { 53 | fn new(flow_path: CString, use_udp: bool, use_multicast: bool) -> Self; 54 | fn init(&mut self); 55 | fn join(&mut self) -> ApiResult; 56 | fn get_trading_day<'a>(&mut self) -> &'a CStr; 57 | fn register_front(&mut self, front_socket_address: CString); 58 | fn register_name_server(&mut self, name_server: CString); 59 | fn register_fens_user_info(&mut self, fens_user_info: &CThostFtdcFensUserInfoField); 60 | fn register_spi(&mut self, md_spi: Box); 61 | fn subscribe_market_data(&mut self, instrument_ids: &[CString]) -> ApiResult; 62 | fn unsubscribe_market_data(&mut self, instrument_ids: &[CString]) -> ApiResult; 63 | fn subscribe_for_quote_rsp(&mut self, instrument_ids: &[CString]) -> ApiResult; 64 | fn unsubscribe_for_quote_rsp(&mut self, instrument_ids: &[CString]) -> ApiResult; 65 | fn req_user_login(&mut self, req_user_login: &CThostFtdcReqUserLoginField, request_id: TThostFtdcRequestIDType) -> ApiResult; 66 | fn req_user_logout(&mut self, req_user_logout: &CThostFtdcUserLogoutField, request_id: TThostFtdcRequestIDType) -> ApiResult; 67 | } 68 | 69 | #[derive(Debug)] 70 | pub struct MdApi { 71 | md_api_ptr: *mut c_void, 72 | registered_spi: Option<*mut CThostFtdcMdSpi>, 73 | } 74 | 75 | unsafe impl Send for MdApi {} 76 | 77 | impl GenericMdApi for MdApi { 78 | fn new(flow_path: CString, use_udp: bool, use_multicast: bool) -> Self { 79 | let flow_path_ptr = flow_path.into_raw(); 80 | let api = unsafe { CThostFtdcMdApiCreateFtdcMdApi(flow_path_ptr, use_udp as c_bool, use_multicast as c_bool) }; 81 | let flow_path = unsafe { CString::from_raw(flow_path_ptr) }; 82 | drop(flow_path); 83 | MdApi{ md_api_ptr: api, registered_spi: None } 84 | } 85 | 86 | fn init(&mut self) { 87 | unsafe { CFtdcMdApiImplInit(self.md_api_ptr) }; 88 | } 89 | 90 | fn join(&mut self) -> ApiResult { 91 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplJoin(self.md_api_ptr) }) 92 | } 93 | 94 | fn get_trading_day<'a>(&mut self) -> &'a CStr { 95 | let trading_day_cstr = unsafe { CFtdcMdApiImplGetTradingDay(self.md_api_ptr) }; 96 | unsafe { CStr::from_ptr(trading_day_cstr) } 97 | } 98 | 99 | fn register_front(&mut self, front_socket_address: CString) { 100 | let front_socket_address_ptr = front_socket_address.into_raw(); 101 | unsafe { CFtdcMdApiImplRegisterFront(self.md_api_ptr, front_socket_address_ptr) }; 102 | let front_socket_address = unsafe { CString::from_raw(front_socket_address_ptr) }; 103 | drop(front_socket_address); 104 | } 105 | 106 | fn register_name_server(&mut self, name_server: CString) { 107 | let name_server_ptr = name_server.into_raw(); 108 | unsafe { CFtdcMdApiImplRegisterNameServer(self.md_api_ptr, name_server_ptr) }; 109 | let name_server = unsafe { CString::from_raw(name_server_ptr) }; 110 | drop(name_server); 111 | } 112 | 113 | fn register_fens_user_info(&mut self, fens_user_info: &CThostFtdcFensUserInfoField) { 114 | unsafe { CFtdcMdApiImplRegisterFensUserInfo(self.md_api_ptr, fens_user_info) }; 115 | } 116 | 117 | fn register_spi(&mut self, md_spi: Box) { 118 | let last_registered_spi_ptr = self.registered_spi.take(); 119 | let md_spi_ptr = Box::into_raw(md_spi); 120 | let spi_ptr = Box::into_raw(Box::new(new_spi(md_spi_ptr))); 121 | unsafe { CFtdcMdApiImplRegisterSpi(self.md_api_ptr, spi_ptr as *mut c_void) }; 122 | self.registered_spi = Some(spi_ptr); 123 | if let Some(last_registered_spi_ptr) = last_registered_spi_ptr { 124 | unsafe { 125 | let last_registered_spi = Box::from_raw(last_registered_spi_ptr); 126 | let md_spi = Box::from_raw(last_registered_spi.md_spi_ptr); 127 | drop(md_spi); 128 | drop(last_registered_spi); 129 | } 130 | }; 131 | } 132 | 133 | fn subscribe_market_data(&mut self, instrument_ids: &[CString]) -> ApiResult { 134 | let v = cstring_slice_to_char_star_vec(instrument_ids); 135 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplSubscribeMarketData(self.md_api_ptr, v.as_ptr(), v.len() as c_int) }) 136 | } 137 | 138 | fn unsubscribe_market_data(&mut self, instrument_ids: &[CString]) -> ApiResult { 139 | let v = cstring_slice_to_char_star_vec(instrument_ids); 140 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplUnSubscribeMarketData(self.md_api_ptr, v.as_ptr(), v.len() as c_int) }) 141 | } 142 | 143 | fn subscribe_for_quote_rsp(&mut self, instrument_ids: &[CString]) -> ApiResult { 144 | let v = cstring_slice_to_char_star_vec(instrument_ids); 145 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplSubscribeForQuoteRsp(self.md_api_ptr, v.as_ptr(), v.len() as c_int) }) 146 | } 147 | 148 | fn unsubscribe_for_quote_rsp(&mut self, instrument_ids: &[CString]) -> ApiResult { 149 | let v = cstring_slice_to_char_star_vec(instrument_ids); 150 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplUnSubscribeForQuoteRsp(self.md_api_ptr, v.as_ptr(), v.len() as c_int) }) 151 | } 152 | 153 | fn req_user_login(&mut self, req_user_login: &CThostFtdcReqUserLoginField, request_id: TThostFtdcRequestIDType) -> ApiResult { 154 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplReqUserLogin(self.md_api_ptr, req_user_login, request_id) }) 155 | } 156 | 157 | fn req_user_logout(&mut self, req_user_logout: &CThostFtdcUserLogoutField, request_id: TThostFtdcRequestIDType) -> ApiResult { 158 | from_api_return_to_api_result(unsafe { CFtdcMdApiImplReqUserLogout(self.md_api_ptr, req_user_logout, request_id) }) 159 | } 160 | } 161 | 162 | impl Drop for MdApi { 163 | fn drop(&mut self) { 164 | let last_registered_spi_ptr = self.registered_spi.take(); 165 | if let Some(last_registered_spi_ptr) = last_registered_spi_ptr { 166 | unsafe { 167 | CFtdcMdApiImplRegisterSpi(self.md_api_ptr, ::std::ptr::null_mut::()); 168 | let last_registered_spi = Box::from_raw(last_registered_spi_ptr); 169 | let md_spi = Box::from_raw(last_registered_spi.md_spi_ptr); 170 | drop(md_spi); 171 | drop(last_registered_spi); 172 | } 173 | }; 174 | unsafe { 175 | if !self.md_api_ptr.is_null() { 176 | CFtdcMdApiImplRelease(self.md_api_ptr) 177 | } 178 | }; 179 | } 180 | } 181 | 182 | fn cstring_slice_to_char_star_vec(cstring_vec: &[CString]) -> Vec<*const c_char> { 183 | cstring_vec.iter().map(|cstring| cstring.as_ptr()).collect() 184 | } 185 | 186 | pub trait MdSpi : Send { 187 | fn on_front_connected(&mut self) { 188 | println!("on_front_connected"); 189 | } 190 | 191 | fn on_front_disconnected(&mut self, reason: DisconnectionReason) { 192 | println!("on_front_disconnected: {:?}", reason); 193 | } 194 | 195 | #[allow(unused_variables)] 196 | fn on_rsp_user_login(&mut self, rsp_user_login: Option<&CThostFtdcRspUserLoginField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 197 | println!("on_rsp_user_login: {:?}, {}, {:?}, {:?}", rsp_user_login, from_rsp_result_to_string(&result), request_id, is_last); 198 | } 199 | 200 | #[allow(unused_variables)] 201 | fn on_rsp_user_logout(&mut self, rsp_user_logout: Option<&CThostFtdcUserLogoutField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 202 | println!("on_rsp_user_logout: {:?}, {}, {:?}, {:?}", rsp_user_logout, from_rsp_result_to_string(&result), request_id, is_last); 203 | } 204 | 205 | #[allow(unused_variables)] 206 | fn on_rsp_error(&mut self, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 207 | println!("on_rsp_error: {}, {:?}, {:?}", from_rsp_result_to_string(&result), request_id, is_last); 208 | } 209 | 210 | #[allow(unused_variables)] 211 | fn on_rsp_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 212 | println!("on_rsp_sub_market_data: {:?}, {}, {:?}, {:?}", specific_instrument, from_rsp_result_to_string(&result), request_id, is_last); 213 | } 214 | 215 | #[allow(unused_variables)] 216 | fn on_rsp_un_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 217 | println!("on_rsp_un_sub_market_data: {:?}, {}, {:?}, {:?}", specific_instrument, from_rsp_result_to_string(&result), request_id, is_last); 218 | } 219 | 220 | #[allow(unused_variables)] 221 | fn on_rsp_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 222 | println!("on_rsp_sub_for_quote_rsp: {:?}, {}, {:?}, {:?}", specific_instrument, from_rsp_result_to_string(&result), request_id, is_last); 223 | } 224 | 225 | #[allow(unused_variables)] 226 | fn on_rsp_un_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 227 | println!("on_rsp_un_sub_for_quote_rsp: {:?}, {}, {:?}, {:?}", specific_instrument, from_rsp_result_to_string(&result), request_id, is_last); 228 | } 229 | 230 | #[allow(unused_variables)] 231 | fn on_rtn_depth_market_data(&mut self, depth_market_data: Option<&CThostFtdcDepthMarketDataField>) { 232 | println!("on_rtn_depth_market_data: {:?}", depth_market_data); 233 | } 234 | 235 | #[allow(unused_variables)] 236 | fn on_rtn_for_quote_rsp(&mut self, for_quote_rsp: Option<&CThostFtdcForQuoteRspField>) { 237 | println!("on_rtn_for_quote_rsp: {:?}", for_quote_rsp); 238 | } 239 | } 240 | 241 | #[derive(Clone, Debug)] 242 | pub struct MdSpiOnFrontConnected { 243 | } 244 | 245 | #[derive(Clone, Debug)] 246 | pub struct MdSpiOnFrontDisconnected { 247 | pub reason: DisconnectionReason, 248 | } 249 | 250 | #[derive(Clone, Debug)] 251 | pub struct MdSpiOnRspUserLogin { 252 | pub user_login: Option, 253 | pub result: RspResult, 254 | pub request_id: TThostFtdcRequestIDType, 255 | pub is_last: bool, 256 | } 257 | 258 | #[derive(Clone, Debug)] 259 | pub struct MdSpiOnRspUserLogout { 260 | pub user_logout: Option, 261 | pub result: RspResult, 262 | pub request_id: TThostFtdcRequestIDType, 263 | pub is_last: bool, 264 | } 265 | 266 | #[derive(Clone, Debug)] 267 | pub struct MdSpiOnRspError { 268 | pub result: RspResult, 269 | pub request_id: TThostFtdcRequestIDType, 270 | pub is_last: bool, 271 | } 272 | 273 | #[derive(Clone, Debug)] 274 | pub struct MdSpiOnRspSubMarketData { 275 | pub specific_instrument: Option, 276 | pub result: RspResult, 277 | pub request_id: TThostFtdcRequestIDType, 278 | pub is_last: bool, 279 | } 280 | 281 | #[derive(Clone, Debug)] 282 | pub struct MdSpiOnRspUnSubMarketData { 283 | pub specific_instrument: Option, 284 | pub result: RspResult, 285 | pub request_id: TThostFtdcRequestIDType, 286 | pub is_last: bool, 287 | } 288 | 289 | #[derive(Clone, Debug)] 290 | pub struct MdSpiOnRspSubForQuoteRsp { 291 | pub specific_instrument: Option, 292 | pub result: RspResult, 293 | pub request_id: TThostFtdcRequestIDType, 294 | pub is_last: bool, 295 | } 296 | 297 | #[derive(Clone, Debug)] 298 | pub struct MdSpiOnRspUnSubForQuoteRsp { 299 | pub specific_instrument: Option, 300 | pub result: RspResult, 301 | pub request_id: TThostFtdcRequestIDType, 302 | pub is_last: bool, 303 | } 304 | 305 | #[derive(Clone, Debug)] 306 | pub struct MdSpiOnRtnDepthMarketData { 307 | pub depth_market_data: CThostFtdcDepthMarketDataField, 308 | } 309 | 310 | #[derive(Clone, Debug)] 311 | pub struct MdSpiOnRtnForQuoteRsp { 312 | pub for_quote_rsp: CThostFtdcForQuoteRspField, 313 | } 314 | 315 | #[allow(clippy::large_enum_variant)] // For consistency 316 | #[derive(Clone, Debug)] 317 | pub enum MdSpiOutput { 318 | FrontConnected(MdSpiOnFrontConnected), 319 | RspUserLogin(MdSpiOnRspUserLogin), 320 | FrontDisconnected(MdSpiOnFrontDisconnected), 321 | RspUserLogout(MdSpiOnRspUserLogout), 322 | RspError(MdSpiOnRspError), 323 | SubMarketData(MdSpiOnRspSubMarketData), 324 | UnSubMarketData(MdSpiOnRspUnSubMarketData), 325 | SubForQuoteRsp(MdSpiOnRspSubForQuoteRsp), 326 | UnSubForQuoteRsp(MdSpiOnRspUnSubForQuoteRsp), 327 | DepthMarketData(MdSpiOnRtnDepthMarketData), 328 | ForQuoteRsp(MdSpiOnRtnForQuoteRsp), 329 | } 330 | 331 | #[derive(Clone, Debug)] 332 | pub struct SenderMdSpi + Send + 'static> { 333 | sender: mpsc::Sender, 334 | } 335 | 336 | impl SenderMdSpi where T: From + Send + 'static { 337 | pub fn new(sender: mpsc::Sender) -> Self { 338 | SenderMdSpi { 339 | sender, 340 | } 341 | } 342 | } 343 | 344 | impl MdSpi for SenderMdSpi where T: From + Send + 'static { 345 | fn on_front_connected(&mut self) { 346 | self.sender.send(T::from(MdSpiOutput::FrontConnected(MdSpiOnFrontConnected{ }))).expect("spi callback send front_connected failed"); 347 | } 348 | 349 | fn on_front_disconnected(&mut self, reason: DisconnectionReason) { 350 | self.sender.send(T::from(MdSpiOutput::FrontDisconnected(MdSpiOnFrontDisconnected{ reason }))).expect("spi callback send front_disconnected failed"); 351 | } 352 | 353 | fn on_rsp_user_login(&mut self, rsp_user_login: Option<&CThostFtdcRspUserLoginField>, result: RspResult, request_id: i32, is_last: bool) { 354 | self.sender.send(T::from(MdSpiOutput::RspUserLogin(MdSpiOnRspUserLogin{ user_login: rsp_user_login.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_user_login failed"); 355 | } 356 | 357 | fn on_rsp_user_logout(&mut self, rsp_user_logout: Option<&CThostFtdcUserLogoutField>, result: RspResult, request_id: i32, is_last: bool) { 358 | self.sender.send(T::from(MdSpiOutput::RspUserLogout(MdSpiOnRspUserLogout{ user_logout: rsp_user_logout.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_user_logout failed"); 359 | } 360 | 361 | fn on_rsp_error(&mut self, result: RspResult, request_id: i32, is_last: bool) { 362 | self.sender.send(T::from(MdSpiOutput::RspError(MdSpiOnRspError{ result, request_id, is_last }))).expect("spi callback send rsp_error failed"); 363 | } 364 | 365 | fn on_rsp_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: i32, is_last: bool) { 366 | self.sender.send(T::from(MdSpiOutput::SubMarketData(MdSpiOnRspSubMarketData{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_sub_market_data failed"); 367 | } 368 | 369 | fn on_rsp_un_sub_market_data(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: i32, is_last: bool) { 370 | self.sender.send(T::from(MdSpiOutput::UnSubMarketData(MdSpiOnRspUnSubMarketData{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_sub_market_data failed"); 371 | } 372 | 373 | fn on_rsp_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 374 | self.sender.send(T::from(MdSpiOutput::SubForQuoteRsp(MdSpiOnRspSubForQuoteRsp{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_sub_sub_for_quote_rsp failed"); 375 | } 376 | 377 | fn on_rsp_un_sub_for_quote_rsp(&mut self, specific_instrument: Option<&CThostFtdcSpecificInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 378 | self.sender.send(T::from(MdSpiOutput::UnSubForQuoteRsp(MdSpiOnRspUnSubForQuoteRsp{ specific_instrument: specific_instrument.cloned(), result, request_id, is_last }))).expect("spi callback send rsp_sub_sub_for_quote_rsp failed"); 379 | } 380 | 381 | fn on_rtn_depth_market_data(&mut self, depth_market_data: Option<&CThostFtdcDepthMarketDataField>) { 382 | self.sender.send(T::from(MdSpiOutput::DepthMarketData(MdSpiOnRtnDepthMarketData{ depth_market_data: *depth_market_data.expect("depth_market_data is none") }))).expect("spi callback send depth_market_data failed"); 383 | } 384 | 385 | fn on_rtn_for_quote_rsp(&mut self, for_quote_rsp: Option<&CThostFtdcForQuoteRspField>) { 386 | self.sender.send(T::from(MdSpiOutput::ForQuoteRsp(MdSpiOnRtnForQuoteRsp{ for_quote_rsp: *for_quote_rsp.expect("for_quote_rsp is none") }))).expect("spi callback send depth_market_data failed"); 387 | } 388 | } 389 | 390 | #[allow(non_snake_case)] 391 | extern "C" fn spi_on_front_connected(spi: *mut CThostFtdcMdSpi) { 392 | unsafe { (*(*spi).md_spi_ptr).on_front_connected() }; 393 | } 394 | 395 | #[allow(non_snake_case)] 396 | extern "C" fn spi_on_front_disconnected(spi: *mut CThostFtdcMdSpi, nReason: c_int) { 397 | let reason = std::convert::From::from(nReason); 398 | unsafe { (*(*spi).md_spi_ptr).on_front_disconnected(reason) }; 399 | } 400 | 401 | #[allow(non_snake_case, unused_variables)] 402 | extern "C" fn spi_on_heart_beat_warning(spi: *mut CThostFtdcMdSpi, nTimeLapse: c_int) { 403 | // CTP API specification shows this will never be called 404 | unreachable!(); 405 | } 406 | 407 | #[allow(non_snake_case)] 408 | extern "C" fn spi_on_rsp_user_login(spi: *mut CThostFtdcMdSpi, pRspUserLogin: *const CThostFtdcRspUserLoginField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 409 | unsafe { 410 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 411 | (*(*spi).md_spi_ptr).on_rsp_user_login(pRspUserLogin.as_ref(), rsp_info, nRequestID, bIsLast != 0); 412 | } 413 | } 414 | 415 | #[allow(non_snake_case)] 416 | extern "C" fn spi_on_rsp_user_logout(spi: *mut CThostFtdcMdSpi, pUserLogout: *const CThostFtdcUserLogoutField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 417 | unsafe { 418 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 419 | (*(*spi).md_spi_ptr).on_rsp_user_logout(pUserLogout.as_ref(), rsp_info, nRequestID, bIsLast != 0); 420 | } 421 | } 422 | 423 | #[allow(non_snake_case)] 424 | extern "C" fn spi_on_rsp_error(spi: *mut CThostFtdcMdSpi, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 425 | unsafe { 426 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 427 | (*(*spi).md_spi_ptr).on_rsp_error(rsp_info, nRequestID, bIsLast != 0); 428 | } 429 | } 430 | 431 | #[allow(non_snake_case)] 432 | extern "C" fn spi_on_rsp_sub_market_data(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 433 | unsafe { 434 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 435 | (*(*spi).md_spi_ptr).on_rsp_sub_market_data(pSpecificInstrument.as_ref(), rsp_info, nRequestID, bIsLast != 0); 436 | } 437 | } 438 | 439 | #[allow(non_snake_case)] 440 | extern "C" fn spi_on_rsp_un_sub_market_data(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 441 | unsafe { 442 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 443 | (*(*spi).md_spi_ptr).on_rsp_un_sub_market_data(pSpecificInstrument.as_ref(), rsp_info, nRequestID, bIsLast != 0); 444 | } 445 | } 446 | 447 | #[allow(non_snake_case)] 448 | extern "C" fn spi_on_rsp_sub_for_quote_rsp(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 449 | unsafe { 450 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 451 | (*(*spi).md_spi_ptr).on_rsp_sub_for_quote_rsp(pSpecificInstrument.as_ref(), rsp_info, nRequestID, bIsLast != 0); 452 | } 453 | } 454 | 455 | #[allow(non_snake_case)] 456 | extern "C" fn spi_on_rsp_un_sub_for_quote_rsp(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool) { 457 | unsafe { 458 | let rsp_info = from_rsp_info_to_rsp_result(pRspInfo); 459 | (*(*spi).md_spi_ptr).on_rsp_un_sub_for_quote_rsp(pSpecificInstrument.as_ref(), rsp_info, nRequestID, bIsLast != 0); 460 | } 461 | } 462 | 463 | #[allow(non_snake_case)] 464 | extern "C" fn spi_on_rtn_depth_market_data(spi: *mut CThostFtdcMdSpi, pDepthMarketData: *const CThostFtdcDepthMarketDataField ) { 465 | unsafe { (*(*spi).md_spi_ptr).on_rtn_depth_market_data(pDepthMarketData.as_ref()) }; 466 | } 467 | 468 | #[allow(non_snake_case)] 469 | extern "C" fn spi_on_rtn_for_quote_rsp(spi: *mut CThostFtdcMdSpi, pForQuoteRsp: *const CThostFtdcForQuoteRspField ) { 470 | unsafe { (*(*spi).md_spi_ptr).on_rtn_for_quote_rsp(pForQuoteRsp.as_ref()) }; 471 | } 472 | 473 | #[repr(C)] 474 | #[derive(Debug)] 475 | struct SpiVTable { 476 | #[allow(non_snake_case)] 477 | on_front_connected: extern "C" fn(spi: *mut CThostFtdcMdSpi), 478 | #[allow(non_snake_case)] 479 | on_front_disconnected: extern "C" fn(spi: *mut CThostFtdcMdSpi, nReason: c_int), 480 | #[allow(non_snake_case)] 481 | on_heart_beat_warning: extern "C" fn(spi: *mut CThostFtdcMdSpi, nTimeLapse: c_int), 482 | #[allow(non_snake_case)] 483 | on_rsp_user_login: extern "C" fn(spi: *mut CThostFtdcMdSpi, pRspUserLogin: *const CThostFtdcRspUserLoginField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 484 | #[allow(non_snake_case)] 485 | on_rsp_user_logout: extern "C" fn(spi: *mut CThostFtdcMdSpi, pUserLogout: *const CThostFtdcUserLogoutField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 486 | #[allow(non_snake_case)] 487 | on_rsp_error: extern "C" fn(spi: *mut CThostFtdcMdSpi, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 488 | #[allow(non_snake_case)] 489 | on_rsp_sub_market_data: extern "C" fn(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 490 | #[allow(non_snake_case)] 491 | on_rsp_un_sub_market_data: extern "C" fn(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 492 | #[allow(non_snake_case)] 493 | on_rsp_sub_for_quote_rsp: extern "C" fn(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 494 | #[allow(non_snake_case)] 495 | on_rsp_un_sub_for_quote_rsp: extern "C" fn(spi: *mut CThostFtdcMdSpi, pSpecificInstrument: *const CThostFtdcSpecificInstrumentField, pRspInfo: *const CThostFtdcRspInfoField, nRequestID: c_int, bIsLast: c_bool), 496 | #[allow(non_snake_case)] 497 | on_rtn_depth_market_data: extern "C" fn(spi: *mut CThostFtdcMdSpi, pDepthMarketData: *const CThostFtdcDepthMarketDataField ), 498 | #[allow(non_snake_case)] 499 | on_rtn_for_quote_rsp: extern "C" fn(spi: *mut CThostFtdcMdSpi, pForQuoteRsp: *const CThostFtdcForQuoteRspField ), 500 | } 501 | 502 | static SPI_VTABLE: SpiVTable = SpiVTable{ 503 | on_front_connected: spi_on_front_connected, 504 | on_front_disconnected: spi_on_front_disconnected, 505 | on_heart_beat_warning: spi_on_heart_beat_warning, 506 | on_rsp_user_login: spi_on_rsp_user_login, 507 | on_rsp_user_logout: spi_on_rsp_user_logout, 508 | on_rsp_error: spi_on_rsp_error, 509 | on_rsp_sub_market_data: spi_on_rsp_sub_market_data, 510 | on_rsp_un_sub_market_data: spi_on_rsp_un_sub_market_data, 511 | on_rsp_sub_for_quote_rsp: spi_on_rsp_sub_for_quote_rsp, 512 | on_rsp_un_sub_for_quote_rsp: spi_on_rsp_un_sub_for_quote_rsp, 513 | on_rtn_depth_market_data: spi_on_rtn_depth_market_data, 514 | on_rtn_for_quote_rsp: spi_on_rtn_for_quote_rsp, 515 | }; 516 | 517 | #[repr(C)] 518 | pub struct CThostFtdcMdSpi { 519 | vtable: *const SpiVTable, 520 | pub md_spi_ptr: *mut dyn MdSpi 521 | } 522 | 523 | fn new_spi(md_spi: *mut dyn MdSpi) -> CThostFtdcMdSpi { 524 | CThostFtdcMdSpi{ vtable: &SPI_VTABLE, md_spi_ptr: md_spi } 525 | } 526 | 527 | #[cfg(test)] 528 | mod tests { 529 | use super::*; 530 | use std::ffi::CString; 531 | use std::mem::size_of; 532 | 533 | #[test] 534 | fn spi_output_size() { 535 | let expected_size = 416; 536 | let actual_size = size_of::(); 537 | assert_eq!(expected_size, actual_size, "MdSpiOutput expected size {}, actual size {}", expected_size, actual_size); 538 | } 539 | 540 | #[test] 541 | fn create_release() { 542 | let flow_path = CString::new("").unwrap(); 543 | let md_api = MdApi::new(flow_path, false, false); 544 | println!("{:?}", md_api); 545 | drop(md_api); 546 | assert!(true); 547 | } 548 | } 549 | -------------------------------------------------------------------------------- /ctp-md/test_all.sh: -------------------------------------------------------------------------------- 1 | cargo test 2 | cargo test --features="channel" 3 | -------------------------------------------------------------------------------- /ctp-trader/Cargo.toml: -------------------------------------------------------------------------------- 1 | [package] 2 | name = "ctp-trader" 3 | version = "0.10.0" 4 | authors = ["Wangshan Lu "] 5 | edition = "2018" 6 | links = "ctp-trader" 7 | 8 | [features] 9 | channel = ["crossbeam-channel"] 10 | 11 | [dependencies] 12 | crossbeam-channel = { version = "0.4.0", optional = true } 13 | 14 | [dependencies.ctp-common] 15 | version = "0.9.0" 16 | path = "../ctp-common" 17 | 18 | [dev-dependencies] 19 | simple-error = "0.2.0" 20 | serde = { version = "1.0.91", features = [ "derive" ] } 21 | serde_yaml = "0.8.9" 22 | -------------------------------------------------------------------------------- /ctp-trader/build.rs: -------------------------------------------------------------------------------- 1 | const SO_FILENAME: &str = "thosttraderapi_se.so"; 2 | 3 | fn main() { 4 | let out_dir = std::env::var("OUT_DIR").unwrap(); 5 | let current_dir = std::env::var("CARGO_MANIFEST_DIR").unwrap(); 6 | let so_symlink_string = format!("{}/lib{}", out_dir, SO_FILENAME); 7 | let so_symlink = std::path::Path::new(&so_symlink_string); 8 | if so_symlink.exists() { 9 | std::fs::remove_file(so_symlink).expect("symlink exists, but failed to remove it"); 10 | } 11 | std::os::unix::fs::symlink(&format!("{}/../api-ctp/lib/{}", current_dir, SO_FILENAME), so_symlink).expect("failed to create new symlink"); 12 | println!("cargo:rustc-link-search=native={}", out_dir); 13 | let target_so = format!("{}/{}", out_dir, SO_FILENAME); 14 | std::fs::copy(&format!("{}/../api-ctp/lib/{}", current_dir, SO_FILENAME), &target_so).expect("failed to copy so to outdir"); 15 | println!("cargo:resource={}", target_so); 16 | } 17 | -------------------------------------------------------------------------------- /ctp-trader/examples/api-trader.rs: -------------------------------------------------------------------------------- 1 | use std::io::Write; 2 | 3 | use ctp_trader::*; 4 | 5 | const TRADER_FRONT: &'static str = "tcp://180.168.146.187:10030"; 6 | const BROKER_ID: &'static str = "9999"; 7 | struct Spi; 8 | impl TraderSpi for Spi { 9 | } 10 | 11 | fn new_login(user_id: &str, password: &str) -> CThostFtdcReqUserLoginField { 12 | let mut f: CThostFtdcReqUserLoginField = Default::default(); 13 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 14 | set_cstr_from_str_truncate(&mut f.UserID, user_id); 15 | set_cstr_from_str_truncate(&mut f.Password, password); 16 | f 17 | } 18 | 19 | fn new_qry_settlement_info(user_id: &str) -> CThostFtdcQrySettlementInfoField { 20 | let mut f: CThostFtdcQrySettlementInfoField = Default::default(); 21 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 22 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 23 | f 24 | } 25 | 26 | fn new_settlement_info_confirm(user_id: &str) -> CThostFtdcSettlementInfoConfirmField { 27 | let mut f: CThostFtdcSettlementInfoConfirmField = Default::default(); 28 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 29 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 30 | f 31 | } 32 | 33 | fn new_qry_settlement_info_confirm(user_id: &str) -> CThostFtdcQrySettlementInfoConfirmField { 34 | let mut f: CThostFtdcQrySettlementInfoConfirmField = Default::default(); 35 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 36 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 37 | f 38 | } 39 | 40 | fn new_qry_instrument(pattern: &str) -> CThostFtdcQryInstrumentField { 41 | let mut f: CThostFtdcQryInstrumentField = Default::default(); 42 | set_cstr_from_str_truncate(&mut f.InstrumentID, pattern); 43 | f 44 | } 45 | 46 | fn new_qry_exchange(pattern: &str) -> CThostFtdcQryExchangeField { 47 | let mut f: CThostFtdcQryExchangeField = Default::default(); 48 | set_cstr_from_str_truncate(&mut f.ExchangeID, pattern); 49 | f 50 | } 51 | 52 | fn new_qry_product(pattern: &str) -> CThostFtdcQryProductField { 53 | let mut f: CThostFtdcQryProductField = Default::default(); 54 | set_cstr_from_str_truncate(&mut f.ProductID, pattern); 55 | f 56 | } 57 | 58 | fn new_qry_order(user_id: &str) -> CThostFtdcQryOrderField { 59 | let mut f: CThostFtdcQryOrderField = Default::default(); 60 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 61 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 62 | f 63 | } 64 | 65 | fn new_qry_trade(user_id: &str) -> CThostFtdcQryTradeField { 66 | let mut f: CThostFtdcQryTradeField = Default::default(); 67 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 68 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 69 | f 70 | } 71 | 72 | fn new_qry_investor_position(user_id: &str) -> CThostFtdcQryInvestorPositionField { 73 | let mut f: CThostFtdcQryInvestorPositionField = Default::default(); 74 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 75 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 76 | f 77 | } 78 | 79 | fn new_qry_trading_account(user_id: &str) -> CThostFtdcQryTradingAccountField { 80 | let mut f: CThostFtdcQryTradingAccountField = Default::default(); 81 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 82 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 83 | f 84 | } 85 | 86 | fn new_qry_investor(user_id: &str) -> CThostFtdcQryInvestorField { 87 | let mut f: CThostFtdcQryInvestorField = Default::default(); 88 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 89 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 90 | f 91 | } 92 | 93 | fn new_qry_trading_code(user_id: &str) -> CThostFtdcQryTradingCodeField { 94 | let mut f: CThostFtdcQryTradingCodeField = Default::default(); 95 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 96 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 97 | f 98 | } 99 | 100 | fn new_input_order(user_id: &str) -> CThostFtdcInputOrderField { 101 | let mut f: CThostFtdcInputOrderField = Default::default(); 102 | set_cstr_from_str_truncate(&mut f.BrokerID, BROKER_ID); 103 | set_cstr_from_str_truncate(&mut f.InvestorID, user_id); 104 | set_cstr_from_str_truncate(&mut f.InstrumentID, "IF1703"); 105 | set_cstr_from_str_truncate(&mut f.UserID, user_id); 106 | f.Direction = THOST_FTDC_D_Buy; 107 | f.OrderPriceType = THOST_FTDC_OPT_LimitPrice; 108 | f.LimitPrice = 1f64; 109 | f.VolumeTotalOriginal = 1; 110 | f.CombOffsetFlag[0] = THOST_FTDC_OF_Open; 111 | f.CombHedgeFlag[0] = THOST_FTDC_HF_Speculation; 112 | f.TimeCondition = THOST_FTDC_TC_GFD; 113 | f.VolumeCondition = THOST_FTDC_VC_AV; 114 | f.MinVolume = 1; 115 | f.ContingentCondition = THOST_FTDC_CC_Immediately; 116 | f.ForceCloseReason = THOST_FTDC_FCC_NotForceClose; 117 | f.RequestID = 20; 118 | f 119 | } 120 | 121 | fn new_input_order_action() -> CThostFtdcInputOrderActionField { 122 | let mut f: CThostFtdcInputOrderActionField = Default::default(); 123 | f.ActionFlag = THOST_FTDC_AF_Delete; 124 | f 125 | } 126 | 127 | fn main() { 128 | println!("Going to connect to simnow {} with broker_id {}", TRADER_FRONT, BROKER_ID); 129 | let mut user_id = String::new(); 130 | print!("user_id: "); 131 | std::io::stdout().flush().unwrap(); 132 | match std::io::stdin().read_line(&mut user_id) { 133 | Ok(_) => (), 134 | Err(e) => { 135 | println!("invalid user_id, {}", e); 136 | }, 137 | } 138 | user_id = user_id.trim_end().to_string(); 139 | let mut password = String::new(); 140 | print!("password: "); 141 | std::io::stdout().flush().unwrap(); 142 | match std::io::stdin().read_line(&mut password) { 143 | Ok(_) => (), 144 | Err(e) => { 145 | println!("invalid password, {}", e); 146 | }, 147 | } 148 | password = password.trim_end().to_string(); 149 | let mut last_request_id = 0; 150 | let flow_path = ::std::ffi::CString::new("").unwrap(); 151 | let mut trader_api = TraderApi::new(flow_path); 152 | trader_api.register_spi(Box::new(Spi)); 153 | trader_api.register_front(std::ffi::CString::new(TRADER_FRONT).unwrap()); 154 | trader_api.subscribe_private_topic(ResumeType::Quick); 155 | trader_api.subscribe_public_topic(ResumeType::Quick); 156 | trader_api.init(); 157 | std::thread::sleep(std::time::Duration::from_secs(1)); 158 | last_request_id += 1; 159 | match trader_api.req_user_login(&new_login(&user_id, &password), last_request_id) { 160 | Ok(()) => println!("req_user_login ok"), 161 | Err(err) => println!("req_user_login err: {:?}", err), 162 | }; 163 | std::thread::sleep(std::time::Duration::from_secs(1)); 164 | last_request_id += 1; 165 | match trader_api.req_qry_instrument(&new_qry_instrument(""), last_request_id) { 166 | Ok(()) => println!("req_qry_instrument ok"), 167 | Err(err) => println!("req_qry_instrument err: {:?}", err), 168 | }; 169 | std::thread::sleep(std::time::Duration::from_secs(2)); 170 | last_request_id += 1; 171 | match trader_api.req_qry_product(&new_qry_product(""), last_request_id) { 172 | Ok(()) => println!("req_qry_product ok"), 173 | Err(err) => println!("req_qry_product err: {:?}", err), 174 | }; 175 | std::thread::sleep(std::time::Duration::from_secs(1)); 176 | last_request_id += 1; 177 | match trader_api.req_qry_exchange(&new_qry_exchange(""), last_request_id) { 178 | Ok(()) => println!("req_qry_exchange ok"), 179 | Err(err) => println!("req_qry_exchange err: {:?}", err), 180 | }; 181 | std::thread::sleep(std::time::Duration::from_secs(1)); 182 | last_request_id += 1; 183 | match trader_api.req_qry_settlement_info(&new_qry_settlement_info(&user_id), last_request_id) { 184 | Ok(()) => println!("req_qry_settlement_info ok"), 185 | Err(err) => println!("req_qry_settlement_info err: {:?}", err), 186 | }; 187 | std::thread::sleep(std::time::Duration::from_secs(1)); 188 | last_request_id += 1; 189 | match trader_api.req_qry_settlement_info_confirm(&new_qry_settlement_info_confirm(&user_id), last_request_id) { 190 | Ok(()) => println!("req_qry_settlement_info_confirm ok"), 191 | Err(err) => println!("req_qry_settlement_info_confirm err: {:?}", err), 192 | }; 193 | std::thread::sleep(std::time::Duration::from_secs(1)); 194 | last_request_id += 1; 195 | match trader_api.req_settlement_info_confirm(&new_settlement_info_confirm(&user_id), last_request_id) { 196 | Ok(()) => println!("req_settlement_info_confirm ok"), 197 | Err(err) => println!("req_settlement_info_confirm err: {:?}", err), 198 | }; 199 | std::thread::sleep(std::time::Duration::from_secs(1)); 200 | last_request_id += 1; 201 | match trader_api.req_qry_order(&new_qry_order(&user_id), last_request_id) { 202 | Ok(()) => println!("req_qry_order ok"), 203 | Err(err) => println!("req_qry_order err: {:?}", err), 204 | }; 205 | std::thread::sleep(std::time::Duration::from_secs(1)); 206 | last_request_id += 1; 207 | match trader_api.req_qry_trade(&new_qry_trade(&user_id), last_request_id) { 208 | Ok(()) => println!("req_qry_trade ok"), 209 | Err(err) => println!("req_qry_trade err: {:?}", err), 210 | }; 211 | std::thread::sleep(std::time::Duration::from_secs(1)); 212 | last_request_id += 1; 213 | match trader_api.req_qry_investor_position(&new_qry_investor_position(&user_id), last_request_id) { 214 | Ok(()) => println!("req_qry_investor_position ok"), 215 | Err(err) => println!("req_qry_investor_position err: {:?}", err), 216 | }; 217 | std::thread::sleep(std::time::Duration::from_secs(1)); 218 | last_request_id += 1; 219 | match trader_api.req_qry_trading_account(&new_qry_trading_account(&user_id), last_request_id) { 220 | Ok(()) => println!("req_qry_trading_account ok"), 221 | Err(err) => println!("req_qry_trading_account err: {:?}", err), 222 | }; 223 | std::thread::sleep(std::time::Duration::from_secs(1)); 224 | last_request_id += 1; 225 | match trader_api.req_qry_investor(&new_qry_investor(&user_id), last_request_id) { 226 | Ok(()) => println!("req_qry_investor ok"), 227 | Err(err) => println!("req_qry_investor err: {:?}", err), 228 | }; 229 | std::thread::sleep(std::time::Duration::from_secs(1)); 230 | last_request_id += 1; 231 | match trader_api.req_qry_trading_code(&new_qry_trading_code(&user_id), last_request_id) { 232 | Ok(()) => println!("req_qry_trading_code ok"), 233 | Err(err) => println!("req_qry_trading_code err: {:?}", err), 234 | }; 235 | std::thread::sleep(std::time::Duration::from_secs(1)); 236 | last_request_id += 1; 237 | match trader_api.req_order_insert(&new_input_order(&user_id), last_request_id) { 238 | Ok(()) => println!("req_order_insert ok"), 239 | Err(err) => println!("req_order_insert err: {:?}", err), 240 | }; 241 | std::thread::sleep(std::time::Duration::from_secs(1)); 242 | last_request_id += 1; 243 | match trader_api.req_order_action(&new_input_order_action(), last_request_id) { 244 | Ok(()) => println!("req_order_action ok"), 245 | Err(err) => println!("req_order_action err: {:?}", err), 246 | }; 247 | std::thread::sleep(std::time::Duration::from_secs(1)); 248 | } 249 | -------------------------------------------------------------------------------- /ctp-trader/examples/password_update.rs: -------------------------------------------------------------------------------- 1 | use ctp_trader::*; 2 | use serde::Deserialize; 3 | use simple_error::{try_with, SimpleResult}; 4 | 5 | #[derive(Deserialize)] 6 | #[derive(Clone, Debug, PartialEq, Eq)] 7 | pub struct Config { 8 | pub trader_front: String, 9 | pub broker_id: String, 10 | pub user_id: String, 11 | pub old_password: String, 12 | pub new_password: String, 13 | pub app_id: String, 14 | pub auth_code: String, 15 | } 16 | 17 | struct Spi; 18 | impl TraderSpi for Spi { 19 | } 20 | 21 | fn new_authenticate(config: &Config) -> CThostFtdcReqAuthenticateField { 22 | let mut f: CThostFtdcReqAuthenticateField = Default::default(); 23 | set_cstr_from_str_truncate(&mut f.BrokerID, &config.broker_id); 24 | set_cstr_from_str_truncate(&mut f.UserID, &config.user_id); 25 | set_cstr_from_str_truncate(&mut f.AuthCode, &config.auth_code); 26 | set_cstr_from_str_truncate(&mut f.AppID, &config.app_id); 27 | f 28 | } 29 | 30 | fn new_login(config: &Config) -> CThostFtdcReqUserLoginField { 31 | let mut f: CThostFtdcReqUserLoginField = Default::default(); 32 | set_cstr_from_str_truncate(&mut f.BrokerID, &config.broker_id); 33 | set_cstr_from_str_truncate(&mut f.UserID, &config.user_id); 34 | set_cstr_from_str_truncate(&mut f.Password, &config.old_password); 35 | f 36 | } 37 | 38 | fn new_user_password_update(config: &Config) -> CThostFtdcUserPasswordUpdateField { 39 | let mut f: CThostFtdcUserPasswordUpdateField = Default::default(); 40 | set_cstr_from_str_truncate(&mut f.BrokerID, &config.broker_id); 41 | set_cstr_from_str_truncate(&mut f.UserID, &config.user_id); 42 | set_cstr_from_str_truncate(&mut f.OldPassword, &config.old_password); 43 | set_cstr_from_str_truncate(&mut f.NewPassword, &config.new_password); 44 | f 45 | } 46 | 47 | fn main() -> SimpleResult<()> { 48 | let yaml_str = try_with!(std::fs::read_to_string("config.password_update.yml"), "cannot read config.password_update.yml"); 49 | let config: Config = try_with!(serde_yaml::from_str(&yaml_str), "cannot parse config"); 50 | 51 | let mut last_request_id = 0; 52 | let flow_path = ::std::ffi::CString::new("").unwrap(); 53 | let mut trader_api = TraderApi::new(flow_path); 54 | trader_api.register_spi(Box::new(Spi)); 55 | trader_api.register_front(std::ffi::CString::new(config.trader_front.clone()).unwrap()); 56 | trader_api.subscribe_private_topic(ResumeType::Quick); 57 | trader_api.subscribe_public_topic(ResumeType::Quick); 58 | trader_api.init(); 59 | std::thread::sleep(std::time::Duration::from_secs(2)); 60 | last_request_id += 1; 61 | match trader_api.req_authenticate(&new_authenticate(&config), last_request_id) { 62 | Ok(()) => println!("req_authenticate ok"), 63 | Err(err) => println!("req_authenticate err: {:?}", err), 64 | }; 65 | std::thread::sleep(std::time::Duration::from_secs(2)); 66 | last_request_id += 1; 67 | match trader_api.req_user_login(&new_login(&config), last_request_id) { 68 | Ok(()) => println!("req_user_login ok"), 69 | Err(err) => println!("req_user_login err: {:?}", err), 70 | }; 71 | std::thread::sleep(std::time::Duration::from_secs(2)); 72 | 73 | last_request_id += 1; 74 | let f = new_user_password_update(&config); 75 | match trader_api.req_user_password_update(&f, last_request_id) { 76 | Ok(()) => println!("req_user_password_update ok"), 77 | Err(err) => println!("req_user_password_update err: {:?}", err), 78 | }; 79 | std::thread::sleep(std::time::Duration::from_secs(10)); 80 | Ok(()) 81 | } 82 | -------------------------------------------------------------------------------- /ctp-trader/src/channel.rs: -------------------------------------------------------------------------------- 1 | use crossbeam_channel as channel; 2 | 3 | use std::time::SystemTime; 4 | 5 | use super::*; 6 | 7 | #[derive(Clone, Debug)] 8 | pub struct ChannelTraderSpi { 9 | sender: channel::Sender<(TraderSpiOutput, SystemTime)>, 10 | } 11 | 12 | impl ChannelTraderSpi { 13 | pub fn new(sender: channel::Sender<(TraderSpiOutput, SystemTime)>) -> Self { 14 | ChannelTraderSpi { 15 | sender, 16 | } 17 | } 18 | } 19 | 20 | impl TraderSpi for ChannelTraderSpi { 21 | fn on_front_connected(&mut self) { 22 | self.sender.send((TraderSpiOutput::FrontConnected(TraderSpiOnFrontConnected{ }), SystemTime::now())).expect("cannot send trader spi") 23 | } 24 | 25 | fn on_front_disconnected(&mut self, reason: DisconnectionReason) { 26 | self.sender.send((TraderSpiOutput::FrontDisconnected(TraderSpiOnFrontDisconnected{ reason }), SystemTime::now())).expect("cannot send trader spi") 27 | } 28 | 29 | fn on_rsp_authenticate(&mut self, rsp_authenticate: Option<&CThostFtdcRspAuthenticateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 30 | self.sender.send((TraderSpiOutput::RspAuthenticate(TraderSpiOnRspAuthenticate{ authenticate: rsp_authenticate.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 31 | } 32 | 33 | fn on_rsp_user_login(&mut self, rsp_user_login: Option<&CThostFtdcRspUserLoginField>, result: RspResult, request_id: i32, is_last: bool) { 34 | self.sender.send((TraderSpiOutput::RspUserLogin(TraderSpiOnRspUserLogin{ user_login: rsp_user_login.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 35 | } 36 | 37 | fn on_rsp_user_logout(&mut self, rsp_user_logout: Option<&CThostFtdcUserLogoutField>, result: RspResult, request_id: i32, is_last: bool) { 38 | self.sender.send((TraderSpiOutput::RspUserLogout(TraderSpiOnRspUserLogout{ user_logout: rsp_user_logout.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 39 | } 40 | 41 | fn on_rsp_user_password_update(&mut self, rsp_user_password_update: Option<&CThostFtdcUserPasswordUpdateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 42 | self.sender.send((TraderSpiOutput::RspUserPasswordUpdate(TraderSpiOnRspUserPasswordUpdate{ user_password_update: rsp_user_password_update.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 43 | } 44 | 45 | fn on_rsp_order_insert(&mut self, input_order: Option<&CThostFtdcInputOrderField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 46 | self.sender.send((TraderSpiOutput::RspOrderInsert(TraderSpiOnRspOrderInsert{ input_order: input_order.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 47 | } 48 | 49 | fn on_rsp_order_action(&mut self, input_order_action: Option<&CThostFtdcInputOrderActionField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 50 | self.sender.send((TraderSpiOutput::RspOrderAction(TraderSpiOnRspOrderAction{ input_order_action: input_order_action.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 51 | } 52 | 53 | fn on_rsp_settlement_info_confirm(&mut self, settlement_info_confirm: Option<&CThostFtdcSettlementInfoConfirmField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 54 | self.sender.send((TraderSpiOutput::RspSettlementInfoConfirm(TraderSpiOnRspSettlementInfoConfirm{ settlement_info_confirm: settlement_info_confirm.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 55 | } 56 | 57 | fn on_rsp_qry_order(&mut self, order: Option<&CThostFtdcOrderField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 58 | self.sender.send((TraderSpiOutput::RspQryOrder(TraderSpiOnRspQryOrder{ order: order.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 59 | } 60 | 61 | fn on_rsp_qry_trade(&mut self, trade: Option<&CThostFtdcTradeField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 62 | self.sender.send((TraderSpiOutput::RspQryTrade(TraderSpiOnRspQryTrade{ trade: trade.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 63 | } 64 | 65 | fn on_rsp_qry_investor_position(&mut self, investor_position: Option<&CThostFtdcInvestorPositionField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 66 | self.sender.send((TraderSpiOutput::RspQryInvestorPosition(TraderSpiOnRspQryInvestorPosition{ investor_position: investor_position.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 67 | } 68 | 69 | fn on_rsp_qry_trading_account(&mut self, trading_account: Option<&CThostFtdcTradingAccountField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 70 | self.sender.send((TraderSpiOutput::RspQryTradingAccount(TraderSpiOnRspQryTradingAccount{ trading_account: trading_account.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 71 | } 72 | 73 | fn on_rsp_qry_investor(&mut self, investor: Option<&CThostFtdcInvestorField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 74 | self.sender.send((TraderSpiOutput::RspQryInvestor(TraderSpiOnRspQryInvestor{ investor: investor.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 75 | } 76 | 77 | fn on_rsp_qry_trading_code(&mut self, trading_code: Option<&CThostFtdcTradingCodeField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 78 | self.sender.send((TraderSpiOutput::RspQryTradingCode(TraderSpiOnRspQryTradingCode{ trading_code: trading_code.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 79 | } 80 | 81 | fn on_rsp_qry_instrument_margin_rate(&mut self, instrument_margin_rate: Option<&CThostFtdcInstrumentMarginRateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 82 | self.sender.send((TraderSpiOutput::RspQryInstrumentMarginRate(TraderSpiOnRspQryInstrumentMarginRate{ instrument_margin_rate: instrument_margin_rate.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 83 | } 84 | 85 | fn on_rsp_qry_instrument_commission_rate(&mut self, instrument_commission_rate: Option<&CThostFtdcInstrumentCommissionRateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 86 | self.sender.send((TraderSpiOutput::RspQryInstrumentCommissionRate(TraderSpiOnRspQryInstrumentCommissionRate{ instrument_commission_rate: instrument_commission_rate.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 87 | } 88 | 89 | fn on_rsp_qry_exchange(&mut self, exchange: Option<&CThostFtdcExchangeField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 90 | self.sender.send((TraderSpiOutput::RspQryExchange(TraderSpiOnRspQryExchange{ exchange: exchange.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 91 | } 92 | 93 | fn on_rsp_qry_product(&mut self, product: Option<&CThostFtdcProductField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 94 | self.sender.send((TraderSpiOutput::RspQryProduct(TraderSpiOnRspQryProduct{ product: product.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 95 | } 96 | 97 | fn on_rsp_qry_instrument(&mut self, instrument: Option<&CThostFtdcInstrumentField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 98 | self.sender.send((TraderSpiOutput::RspQryInstrument(TraderSpiOnRspQryInstrument{ instrument: instrument.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 99 | } 100 | 101 | fn on_rsp_qry_settlement_info(&mut self, settlement_info: Option<&CThostFtdcSettlementInfoField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 102 | self.sender.send((TraderSpiOutput::RspQrySettlementInfo(TraderSpiOnRspQrySettlementInfo{ settlement_info: settlement_info.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 103 | } 104 | 105 | fn on_rsp_qry_settlement_info_confirm(&mut self, settlement_info_confirm: Option<&CThostFtdcSettlementInfoConfirmField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 106 | self.sender.send((TraderSpiOutput::RspQrySettlementInfoConfirm(TraderSpiOnRspQrySettlementInfoConfirm{ settlement_info_confirm: settlement_info_confirm.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 107 | } 108 | 109 | fn on_rsp_qry_exchange_margin_rate(&mut self, exchange_margin_rate: Option<&CThostFtdcExchangeMarginRateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 110 | self.sender.send((TraderSpiOutput::RspQryExchangeMarginRate(TraderSpiOnRspQryExchangeMarginRate{ exchange_margin_rate: exchange_margin_rate.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 111 | } 112 | 113 | fn on_rsp_qry_exchange_margin_rate_adjust(&mut self, exchange_margin_rate_adjust: Option<&CThostFtdcExchangeMarginRateAdjustField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 114 | self.sender.send((TraderSpiOutput::RspQryExchangeMarginRateAdjust(TraderSpiOnRspQryExchangeMarginRateAdjust{ exchange_margin_rate_adjust: exchange_margin_rate_adjust.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 115 | } 116 | 117 | fn on_rsp_qry_exchange_rate(&mut self, exchange_rate: Option<&CThostFtdcExchangeRateField>, result: RspResult, request_id: TThostFtdcRequestIDType, is_last: bool) { 118 | self.sender.send((TraderSpiOutput::RspQryExchangeRate(TraderSpiOnRspQryExchangeRate{ exchange_rate: exchange_rate.cloned(), result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 119 | } 120 | 121 | fn on_rsp_error(&mut self, result: RspResult, request_id: i32, is_last: bool) { 122 | self.sender.send((TraderSpiOutput::RspError(TraderSpiOnRspError{ result, request_id, is_last }), SystemTime::now())).expect("cannot send trader spi") 123 | } 124 | 125 | fn on_rtn_order(&mut self, order: Option<&CThostFtdcOrderField>) { 126 | self.sender.send((TraderSpiOutput::RtnOrder(TraderSpiOnRtnOrder{ order: order.cloned() }), SystemTime::now())).expect("cannot send trader spi") 127 | } 128 | 129 | fn on_rtn_trade(&mut self, trade: Option<&CThostFtdcTradeField>) { 130 | self.sender.send((TraderSpiOutput::RtnTrade(TraderSpiOnRtnTrade{ trade: trade.cloned() }), SystemTime::now())).expect("cannot send trader spi") 131 | } 132 | 133 | fn on_err_rtn_order_insert(&mut self, input_order: Option<&CThostFtdcInputOrderField>, result: RspResult) { 134 | self.sender.send((TraderSpiOutput::ErrRtnOrderInsert(TraderSpiOnErrRtnOrderInsert{ input_order: input_order.cloned(), result }), SystemTime::now())).expect("cannot send trader spi") 135 | } 136 | 137 | fn on_err_rtn_order_action(&mut self, order_action: Option<&CThostFtdcOrderActionField>, result: RspResult) { 138 | self.sender.send((TraderSpiOutput::ErrRtnOrderAction(TraderSpiOnErrRtnOrderAction{ order_action: order_action.cloned(), result }), SystemTime::now())).expect("cannot send trader spi") 139 | } 140 | 141 | fn on_rtn_instrument_status(&mut self, instrument_status: Option<&CThostFtdcInstrumentStatusField>) { 142 | self.sender.send((TraderSpiOutput::RtnInstrumentStatus(TraderSpiOnRtnInstrumentStatus{ instrument_status: instrument_status.cloned() }), SystemTime::now())).expect("cannot send trader spi") 143 | } 144 | 145 | fn on_rtn_trading_notice(&mut self, trading_notice_info: Option<&CThostFtdcTradingNoticeInfoField>) { 146 | self.sender.send((TraderSpiOutput::RtnTradingNotice(TraderSpiOnRtnTradingNotice{ trading_notice_info: trading_notice_info.cloned() }), SystemTime::now())).expect("cannot send trader spi") 147 | } 148 | } 149 | -------------------------------------------------------------------------------- /ctp-trader/test_all.sh: -------------------------------------------------------------------------------- 1 | cargo test 2 | cargo test --features="channel" 3 | --------------------------------------------------------------------------------