1use crate::driver::{self, MdDriver, MdEvent, MdOut, TdDriver, TdEvent, TdOut};
20use crate::wire::InputOrder;
21use ek_core::context::PluginContext;
22use ek_core::envelope::{Envelope, ReplyErr, e_req_type};
23use ek_core::plugin::Plugin;
24use ek_core::section::{ApplyReport, Configurable, Field, SectionMap};
25use ek_core::service::ServiceDesc;
26use ek_data::cancel_request::CancelRequest;
27use ek_data::contract::ContractData;
28use ek_data::log::LogData;
29#[cfg(feature = "native")]
31use ek_data::log::LEVEL_ERROR;
32use ek_data::log::LEVEL_INFO;
34use ek_data::order_request::OrderRequest;
35use ek_event::Event;
36use std::borrow::Cow;
37use std::sync::atomic::{AtomicU32, Ordering};
38use std::sync::{Arc, Mutex, OnceLock};
39
40const W_TICK: &str = "eTick.";
43const W_ORDER: &str = "eOrder.";
44const W_TRADE: &str = "eTrade.";
45const W_POSITION: &str = "ePosition.";
46const W_ACCOUNT: &str = "eAccount.";
47const W_CONTRACT: &str = "eContract.";
48
49pub const CONTRACT_BATCH_ROWS: usize = 2000;
58const W_POS_DONE: &str = "ePositionDone.";
61const W_ACCOUNT_DONE: &str = "eAccountDone.";
63pub const EVENT_CONTRACT_FINISH: &str = "eContractFinish";
66pub const SVC_STATUS: &str = "status";
69use ek_data::event::EVENT_LOG as W_LOG;
71pub use ek_event::EVENT_TIMER as ROUTE_TIMER;
74
75#[derive(Debug, Clone)]
79pub struct SubscribeReq {
80 pub symbols: Vec<String>,
82}
83
84#[derive(Debug, Clone, PartialEq, Eq)]
86pub struct SubscribeReply {
87 pub ok: bool,
89 pub msg: String,
91}
92
93#[derive(Debug, Clone)]
95pub struct SendOrderReq {
96 pub req: OrderRequest,
98}
99
100#[derive(Debug, Clone, PartialEq)]
102pub struct SendOrderReply {
103 pub ok: bool,
105 pub en_orderid: String,
107 pub msg: String,
109}
110
111#[derive(Debug, Clone)]
113pub struct CancelOrderReq {
114 pub req: CancelRequest,
116}
117
118#[derive(Debug, Clone, PartialEq, Eq)]
120pub struct CancelOrderReply {
121 pub ok: bool,
123 pub msg: String,
125}
126
127#[derive(Debug, Clone)]
132pub struct QueryRateReq {
133 pub kind: String,
135 pub instrument_id: String,
137 pub hedge_flag: String,
139 pub exchange_id: String,
141}
142
143#[derive(Debug, Clone, PartialEq, Eq)]
145pub struct QueryRateReply {
146 pub ok: bool,
148 pub msg: String,
150}
151
152#[derive(Clone, Default)]
162pub struct ConfigureReq {
163 pub broker_id: String,
165 pub td_addr: String,
167 pub td_user: String,
169 pub td_password: ek_cfg::SecretString,
171 pub td_auth_code: String,
173 pub td_app_id: String,
175 pub md_addr: String,
177 pub md_user: String,
179 pub md_password: ek_cfg::SecretString,
181 pub product_classes: Vec<String>,
184}
185
186impl std::fmt::Debug for ConfigureReq {
187 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
188 f.debug_struct("ConfigureReq")
189 .field("broker_id", &self.broker_id)
190 .field("td_addr", &self.td_addr)
191 .field("td_password", &"****")
193 .field("td_auth_code", &self.td_auth_code)
194 .field("td_app_id", &self.td_app_id)
195 .field("md_addr", &self.md_addr)
196 .field("md_user", &self.md_user)
197 .field("md_password", &"****")
198 .field("product_classes", &self.product_classes)
199 .finish()
200 }
201}
202
203#[derive(Debug, Clone, PartialEq, Eq)]
205pub struct ConfigureReply {
206 pub ok: bool,
208 pub msg: String,
210 pub product_classes: Vec<String>,
214}
215
216#[derive(Debug, Clone, Default, PartialEq, Eq)]
218pub struct StatusReq;
219
220#[derive(Debug, Clone, Default, PartialEq, Eq)]
232pub struct GatewayStatus {
233 pub gateway: String,
235 pub sampled_ms: i64,
237 pub td_handle_up: bool,
240 pub td_logged_in: bool,
242 pub td_login_failed: bool,
244 pub td_auth_ok: bool,
246 pub td_auth_failed: bool,
248 pub td_front_id: i64,
250 pub td_session_id: i64,
252 pub td_disconnects: u32,
254 pub td_last_login_ok_ms: Option<i64>,
256 pub td_last_account_ms: Option<i64>,
258 pub td_last_position_ms: Option<i64>,
260 pub td_query_rejects: u32,
262 pub td_query_reject_streak: u32,
264 pub td_instrument_rejects: u32,
266 pub td_last_error: Option<String>,
268 pub md_handle_up: bool,
271 pub md_logged_in: bool,
273 pub md_disconnects: u32,
275 pub md_last_login_ok_ms: Option<i64>,
277 pub md_last_tick_ms: Option<i64>,
279 pub md_sub_rejects: u32,
281 pub md_last_error: Option<String>,
283 pub md_subscribed: usize,
285 pub td_anomalies: crate::driver::AnomalyCounts,
293 pub md_anomalies: crate::driver::AnomalyCounts,
295 pub last_anomaly_ms: Option<i64>,
297}
298
299#[derive(Debug, Clone, Copy, PartialEq, Eq)]
309pub enum GatewayChange {
310 TdConnected,
312 TdDisconnected,
314 TdAuthOk,
316 TdAuthFailed,
318 TdLoginOk,
320 TdLoginFailed,
322 SettlementConfirmed,
324 ContractArrived,
326 ContractQueryStopped,
328 QueryRejected,
330 MdConnected,
332 MdDisconnected,
334 MdLoginOk,
336 MdLoginFailed,
338 SubscribeRejected,
340}
341
342impl GatewayChange {
343 pub fn kind(&self) -> &'static str {
345 match self {
346 Self::TdConnected => "td-connected",
347 Self::TdDisconnected => "td-disconnected",
348 Self::TdAuthOk => "td-auth-ok",
349 Self::TdAuthFailed => "td-auth-failed",
350 Self::TdLoginOk => "td-login-ok",
351 Self::TdLoginFailed => "td-login-failed",
352 Self::SettlementConfirmed => "settlement-confirmed",
353 Self::ContractArrived => "contract-arrived",
354 Self::ContractQueryStopped => "contract-query-stopped",
355 Self::QueryRejected => "query-rejected",
356 Self::MdConnected => "md-connected",
357 Self::MdDisconnected => "md-disconnected",
358 Self::MdLoginOk => "md-login-ok",
359 Self::MdLoginFailed => "md-login-failed",
360 Self::SubscribeRejected => "subscribe-rejected",
361 }
362 }
363
364 pub fn describe(&self, gw: &str, st: &GatewayStatus) -> String {
370 let p = driver::CHAIN_STATE_PREFIX;
371 match self {
372 Self::TdConnected => format!("{p}{gw} 交易前置已连接(下一步:认证)"),
373 Self::TdDisconnected => format!(
374 "{p}{gw} 交易前置断开(累计 {} 次;会话作废,查询阶段回 Idle)",
375 st.td_disconnects
376 ),
377 Self::TdAuthOk => format!("{p}{gw} 授权通过(下一步:登录)"),
378 Self::TdAuthFailed => format!(
379 "{p}{gw} 授权失败:{}",
380 st.td_last_error.as_deref().unwrap_or("无错误文本")
381 ),
382 Self::TdLoginOk => format!(
383 "{p}{gw} 登录成功(Front {} / Session {})",
384 st.td_front_id, st.td_session_id
385 ),
386 Self::TdLoginFailed => format!(
387 "{p}{gw} 登录失败:{}",
388 st.td_last_error.as_deref().unwrap_or("无错误文本")
389 ),
390 Self::SettlementConfirmed => {
391 format!("{p}{gw} 结算单已确认(开始问合约)")
392 }
393 Self::ContractArrived => {
394 format!("{p}{gw} 合约已到手,开始资金/持仓轮询")
395 }
396 Self::ContractQueryStopped => {
397 format!("{p}{gw} 合约查询问满:本轮查询已停止(不再问资金与持仓)")
398 }
399 Self::QueryRejected => format!(
400 "{p}{gw} 查询被拒(连续 {} 次 / 累计 {} 次):{}",
401 st.td_query_reject_streak,
402 st.td_query_rejects,
403 st.td_last_error.as_deref().unwrap_or("无错误文本")
404 ),
405 Self::MdConnected => format!("{p}{gw} 行情前置已连接(下一步:登录)"),
406 Self::MdDisconnected => format!("{p}{gw} 行情前置断开(累计 {} 次)", st.md_disconnects),
407 Self::MdLoginOk => format!(
408 "{p}{gw} 行情登录成功(已登记订阅 {} 项,自会重推)",
409 st.md_subscribed
410 ),
411 Self::MdLoginFailed => format!(
412 "{p}{gw} 行情登录失败:{}",
413 st.md_last_error.as_deref().unwrap_or("无错误文本")
414 ),
415 Self::SubscribeRejected => format!(
416 "{p}{gw} 行情订阅被拒(累计 {} 次):{}",
417 st.md_sub_rejects,
418 st.md_last_error.as_deref().unwrap_or("无错误文本")
419 ),
420 }
421 }
422}
423
424#[derive(Debug, Clone)]
431pub struct GatewayStatusEvent {
432 pub gateway: String,
434 pub at_ms: i64,
436 pub change: GatewayChange,
438 pub status: Box<GatewayStatus>,
440}
441
442impl GatewayStatusEvent {
443 pub fn describe(&self) -> String {
445 self.change.describe(&self.gateway, &self.status)
446 }
447}
448
449pub const EVENT_STATUS_PREFIX: &str = "eStatus.";
451
452#[derive(Debug, Clone, Default, PartialEq, Eq)]
454pub struct DisconnectReq;
455
456#[derive(Debug, Clone, PartialEq, Eq)]
458pub struct DisconnectReply {
459 pub ok: bool,
461 pub msg: String,
463}
464
465pub trait CtpTransport: Send + Sync {
469 fn md_connect(&self, addr: &str, flow_path: &str);
471 fn md_login(&self, user: &str, password: &str, broker: &str);
473 fn md_subscribe(&self, symbols: &[String]);
475 fn td_connect(&self, addr: &str, flow_path: &str);
477 fn td_auth(&self, user: &str, broker: &str, auth_code: &str, app_id: &str);
479 fn td_login(&self, user: &str, password: &str, broker: &str);
481 fn td_settlement_confirm(&self, broker: &str, user: &str);
483 fn td_qry_instrument(&self) -> i32;
488 fn td_qry_account(&self, broker: &str, user: &str) -> i32;
492 fn td_qry_position(&self, broker: &str, user: &str) -> i32;
494 fn td_insert(&self, order: &InputOrder) -> i32;
497 fn td_cancel(&self, action: &crate::wire::InputOrderAction) -> i32;
499 fn td_qry_margin_rate(&self, query: &crate::wire::QryInstrumentMarginRate) -> i32;
501 fn td_qry_commission_rate(&self, query: &crate::wire::QryInstrumentCommissionRate) -> i32;
503
504 fn shutdown(&self) {}
512
513 fn td_release(&self) {}
516 fn md_release(&self) {}
518}
519
520#[derive(Clone, Default)]
528pub struct CtpConfig {
529 pub gateway_name: String,
531 pub broker_id: String,
533 pub td_addr: String,
535 pub td_user: String,
537 pub td_password: ek_cfg::SecretString,
539 pub td_auth_code: String,
541 pub td_app_id: String,
543 pub md_addr: String,
545 pub md_user: String,
547 pub md_password: ek_cfg::SecretString,
549 pub product_classes: crate::map::ProductClasses,
552}
553
554impl std::fmt::Debug for CtpConfig {
555 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
556 f.debug_struct("CtpConfig")
557 .field("gateway_name", &self.gateway_name)
558 .field("broker_id", &self.broker_id)
559 .field("td_addr", &self.td_addr)
560 .field("td_user", &self.td_user)
561 .field("td_password", &"****")
563 .field("td_auth_code", &self.td_auth_code)
564 .field("td_app_id", &self.td_app_id)
565 .field("md_addr", &self.md_addr)
566 .field("md_user", &self.md_user)
567 .field("md_password", &"****")
568 .field("product_classes", &self.product_classes)
569 .finish()
570 }
571}
572
573impl CtpConfig {
574 pub fn gateway_name_or_default(&self) -> String {
576 if self.gateway_name.is_empty() {
577 "ctp".to_string()
578 } else {
579 self.gateway_name.clone()
580 }
581 }
582}
583
584#[derive(Debug, Clone, PartialEq, Eq)]
586pub struct CredentialEntry {
587 pub field: String,
589 pub state: String,
592 pub ref_key: String,
594}
595
596#[derive(Debug, Clone, Default, PartialEq, Eq)]
598pub struct CredentialStateReq;
599
600#[must_use]
617pub fn derived_gateway_name(broker_id: &str, account: &str) -> Option<String> {
618 let broker = sanitize_id_field(broker_id)?;
619 let account = sanitize_id_field(account)?;
620 let name = format!("ctp_{broker}_{account}");
621 if name.len() > 32 {
623 return None;
624 }
625 Some(name)
626}
627
628fn sanitize_id_field(raw: &str) -> Option<String> {
633 let trimmed = raw.trim();
634 if trimmed.is_empty() || !trimmed.chars().all(|c| c.is_ascii_alphanumeric()) {
635 return None;
636 }
637 Some(trimmed.to_ascii_lowercase())
638}
639
640#[must_use]
645pub fn is_derived_gateway_name(name: &str) -> bool {
646 let Some(rest) = name.strip_prefix("ctp_") else {
647 return false;
648 };
649 if name.len() > 32 {
650 return false;
651 }
652 let ok = |s: &str| {
653 !s.is_empty()
654 && s.chars()
655 .all(|c| c.is_ascii_lowercase() || c.is_ascii_digit())
656 };
657 match rest.split_once('_') {
658 Some((broker, account)) => ok(broker) && ok(account),
659 None => false,
660 }
661}
662
663fn lock_clone<T: Clone>(l: &std::sync::Mutex<T>) -> T {
667 match l.lock() {
668 Ok(g) => g.clone(),
669 Err(e) => e.into_inner().clone(),
670 }
671}
672
673pub struct CtpPlugin {
675 gw: String,
676 ns: String,
678 cfg_rev: AtomicU32,
680 sec_refs: Mutex<Vec<(String, String)>>,
683 cfg: Mutex<CtpConfig>,
685 broker: Mutex<String>,
687 user: Mutex<String>,
689 md: Mutex<MdDriver>,
690 td: Mutex<TdDriver>,
691 timer_count: Mutex<u32>,
693 trans: Option<Box<dyn CtpTransport>>,
695 ctx: OnceLock<PluginContext>,
696 trans_warned: Mutex<bool>,
698 save_fallback: Mutex<bool>,
700 contract_batch: Mutex<Vec<ContractData>>,
703}
704
705impl CtpPlugin {
706 pub fn new(cfg: CtpConfig) -> Self {
708 let gw = cfg.gateway_name_or_default();
709 Self::with_parts(cfg, gw, None, None)
710 }
711
712 pub fn with_transport(cfg: CtpConfig, trans: Box<dyn CtpTransport>) -> Self {
714 let gw = cfg.gateway_name_or_default();
715 Self::with_parts(cfg, gw, Some(trans), None)
716 }
717
718 pub fn with_transport_and_clock(
725 cfg: CtpConfig,
726 trans: Box<dyn CtpTransport>,
727 now_ms: Arc<dyn Fn() -> u64 + Send + Sync>,
728 ) -> Self {
729 let gw = cfg.gateway_name_or_default();
730 Self::with_parts(cfg, gw, Some(trans), Some(now_ms))
731 }
732
733 fn with_parts(
734 cfg: CtpConfig,
735 gw: String,
736 trans: Option<Box<dyn CtpTransport>>,
737 clock: Option<Arc<dyn Fn() -> u64 + Send + Sync>>,
738 ) -> Self {
739 let contracts = driver::contracts_empty();
740 let cfg_broker = cfg.broker_id.clone();
741 let cfg_user = cfg.td_user.clone();
742 let md_cfg = driver::MdConfig {
743 addr: cfg.md_addr.clone(),
744 user: cfg.md_user.clone(),
745 password: cfg.md_password.to_exposed_string(),
746 broker: cfg.broker_id.clone(),
747 };
748 let td_cfg = driver::TdConfig {
749 addr: cfg.td_addr.clone(),
750 user: cfg.td_user.clone(),
751 password: cfg.td_password.to_exposed_string(),
752 broker: cfg.broker_id.clone(),
753 auth_code: cfg.td_auth_code.clone(),
754 app_id: cfg.td_app_id.clone(),
755 product_classes: cfg.product_classes.clone(),
756 };
757 let md = Mutex::new(MdDriver::new(
759 &gw,
760 md_cfg,
761 contracts.clone(),
762 crate::map::shanghai_now_date(),
763 ));
764 let td = Mutex::new(match clock {
765 Some(f) => TdDriver::with_clock(&gw, td_cfg, contracts.clone(), f),
766 None => TdDriver::new(&gw, td_cfg, contracts.clone()),
767 });
768 let ns = if gw == "ctp" {
769 "ctp".to_owned()
771 } else {
772 format!("ctp.{gw}")
774 };
775 Self {
776 gw,
777 ns,
778 cfg_rev: AtomicU32::new(0),
779 sec_refs: Mutex::new(Vec::new()),
780 cfg: Mutex::new(cfg),
781 broker: Mutex::new(cfg_broker),
782 user: Mutex::new(cfg_user),
783 md,
784 td,
785 timer_count: Mutex::new(0),
786 trans,
787 ctx: OnceLock::new(),
788 trans_warned: Mutex::new(false),
789 save_fallback: Mutex::new(false),
790 contract_batch: Mutex::new(Vec::with_capacity(CONTRACT_BATCH_ROWS)),
791 }
792 }
793
794 #[cfg(feature = "native")]
803 pub fn with_native(cfg: CtpConfig, flow_root: impl Into<std::path::PathBuf>) -> Arc<Self> {
804 let (trans, ingress) = crate::native::transport::native_parts(flow_root);
805 let plugin = Arc::new(Self::with_transport(cfg, trans));
806 crate::native::transport::attach_plugin(&ingress, &plugin);
807 plugin
808 }
809
810 pub fn connect(&self) {
820 let (td_empty, md_empty) = {
823 let c = self.cfg.lock().unwrap();
824 (c.td_addr.is_empty(), c.md_addr.is_empty())
825 };
826 if td_empty && md_empty {
827 self.put_log(LogData {
828 gateway_name: self.gw.clone(),
829 msg: "未配置前置地址,跳过连接(等待 configure)".to_string(),
830 level: 20,
831 });
832 return;
833 }
834 let td_outs = if td_empty {
838 Vec::new()
839 } else {
840 self.td.lock().unwrap().connect()
841 };
842 let md_outs = if md_empty {
843 Vec::new()
844 } else {
845 self.md.lock().unwrap().connect()
846 };
847 for o in td_outs {
848 self.emit_td(o);
849 }
850 for o in md_outs {
851 self.emit_md(o);
852 }
853 }
854
855 #[cfg(feature = "native")]
861 pub(crate) fn transport_log(&self, msg: String) {
862 self.put_log(LogData {
863 gateway_name: self.gw.clone(),
864 msg,
865 level: LEVEL_ERROR,
866 });
867 }
868
869 pub fn on_md_event(&self, ev: MdEvent) {
871 let outs = self.md.lock().unwrap().step(ev);
872 for o in outs {
873 self.emit_md(o);
874 }
875 }
876
877 pub fn on_td_event(&self, ev: TdEvent) {
879 let outs = self.td.lock().unwrap().step(ev);
880 for o in outs {
881 self.emit_td(o);
882 }
883 }
884
885 pub fn on_timer(&self) {
892 let outs = self.td.lock().unwrap().on_timer();
893 for o in outs {
894 self.emit_td(o);
895 }
896 let gate_pass = {
897 let mut c = self.timer_count.lock().unwrap();
898 *c += 1;
899 if *c < 2 {
900 false
901 } else {
902 *c = 0;
903 true
904 }
905 };
906 if !gate_pass {
907 return;
908 }
909 let date = crate::map::shanghai_now_date();
911 self.md.lock().unwrap().update_date(date);
912 }
913
914 fn emit_md(&self, o: MdOut) {
915 match o {
916 MdOut::Log(l) => self.put_log(l),
917 MdOut::Status(ch) => self.publish_status(ch),
919 MdOut::Tick(t) => self.put_data(W_TICK, t),
920 MdOut::ReqConnect { addr, flow_path } => {
921 if let Some(t) = &self.trans {
922 t.md_connect(&addr, &flow_path);
923 } else {
924 self.warn_no_transport();
925 }
926 }
927 MdOut::ReqLogin {
928 user,
929 password,
930 broker,
931 } => {
932 if let Some(t) = &self.trans {
933 t.md_login(&user, &password, &broker);
934 } else {
935 self.warn_no_transport();
936 }
937 }
938 MdOut::ReqSubscribe(symbols) => {
939 if let Some(t) = &self.trans {
940 t.md_subscribe(&symbols);
941 } else {
942 self.warn_no_transport();
943 }
944 }
945 }
946 }
947
948 fn emit_td(&self, o: TdOut) {
949 match o {
950 TdOut::Log(l) => self.put_log(l),
951 TdOut::Status(ch) => self.publish_status(ch),
953 TdOut::Contract(c) => self.emit_contract(c),
954 TdOut::Order(o) => self.publish_enriched(W_ORDER, o),
955 TdOut::Trade(t) => self.publish_enriched(W_TRADE, t),
956 TdOut::Position(p) => self.publish_enriched(W_POSITION, p),
957 TdOut::Account(a) => self.put_data(W_ACCOUNT, a),
958 TdOut::PositionQueryDone(gw) => {
959 if let Some(ctx) = self.ctx.get() {
962 let _ = ctx.put(Event::with_data(W_POS_DONE, Arc::new(gw)));
963 }
964 }
965 TdOut::AccountQueryDone(gw) => {
966 if let Some(ctx) = self.ctx.get() {
968 let _ = ctx.put(Event::with_data(W_ACCOUNT_DONE, Arc::new(gw)));
969 }
970 }
971 TdOut::ContractFinish(gw) => {
974 self.flush_contract();
976 if let Some(ctx) = self.ctx.get() {
977 let _ = ctx.put(Event::with_data(EVENT_CONTRACT_FINISH, Arc::new(gw)));
978 }
979 }
980 TdOut::ReqConnect { addr, flow_path } => {
981 if let Some(t) = &self.trans {
982 t.td_connect(&addr, &flow_path);
983 } else {
984 self.warn_no_transport();
985 }
986 }
987 TdOut::ReqAuthenticate {
988 user,
989 broker,
990 auth_code,
991 app_id,
992 } => {
993 if let Some(t) = &self.trans {
994 t.td_auth(&user, &broker, &auth_code, &app_id);
995 } else {
996 self.warn_no_transport();
997 }
998 }
999 TdOut::ReqLogin {
1000 user,
1001 password,
1002 broker,
1003 } => {
1004 if let Some(t) = &self.trans {
1005 t.td_login(&user, &password, &broker);
1006 } else {
1007 self.warn_no_transport();
1008 }
1009 }
1010 TdOut::ReqSettlementConfirm { broker, user } => {
1011 if let Some(t) = &self.trans {
1012 t.td_settlement_confirm(&broker, &user);
1013 } else {
1014 self.warn_no_transport();
1015 }
1016 }
1017 TdOut::ReqQryInstrument => {
1018 if let Some(t) = &self.trans {
1019 let n = t.td_qry_instrument();
1020 for l in self.td.lock().unwrap().note_instrument_send_failure(n) {
1023 self.put_log(l);
1024 }
1025 } else {
1026 self.warn_no_transport();
1027 }
1028 }
1029 TdOut::ReqQryAccount { broker, user } => {
1030 if let Some(t) = &self.trans {
1031 let n = t.td_qry_account(&broker, &user);
1032 for l in self.td.lock().unwrap().note_query_send_failure("资金", n) {
1033 self.put_log(l);
1034 }
1035 } else {
1036 self.warn_no_transport();
1037 }
1038 }
1039 TdOut::ReqQryPosition { broker, user } => {
1040 if let Some(t) = &self.trans {
1041 let n = t.td_qry_position(&broker, &user);
1042 for l in self.td.lock().unwrap().note_query_send_failure("持仓", n) {
1043 self.put_log(l);
1044 }
1045 } else {
1046 self.warn_no_transport();
1047 }
1048 }
1049 TdOut::Insert(o) => {
1050 if let Some(t) = &self.trans {
1051 t.td_insert(&o);
1052 } else {
1053 self.warn_no_transport();
1054 }
1055 }
1056 TdOut::Cancel(a) => {
1057 if let Some(t) = &self.trans {
1058 t.td_cancel(&a);
1059 } else {
1060 self.warn_no_transport();
1061 }
1062 }
1063 }
1064 }
1065
1066 fn put_log(&self, l: LogData) {
1068 if let Some(ctx) = self.ctx.get() {
1069 let _ = ctx.put(Event::with_data(W_LOG, Arc::new(l)));
1070 }
1071 }
1072
1073 fn put_data<T: Send + Sync + 'static>(&self, prefix: &str, payload: T) {
1082 if let Some(ctx) = self.ctx.get() {
1083 let _ = ctx.put(Event::with_data(prefix, Arc::new(payload)));
1084 }
1085 }
1086
1087 fn emit_contract(&self, c: ContractData) {
1099 let mut batch = self.contract_batch.lock().unwrap();
1100 batch.push(c);
1101 if batch.len() >= CONTRACT_BATCH_ROWS {
1102 let payload = std::mem::take(&mut *batch);
1103 drop(batch);
1104 self.put_data(W_CONTRACT, payload);
1105 }
1106 }
1107
1108 pub fn flush_contract(&self) {
1110 let payload = {
1111 let mut batch = self.contract_batch.lock().unwrap();
1112 std::mem::take(&mut *batch)
1113 };
1114 if !payload.is_empty() {
1115 self.put_data(W_CONTRACT, payload);
1116 }
1117 }
1118
1119 fn publish_enriched<T>(&self, line: &str, mut row: T)
1124 where
1125 T: EnrichName + Send + Sync + 'static,
1126 {
1127 if row.name_mut().is_empty() {
1128 let symbol = row.symbol().to_string();
1129 if let Some(n) = self.td.lock().unwrap().contract_name(&symbol) {
1130 *row.name_mut() = n;
1131 }
1132 }
1133 self.put_data(line, row);
1134 }
1135
1136 fn publish_status(&self, change: GatewayChange) {
1137 let st = self.status();
1138 self.put_data(
1139 EVENT_STATUS_PREFIX,
1140 GatewayStatusEvent {
1141 gateway: self.gw.clone(),
1142 at_ms: st.sampled_ms,
1143 change,
1144 status: Box::new(st),
1145 },
1146 );
1147 }
1148
1149 fn warn_no_transport(&self) {
1151 let mut warned = self.trans_warned.lock().unwrap();
1152 if !*warned {
1153 *warned = true;
1154 self.put_log(LogData {
1155 gateway_name: self.gw.clone(),
1156 msg: "传输层未接入(默认构建):连接/查询/发送动作已被吸收".to_string(),
1157 level: 20,
1158 });
1159 }
1160 }
1161
1162 fn on_request(&self, event: &Event) {
1164 let Some(env) = event.data.downcast_ref::<Envelope>() else {
1165 return;
1166 };
1167 macro_rules! arm {
1168 ($ty:ty, $res:ty, $body:expr) => {
1169 if let Some(req) = env.payload.downcast_ref::<$ty>() {
1170 let res: $res = $body(req);
1171 let _ = env.reply.send(Box::new(Ok::<$res, ReplyErr>(res)));
1172 return;
1173 }
1174 };
1175 }
1176 arm!(SubscribeReq, SubscribeReply, |r: &SubscribeReq| self
1177 .do_subscribe(r));
1178 arm!(SendOrderReq, SendOrderReply, |r: &SendOrderReq| self
1179 .do_send_order(r));
1180 arm!(CancelOrderReq, CancelOrderReply, |r: &CancelOrderReq| self
1181 .do_cancel_order(r));
1182 arm!(QueryRateReq, QueryRateReply, |r: &QueryRateReq| self
1183 .do_query_rate(r));
1184 arm!(ConfigureReq, ConfigureReply, |r: &ConfigureReq| self
1185 .do_configure(r));
1186 arm!(StatusReq, GatewayStatus, |r: &StatusReq| self.do_status(r));
1187 arm!(DisconnectReq, DisconnectReply, |r: &DisconnectReq| self
1188 .do_disconnect(r));
1189 arm!(
1191 CredentialStateReq,
1192 Vec<CredentialEntry>,
1193 |_: &CredentialStateReq| self.credentials_state()
1194 );
1195 let _ = env
1197 .reply
1198 .send(Box::new(Err::<(), ReplyErr>(ReplyErr::InvalidPayload)));
1199 }
1200
1201 fn do_subscribe(&self, r: &SubscribeReq) -> SubscribeReply {
1202 let mut normalized: Vec<String> = Vec::with_capacity(r.symbols.len());
1205 let mut bad: Vec<String> = Vec::new();
1206 for s in &r.symbols {
1207 match crate::map::instrument_id(s) {
1208 Some(id) => normalized.push(id),
1209 None => bad.push(s.clone()),
1210 }
1211 }
1212 if !bad.is_empty() {
1213 let msg = format!("订阅符号无法归一(空或交易所前缀未知):{}", bad.join(","));
1214 self.put_log(LogData {
1215 gateway_name: self.gw.clone(),
1216 msg: msg.clone(),
1217 level: 20,
1218 });
1219 return SubscribeReply { ok: false, msg };
1220 }
1221 let mut outs = Vec::new();
1222 {
1223 let mut md = self.md.lock().unwrap();
1224 for s in &normalized {
1225 outs.extend(md.subscribe(s));
1226 }
1227 }
1228 for o in outs {
1229 self.emit_md(o);
1230 }
1231 SubscribeReply {
1232 ok: true,
1233 msg: String::new(),
1234 }
1235 }
1236
1237 fn do_send_order(&self, r: &SendOrderReq) -> SendOrderReply {
1238 let outcome = {
1239 let mut td = self.td.lock().unwrap();
1240 td.send_order(&r.req)
1241 };
1242 if let Some(log) = &outcome.error {
1243 self.put_log(log.clone());
1244 }
1245 let mut submit_order: Option<ek_data::order::OrderData> = None;
1249 let mut send_failed: Option<String> = None;
1250 for o in outcome.outs {
1251 match o {
1252 TdOut::Insert(input) => match &self.trans {
1253 Some(t) => {
1254 let n = t.td_insert(&input);
1255 if n != 0 {
1256 send_failed = Some(format!("委托请求发送失败,错误代码:{n}"));
1257 }
1258 }
1259 None => self.warn_no_transport(),
1260 },
1261 TdOut::Order(od) => submit_order = Some(od),
1262 other => self.emit_td(other),
1263 }
1264 }
1265 if let Some(msg) = send_failed {
1266 let log = LogData {
1267 gateway_name: self.gw.clone(),
1268 msg: msg.clone(),
1269 level: 20,
1270 };
1271 self.put_log(log);
1272 return SendOrderReply {
1273 ok: false,
1274 en_orderid: String::new(),
1275 msg,
1276 };
1277 }
1278 if let Some(od) = submit_order {
1279 self.emit_td(TdOut::Order(od));
1280 }
1281 if outcome.en_orderid.is_empty() {
1282 let msg = outcome.error.map(|l| l.msg).unwrap_or_default();
1283 SendOrderReply {
1284 ok: false,
1285 en_orderid: String::new(),
1286 msg,
1287 }
1288 } else if self.trans.is_none() {
1289 self.warn_no_transport();
1290 SendOrderReply {
1291 ok: false,
1292 en_orderid: outcome.en_orderid,
1293 msg: "传输层未接入(默认构建)".to_string(),
1294 }
1295 } else {
1296 SendOrderReply {
1297 ok: true,
1298 en_orderid: outcome.en_orderid,
1299 msg: String::new(),
1300 }
1301 }
1302 }
1303
1304 fn do_query_rate(&self, r: &QueryRateReq) -> QueryRateReply {
1308 let broker = self.broker.lock().unwrap().clone();
1309 let user = self.user.lock().unwrap().clone();
1310 let is_margin = r.kind == "margin";
1311 let is_commission = r.kind == "commission";
1312 if !is_margin && !is_commission {
1313 self.put_log(LogData {
1314 gateway_name: self.gw.clone(),
1315 msg: format!("费率查询类型未知:{}", r.kind),
1316 level: 20,
1317 });
1318 return QueryRateReply {
1319 ok: false,
1320 msg: format!("费率查询类型未知:{}", r.kind),
1321 };
1322 }
1323 let Some(rate_instrument) = crate::map::instrument_id(&r.instrument_id) else {
1325 let msg = format!("费率查询合约代码无法归一:{}", r.instrument_id);
1326 self.put_log(LogData {
1327 gateway_name: self.gw.clone(),
1328 msg: msg.clone(),
1329 level: 20,
1330 });
1331 return QueryRateReply { ok: false, msg };
1332 };
1333 if let Some(t) = &self.trans {
1334 let n = if is_margin {
1335 let query = crate::wire::QryInstrumentMarginRate {
1336 broker_id: broker.clone(),
1337 investor_id: user.clone(),
1338 hedge_flag: r.hedge_flag.clone(),
1339 exchange_id: r.exchange_id.clone(),
1340 invest_unit_id: String::new(),
1341 instrument_id: rate_instrument.clone(),
1342 };
1343 t.td_qry_margin_rate(&query)
1344 } else {
1345 let query = crate::wire::QryInstrumentCommissionRate {
1346 broker_id: broker.clone(),
1347 investor_id: user.clone(),
1348 exchange_id: r.exchange_id.clone(),
1349 invest_unit_id: String::new(),
1350 instrument_id: rate_instrument.clone(),
1351 };
1352 t.td_qry_commission_rate(&query)
1353 };
1354 let label = if is_margin {
1355 "保证金率"
1356 } else {
1357 "手续费率"
1358 };
1359 if n != 0 {
1360 let msg =
1371 format!("{label}查询请求发送失败,错误代码:{n}(不自动重发:费率查询按需调用)");
1372 self.put_log(LogData {
1373 gateway_name: self.gw.clone(),
1374 msg: msg.clone(),
1375 level: 20,
1376 });
1377 return QueryRateReply { ok: false, msg };
1378 }
1379 QueryRateReply {
1380 ok: true,
1381 msg: String::new(),
1382 }
1383 } else {
1384 self.warn_no_transport();
1385 QueryRateReply {
1386 ok: false,
1387 msg: "传输层未接入(默认构建)".to_string(),
1388 }
1389 }
1390 }
1391
1392 fn do_cancel_order(&self, r: &CancelOrderReq) -> CancelOrderReply {
1393 let outs = {
1394 let mut td = self.td.lock().unwrap();
1395 td.cancel_order(&r.req)
1396 };
1397 let mut rejected_log: Option<LogData> = None;
1398 let outs: Vec<TdOut> = outs
1399 .into_iter()
1400 .filter(|o| match o {
1401 TdOut::Log(l) => {
1402 rejected_log = Some(l.clone());
1403 false
1404 }
1405 _ => true,
1406 })
1407 .collect();
1408 if let Some(l) = rejected_log {
1409 let msg = l.msg.clone();
1410 self.put_log(l);
1411 return CancelOrderReply { ok: false, msg };
1412 }
1413 for o in outs {
1414 self.emit_td(o);
1415 }
1416 if self.trans.is_none() {
1417 self.warn_no_transport();
1418 return CancelOrderReply {
1419 ok: false,
1420 msg: "传输层未接入(默认构建)".to_string(),
1421 };
1422 }
1423 CancelOrderReply {
1424 ok: true,
1425 msg: String::new(),
1426 }
1427 }
1428
1429 fn do_configure(&self, r: &ConfigureReq) -> ConfigureReply {
1438 self.configure(r)
1439 }
1440
1441 fn do_status(&self, _r: &StatusReq) -> GatewayStatus {
1442 self.status()
1443 }
1444
1445 fn do_disconnect(&self, _r: &DisconnectReq) -> DisconnectReply {
1446 self.disconnect()
1447 }
1448
1449 pub fn status(&self) -> GatewayStatus {
1454 let td_drv = self.td.lock().unwrap();
1455 let td = td_drv.status_view();
1456 let td_anomalies = td_drv.anomaly_counts();
1457 let td_last_anomaly = td_drv.last_anomaly_ms();
1458 drop(td_drv);
1459 let md_drv = self.md.lock().unwrap();
1460 let md = md_drv.status_view();
1461 let md_anomalies = md_drv.anomaly_counts();
1462 let md_last_anomaly = md_drv.last_anomaly_ms();
1463 drop(md_drv);
1464 GatewayStatus {
1465 gateway: self.gw.clone(),
1466 sampled_ms: crate::map::now_epoch_ms(),
1467 td_handle_up: td.handle_up,
1468 td_logged_in: td.logged_in,
1469 td_login_failed: td.login_failed,
1470 td_auth_ok: td.auth_ok,
1471 td_auth_failed: td.auth_failed,
1472 td_front_id: td.front_id,
1473 td_session_id: td.session_id,
1474 td_disconnects: td.disconnects,
1475 td_last_login_ok_ms: td.last_login_ok_ms,
1476 td_last_account_ms: td.last_account_ms,
1477 td_last_position_ms: td.last_position_ms,
1478 td_query_rejects: td.query_rejects,
1479 td_query_reject_streak: td.query_reject_streak,
1480 td_instrument_rejects: td.instrument_rejects,
1481 td_last_error: td.last_error,
1482 md_handle_up: md.handle_up,
1483 md_logged_in: md.logged_in,
1484 md_disconnects: md.disconnects,
1485 md_last_login_ok_ms: md.last_login_ok_ms,
1486 md_last_tick_ms: md.last_tick_ms,
1487 md_sub_rejects: md.sub_rejects,
1488 md_last_error: md.last_error,
1489 md_subscribed: md.subscribed,
1490 td_anomalies,
1491 md_anomalies,
1492 last_anomaly_ms: match (td_last_anomaly, md_last_anomaly) {
1494 (Some(a), Some(b)) => Some(a.max(b)),
1495 (a, b) => a.or(b),
1496 },
1497 }
1498 }
1499
1500 pub fn disconnect(&self) -> DisconnectReply {
1506 if let Some(t) = &self.trans {
1507 t.td_release();
1508 t.md_release();
1509 }
1510 {
1511 self.td.lock().unwrap().reset_session();
1512 }
1513 {
1514 self.md.lock().unwrap().reset_session();
1515 }
1516 self.flush_contract();
1519 self.put_log(LogData {
1520 gateway_name: self.gw.clone(),
1521 msg: "会话已停(实例与订阅保留;『连接』可恢复)".to_string(),
1522 level: LEVEL_INFO,
1523 });
1524 self.put_log(LogData {
1526 gateway_name: self.gw.clone(),
1527 msg: format!("{}会话已复位", driver::CHAIN_STATE_PREFIX),
1528 level: LEVEL_INFO,
1529 });
1530 DisconnectReply {
1531 ok: true,
1532 msg: "会话已停(实例与订阅保留;『连接』可恢复)".to_string(),
1533 }
1534 }
1535
1536 fn effective_classes(&self) -> Vec<String> {
1541 self.td.lock().unwrap().product_classes()
1542 }
1543
1544 pub fn configure(&self, r: &ConfigureReq) -> ConfigureReply {
1547 if r.td_addr.is_empty() && r.md_addr.is_empty() {
1548 return ConfigureReply {
1549 ok: false,
1550 msg: "配置校验: td_addr/md_addr 至少一个非空".to_string(),
1551 product_classes: self.effective_classes(),
1552 };
1553 }
1554 if !r.td_addr.is_empty()
1555 && (r.broker_id.is_empty() || r.td_user.is_empty() || r.td_password.is_empty())
1556 {
1557 return ConfigureReply {
1558 ok: false,
1559 msg: "配置校验: TD 侧需 broker_id/td_user/td_password 齐".to_string(),
1560 product_classes: self.effective_classes(),
1561 };
1562 }
1563 let md_user = if !r.md_user.is_empty() {
1564 r.md_user.clone()
1565 } else {
1566 r.td_user.clone()
1567 };
1568 let md_password = if !r.md_password.is_empty() {
1569 r.md_password.to_exposed_string()
1570 } else {
1571 r.td_password.to_exposed_string()
1572 };
1573 if !r.md_addr.is_empty() && (md_user.is_empty() || md_password.is_empty()) {
1574 return ConfigureReply {
1575 ok: false,
1576 msg: "配置校验: MD 侧需 md_user/md_password(或 td 侧回落)".to_string(),
1577 product_classes: self.effective_classes(),
1578 };
1579 }
1580 let bad_codes: Vec<String> = r
1583 .product_classes
1584 .iter()
1585 .map(|c| c.trim().to_string())
1586 .filter(|c| !c.is_empty() && c.chars().count() != 1)
1587 .collect();
1588 if !bad_codes.is_empty() {
1589 return ConfigureReply {
1590 ok: false,
1591 msg: format!(
1592 "配置校验: product_classes 需为单字符 CTP 码(非法项:{})",
1593 bad_codes.join(",")
1594 ),
1595 product_classes: self.effective_classes(),
1596 };
1597 }
1598 let classes = crate::map::ProductClasses(r.product_classes.clone());
1599 let effective_classes = classes.normalize();
1600 {
1601 self.td.lock().unwrap().reconfigure(driver::TdConfig {
1602 addr: r.td_addr.clone(),
1603 user: r.td_user.clone(),
1604 password: r.td_password.to_exposed_string(),
1605 broker: r.broker_id.clone(),
1606 auth_code: r.td_auth_code.clone(),
1607 app_id: r.td_app_id.clone(),
1608 product_classes: classes.clone(),
1609 });
1610 }
1611 {
1612 self.md.lock().unwrap().reconfigure(driver::MdConfig {
1613 addr: r.md_addr.clone(),
1614 user: md_user,
1615 password: md_password,
1616 broker: r.broker_id.clone(),
1617 });
1618 }
1619 *self.broker.lock().unwrap() = r.broker_id.clone();
1620 *self.user.lock().unwrap() = r.td_user.clone();
1621 *self.cfg.lock().unwrap() = CtpConfig {
1622 gateway_name: self.gw.clone(),
1623 broker_id: r.broker_id.clone(),
1624 td_addr: r.td_addr.clone(),
1625 td_user: r.td_user.clone(),
1626 td_password: r.td_password.clone(),
1627 td_auth_code: r.td_auth_code.clone(),
1628 td_app_id: r.td_app_id.clone(),
1629 md_addr: r.md_addr.clone(),
1630 md_user: r.md_user.clone(),
1631 md_password: r.md_password.clone(),
1632 product_classes: classes,
1633 };
1634 if let Some(t) = &self.trans {
1636 t.td_release();
1637 t.md_release();
1638 }
1639 self.connect();
1640 self.persist_config();
1644 ConfigureReply {
1645 ok: true,
1646 msg: format!(
1647 "已配置并发起连接(先 TD 后 MD);收录产品类:{}",
1648 effective_classes.join(" ")
1649 ),
1650 product_classes: effective_classes,
1651 }
1652 }
1653
1654 #[must_use]
1657 pub fn credentials_state(&self) -> Vec<CredentialEntry> {
1658 let refs = lock_clone(&self.sec_refs);
1659 let cfg = lock_clone(&self.cfg);
1660 let mut out = Vec::with_capacity(2);
1661 for field in ["td_password", "md_password"] {
1662 let sk = refs
1663 .iter()
1664 .find(|(f, _)| f == field)
1665 .map(|(_, k)| k.clone())
1666 .unwrap_or_default();
1667 let resolved = if field == "td_password" {
1668 !cfg.td_password.is_empty()
1669 } else {
1670 !cfg.md_password.is_empty()
1671 };
1672 let state = if resolved {
1673 "RESOLVED"
1674 } else if sk.is_empty() {
1675 "ABSENT"
1676 } else {
1677 "REFERENCED"
1678 };
1679 out.push(CredentialEntry {
1680 field: field.to_owned(),
1681 state: state.to_owned(),
1682 ref_key: sk,
1683 });
1684 }
1685 out
1686 }
1687
1688 fn resolve_pending_secrets(&self) {
1693 let refs = lock_clone(&self.sec_refs);
1694 if refs.is_empty() {
1695 return;
1696 }
1697 let mut cfg = lock_clone(&self.cfg);
1698 let mut hit = false;
1699 for (field, sk) in &refs {
1700 match ek_cfg::secret::get(&self.ns, sk) {
1701 Some(v) => match std::str::from_utf8(v.as_bytes()) {
1702 Ok(text) => {
1703 if field == "td_password" {
1704 cfg.td_password = text.to_owned().into();
1705 hit = true;
1706 } else if field == "md_password" {
1707 cfg.md_password = text.to_owned().into();
1708 hit = true;
1709 }
1710 }
1711 Err(_) => {
1712 self.put_log(LogData {
1715 gateway_name: self.gw.clone(),
1716 msg: format!("{field} 的秘值不是文本(键 {sk}),按待手配处理"),
1717 level: 40,
1718 });
1719 }
1720 },
1721 None => {
1722 self.put_log(LogData {
1723 gateway_name: self.gw.clone(),
1724 msg: format!("{field} 的引用在库值缺(键 {sk}),待手配"),
1725 level: 20,
1726 });
1727 }
1728 }
1729 }
1730 if !hit {
1731 return;
1732 }
1733 let td_cfg = driver::TdConfig {
1736 addr: cfg.td_addr.clone(),
1737 user: cfg.td_user.clone(),
1738 password: cfg.td_password.to_exposed_string(),
1739 broker: cfg.broker_id.clone(),
1740 auth_code: cfg.td_auth_code.clone(),
1741 app_id: cfg.td_app_id.clone(),
1742 product_classes: cfg.product_classes.clone(),
1743 };
1744 let md_cfg = driver::MdConfig {
1745 addr: cfg.md_addr.clone(),
1746 user: if cfg.md_user.is_empty() {
1747 cfg.td_user.clone()
1748 } else {
1749 cfg.md_user.clone()
1750 },
1751 password: if cfg.md_password.is_empty() {
1752 cfg.td_password.to_exposed_string()
1753 } else {
1754 cfg.md_password.to_exposed_string()
1755 },
1756 broker: cfg.broker_id.clone(),
1757 };
1758 self.td.lock().unwrap().reconfigure(td_cfg);
1759 self.md.lock().unwrap().reconfigure(md_cfg);
1760 *self.cfg.lock().unwrap() = cfg;
1761 }
1762
1763 fn persist_config(&self) {
1770 let map = self.dump_config();
1773 if map.is_empty() {
1774 return; }
1776 let ns = Configurable::config_ns(self).to_owned();
1777 let req = ek_cfg::wire::SaveReq {
1778 ns: ns.clone(),
1779 map: map.clone(),
1780 };
1781 let ctx = self.ctx.get();
1782 let res = match ctx {
1783 Some(c) => c.ask_service::<ek_cfg::wire::SaveReq, ek_cfg::wire::SaveRes>(
1784 "cfg.save",
1785 req,
1786 std::time::Duration::from_millis(1500),
1787 ),
1788 None => Err(ek_core::error::AskError::UnknownService {
1790 service: "cfg.save".to_owned(),
1791 }),
1792 };
1793 match res {
1794 Ok(r) if r.ok => {
1795 self.put_log(LogData {
1796 gateway_name: self.gw.clone(),
1797 msg: format!("配置已持久化(段 {});警告{:?}", ns, r.warns),
1798 level: 20,
1799 });
1800 }
1801 Ok(r) => {
1802 self.put_log(LogData {
1803 gateway_name: self.gw.clone(),
1804 msg: format!("配置落盘失败(段 {}):{}", ns, r.msg),
1805 level: 40,
1806 });
1807 }
1808 Err(_) => {
1809 let once = {
1811 let mut f = self.save_fallback.lock().unwrap();
1812 let first = !*f;
1813 *f = true;
1814 first
1815 };
1816 if once {
1817 self.put_log(LogData {
1818 gateway_name: self.gw.clone(),
1819 msg: "cfg 缺席,已直写落盘".to_string(),
1820 level: 20,
1821 });
1822 }
1823 let ok = Self::store_direct(&ns, &map);
1824 if !ok {
1825 self.put_log(LogData {
1826 gateway_name: self.gw.clone(),
1827 msg: format!("直写托底也失败(段 {})", ns),
1828 level: 40,
1829 });
1830 }
1831 }
1832 }
1833 }
1834
1835 fn store_direct(ns: &str, map: &SectionMap) -> bool {
1842 let store = ek_cfg::Store::eco()
1843 .unwrap_or_else(|_| ek_cfg::Store::with_root("./ekcfg-local".into()));
1844 ek_cfg::cfg_save_at(store.root(), ns, map).is_ok()
1845 }
1846}
1847
1848impl Configurable for CtpPlugin {
1851 fn config_ns(&self) -> &str {
1852 &self.ns
1853 }
1854
1855 fn apply_config(&self, m: &SectionMap) -> Result<ApplyReport, String> {
1859 if m.rev > self.cfg_rev.load(Ordering::Relaxed) {
1860 self.cfg_rev.store(m.rev, Ordering::Relaxed);
1861 }
1862 let mut rep = ApplyReport::all_adopted();
1863 let mut patch = lock_clone(&self.cfg);
1864 let mut new_refs: Vec<(String, String)> = Vec::new();
1865 for (k, f) in m.iter() {
1866 match k {
1867 "broker_id" => {
1868 patch.broker_id = f.plain_value().unwrap_or("").to_owned();
1869 rep.adopted.push(k.to_owned());
1870 }
1871 "td_addr" => {
1872 patch.td_addr = f.plain_value().unwrap_or("").to_owned();
1873 rep.adopted.push(k.to_owned());
1874 }
1875 "td_user" => {
1876 patch.td_user = f.plain_value().unwrap_or("").to_owned();
1877 rep.adopted.push(k.to_owned());
1878 }
1879 "td_auth_code" => {
1880 patch.td_auth_code = f.plain_value().unwrap_or("").to_owned();
1881 rep.adopted.push(k.to_owned());
1882 }
1883 "td_app_id" => {
1884 patch.td_app_id = f.plain_value().unwrap_or("").to_owned();
1885 rep.adopted.push(k.to_owned());
1886 }
1887 "md_addr" => {
1888 patch.md_addr = f.plain_value().unwrap_or("").to_owned();
1889 rep.adopted.push(k.to_owned());
1890 }
1891 "md_user" => {
1892 patch.md_user = f.plain_value().unwrap_or("").to_owned();
1893 rep.adopted.push(k.to_owned());
1894 }
1895 "product_classes" => {
1896 patch.product_classes = crate::map::ProductClasses(
1897 f.plain_value()
1898 .unwrap_or("")
1899 .split(',')
1900 .map(str::trim)
1901 .filter(|c| !c.is_empty())
1902 .map(str::to_owned)
1903 .collect(),
1904 );
1905 rep.adopted.push(k.to_owned());
1906 }
1907 "td_password" | "md_password" => {
1908 if let Some(sk) = f.secret_key() {
1909 new_refs.push((k.to_owned(), sk.to_owned())); rep.adopted.push(k.to_owned());
1911 } else {
1912 rep.deferred.push(k.to_owned()); }
1914 }
1915 _ => rep.deferred.push(k.to_owned()), }
1917 }
1918 *self.sec_refs.lock().unwrap() = new_refs;
1919 let td_cfg = driver::TdConfig {
1922 addr: patch.td_addr.clone(),
1923 user: patch.td_user.clone(),
1924 password: patch.td_password.to_exposed_string(),
1925 broker: patch.broker_id.clone(),
1926 auth_code: patch.td_auth_code.clone(),
1927 app_id: patch.td_app_id.clone(),
1928 product_classes: patch.product_classes.clone(),
1929 };
1930 let md_cfg = driver::MdConfig {
1931 addr: patch.md_addr.clone(),
1932 user: if patch.md_user.is_empty() {
1933 patch.td_user.clone()
1934 } else {
1935 patch.md_user.clone()
1936 },
1937 password: if patch.md_password.is_empty() {
1938 patch.td_password.to_exposed_string()
1939 } else {
1940 patch.md_password.to_exposed_string()
1941 },
1942 broker: patch.broker_id.clone(),
1943 };
1944 self.td
1945 .lock()
1946 .map_err(|e| e.to_string())?
1947 .reconfigure(td_cfg);
1948 self.md
1949 .lock()
1950 .map_err(|e| e.to_string())?
1951 .reconfigure(md_cfg);
1952 *self.broker.lock().unwrap() = patch.broker_id.clone();
1953 *self.user.lock().unwrap() = patch.td_user.clone();
1954 *self.cfg.lock().unwrap() = patch;
1955 Ok(rep)
1956 }
1957
1958 fn dump_config(&self) -> SectionMap {
1962 let next = self.cfg_rev.load(Ordering::Relaxed) + 1;
1963 self.cfg_rev.store(next, Ordering::Relaxed);
1964 let cfg = lock_clone(&self.cfg);
1965 let mut map = SectionMap::new(next);
1966 for (k, v) in [
1967 ("broker_id", &cfg.broker_id),
1968 ("td_addr", &cfg.td_addr),
1969 ("td_user", &cfg.td_user),
1970 ("td_auth_code", &cfg.td_auth_code),
1971 ("td_app_id", &cfg.td_app_id),
1972 ("md_addr", &cfg.md_addr),
1973 ("md_user", &cfg.md_user),
1974 ] {
1975 if !v.is_empty() {
1976 map = map.set(k, Field::plain(v));
1977 }
1978 }
1979 if !cfg.product_classes.0.is_empty() {
1980 map = map.set(
1981 "product_classes",
1982 Field::plain(cfg.product_classes.0.join(",")),
1983 );
1984 }
1985 for (field, value) in [
1986 ("td_password", &cfg.td_password),
1987 ("md_password", &cfg.md_password),
1988 ] {
1989 let refs = lock_clone(&self.sec_refs);
1990 if let Some((_, sk)) = refs.iter().find(|(f, _)| f == field) {
1991 if !value.is_empty() {
1995 let _ = ek_cfg::secret::register(
1996 &self.ns,
1997 sk,
1998 ek_cfg::SecretValue::from_bytes(value.to_exposed_string()),
1999 );
2000 }
2001 map = map.set(field, Field::secret(sk.as_str()));
2002 } else if !value.is_empty() {
2003 let sk = format!("gw_{field}");
2005 if ek_cfg::secret::register(
2006 &self.ns,
2007 &sk,
2008 ek_cfg::SecretValue::from_bytes(value.to_exposed_string()),
2009 )
2010 .is_ok()
2011 {
2012 self.sec_refs
2013 .lock()
2014 .unwrap()
2015 .push((field.to_owned(), sk.clone()));
2016 map = map.set(field, Field::secret(sk.as_str()));
2017 }
2018 }
2020 }
2022 map
2023 }
2024}
2025
2026impl Default for CtpPlugin {
2027 fn default() -> Self {
2028 Self::new(CtpConfig::default())
2029 }
2030}
2031
2032impl Plugin for CtpPlugin {
2033 fn name(&self) -> &str {
2034 &self.gw
2035 }
2036
2037 fn deps(&self) -> &[&str] {
2038 &["cfg"]
2040 }
2041
2042 fn routes(&self) -> Vec<Cow<'static, str>> {
2043 vec![Cow::Owned(e_req_type(&self.gw)), Cow::Borrowed(ROUTE_TIMER)]
2044 }
2045
2046 fn version(&self) -> Cow<'static, str> {
2047 Cow::Borrowed(env!("CARGO_PKG_VERSION"))
2048 }
2049
2050 fn services(&self) -> Vec<ServiceDesc> {
2051 vec![
2052 ServiceDesc::describe::<SubscribeReq, SubscribeReply>(
2053 format!("{}.subscribe", self.gw),
2054 "登记并(已登录时)下发行情订阅",
2055 ),
2056 ServiceDesc::describe::<SendOrderReq, SendOrderReply>(
2057 format!("{}.send_order", self.gw),
2058 "下单(InputOrder + SUBMITTING 单已上总线)",
2059 ),
2060 ServiceDesc::describe::<CancelOrderReq, CancelOrderReply>(
2061 format!("{}.cancel_order", self.gw),
2062 "撤单(三段订单号解析)",
2063 ),
2064 ServiceDesc::describe::<QueryRateReq, QueryRateReply>(
2065 format!("{}.query_rate", self.gw),
2066 "费率查询(margin=保证金率/commission=手续费率;状态为纯被动推送)",
2067 ),
2068 ServiceDesc::describe::<ConfigureReq, ConfigureReply>(
2069 format!("{}.configure", self.gw),
2070 "网关配置(插件自带配置面;手工输入账号/前置 → 复位守卫并发起连接)",
2071 ),
2072 ServiceDesc::describe::<StatusReq, GatewayStatus>(
2073 format!("{}.{}", self.gw, SVC_STATUS),
2074 "状态读数(0.9.0;登录态 / 断开计数 / 查询被拒计数 / 最近回报时刻;只读、不写日志)",
2075 ),
2076 ServiceDesc::describe::<DisconnectReq, DisconnectReply>(
2077 format!("{}.disconnect", self.gw),
2078 "停会话(0.9.0;实例与订阅保留,『连接』可恢复)",
2079 ),
2080 ServiceDesc::describe::<CredentialStateReq, Vec<CredentialEntry>>(
2082 format!("{}.credentials_state", self.gw),
2083 "凭据档位(REFERENCED/RESOLVED/ABSENT;只读)",
2084 ),
2085 ]
2086 }
2087
2088 fn on_init(&self, ctx: &PluginContext) {
2089 let _ = self.ctx.set(ctx.clone());
2090 }
2091
2092 fn on_start(&self) {
2093 self.resolve_pending_secrets();
2097 self.connect();
2100 }
2101
2102 fn on_stop(&self) {
2103 if let Some(t) = &self.trans {
2105 t.shutdown();
2106 }
2107 self.flush_contract();
2109 }
2110
2111 fn on_mail(&self, event: &Event) {
2112 if event.event_type == ROUTE_TIMER {
2113 self.on_timer();
2114 return;
2115 }
2116 if event.event_type == e_req_type(&self.gw) {
2117 self.on_request(event);
2118 }
2119 }
2120
2121 fn as_configurable(&self) -> Option<&dyn Configurable> {
2122 Some(self)
2124 }
2125}
2126
2127trait EnrichName {
2132 fn symbol(&self) -> &str;
2134 fn name_mut(&mut self) -> &mut String;
2136}
2137
2138impl EnrichName for ek_data::order::OrderData {
2139 fn symbol(&self) -> &str {
2140 &self.symbol
2141 }
2142 fn name_mut(&mut self) -> &mut String {
2143 &mut self.name
2144 }
2145}
2146
2147impl EnrichName for ek_data::trade::TradeData {
2148 fn symbol(&self) -> &str {
2149 &self.symbol
2150 }
2151 fn name_mut(&mut self) -> &mut String {
2152 &mut self.name
2153 }
2154}
2155
2156impl EnrichName for ek_data::position::PositionData {
2157 fn symbol(&self) -> &str {
2158 &self.symbol
2159 }
2160 fn name_mut(&mut self) -> &mut String {
2161 &mut self.name
2162 }
2163}