use crate::driver::{self, MdDriver, MdEvent, MdOut, TdDriver, TdEvent, TdOut};
use crate::wire::InputOrder;
use ek_core::context::PluginContext;
use ek_core::envelope::{e_req_type, Envelope, ReplyErr};
use ek_core::plugin::Plugin;
use ek_core::service::ServiceDesc;
use ek_data::cancel_request::CancelRequest;
use ek_data::log::LogData;
use ek_data::order_request::OrderRequest;
use ek_event::Event;
use std::borrow::Cow;
use std::sync::{Arc, Mutex, OnceLock};
const W_TICK: &str = "eTick.";
const W_ORDER: &str = "eOrder.";
const W_TRADE: &str = "eTrade.";
const W_POSITION: &str = "ePosition.";
const W_ACCOUNT: &str = "eAccount.";
const W_CONTRACT: &str = "eContract.";
const W_POS_DONE: &str = "ePositionDone.";
use ek_data::event::EVENT_LOG as W_LOG;
pub use ek_event::EVENT_TIMER as ROUTE_TIMER;
#[derive(Debug, Clone)]
pub struct SubscribeReq {
pub symbols: Vec<String>,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct SubscribeReply {
pub ok: bool,
pub msg: String,
}
#[derive(Debug, Clone)]
pub struct SendOrderReq {
pub req: OrderRequest,
}
#[derive(Debug, Clone, PartialEq)]
pub struct SendOrderReply {
pub ok: bool,
pub en_orderid: String,
pub msg: String,
}
#[derive(Debug, Clone)]
pub struct CancelOrderReq {
pub req: CancelRequest,
}
#[derive(Debug, Clone, PartialEq, Eq)]
pub struct CancelOrderReply {
pub ok: bool,
pub msg: String,
}
pub trait CtpTransport: Send + Sync {
fn md_connect(&self, addr: &str, flow_path: &str);
fn md_login(&self, user: &str, password: &str, broker: &str);
fn md_subscribe(&self, symbols: &[String]);
fn td_connect(&self, addr: &str, flow_path: &str);
fn td_auth(&self, user: &str, broker: &str, auth_code: &str, app_id: &str);
fn td_login(&self, user: &str, password: &str, broker: &str);
fn td_settlement_confirm(&self, broker: &str, user: &str);
fn td_qry_instrument(&self);
fn td_qry_account(&self, broker: &str, user: &str);
fn td_qry_position(&self, broker: &str, user: &str);
fn td_insert(&self, order: &InputOrder) -> i32;
fn td_cancel(&self, action: &crate::wire::InputOrderAction) -> i32;
}
#[derive(Debug, Clone, Default)]
pub struct CtpConfig {
pub gateway_name: String,
pub broker_id: String,
pub td_addr: String,
pub td_user: String,
pub td_password: String,
pub td_auth_code: String,
pub td_app_id: String,
pub md_addr: String,
pub md_user: String,
pub md_password: String,
}
impl CtpConfig {
pub fn gateway_name_or_default(&self) -> String {
if self.gateway_name.is_empty() {
"ctp".to_string()
} else {
self.gateway_name.clone()
}
}
}
pub struct CtpPlugin {
gw: String,
md: Mutex<MdDriver>,
td: Mutex<TdDriver>,
timer_count: Mutex<u32>,
trans: Option<Box<dyn CtpTransport>>,
ctx: OnceLock<PluginContext>,
trans_warned: Mutex<bool>,
}
impl CtpPlugin {
pub fn new(cfg: CtpConfig) -> Self {
let gw = cfg.gateway_name_or_default();
Self::with_parts(cfg, gw, None)
}
pub fn with_transport(cfg: CtpConfig, trans: Box<dyn CtpTransport>) -> Self {
let gw = cfg.gateway_name_or_default();
Self::with_parts(cfg, gw, Some(trans))
}
fn with_parts(cfg: CtpConfig, gw: String, trans: Option<Box<dyn CtpTransport>>) -> Self {
let contracts = driver::contracts_empty();
let md_cfg = driver::MdConfig {
addr: cfg.md_addr.clone(),
user: cfg.md_user.clone(),
password: cfg.md_password.clone(),
broker: cfg.broker_id.clone(),
};
let td_cfg = driver::TdConfig {
addr: cfg.td_addr.clone(),
user: cfg.td_user.clone(),
password: cfg.td_password.clone(),
broker: cfg.broker_id.clone(),
auth_code: cfg.td_auth_code.clone(),
app_id: cfg.td_app_id.clone(),
};
let md = Mutex::new(MdDriver::new(&gw, md_cfg, contracts.clone(), crate::map::shanghai_now_date()));
let td = Mutex::new(TdDriver::new(&gw, td_cfg, contracts.clone()));
Self {
gw,
md,
td,
timer_count: Mutex::new(0),
trans,
ctx: OnceLock::new(),
trans_warned: Mutex::new(false),
}
}
pub fn on_md_event(&self, ev: MdEvent) {
let outs = self.md.lock().unwrap().step(ev);
for o in outs {
self.emit_md(o);
}
}
pub fn on_td_event(&self, ev: TdEvent) {
let outs = self.td.lock().unwrap().step(ev);
for o in outs {
self.emit_td(o);
}
}
pub fn on_timer(&self) {
let gate_pass = {
let mut c = self.timer_count.lock().unwrap();
*c += 1;
if *c < 2 {
false
} else {
*c = 0;
true
}
};
if !gate_pass {
return;
}
let outs = self.td.lock().unwrap().on_timer();
for o in outs {
self.emit_td(o);
}
let date = crate::map::shanghai_now_date();
self.md.lock().unwrap().update_date(date);
}
fn emit_md(&self, o: MdOut) {
match o {
MdOut::Log(l) => self.put_log(l),
MdOut::Tick(t) => self.put_data(W_TICK, t),
MdOut::ReqConnect { addr, flow_path } => {
if let Some(t) = &self.trans {
t.md_connect(&addr, &flow_path);
} else {
self.warn_no_transport();
}
}
MdOut::ReqLogin { user, password, broker } => {
if let Some(t) = &self.trans {
t.md_login(&user, &password, &broker);
} else {
self.warn_no_transport();
}
}
MdOut::ReqSubscribe(symbols) => {
if let Some(t) = &self.trans {
t.md_subscribe(&symbols);
} else {
self.warn_no_transport();
}
}
}
}
fn emit_td(&self, o: TdOut) {
match o {
TdOut::Log(l) => self.put_log(l),
TdOut::Contract(c) => self.put_data(W_CONTRACT, c),
TdOut::Order(o) => self.put_data(W_ORDER, o),
TdOut::Trade(t) => self.put_data(W_TRADE, t),
TdOut::Position(p) => self.put_data(W_POSITION, p),
TdOut::Account(a) => self.put_data(W_ACCOUNT, a),
TdOut::PositionQueryDone(gw) => {
if let Some(ctx) = self.ctx.get() {
let _ = ctx.put(Event::with_data(W_POS_DONE, Arc::new(gw)));
}
}
TdOut::ContractFinish(gw) => {
if let Some(ctx) = self.ctx.get() {
let _ = ctx.put(Event::with_data("eContractFinish", Arc::new(gw)));
}
}
TdOut::ReqConnect { addr, flow_path } => {
if let Some(t) = &self.trans {
t.td_connect(&addr, &flow_path);
} else {
self.warn_no_transport();
}
}
TdOut::ReqAuthenticate { user, broker, auth_code, app_id } => {
if let Some(t) = &self.trans {
t.td_auth(&user, &broker, &auth_code, &app_id);
} else {
self.warn_no_transport();
}
}
TdOut::ReqLogin { user, password, broker } => {
if let Some(t) = &self.trans {
t.td_login(&user, &password, &broker);
} else {
self.warn_no_transport();
}
}
TdOut::ReqSettlementConfirm { broker, user } => {
if let Some(t) = &self.trans {
t.td_settlement_confirm(&broker, &user);
} else {
self.warn_no_transport();
}
}
TdOut::ReqQryInstrument => {
if let Some(t) = &self.trans {
t.td_qry_instrument();
} else {
self.warn_no_transport();
}
}
TdOut::ReqQryAccount { broker, user } => {
if let Some(t) = &self.trans {
t.td_qry_account(&broker, &user);
} else {
self.warn_no_transport();
}
}
TdOut::ReqQryPosition { broker, user } => {
if let Some(t) = &self.trans {
t.td_qry_position(&broker, &user);
} else {
self.warn_no_transport();
}
}
TdOut::Insert(o) => {
if let Some(t) = &self.trans {
t.td_insert(&o);
} else {
self.warn_no_transport();
}
}
TdOut::Cancel(a) => {
if let Some(t) = &self.trans {
t.td_cancel(&a);
} else {
self.warn_no_transport();
}
}
}
}
fn put_log(&self, l: LogData) {
if let Some(ctx) = self.ctx.get() {
let _ = ctx.put(Event::with_data(W_LOG, Arc::new(l)));
}
}
fn put_data<T: Send + Sync + 'static>(&self, prefix: &str, payload: T) {
if let Some(ctx) = self.ctx.get() {
let _ = ctx.put(Event::with_data(format!("{}{}", prefix, self.gw), Arc::new(payload)));
}
}
fn warn_no_transport(&self) {
let mut warned = self.trans_warned.lock().unwrap();
if !*warned {
*warned = true;
self.put_log(LogData {
gateway_name: self.gw.clone(),
msg: "传输层未接入(默认构建):连接/查询/发送动作已被吸收".to_string(),
level: 20,
});
}
}
fn on_request(&self, event: &Event) {
let Some(env) = event.data.downcast_ref::<Envelope>() else {
return;
};
macro_rules! arm {
($ty:ty, $res:ty, $body:expr) => {
if let Some(req) = env.payload.downcast_ref::<$ty>() {
let res: $res = $body(req);
let _ = env.reply.send(Box::new(Ok::<$res, ReplyErr>(res)));
return;
}
};
}
arm!(SubscribeReq, SubscribeReply, |r: &SubscribeReq| self.do_subscribe(r));
arm!(SendOrderReq, SendOrderReply, |r: &SendOrderReq| self.do_send_order(r));
arm!(CancelOrderReq, CancelOrderReply, |r: &CancelOrderReq| self.do_cancel_order(r));
let _ = env.reply.send(Box::new(Err::<(), ReplyErr>(ReplyErr::InvalidPayload)));
}
fn do_subscribe(&self, r: &SubscribeReq) -> SubscribeReply {
let mut outs = Vec::new();
{
let mut md = self.md.lock().unwrap();
for s in &r.symbols {
outs.extend(md.subscribe(s));
}
}
for o in outs {
self.emit_md(o);
}
SubscribeReply { ok: true, msg: String::new() }
}
fn do_send_order(&self, r: &SendOrderReq) -> SendOrderReply {
let outcome = {
let mut td = self.td.lock().unwrap();
td.send_order(&r.req)
};
if let Some(log) = &outcome.error {
self.put_log(log.clone());
}
let mut submit_order: Option<ek_data::order::OrderData> = None;
let mut send_failed: Option<String> = None;
for o in outcome.outs {
match o {
TdOut::Insert(input) => match &self.trans {
Some(t) => {
let n = t.td_insert(&input);
if n != 0 {
send_failed = Some(format!("委托请求发送失败,错误代码:{n}"));
}
}
None => self.warn_no_transport(),
},
TdOut::Order(od) => submit_order = Some(od),
other => self.emit_td(other),
}
}
if let Some(msg) = send_failed {
let log = LogData { gateway_name: self.gw.clone(), msg: msg.clone(), level: 20 };
self.put_log(log);
return SendOrderReply { ok: false, en_orderid: String::new(), msg };
}
if let Some(od) = submit_order {
self.emit_td(TdOut::Order(od));
}
if outcome.en_orderid.is_empty() {
let msg = outcome.error.map(|l| l.msg).unwrap_or_default();
SendOrderReply { ok: false, en_orderid: String::new(), msg }
} else if self.trans.is_none() {
self.warn_no_transport();
SendOrderReply {
ok: false,
en_orderid: outcome.en_orderid,
msg: "传输层未接入(默认构建)".to_string(),
}
} else {
SendOrderReply { ok: true, en_orderid: outcome.en_orderid, msg: String::new() }
}
}
fn do_cancel_order(&self, r: &CancelOrderReq) -> CancelOrderReply {
let outs = {
let mut td = self.td.lock().unwrap();
td.cancel_order(&r.req)
};
let mut rejected_log: Option<LogData> = None;
let outs: Vec<TdOut> = outs
.into_iter()
.filter(|o| match o {
TdOut::Log(l) => {
rejected_log = Some(l.clone());
false
}
_ => true,
})
.collect();
if let Some(l) = rejected_log {
let msg = l.msg.clone();
self.put_log(l);
return CancelOrderReply { ok: false, msg };
}
for o in outs {
self.emit_td(o);
}
if self.trans.is_none() {
self.warn_no_transport();
return CancelOrderReply { ok: false, msg: "传输层未接入(默认构建)".to_string() };
}
CancelOrderReply { ok: true, msg: String::new() }
}
}
impl Default for CtpPlugin {
fn default() -> Self {
Self::new(CtpConfig::default())
}
}
impl Plugin for CtpPlugin {
fn name(&self) -> &str {
&self.gw
}
fn routes(&self) -> Vec<Cow<'static, str>> {
vec![Cow::Owned(e_req_type(&self.gw)), Cow::Borrowed(ROUTE_TIMER)]
}
fn version(&self) -> Cow<'static, str> {
Cow::Borrowed(env!("CARGO_PKG_VERSION"))
}
fn services(&self) -> Vec<ServiceDesc> {
vec![
ServiceDesc::describe::<SubscribeReq, SubscribeReply>(
format!("{}.subscribe", self.gw),
"登记并(已登录时)下发行情订阅",
),
ServiceDesc::describe::<SendOrderReq, SendOrderReply>(
format!("{}.send_order", self.gw),
"下单(InputOrder + SUBMITTING 单已上总线)",
),
ServiceDesc::describe::<CancelOrderReq, CancelOrderReply>(
format!("{}.cancel_order", self.gw),
"撤单(三段订单号解析)",
),
]
}
fn on_init(&self, ctx: &PluginContext) {
let _ = self.ctx.set(ctx.clone());
}
fn on_start(&self) {
}
fn on_mail(&self, event: &Event) {
if event.event_type == ROUTE_TIMER {
self.on_timer();
return;
}
if event.event_type == e_req_type(&self.gw) {
self.on_request(event);
}
}
}