1use std::collections::BTreeMap;
19use std::sync::{Arc, Mutex, MutexGuard, OnceLock};
20use std::time::{Duration, Instant};
21
22use ek_core::context::PluginContext;
23use ek_core::envelope::{Envelope, ReplyErr, e_req_type};
24use ek_core::plugin::Plugin;
25use ek_core::service::ServiceDesc;
26use ek_ctp::plugin::{
27 CancelOrderReply, CancelOrderReq, SendOrderReply, SendOrderReq, SubscribeReply, SubscribeReq,
28};
29use ek_data::bar::BarData;
30use ek_data::constant::{Direction, Offset, OrderType, Status};
31use ek_data::event::{EVENT_BAR, EVENT_ORDER, EVENT_POSITION, EVENT_TICK, EVENT_TRADE};
32use ek_data::order::OrderData;
33use ek_data::order_request::OrderRequest;
34use ek_data::position::PositionData;
35use ek_data::tick::TickData;
36use ek_data::trade::TradeData;
37use ek_dm::wire::BarsFrame;
38use ek_event::Event;
39
40pub const SIM_NAME: &str = "sim";
42pub const RATE_LIMIT: u32 = 6;
44
45#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
47pub struct QueryRateReq;
48
49#[derive(Debug, Clone, Copy, PartialEq, Eq)]
51pub struct QueryRateReply {
52 pub used: u32,
53 pub limit: u32,
54 pub remaining: u32,
55}
56
57#[derive(Debug, Clone)]
59pub struct FillOut {
60 pub order: OrderData,
61 pub trade: TradeData,
62 pub position: PositionData,
63}
64
65#[derive(Debug)]
67struct RateWindow {
68 used: u32,
69 started: Instant,
70}
71
72impl RateWindow {
73 fn new() -> Self {
74 Self {
75 used: 0,
76 started: Instant::now(),
77 }
78 }
79 fn roll(&mut self) {
80 if self.started.elapsed() >= Duration::from_secs(1) {
81 self.used = 0;
82 self.started = Instant::now();
83 }
84 }
85 fn hit(&mut self) -> bool {
86 self.roll();
87 if self.used >= RATE_LIMIT {
88 return false;
90 }
91 self.used += 1;
92 true
93 }
94 fn snapshot(&mut self) -> QueryRateReply {
95 self.roll();
96 QueryRateReply {
97 used: self.used,
98 limit: RATE_LIMIT,
99 remaining: RATE_LIMIT.saturating_sub(self.used),
100 }
101 }
102}
103
104#[derive(Debug, Default)]
106pub struct SimMatcher {
107 pub resting: Vec<OrderData>,
109 pub trade_seq: u64,
111 pub order_ref: u64,
113 pub positions: BTreeMap<(String, String), PositionData>,
115 pub last_bar_dt: BTreeMap<String, ek_data::time::DtNs>,
117 pub tick_filled: BTreeMap<String, f64>,
120}
121
122impl SimMatcher {
123 pub fn place(&mut self, req: &OrderRequest) -> Result<OrderData, String> {
125 if req.offset == Offset::NONE {
126 return Err("请选择开平方向".to_string());
127 }
128 if !matches!(req.order_type, OrderType::LIMIT | OrderType::MARKET) {
129 return Err(format!(
130 "当前接口不支持该类型的委托{}",
131 req.order_type.as_str()
132 ));
133 }
134 if req.volume <= 0.0 {
135 return Err("委托量必须为正".to_string());
136 }
137 self.order_ref += 1;
138 let orderid = format!("SIM_0_{}", self.order_ref);
139 let od = req.create_order_data(&orderid, SIM_NAME);
140 self.resting.push(od.clone());
141 Ok(od)
142 }
143
144 pub fn cancel(&mut self, orderid: &str) -> Option<OrderData> {
146 let local = orderid.strip_prefix("sim.").unwrap_or(orderid);
147 let idx = self.resting.iter().position(|o| o.orderid == local)?;
148 let mut od = self.resting.remove(idx);
149 od.status = Status::CANCELLED;
150 Some(od)
151 }
152
153 pub fn on_bar(&mut self, bar: &BarData) -> Vec<FillOut> {
159 if self
160 .last_bar_dt
161 .get(&bar.symbol)
162 .is_some_and(|&last| bar.datetime <= last)
163 {
164 return Vec::new();
165 }
166 self.last_bar_dt.insert(bar.symbol.clone(), bar.datetime);
167 let pending = self.tick_filled.remove(&bar.symbol).unwrap_or(0.0);
168 let mut budget = (bar.volume - pending).max(0.0);
169 let mut outs = Vec::new();
170 let mut i = 0;
171 while i < self.resting.len() && budget > 0.0 {
172 let (fillable, price) = {
173 let o = &self.resting[i];
174 if o.symbol != bar.symbol {
175 (false, 0.0)
176 } else {
177 match o.direction {
178 Some(Direction::LONG) => match o.order_type {
179 OrderType::MARKET => (true, bar.close_price),
180 _ => (bar.low_price <= o.price, o.price),
181 },
182 Some(Direction::SHORT) => match o.order_type {
183 OrderType::MARKET => (true, bar.close_price),
184 _ => (bar.high_price >= o.price, o.price),
185 },
186 _ => (false, 0.0),
187 }
188 }
189 };
190 if fillable {
191 if let Some(out) = self.fill_at(i, price, &mut budget, bar.datetime) {
192 outs.push(out);
193 }
194 }
195 if self
197 .resting
198 .get(i)
199 .is_some_and(|o| o.status == Status::ALLTRADED)
200 {
201 self.resting.remove(i);
202 } else {
203 i += 1;
204 }
205 }
206 outs
207 }
208
209 pub fn on_tick(&mut self, t: &TickData) -> Vec<FillOut> {
212 let mut budget = t.last_volume.max(0.0);
213 let mut outs = Vec::new();
214 let mut i = 0;
215 while i < self.resting.len() && budget > 0.0 {
216 let (fillable, price) = {
217 let o = &self.resting[i];
218 if o.symbol != t.symbol {
219 (false, 0.0)
220 } else {
221 match o.direction {
222 Some(Direction::LONG) => match o.order_type {
223 OrderType::MARKET => (true, t.last_price),
224 _ => (t.last_price <= o.price, o.price),
225 },
226 Some(Direction::SHORT) => match o.order_type {
227 OrderType::MARKET => (true, t.last_price),
228 _ => (t.last_price >= o.price, o.price),
229 },
230 _ => (false, 0.0),
231 }
232 }
233 };
234 if fillable {
235 if let Some(out) = self.fill_at(i, price, &mut budget, t.datetime) {
236 outs.push(out);
237 }
238 }
239 if self
240 .resting
241 .get(i)
242 .is_some_and(|o| o.status == Status::ALLTRADED)
243 {
244 self.resting.remove(i);
245 } else {
246 i += 1;
247 }
248 }
249 let tick_vol: f64 = outs.iter().map(|o| o.trade.volume).sum();
251 if tick_vol > 0.0 {
252 *self.tick_filled.entry(t.symbol.clone()).or_insert(0.0) += tick_vol;
253 }
254 outs
255 }
256
257 fn fill_at(
258 &mut self,
259 idx: usize,
260 price: f64,
261 budget: &mut f64,
262 dt: ek_data::time::DtNs,
263 ) -> Option<FillOut> {
264 let remaining = self.resting[idx].volume - self.resting[idx].traded;
265 let vol = remaining.min(*budget);
266 if vol <= 0.0 {
267 return None;
268 }
269 *budget -= vol;
270 self.trade_seq += 1;
271 let tradeid = format!("T{}", self.trade_seq);
272 {
273 let o = &mut self.resting[idx];
274 o.traded += vol;
275 o.traded_price = price;
276 o.datetime = Some(dt);
277 o.status = if o.traded >= o.volume {
278 Status::ALLTRADED
279 } else {
280 Status::PARTTRADED
281 };
282 }
283 let order = self.resting[idx].clone();
284 let trade = TradeData {
285 gateway_name: SIM_NAME.to_string(),
286 symbol: order.symbol.clone(),
287 exchange: order.exchange,
288 orderid: order.orderid.clone(),
289 tradeid,
290 direction: order.direction,
291 offset: order.offset,
292 price,
293 volume: vol,
294 datetime: Some(dt),
295 traded_amount: price * vol,
296 ..TradeData::default()
297 };
298 let position = self.bump_position(&trade);
299 Some(FillOut {
300 order,
301 trade,
302 position,
303 })
304 }
305
306 fn bump_position(&mut self, t: &TradeData) -> PositionData {
307 let dir = t.direction.unwrap_or(Direction::NET);
308 let row = self
309 .positions
310 .entry((t.symbol.clone(), dir.as_str().to_string()))
311 .or_insert_with(|| PositionData {
312 gateway_name: SIM_NAME.to_string(),
313 symbol: t.symbol.clone(),
314 exchange: t.exchange,
315 direction: dir,
316 ..PositionData::default()
317 });
318 let before = row.volume;
319 row.volume += t.volume;
320 if row.volume > 0.0 {
321 row.avg_price = (row.avg_price * before + t.price * t.volume) / row.volume;
322 }
323 row.can_use_volume = row.volume;
324 row.last_price = t.price;
325 row.clone()
326 }
327}
328
329pub struct SimGatewayPlugin {
331 inner: Mutex<SimMatcher>,
332 subscribed: Mutex<Vec<String>>,
333 rate: Mutex<RateWindow>,
334 ctx: OnceLock<PluginContext>,
335}
336
337impl SimGatewayPlugin {
338 #[must_use]
340 pub fn new() -> Self {
341 Self {
342 inner: Mutex::new(SimMatcher::default()),
343 subscribed: Mutex::new(Vec::new()),
344 rate: Mutex::new(RateWindow::new()),
345 ctx: OnceLock::new(),
346 }
347 }
348
349 fn lock(&self) -> MutexGuard<'_, SimMatcher> {
350 self.inner.lock().unwrap_or_else(|e| e.into_inner())
351 }
352
353 fn emit_fills(&self, outs: &[FillOut]) {
355 let Some(ctx) = self.ctx.get() else { return };
356 for o in outs {
357 let _ = ctx.put(Event::with_data(
358 EVENT_ORDER.to_string(),
359 Arc::new(o.order.clone()),
360 ));
361 let _ = ctx.put(Event::with_data(
362 EVENT_TRADE.to_string(),
363 Arc::new(o.trade.clone()),
364 ));
365 let _ = ctx.put(Event::with_data(
366 EVENT_POSITION.to_string(),
367 Arc::new(o.position.clone()),
368 ));
369 }
370 }
371
372 fn emit_order(&self, od: &OrderData) {
373 let Some(ctx) = self.ctx.get() else { return };
374 let _ = ctx.put(Event::with_data(
375 EVENT_ORDER.to_string(),
376 Arc::new(od.clone()),
377 ));
378 }
379
380 fn on_request(&self, event: &Event) {
382 let Some(env) = event.data.downcast_ref::<Envelope>() else {
383 return;
384 };
385 macro_rules! arm {
386 ($ty:ty, $res:ty, $body:expr) => {
387 if let Some(req) = env.payload.downcast_ref::<$ty>() {
388 let res: $res = $body(req);
389 let _ = env.reply.send(Box::new(Ok::<$res, ReplyErr>(res)));
390 return;
391 }
392 };
393 }
394 arm!(SubscribeReq, SubscribeReply, |r: &SubscribeReq| {
395 if !self.rate.lock().unwrap().hit() {
396 return SubscribeReply {
397 ok: false,
398 msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
399 };
400 }
401 let mut sub = self.subscribed.lock().unwrap();
402 for s in &r.symbols {
403 if !sub.contains(s) {
404 sub.push(s.clone());
405 }
406 }
407 SubscribeReply {
408 ok: true,
409 msg: format!("已登记 {} 个合约(纸面)", sub.len()),
410 }
411 });
412 arm!(SendOrderReq, SendOrderReply, |r: &SendOrderReq| {
413 if !self.rate.lock().unwrap().hit() {
414 return SendOrderReply {
415 ok: false,
416 en_orderid: String::new(),
417 msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
418 };
419 }
420 let placed = { self.lock().place(&r.req) };
422 match placed {
423 Ok(od) => {
424 self.emit_order(&od);
425 SendOrderReply {
426 ok: true,
427 en_orderid: format!("{SIM_NAME}.{}", od.orderid),
428 msg: String::new(),
429 }
430 }
431 Err(msg) => SendOrderReply {
432 ok: false,
433 en_orderid: String::new(),
434 msg,
435 },
436 }
437 });
438 arm!(CancelOrderReq, CancelOrderReply, |r: &CancelOrderReq| {
439 if !self.rate.lock().unwrap().hit() {
440 return CancelOrderReply {
441 ok: false,
442 msg: format!("流控被拒:1s 窗内请求已达上限 {RATE_LIMIT}"),
443 };
444 }
445 let cancelled = { self.lock().cancel(&r.req.orderid) };
446 match cancelled {
447 Some(od) => {
448 self.emit_order(&od);
449 CancelOrderReply {
450 ok: true,
451 msg: format!("已撤 {}", od.orderid),
452 }
453 }
454 None => CancelOrderReply {
455 ok: false,
456 msg: "委托不存在/已终态".to_string(),
457 },
458 }
459 });
460 arm!(QueryRateReq, QueryRateReply, |_r: &QueryRateReq| {
461 self.rate.lock().unwrap().snapshot()
462 });
463 }
464}
465
466impl Default for SimGatewayPlugin {
467 fn default() -> Self {
468 Self::new()
469 }
470}
471
472impl Plugin for SimGatewayPlugin {
473 fn name(&self) -> &str {
474 SIM_NAME
475 }
476
477 fn deps(&self) -> &[&str] {
478 &[] }
480
481 fn routes(&self) -> Vec<std::borrow::Cow<'static, str>> {
482 vec![
483 std::borrow::Cow::Borrowed(EVENT_BAR),
484 std::borrow::Cow::Borrowed(EVENT_TICK),
485 std::borrow::Cow::Owned(e_req_type(SIM_NAME)),
486 ]
487 }
488
489 fn mailbox_cap(&self) -> usize {
490 2048
491 }
492
493 fn version(&self) -> std::borrow::Cow<'static, str> {
494 std::borrow::Cow::Borrowed(env!("CARGO_PKG_VERSION"))
495 }
496
497 fn services(&self) -> Vec<ServiceDesc> {
498 vec![
499 ServiceDesc::describe::<SubscribeReq, SubscribeReply>(
500 format!("{SIM_NAME}.subscribe"),
501 "登记订阅(纸面:仅登记不外发)",
502 ),
503 ServiceDesc::describe::<SendOrderReq, SendOrderReply>(
504 format!("{SIM_NAME}.send_order"),
505 "下单(SUBMITTING 单上总线;撮合由 eBar./eTick. 驱动)",
506 ),
507 ServiceDesc::describe::<CancelOrderReq, CancelOrderReply>(
508 format!("{SIM_NAME}.cancel_order"),
509 "撤单(CANCELLED 单上总线)",
510 ),
511 ServiceDesc::describe::<QueryRateReq, QueryRateReply>(
512 format!("{SIM_NAME}.query_rate"),
513 "流控查询(1s 窗内已用/上限/余量)",
514 ),
515 ]
516 }
517
518 fn on_init(&self, ctx: &PluginContext) {
519 let _ = self.ctx.set(ctx.clone());
520 }
521
522 fn on_mail(&self, event: &Event) {
523 let ty = event.event_type.as_str();
524 if ty == EVENT_BAR {
525 if let Some(f) = event.data.downcast_ref::<BarsFrame>() {
526 let outs: Vec<FillOut> = {
527 let mut m = self.lock();
528 f.bars.iter().flat_map(|b| m.on_bar(b)).collect()
529 };
530 self.emit_fills(&outs);
531 }
532 } else if ty == EVENT_TICK {
533 if let Some(t) = event.data.downcast_ref::<TickData>() {
534 let outs = {
535 let mut m = self.lock();
536 m.on_tick(t)
537 };
538 self.emit_fills(&outs);
539 }
540 } else if ty == e_req_type(SIM_NAME) {
541 self.on_request(event);
542 }
543 }
544}