use crate::map::{self, Mapped};
use crate::wire::{
DepthMarketData, ErrorField, InputOrder, InputOrderAction, InstrumentField, InvestorPositionField,
OrderField, TradeField, TradingAccountField,
};
use ek_data::constant::{Direction, Offset, OrderType, Status};
use ek_data::log::{LogData, LEVEL_ERROR, LEVEL_INFO};
use ek_data::{AccountData, ContractData, OrderData, OrderRequest, PositionData, TickData, TradeData, CancelRequest};
use std::collections::HashMap;
use std::sync::{Arc, Mutex};
use ek_data::log::LEVEL_WARNING;
pub type Contracts = Arc<Mutex<HashMap<String, ContractData>>>;
pub fn contracts_empty() -> Contracts {
Arc::new(Mutex::new(HashMap::new()))
}
pub fn log_info(gw: &str, msg: impl Into<String>) -> LogData {
LogData { gateway_name: gw.to_string(), msg: msg.into(), level: LEVEL_INFO }
}
pub fn log_error_with(api_msg: &str, gw: &str, e: &ErrorField) -> LogData {
LogData {
gateway_name: gw.to_string(),
msg: map::error_text(api_msg, e),
level: LEVEL_ERROR,
}
}
#[derive(Debug, Clone, Default)]
pub struct MdConfig {
pub addr: String,
pub user: String,
pub password: String,
pub broker: String,
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum MdEvent {
FrontConnected,
FrontDisconnected(i64),
RspUserLogin {
error: Option<ErrorField>,
},
RspError(ErrorField),
RspSubMarketData {
error: Option<ErrorField>,
},
RtnDepthMarketData(DepthMarketData),
}
#[derive(Debug, Clone)]
#[allow(clippy::large_enum_variant)]
pub enum MdOut {
ReqConnect {
addr: String,
flow_path: String,
},
ReqLogin {
user: String,
password: String,
broker: String,
},
ReqSubscribe(Vec<String>),
Tick(TickData),
Log(LogData),
}
pub struct MdDriver {
pub gw: String,
cfg: MdConfig,
contracts: Contracts,
subscribed: Vec<String>,
login_status: bool,
connect_status: bool,
current_date: String,
}
impl MdDriver {
pub fn new(gw: &str, cfg: MdConfig, contracts: Contracts, current_date: String) -> Self {
Self {
gw: gw.to_string(),
cfg,
contracts,
subscribed: Vec::new(),
login_status: false,
connect_status: false,
current_date,
}
}
pub fn update_date(&mut self, date: String) {
self.current_date = date;
}
pub fn subscribe(&mut self, symbol: &str) -> Vec<MdOut> {
if !self.subscribed.iter().any(|s| s == symbol) {
self.subscribed.push(symbol.to_string());
}
if self.login_status {
vec![MdOut::ReqSubscribe(vec![symbol.to_string()])]
} else {
Vec::new()
}
}
pub fn connect(&mut self) -> Vec<MdOut> {
if self.connect_status {
return Vec::new();
}
self.connect_status = true;
vec![MdOut::ReqConnect { addr: self.cfg.addr.clone(), flow_path: format!("{}\\Md", self.gw) }]
}
pub fn step(&mut self, ev: MdEvent) -> Vec<MdOut> {
match ev {
MdEvent::FrontConnected => {
self.connect_status = true;
vec![
MdOut::Log(log_info(&self.gw, "行情服务器连接成功")),
MdOut::ReqLogin {
user: self.cfg.user.clone(),
password: self.cfg.password.clone(),
broker: self.cfg.broker.clone(),
},
]
}
MdEvent::FrontDisconnected(reason) => {
self.login_status = false;
vec![MdOut::Log(log_info(&self.gw, format!("行情服务器连接断开,原因{reason}")))]
}
MdEvent::RspUserLogin { error } => match error {
None => {
self.login_status = true;
let mut outs = vec![MdOut::Log(log_info(&self.gw, "行情服务器登录成功"))];
if !self.subscribed.is_empty() {
outs.push(MdOut::ReqSubscribe(self.subscribed.clone()));
}
outs
}
Some(e) => vec![MdOut::Log(log_error_with("行情服务器登录失败", &self.gw, &e))],
},
MdEvent::RspError(e) => vec![MdOut::Log(log_error_with("行情接口报错", &self.gw, &e))],
MdEvent::RspSubMarketData { error } => match error {
Some(e) => vec![MdOut::Log(log_error_with("行情订阅失败", &self.gw, &e))],
None => Vec::new(),
},
MdEvent::RtnDepthMarketData(dm) => {
let contract = {
let m = self.contracts.lock().unwrap();
m.get(&dm.instrument_id).cloned()
};
match map::tick_from(&dm, contract.as_ref(), &self.gw, &self.current_date) {
Mapped::Ok(t) => vec![MdOut::Tick(t)],
Mapped::Skip(_) => Vec::new(),
}
}
}
}
}
#[derive(Debug, Clone, Default)]
pub struct TdConfig {
pub addr: String,
pub user: String,
pub password: String,
pub broker: String,
pub auth_code: String,
pub app_id: String,
}
#[derive(Debug, Clone)]
pub enum TdEvent {
FrontConnected,
FrontDisconnected(i64),
RspAuthenticate {
error: Option<ErrorField>,
},
RspUserLogin {
error: Option<ErrorField>,
front_id: i64,
session_id: i64,
},
RspSettlementInfoConfirm {
error: Option<ErrorField>,
},
RspQryInstrument {
last: bool,
row: Option<InstrumentField>,
},
RspQryInvestorPosition {
last: bool,
row: Option<InvestorPositionField>,
},
RspQryTradingAccount {
last: bool,
row: Option<TradingAccountField>,
},
RspOrderInsert {
error: Option<ErrorField>,
req: Option<InputOrder>,
},
RspOrderAction {
error: Option<ErrorField>,
},
RtnOrder(OrderField),
RtnTrade(TradeField),
}
#[derive(Debug, Clone)]
pub enum TdOut {
ReqConnect {
addr: String,
flow_path: String,
},
ReqAuthenticate {
user: String,
broker: String,
auth_code: String,
app_id: String,
},
ReqLogin {
user: String,
password: String,
broker: String,
},
ReqSettlementConfirm {
broker: String,
user: String,
},
ReqQryInstrument,
ReqQryAccount {
broker: String,
user: String,
},
ReqQryPosition {
broker: String,
user: String,
},
Insert(InputOrder),
Cancel(InputOrderAction),
Contract(ContractData),
Order(OrderData),
Trade(TradeData),
Position(PositionData),
PositionQueryDone(String),
ContractFinish(String),
Account(AccountData),
Log(LogData),
}
struct PositionAccum {
pos: PositionData,
}
pub struct TdDriver {
gw: String,
cfg: TdConfig,
contracts: Contracts,
pub reqid: i64,
order_ref: i64,
connect_status: bool,
login_status: bool,
auth_status: bool,
auth_failed: bool,
login_failed: bool,
contract_inited: bool,
front_id: i64,
session_id: i64,
positions: HashMap<String, PositionAccum>,
order_buf: Vec<OrderField>,
trade_buf: Vec<TradeField>,
sysid_orderid: HashMap<String, String>,
next_pos: bool,
}
impl TdDriver {
pub fn new(gw: &str, cfg: TdConfig, contracts: Contracts) -> Self {
Self {
gw: gw.to_string(),
cfg,
contracts,
reqid: 0,
order_ref: 0,
connect_status: false,
login_status: false,
auth_status: false,
auth_failed: false,
login_failed: false,
contract_inited: false,
front_id: 0,
session_id: 0,
positions: HashMap::new(),
order_buf: Vec::new(),
trade_buf: Vec::new(),
sysid_orderid: HashMap::new(),
next_pos: false,
}
}
pub fn local_orderid(&self, order_ref: &str) -> String {
format!("{}_{}_{}", self.front_id, self.session_id, order_ref)
}
pub fn auth_failed(&self) -> bool {
self.auth_failed
}
pub fn login_status(&self) -> bool {
self.login_status
}
pub fn login_failed(&self) -> bool {
self.login_failed
}
pub fn contract_inited(&self) -> bool {
self.contract_inited
}
pub fn positions_len(&self) -> usize {
self.positions.len()
}
pub fn front_id(&self) -> i64 {
self.front_id
}
pub fn connect_status(&self) -> bool {
self.connect_status
}
pub fn session_id(&self) -> i64 {
self.session_id
}
pub fn order_ref(&self) -> i64 {
self.order_ref
}
pub fn send_order(&mut self, req: &OrderRequest) -> SendOutcome {
let Some(offset_code) = map::offset_to(req.offset) else {
return SendOutcome::rejected(log_info(&self.gw, "请选择开平方向"));
};
let Some((price_type, time_cond, vol_cond)) = map::order_type_to(req.order_type) else {
return SendOutcome::rejected(log_info(
&self.gw,
format!("当前接口不支持该类型的委托{}", req.order_type.as_str()),
));
};
self.order_ref += 1;
let order_ref = format!("{}", self.order_ref);
let input = InputOrder {
instrument_id: req.symbol.clone(),
exchange_id: req.exchange.as_str().to_string(),
limit_price: req.price,
volume_total_original: req.volume as i64,
order_price_type: price_type.to_string(),
direction: map::direction_to(req.direction).unwrap_or("").to_string(),
comb_offset_flag: offset_code.to_string(),
order_ref: order_ref.clone(),
investor_id: self.cfg.user.clone(),
user_id: self.cfg.user.clone(),
broker_id: self.cfg.broker.clone(),
comb_hedge_flag: "1".to_string(),
contingent_condition: "1".to_string(),
force_close_reason: "0".to_string(),
is_auto_suspend: 0,
time_condition: time_cond.to_string(),
volume_condition: vol_cond.to_string(),
min_volume: 1,
};
let orderid = self.local_orderid(&order_ref);
let outs = vec![
TdOut::Insert(input),
TdOut::Order(req.create_order_data(&orderid, &self.gw)),
];
SendOutcome::placed(outs, format!("{}.{}", self.gw, orderid))
}
pub fn flow_path(&self) -> String {
format!("{}\\Td", self.gw)
}
pub fn connect(&mut self) -> Vec<TdOut> {
if self.connect_status {
return self.authenticate();
}
self.connect_status = true;
vec![TdOut::ReqConnect { addr: self.cfg.addr.clone(), flow_path: self.flow_path() }]
}
pub fn authenticate(&mut self) -> Vec<TdOut> {
if self.auth_failed {
return Vec::new();
}
vec![TdOut::ReqAuthenticate {
user: self.cfg.user.clone(),
broker: self.cfg.broker.clone(),
auth_code: self.cfg.auth_code.clone(),
app_id: self.cfg.app_id.clone(),
}]
}
pub fn login(&mut self) -> Vec<TdOut> {
if self.login_failed {
return Vec::new();
}
vec![TdOut::ReqLogin {
user: self.cfg.user.clone(),
password: self.cfg.password.clone(),
broker: self.cfg.broker.clone(),
}]
}
pub fn cancel_order(&mut self, req: &CancelRequest) -> Vec<TdOut> {
match map::cancel_action_from(&req.orderid, &req.symbol, req.exchange, &self.cfg.broker, &self.cfg.user) {
Some(action) => vec![TdOut::Cancel(action)],
None => vec![TdOut::Log(log_info(&self.gw, format!("撤单请求委托号异常:{}", req.orderid)))],
}
}
fn remember_sysid(&mut self, sysid: &str, orderid: &str) {
self.sysid_orderid.insert(sysid.to_string(), orderid.to_string());
}
pub fn on_timer(&mut self) -> Vec<TdOut> {
if self.next_pos {
self.next_pos = false;
if self.contracts.lock().unwrap().is_empty() {
return Vec::new();
}
vec![TdOut::ReqQryPosition { broker: self.cfg.broker.clone(), user: self.cfg.user.clone() }]
} else {
self.next_pos = true;
vec![TdOut::ReqQryAccount { broker: self.cfg.broker.clone(), user: self.cfg.user.clone() }]
}
}
pub fn step(&mut self, ev: TdEvent) -> Vec<TdOut> {
match ev {
TdEvent::FrontConnected => {
self.connect_status = true;
let mut outs = vec![TdOut::Log(log_info(&self.gw, "交易服务器连接成功"))];
if self.cfg.auth_code.is_empty() {
outs.extend(self.login());
} else {
outs.extend(self.authenticate());
}
outs
}
TdEvent::FrontDisconnected(reason) => {
self.login_status = false;
vec![TdOut::Log(log_info(&self.gw, format!("交易服务器连接断开,原因{reason}")))]
}
TdEvent::RspAuthenticate { error } => match error {
None => {
self.auth_status = true;
vec![
TdOut::Log(log_info(&self.gw, "交易服务器授权验证成功")),
TdOut::ReqLogin {
user: self.cfg.user.clone(),
password: self.cfg.password.clone(),
broker: self.cfg.broker.clone(),
},
]
}
Some(e) => {
let outs = vec![TdOut::Log(log_error_with("交易服务器授权验证失败", &self.gw, &e))];
if e.error_id == 63 {
self.auth_failed = true;
}
outs
}
},
TdEvent::RspUserLogin { error, front_id, session_id } => match error {
None => {
self.front_id = front_id;
self.session_id = session_id;
self.login_status = true;
vec![
TdOut::Log(log_info(&self.gw, "交易服务器登录成功")),
TdOut::ReqSettlementConfirm { broker: self.cfg.broker.clone(), user: self.cfg.user.clone() },
]
}
Some(e) => {
self.login_failed = true;
vec![TdOut::Log(log_error_with("交易服务器登录失败", &self.gw, &e))]
}
},
TdEvent::RspSettlementInfoConfirm { error: _ } => {
vec![
TdOut::Log(log_info(&self.gw, "结算信息确认成功")),
TdOut::ReqQryInstrument,
]
}
TdEvent::RspQryInstrument { last, row } => {
let mut outs = Vec::new();
if let Some(inf) = row {
match map::contract_from(&inf, &self.gw) {
Mapped::Ok(c) => {
self.contracts.lock().unwrap().insert(c.symbol.clone(), c.clone());
if c.asset == ek_data::constant::Asset::FUTURES {
outs.push(TdOut::Contract(c.clone()));
}
}
Mapped::Skip(reason) => {
outs.push(TdOut::Log(LogData {
gateway_name: self.gw.clone(),
msg: format!("合约行丢弃:{}", reason),
level: LEVEL_WARNING,
}));
}
}
}
if last {
self.contract_inited = true;
outs.push(TdOut::Log(log_info(&self.gw, "合约信息查询成功")));
outs.push(TdOut::ContractFinish(self.gw.clone()));
let buffered_orders = std::mem::take(&mut self.order_buf);
for data in buffered_orders {
let mut sub = self.step(TdEvent::RtnOrder(data.clone()));
outs.append(&mut sub);
}
let buffered_trades = std::mem::take(&mut self.trade_buf);
for data in buffered_trades {
let mut sub = self.step(TdEvent::RtnTrade(data.clone()));
outs.append(&mut sub);
}
}
outs
}
TdEvent::RspQryInvestorPosition { last, row } => {
let Some(row) = row else { return Vec::new() };
let mut outs = Vec::new();
{
let contract = {
let m = self.contracts.lock().unwrap();
m.get(&row.instrument_id).cloned()
};
if let Some(contract) = contract {
let key = format!("{}:{}", row.instrument_id, row.posi_direction);
let direction = map::direction_from(&row.posi_direction);
if !self.positions.contains_key(&key) {
let pos = map::new_position_data(
&row.instrument_id,
contract.exchange,
direction,
&self.gw,
).unwrap_or_default();
self.positions.insert(key.clone(), PositionAccum { pos });
}
let entry = self.positions.get_mut(&key).expect("just inserted");
map::accumulate_position(&row, &mut entry.pos, contract.exchange, contract.size);
}
if last {
let done = self.positions.values().map(|p| p.pos.clone()).collect::<Vec<_>>();
for pos in done {
outs.push(TdOut::Position(pos));
}
outs.push(TdOut::PositionQueryDone(self.gw.clone()));
self.positions.clear();
}
}
outs
}
TdEvent::RspQryTradingAccount { last: _, row } => match row {
Some(ta) => vec![TdOut::Account(map::account_from(&ta, &self.gw))],
None => Vec::new(),
},
TdEvent::RspOrderInsert { error, req } => match (error, req) {
(Some(e), Some(input)) => {
let mut outs = vec![TdOut::Log(log_error_with("交易委托失败", &self.gw, &e))];
let contract = {
let m = self.contracts.lock().unwrap();
m.get(&input.instrument_id).cloned()
};
if let Some(contract) = contract {
let orderid = self.local_orderid(&input.order_ref);
let direction = map::direction_from(&input.direction);
let offset = map::offset_from(&input.comb_offset_flag).unwrap_or(Offset::NONE);
outs.push(TdOut::Order(OrderData {
gateway_name: self.gw.clone(),
symbol: contract.symbol.clone(),
exchange: contract.exchange,
orderid,
order_type: OrderType::LIMIT,
direction: Some(direction.unwrap_or(Direction::NET)),
offset,
price: input.limit_price,
volume: input.volume_total_original as f64,
status: Status::REJECTED,
..OrderData::default()
}));
}
outs
}
_ => Vec::new(),
},
TdEvent::RspOrderAction { error } => match error {
Some(e) => vec![TdOut::Log(log_error_with("交易撤单失败", &self.gw, &e))],
None => Vec::new(),
},
TdEvent::RtnOrder(of) => {
if !self.contract_inited {
self.order_buf.push(of);
return Vec::new();
}
let contract = {
let m = self.contracts.lock().unwrap();
m.get(&of.instrument_id).cloned()
};
let Some(contract) = contract else {
return vec![TdOut::Log(LogData {
gateway_name: self.gw.clone(),
msg: format!("委托回报缺少在册合约:{}", of.instrument_id),
level: LEVEL_ERROR,
})];
};
match map::order_from(&of, &contract, &self.gw) {
Mapped::Ok(order) => {
let orderid = order.orderid.clone();
let outs = vec![TdOut::Order(order)];
self.remember_sysid(&of.order_sys_id, &orderid);
outs
}
Mapped::Skip(reason) => vec![TdOut::Log(log_info(&self.gw, reason))],
}
}
TdEvent::RtnTrade(tf) => {
if !self.contract_inited {
self.trade_buf.push(tf);
return Vec::new();
}
let contract = {
let m = self.contracts.lock().unwrap();
m.get(&tf.instrument_id).cloned()
};
let Some(contract) = contract else {
return vec![TdOut::Log(LogData {
gateway_name: self.gw.clone(),
msg: format!("成交回报缺少在册合约:{}", tf.instrument_id),
level: LEVEL_ERROR,
})];
};
match self.sysid_orderid.get(&tf.order_sys_id) {
Some(orderid) => match map::trade_from(&tf, orderid, &contract, &self.gw) {
Mapped::Ok(trade) => vec![TdOut::Trade(trade)],
Mapped::Skip(reason) => vec![TdOut::Log(log_info(&self.gw, reason))],
},
None => vec![TdOut::Log(LogData {
gateway_name: self.gw.clone(),
msg: format!("成交回报缺少在册委托:{}", tf.order_sys_id),
level: LEVEL_ERROR,
})],
}
}
}
}
}
pub struct SendOutcome {
pub en_orderid: String,
pub error: Option<LogData>,
pub outs: Vec<TdOut>,
}
impl SendOutcome {
fn rejected(log: LogData) -> Self {
Self { en_orderid: String::new(), error: Some(log), outs: Vec::new() }
}
fn placed(outs: Vec<TdOut>, en_orderid: String) -> Self {
Self { en_orderid, error: None, outs }
}
}