use std::collections::BTreeMap;
use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
use std::time::{Duration, Instant};
use ek_core::context::PluginContext;
use ek_core::envelope::{Envelope, ReplyErr, e_req_type};
use ek_core::plugin::Plugin;
use ek_core::service::ServiceDesc;
use ek_ctp::plugin::{
CancelOrderReply, CancelOrderReq, SendOrderReply, SendOrderReq, SubscribeReply, SubscribeReq,
};
use ek_data::bar::BarData;
use ek_data::constant::{Direction, Offset, OrderType, Status};
use ek_data::event::{EVENT_BAR, EVENT_ORDER, EVENT_POSITION, EVENT_TICK, EVENT_TRADE};
use ek_data::order::OrderData;
use ek_data::order_request::OrderRequest;
use ek_data::position::PositionData;
use ek_data::tick::TickData;
use ek_data::trade::TradeData;
use ek_dm::wire::BarsFrame;
use ek_event::Event;
pub const SIM_NAME: &str = "sim";
pub const RATE_LIMIT: u32 = 6;
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct QueryRateReq;
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub struct QueryRateReply {
pub used: u32,
pub limit: u32,
pub remaining: u32,
}
#[derive(Debug, Clone)]
pub struct FillOut {
pub order: OrderData,
pub trade: TradeData,
pub position: PositionData,
}
#[derive(Debug)]
struct RateWindow {
used: u32,
started: Instant,
}
impl RateWindow {
fn new() -> Self {
Self {
used: 0,
started: Instant::now(),
}
}
fn roll(&mut self) {
if self.started.elapsed() >= Duration::from_secs(1) {
self.used = 0;
self.started = Instant::now();
}
}
fn hit(&mut self) -> bool {
self.roll();
if self.used >= RATE_LIMIT {
return false;
}
self.used += 1;
true
}
fn snapshot(&mut self) -> QueryRateReply {
self.roll();
QueryRateReply {
used: self.used,
limit: RATE_LIMIT,
remaining: RATE_LIMIT.saturating_sub(self.used),
}
}
}
#[derive(Debug, Default)]
pub struct SimMatcher {
pub resting: Vec<OrderData>,
pub trade_seq: u64,
pub order_ref: u64,
pub positions: BTreeMap<(String, String), PositionData>,
pub last_bar_dt: BTreeMap<String, ek_data::time::DtNs>,
pub tick_filled: BTreeMap<String, f64>,
}
impl SimMatcher {
pub fn place(&mut self, req: &OrderRequest) -> Result<OrderData, String> {
if req.offset == Offset::NONE {
return Err("请选择开平方向".to_string());
}
if !matches!(req.order_type, OrderType::LIMIT | OrderType::MARKET) {
return Err(format!(
"当前接口不支持该类型的委托{}",
req.order_type.as_str()
));
}
if req.volume <= 0.0 {
return Err("委托量必须为正".to_string());
}
self.order_ref += 1;
let orderid = format!("SIM_0_{}", self.order_ref);
let od = req.create_order_data(&orderid, SIM_NAME);
self.resting.push(od.clone());
Ok(od)
}
pub fn cancel(&mut self, orderid: &str) -> Option<OrderData> {
let local = orderid.strip_prefix("sim.").unwrap_or(orderid);
let idx = self.resting.iter().position(|o| o.orderid == local)?;
let mut od = self.resting.remove(idx);
od.status = Status::CANCELLED;
Some(od)
}
pub fn on_bar(&mut self, bar: &BarData) -> Vec<FillOut> {
if self
.last_bar_dt
.get(&bar.symbol)
.is_some_and(|&last| bar.datetime <= last)
{
return Vec::new();
}
self.last_bar_dt.insert(bar.symbol.clone(), bar.datetime);
let pending = self.tick_filled.remove(&bar.symbol).unwrap_or(0.0);
let mut budget = (bar.volume - pending).max(0.0);
let mut outs = Vec::new();
let mut i = 0;
while i < self.resting.len() && budget > 0.0 {
let (fillable, price) = {
let o = &self.resting[i];
if o.symbol != bar.symbol {
(false, 0.0)
} else {
match o.direction {
Some(Direction::LONG) => match o.order_type {
OrderType::MARKET => (true, bar.close_price),
_ => (bar.low_price <= o.price, o.price),
},
Some(Direction::SHORT) => match o.order_type {
OrderType::MARKET => (true, bar.close_price),
_ => (bar.high_price >= o.price, o.price),
},
_ => (false, 0.0),
}
}
};
if fillable {
if let Some(out) = self.fill_at(i, price, &mut budget, bar.datetime) {
outs.push(out);
}
}
if self
.resting
.get(i)
.is_some_and(|o| o.status == Status::ALLTRADED)
{
self.resting.remove(i);
} else {
i += 1;
}
}
outs
}
pub fn on_tick(&mut self, t: &TickData) -> Vec<FillOut> {
let mut budget = t.last_volume.max(0.0);
let mut outs = Vec::new();
let mut i = 0;
while i < self.resting.len() && budget > 0.0 {
let (fillable, price) = {
let o = &self.resting[i];
if o.symbol != t.symbol {
(false, 0.0)
} else {
match o.direction {
Some(Direction::LONG) => match o.order_type {
OrderType::MARKET => (true, t.last_price),
_ => (t.last_price <= o.price, o.price),
},
Some(Direction::SHORT) => match o.order_type {
OrderType::MARKET => (true, t.last_price),
_ => (t.last_price >= o.price, o.price),
},
_ => (false, 0.0),
}
}
};
if fillable {
if let Some(out) = self.fill_at(i, price, &mut budget, t.datetime) {
outs.push(out);
}
}
if self
.resting
.get(i)
.is_some_and(|o| o.status == Status::ALLTRADED)
{
self.resting.remove(i);
} else {
i += 1;
}
}
let tick_vol: f64 = outs.iter().map(|o| o.trade.volume).sum();
if tick_vol > 0.0 {
*self.tick_filled.entry(t.symbol.clone()).or_insert(0.0) += tick_vol;
}
outs
}
fn fill_at(
&mut self,
idx: usize,
price: f64,
budget: &mut f64,
dt: ek_data::time::DtNs,
) -> Option<FillOut> {
let remaining = self.resting[idx].volume - self.resting[idx].traded;
let vol = remaining.min(*budget);
if vol <= 0.0 {
return None;
}
*budget -= vol;
self.trade_seq += 1;
let tradeid = format!("T{}", self.trade_seq);
{
let o = &mut self.resting[idx];
o.traded += vol;
o.traded_price = price;
o.datetime = Some(dt);
o.status = if o.traded >= o.volume {
Status::ALLTRADED
} else {
Status::PARTTRADED
};
}
let order = self.resting[idx].clone();
let trade = TradeData {
gateway_name: SIM_NAME.to_string(),
symbol: order.symbol.clone(),
exchange: order.exchange,
orderid: order.orderid.clone(),
tradeid,
direction: order.direction,
offset: order.offset,
price,
volume: vol,
datetime: Some(dt),
traded_amount: price * vol,
..TradeData::default()
};
let position = self.bump_position(&trade);
Some(FillOut {
order,
trade,
position,
})
}
fn bump_position(&mut self, t: &TradeData) -> PositionData {
let dir = t.direction.unwrap_or(Direction::NET);
let row = self
.positions
.entry((t.symbol.clone(), dir.as_str().to_string()))
.or_insert_with(|| PositionData {
gateway_name: SIM_NAME.to_string(),
symbol: t.symbol.clone(),
exchange: t.exchange,
direction: dir,
..PositionData::default()
});
let before = row.volume;
row.volume += t.volume;
if row.volume > 0.0 {
row.avg_price = (row.avg_price * before + t.price * t.volume) / row.volume;
}
row.can_use_volume = row.volume;
row.last_price = t.price;
row.clone()
}
}
pub struct SimGatewayPlugin {
inner: Mutex<SimMatcher>,
subscribed: Mutex<Vec<String>>,
rate: Mutex<RateWindow>,
ctx: OnceLock<PluginContext>,
}
impl SimGatewayPlugin {
#[must_use]
pub fn new() -> Self {
Self {
inner: Mutex::new(SimMatcher::default()),
subscribed: Mutex::new(Vec::new()),
rate: Mutex::new(RateWindow::new()),
ctx: OnceLock::new(),
}
}
fn lock(&self) -> MutexGuard<'_, SimMatcher> {
self.inner.lock().unwrap_or_else(|e| e.into_inner())
}
fn emit_fills(&self, outs: &[FillOut]) {
let Some(ctx) = self.ctx.get() else { return };
for o in outs {
let _ = ctx.put(Event::with_data(
EVENT_ORDER.to_string(),
Arc::new(o.order.clone()),
));
let _ = ctx.put(Event::with_data(
EVENT_TRADE.to_string(),
Arc::new(o.trade.clone()),
));
let _ = ctx.put(Event::with_data(
EVENT_POSITION.to_string(),
Arc::new(o.position.clone()),
));
}
}
fn emit_order(&self, od: &OrderData) {
let Some(ctx) = self.ctx.get() else { return };
let _ = ctx.put(Event::with_data(
EVENT_ORDER.to_string(),
Arc::new(od.clone()),
));
}
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| {
if !self.rate.lock().unwrap().hit() {
return SubscribeReply {
ok: false,
msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
};
}
let mut sub = self.subscribed.lock().unwrap();
for s in &r.symbols {
if !sub.contains(s) {
sub.push(s.clone());
}
}
SubscribeReply {
ok: true,
msg: format!("已登记 {} 个合约(纸面)", sub.len()),
}
});
arm!(SendOrderReq, SendOrderReply, |r: &SendOrderReq| {
if !self.rate.lock().unwrap().hit() {
return SendOrderReply {
ok: false,
en_orderid: String::new(),
msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
};
}
let placed = { self.lock().place(&r.req) };
match placed {
Ok(od) => {
self.emit_order(&od);
SendOrderReply {
ok: true,
en_orderid: format!("{SIM_NAME}.{}", od.orderid),
msg: String::new(),
}
}
Err(msg) => SendOrderReply {
ok: false,
en_orderid: String::new(),
msg,
},
}
});
arm!(CancelOrderReq, CancelOrderReply, |r: &CancelOrderReq| {
if !self.rate.lock().unwrap().hit() {
return CancelOrderReply {
ok: false,
msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
};
}
let cancelled = { self.lock().cancel(&r.req.orderid) };
match cancelled {
Some(od) => {
self.emit_order(&od);
CancelOrderReply {
ok: true,
msg: format!("已撤 {}", od.orderid),
}
}
None => CancelOrderReply {
ok: false,
msg: "委托不存在/已终态".to_string(),
},
}
});
arm!(QueryRateReq, QueryRateReply, |_r: &QueryRateReq| {
self.rate.lock().unwrap().snapshot()
});
}
}
impl Default for SimGatewayPlugin {
fn default() -> Self {
Self::new()
}
}
impl Plugin for SimGatewayPlugin {
fn name(&self) -> &str {
SIM_NAME
}
fn deps(&self) -> &[&str] {
&[] }
fn routes(&self) -> Vec<std::borrow::Cow<'static, str>> {
vec![
std::borrow::Cow::Borrowed(EVENT_BAR),
std::borrow::Cow::Borrowed(EVENT_TICK),
std::borrow::Cow::Owned(e_req_type(SIM_NAME)),
]
}
fn mailbox_cap(&self) -> usize {
2048
}
fn version(&self) -> std::borrow::Cow<'static, str> {
std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))
}
fn services(&self) -> Vec<ServiceDesc> {
vec![
ServiceDesc::describe::<SubscribeReq, SubscribeReply>(
format!("{SIM_NAME}.subscribe"),
"登记订阅(纸面:仅登记不外发)",
),
ServiceDesc::describe::<SendOrderReq, SendOrderReply>(
format!("{SIM_NAME}.send_order"),
"下单(SUBMITTING 单上总线;撮合由 eBar./eTick. 驱动)",
),
ServiceDesc::describe::<CancelOrderReq, CancelOrderReply>(
format!("{SIM_NAME}.cancel_order"),
"撤单(CANCELLED 单上总线)",
),
ServiceDesc::describe::<QueryRateReq, QueryRateReply>(
format!("{SIM_NAME}.query_rate"),
"流控查询(1s 窗内已用/上限/余量)",
),
]
}
fn on_init(&self, ctx: &PluginContext) {
let _ = self.ctx.set(ctx.clone());
}
fn on_mail(&self, event: &Event) {
let ty = event.event_type.as_str();
if ty == EVENT_BAR {
if let Some(f) = event.data.downcast_ref::<BarsFrame>() {
let outs: Vec<FillOut> = {
let mut m = self.lock();
f.bars.iter().flat_map(|b| m.on_bar(b)).collect()
};
self.emit_fills(&outs);
}
} else if ty == EVENT_TICK {
if let Some(t) = event.data.downcast_ref::<TickData>() {
let outs = {
let mut m = self.lock();
m.on_tick(t)
};
self.emit_fills(&outs);
}
} else if ty == e_req_type(SIM_NAME) {
self.on_request(event);
}
}
}