1use std::collections::{BTreeMap, VecDeque};
7use std::sync::atomic::{AtomicBool, AtomicU8, AtomicU64, Ordering};
8use std::sync::{Arc, Mutex};
9
10use ek_data::bar::BarData;
11use ek_dm::wire::BarsFrame;
12use ek_station::wire::PlugRow;
13
14pub const RING_CAP: usize = 512;
16
17#[derive(Debug)]
19pub struct LogRing {
20 lines: VecDeque<String>,
21 cap: usize,
22 spilled: u64,
23}
24
25impl Default for LogRing {
26 fn default() -> Self {
27 Self::new(RING_CAP)
28 }
29}
30
31impl LogRing {
32 pub fn new(cap: usize) -> Self {
34 Self {
35 lines: VecDeque::with_capacity(cap),
36 cap,
37 spilled: 0,
38 }
39 }
40
41 pub fn push(&mut self, line: impl Into<String>) {
43 if self.cap == 0 {
44 self.spilled += 1;
45 return;
46 }
47 if self.lines.len() == self.cap {
48 self.lines.pop_front();
49 self.spilled += 1;
50 }
51 self.lines.push_back(line.into());
52 }
53
54 pub fn len(&self) -> usize {
56 self.lines.len()
57 }
58
59 pub fn is_empty(&self) -> bool {
61 self.lines.is_empty()
62 }
63
64 pub fn spilled(&self) -> u64 {
66 self.spilled
67 }
68
69 pub fn snapshot(&self) -> Vec<String> {
71 self.lines.iter().cloned().collect()
72 }
73
74 pub fn clear(&mut self) {
76 self.lines.clear();
77 }
78}
79
80#[derive(Debug, Clone, Default)]
82pub struct BusSnap {
83 pub rows: Vec<PlugRow>,
85 pub services: Vec<(String, Vec<String>)>,
87}
88
89#[derive(Debug, Clone, PartialEq)]
91pub enum UiCmd {
92 Start { factory: String },
94 Remove { name: String },
96 Reload {
98 interval: i64,
99 symbol: String,
100 bars: Vec<BarData>,
101 },
102 SendOrder {
108 req: ek_data::order_request::OrderRequest,
109 gateway: String,
110 mode: ek_oms::LockMode,
111 },
112 CancelOrder { orderid: String, gateway: String },
116 CtpConfigure(CtpForm),
119 CtpSubscribe { symbols: Vec<String> },
121}
122
123pub const KLINE_CELL_CAP: usize = 4096;
125
126#[derive(Debug, Clone, Copy, PartialEq, Eq)]
128pub enum ConfirmStep {
129 Armed,
130 Fired,
131 Switched,
132}
133
134#[must_use]
137pub fn is_quarantined(state: &str) -> bool {
138 state.contains("Quarantin")
139}
140
141#[derive(Debug, Default)]
145pub struct MarketStore {
146 cells: Mutex<BTreeMap<(String, i64), Vec<BarData>>>,
147 frames: AtomicU64,
149 updates: AtomicU64,
151 reloads: AtomicU64,
153}
154
155#[derive(Debug, Clone, PartialEq)]
157pub struct CellView {
158 pub symbol: String,
159 pub interval: i64,
160 pub bars: Vec<BarData>,
161}
162
163impl MarketStore {
164 pub fn on_frame(&self, frame: &BarsFrame) {
166 let mut cells = self.cells.lock().unwrap();
167 for b in &frame.bars {
168 let key = (b.symbol.clone(), frame.interval);
169 let cell = cells.entry(key).or_default();
170 match cell.last() {
171 Some(last) if b.datetime < last.datetime => continue,
172 Some(last) if b.datetime == last.datetime => {
173 let slot = cell.last_mut().expect("len>1");
174 *slot = b.clone();
175 }
176 _ => {
177 cell.push(b.clone());
178 if cell.len() > KLINE_CELL_CAP {
179 cell.remove(0);
180 }
181 }
182 }
183 }
184 self.frames.fetch_add(1, Ordering::AcqRel);
185 }
186
187 pub fn on_update(&self, b: &BarData) {
189 let mut cells = self.cells.lock().unwrap();
190 let iv = b
191 .interval
192 .unwrap_or(ek_data::constant::Interval::MINUTE)
193 .seconds();
194 let key = (b.symbol.clone(), iv);
195 let cell = cells.entry(key).or_default();
196 match cell.last() {
197 Some(last) if b.datetime == last.datetime => {
198 let slot = cell.last_mut().expect("len>1");
199 *slot = b.clone();
200 }
201 Some(last) if b.datetime < last.datetime => {}
202 _ => {
203 cell.push(b.clone());
204 if cell.len() > KLINE_CELL_CAP {
205 cell.remove(0);
206 }
207 }
208 }
209 self.updates.fetch_add(1, Ordering::AcqRel);
210 }
211
212 pub fn note_reload(&self) {
214 self.reloads.fetch_add(1, Ordering::AcqRel);
215 }
216
217 pub fn cells(&self) -> Vec<CellView> {
219 self.cells
220 .lock()
221 .unwrap()
222 .iter()
223 .map(|((sym, iv), bars)| CellView {
224 symbol: sym.clone(),
225 interval: *iv,
226 bars: bars.clone(),
227 })
228 .collect()
229 }
230
231 pub fn counts(&self) -> (u64, u64, u64) {
233 (
234 self.frames.load(Ordering::Acquire),
235 self.updates.load(Ordering::Acquire),
236 self.reloads.load(Ordering::Acquire),
237 )
238 }
239}
240
241#[derive(Debug, Default, Clone, PartialEq)]
244pub struct ReloadForm {
245 pub interval: String,
246 pub symbol: String,
247 pub csv: String,
248}
249
250pub fn parse_gateway(raw: &str) -> Result<String, String> {
253 let g = raw.trim();
254 if g.is_empty() {
255 return Ok("ctp".to_string());
256 }
257 if g.chars().any(|c| c.is_whitespace()) {
258 return Err(format!("网关解析: 含空白:{raw:?}"));
259 }
260 Ok(g.to_string())
261}
262
263#[derive(Debug, Clone, PartialEq)]
266pub struct CtpForm {
267 pub broker_id: String,
268 pub td_addr: String,
269 pub td_user: String,
270 pub td_password: String,
271 pub td_auth_code: String,
272 pub td_app_id: String,
273 pub md_addr: String,
274 pub md_user: String,
275 pub md_password: String,
276 pub symbols: String,
278 pub accept_futures: bool,
281 pub accept_options: bool,
282}
283
284impl Default for CtpForm {
287 fn default() -> Self {
288 Self {
289 broker_id: String::new(),
290 td_addr: String::new(),
291 td_user: String::new(),
292 td_password: String::new(),
293 td_auth_code: String::new(),
294 td_app_id: String::new(),
295 md_addr: String::new(),
296 md_user: String::new(),
297 md_password: String::new(),
298 symbols: String::new(),
299 accept_futures: true,
300 accept_options: false,
301 }
302 }
303}
304
305impl CtpForm {
306 #[must_use]
310 pub fn product_classes(&self) -> Vec<String> {
311 let mut codes = Vec::new();
312 if self.accept_futures {
313 codes.push("1".to_string());
314 }
315 if self.accept_options {
316 codes.push("2".to_string());
317 }
318 codes
319 }
320
321 pub fn to_configure_req(&self) -> ek_ctp::plugin::ConfigureReq {
323 ek_ctp::plugin::ConfigureReq {
324 broker_id: self.broker_id.trim().to_string(),
325 td_addr: self.td_addr.trim().to_string(),
326 td_user: self.td_user.trim().to_string(),
327 td_password: self.td_password.clone(),
328 td_auth_code: self.td_auth_code.trim().to_string(),
329 td_app_id: self.td_app_id.trim().to_string(),
330 md_addr: self.md_addr.trim().to_string(),
331 md_user: self.md_user.trim().to_string(),
332 md_password: self.md_password.clone(),
333 product_classes: self.product_classes(),
334 }
335 }
336
337 pub fn parse_symbols(&self) -> Result<Vec<String>, String> {
339 let list: Vec<String> = self
340 .symbols
341 .split([',', ',', ' '])
342 .map(|s| s.trim().to_string())
343 .filter(|s| !s.is_empty())
344 .collect();
345 if list.is_empty() {
346 return Err("订阅解析: 标的位空".to_string());
347 }
348 Ok(list)
349 }
350}
351
352#[derive(Debug, Clone, PartialEq, Default)]
354pub struct TradeSnap {
355 pub orders: Vec<ek_data::order::OrderData>,
356 pub trades: Vec<ek_data::trade::TradeData>,
357 pub positions: Vec<ek_data::position::PositionData>,
358 pub accounts: Vec<ek_data::account::AccountData>,
360}
361
362pub fn parse_reload_csv(form: &ReloadForm) -> Result<(i64, String, Vec<BarData>), String> {
365 let interval: i64 = form
366 .interval
367 .trim()
368 .parse()
369 .map_err(|_| format!("回灌 解析: interval 非整数:{:?}", form.interval))?;
370 let symbol = form.symbol.trim().to_string();
371 if symbol.is_empty() {
372 return Err("回灌解析: symbol 空".to_string());
373 }
374 if form.csv.trim().is_empty() {
375 return Err("回灌解析: csv 空".to_string());
376 }
377 let mut bars = Vec::new();
378 for (i, raw) in form.csv.lines().enumerate() {
379 let line = raw.trim();
380 if line.is_empty() || line.starts_with('#') {
381 continue;
382 }
383 let parts: Vec<&str> = line.split(',').map(str::trim).collect();
384 if parts.len() != 6 {
385 return Err(format!("回灌解析: 第 {} 列数 ≠ 6", i + 1));
386 }
387 let num = |s: &str| -> Result<f64, String> {
388 s.parse::<f64>()
389 .map_err(|_| format!("回灌解析: 第 {} 列数值坏:{:?}", i + 1, s))
390 };
391 let dt_s: i64 = parts[0]
392 .parse()
393 .map_err(|_| format!("回灌解析: 第 {} 列 dt 坏:{:?}", i + 1, parts[0]))?;
394 bars.push(BarData {
395 symbol: symbol.clone(),
396 datetime: dt_s * 1_000_000_000,
397 interval: Some(
398 ek_dm::wire::interval_of_seconds(interval)
399 .unwrap_or(ek_data::constant::Interval::MINUTE),
400 ),
401 open_price: num(parts[1])?,
402 high_price: num(parts[2])?,
403 low_price: num(parts[3])?,
404 close_price: num(parts[4])?,
405 volume: num(parts[5])?,
406 ..BarData::default()
407 });
408 }
409 if bars.is_empty() {
410 return Err("回灌解析: 无有效行".to_string());
411 }
412 Ok((interval, symbol, bars))
413}
414
415#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
420pub enum TopLeftTab {
421 #[default]
422 Orders,
423 Active,
424}
425
426impl TopLeftTab {
427 pub fn label(self) -> &'static str {
428 match self {
429 Self::Orders => "委托",
430 Self::Active => "未成交",
431 }
432 }
433}
434
435#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
437pub enum BottomRightTab {
438 #[default]
439 Account,
440 Logs,
441}
442
443impl BottomRightTab {
444 pub fn label(self) -> &'static str {
445 match self {
446 Self::Account => "资金",
447 Self::Logs => "日志",
448 }
449 }
450}
451
452#[derive(Debug, Clone, Copy, PartialEq, Eq, Default)]
456pub enum Theme {
457 #[default]
459 FinanceDark,
460 EguiDark,
462 EguiLight,
464 QtClassic,
466}
467
468impl Theme {
469 pub fn label(self) -> &'static str {
470 match self {
471 Self::FinanceDark => "金融深色",
472 Self::EguiDark => "egui深色",
473 Self::EguiLight => "egui浅色",
474 Self::QtClassic => "Qt灰白",
475 }
476 }
477
478 pub fn next(self) -> Self {
480 match self {
481 Self::FinanceDark => Self::EguiDark,
482 Self::EguiDark => Self::EguiLight,
483 Self::EguiLight => Self::QtClassic,
484 Self::QtClassic => Self::FinanceDark,
485 }
486 }
487}
488
489#[derive(Debug, Default)]
492pub struct TickStore {
493 ticks: BTreeMap<String, ek_data::tick::TickData>,
494}
495
496impl TickStore {
497 pub fn put(&mut self, t: &ek_data::tick::TickData) {
500 self.ticks
501 .insert(format!("{}.{}", t.exchange.as_str(), t.symbol), t.clone());
502 }
503
504 pub fn rows(&self) -> Vec<ek_data::tick::TickData> {
506 let mut v: Vec<ek_data::tick::TickData> = self.ticks.values().cloned().collect();
507 v.sort_by(|a, b| {
508 (a.exchange.as_str().to_string(), a.symbol.clone())
509 .cmp(&(b.exchange.as_str().to_string(), b.symbol.clone()))
510 });
511 v
512 }
513
514 pub fn groups(&self) -> Vec<(String, Vec<ek_data::tick::TickData>)> {
516 let mut out: Vec<(String, Vec<ek_data::tick::TickData>)> = Vec::new();
517 for t in self.rows() {
518 let ex = t.exchange.as_str().to_string();
519 match out.last_mut() {
520 Some((e, v)) if *e == ex => v.push(t),
521 _ => out.push((ex, vec![t])),
522 }
523 }
524 out
525 }
526
527 pub fn len(&self) -> usize {
528 self.ticks.len()
529 }
530
531 pub fn is_empty(&self) -> bool {
532 self.ticks.is_empty()
533 }
534}
535
536#[derive(Debug, Clone, PartialEq)]
538pub struct OrderForm {
539 pub exchange: String,
541 pub symbol: String,
543 pub direction: ek_data::constant::Direction,
544 pub offset: ek_data::constant::Offset,
545 pub order_type: ek_data::constant::OrderType,
546 pub price: String,
548 pub volume: String,
549 pub gateway: String,
551 pub follow_price: bool,
553 pub auto_split: bool,
556}
557
558impl Default for OrderForm {
559 fn default() -> Self {
560 Self {
561 exchange: "SHFE".to_string(),
562 symbol: String::new(),
563 direction: ek_data::constant::Direction::LONG,
564 offset: ek_data::constant::Offset::OPEN,
565 order_type: ek_data::constant::OrderType::LIMIT,
566 price: String::new(),
567 volume: "1".to_string(),
568 gateway: String::new(),
569 follow_price: true,
570 auto_split: true,
571 }
572 }
573}
574
575impl OrderForm {
576 pub fn to_request(&self) -> Result<ek_data::order_request::OrderRequest, String> {
578 use std::str::FromStr;
579 let symbol = self.symbol.trim();
580 if symbol.is_empty() {
581 return Err("下单: 代码为空".to_string());
582 }
583 if symbol.contains('.') {
584 return Err("下单: 代码不带交易所前缀(交易所走下拉)".to_string());
585 }
586 let exchange = ek_data::constant::Exchange::from_str(self.exchange.trim())
587 .map_err(|e| format!("下单: {e}"))?;
588 let price: f64 = self
589 .price
590 .trim()
591 .parse()
592 .map_err(|_| format!("下单: 价格无效 {:?}", self.price))?;
593 let volume: f64 = self
594 .volume
595 .trim()
596 .parse()
597 .map_err(|_| format!("下单: 数量无效 {:?}", self.volume))?;
598 if self.order_type != ek_data::constant::OrderType::MARKET && price <= 0.0 {
599 return Err("下单: 限价单价格须为正".to_string());
600 }
601 if volume <= 0.0 {
602 return Err("下单: 数量须为正".to_string());
603 }
604 Ok(ek_data::order_request::OrderRequest {
605 symbol: symbol.to_string(),
606 exchange,
607 direction: self.direction,
608 order_type: self.order_type,
609 volume,
610 price,
611 offset: self.offset,
612 ..ek_data::order_request::OrderRequest::default()
613 })
614 }
615}
616
617#[derive(Debug, Default)]
623pub struct ContractStore {
624 contracts: BTreeMap<String, ek_data::contract::ContractData>,
625}
626
627impl ContractStore {
628 pub fn put(&mut self, c: &ek_data::contract::ContractData) {
629 self.contracts
630 .insert(format!("{}.{}", c.exchange.as_str(), c.symbol), c.clone());
631 }
632
633 pub fn query(&self, pattern: &str) -> Vec<ek_data::contract::ContractData> {
635 let p = pattern.trim();
636 self.contracts
637 .values()
638 .filter(|c| {
639 p.is_empty()
640 || c.symbol.contains(p)
641 || c.exchange.as_str().eq_ignore_ascii_case(p)
642 || c.name.contains(p)
643 })
644 .cloned()
645 .collect()
646 }
647
648 pub fn len(&self) -> usize {
649 self.contracts.len()
650 }
651
652 pub fn is_empty(&self) -> bool {
653 self.contracts.is_empty()
654 }
655}
656
657#[derive(Debug, Default)]
659pub struct UiShared {
660 logs: Mutex<LogRing>,
661 snap: Mutex<Arc<BusSnap>>,
662 market: MarketStore,
663 ctp_form: Mutex<CtpForm>,
664 trade_snap: Mutex<Arc<TradeSnap>>,
665 tick_store: Mutex<TickStore>,
666 order_form: Mutex<OrderForm>,
667 selected_exchange: Mutex<String>,
668 topleft_tab: Mutex<TopLeftTab>,
669 bottomright_tab: Mutex<BottomRightTab>,
670 ops_window: AtomicBool,
671 ctp_window: AtomicBool,
672 contracts_window: AtomicBool,
673 theme: AtomicU8,
675 contract_store: Mutex<ContractStore>,
676 contract_query: Mutex<String>,
677 contract_selected: Mutex<Vec<String>>,
678 kline_windows: Mutex<Vec<String>>,
679 rev: AtomicU64,
680 stop: AtomicBool,
682 cmds: Mutex<VecDeque<UiCmd>>,
684 pending_confirm: Mutex<Option<String>>,
686 row_sel: Mutex<BTreeMap<String, String>>,
689}
690
691impl UiShared {
692 pub fn new() -> Self {
694 Self::default()
695 }
696
697 pub fn update_tick(&self, t: &ek_data::tick::TickData) {
701 self.tick_store.lock().unwrap().put(t);
702 self.bump();
703 }
704
705 pub fn tick_rows(&self) -> Vec<ek_data::tick::TickData> {
707 self.tick_store.lock().unwrap().rows()
708 }
709
710 pub fn tick_groups(&self) -> Vec<(String, Vec<ek_data::tick::TickData>)> {
712 self.tick_store.lock().unwrap().groups()
713 }
714
715 pub fn tick_count(&self) -> usize {
717 self.tick_store.lock().unwrap().len()
718 }
719
720 pub fn order_form(&self) -> OrderForm {
722 self.order_form.lock().unwrap().clone()
723 }
724
725 pub fn set_order_form(&self, f: OrderForm) {
727 *self.order_form.lock().unwrap() = f;
728 self.bump();
729 }
730
731 pub fn selected_exchange(&self) -> String {
733 self.selected_exchange.lock().unwrap().clone()
734 }
735
736 pub fn set_selected_exchange(&self, s: impl Into<String>) {
738 *self.selected_exchange.lock().unwrap() = s.into();
739 self.bump();
740 }
741
742 pub fn topleft_tab(&self) -> TopLeftTab {
744 *self.topleft_tab.lock().unwrap()
745 }
746
747 pub fn set_topleft_tab(&self, t: TopLeftTab) {
749 *self.topleft_tab.lock().unwrap() = t;
750 self.bump();
751 }
752
753 pub fn bottomright_tab(&self) -> BottomRightTab {
755 *self.bottomright_tab.lock().unwrap()
756 }
757
758 pub fn set_bottomright_tab(&self, t: BottomRightTab) {
760 *self.bottomright_tab.lock().unwrap() = t;
761 self.bump();
762 }
763
764 pub fn theme(&self) -> Theme {
768 match self.theme.load(Ordering::Acquire) {
769 1 => Theme::EguiDark,
770 2 => Theme::EguiLight,
771 3 => Theme::QtClassic,
772 _ => Theme::FinanceDark,
773 }
774 }
775
776 pub fn set_theme(&self, t: Theme) {
778 self.theme.store(t as u8, Ordering::Release);
779 self.bump();
780 }
781
782 pub fn cycle_theme(&self) -> Theme {
784 let t = self.theme().next();
785 self.set_theme(t);
786 t
787 }
788
789 pub fn ops_window_open(&self) -> bool {
791 self.ops_window.load(Ordering::Acquire)
792 }
793
794 pub fn set_ops_window_open(&self, v: bool) {
796 self.ops_window.store(v, Ordering::Release);
797 self.bump();
798 }
799
800 pub fn ctp_window_open(&self) -> bool {
802 self.ctp_window.load(Ordering::Acquire)
803 }
804
805 pub fn set_ctp_window_open(&self, v: bool) {
807 self.ctp_window.store(v, Ordering::Release);
808 self.bump();
809 }
810
811 pub fn kline_symbols(&self) -> Vec<String> {
813 self.kline_windows.lock().unwrap().clone()
814 }
815
816 pub fn open_kline(&self, symbol: impl Into<String>) {
818 let s = symbol.into();
819 let mut v = self.kline_windows.lock().unwrap();
820 if !v.contains(&s) {
821 v.push(s.clone());
822 }
823 drop(v);
824 self.log_line(format!("[kline] 打开 K线窗 {s}"));
825 }
826
827 pub fn close_kline(&self, symbol: &str) {
829 self.kline_windows.lock().unwrap().retain(|s| s != symbol);
830 self.bump();
831 }
832
833 pub fn contracts_window_open(&self) -> bool {
837 self.contracts_window.load(Ordering::Acquire)
838 }
839
840 pub fn set_contracts_window_open(&self, v: bool) {
842 self.contracts_window.store(v, Ordering::Release);
843 self.bump();
844 }
845
846 pub fn update_contract(&self, c: &ek_data::contract::ContractData) {
848 self.contract_store.lock().unwrap().put(c);
849 self.bump();
850 }
851
852 pub fn contracts(&self, pattern: &str) -> Vec<ek_data::contract::ContractData> {
854 self.contract_store.lock().unwrap().query(pattern)
855 }
856
857 pub fn contract_count(&self) -> usize {
859 self.contract_store.lock().unwrap().len()
860 }
861
862 pub fn contract_query(&self) -> String {
864 self.contract_query.lock().unwrap().clone()
865 }
866
867 pub fn set_contract_query(&self, q: impl Into<String>) {
869 *self.contract_query.lock().unwrap() = q.into();
870 self.bump();
871 }
872
873 pub fn toggle_contract_selection(&self, en_symbol: &str) {
875 let mut v = self.contract_selected.lock().unwrap();
876 match v.iter().position(|s| s == en_symbol) {
877 Some(i) => {
878 v.remove(i);
879 }
880 None => v.push(en_symbol.to_string()),
881 }
882 self.bump();
883 }
884
885 pub fn selected_contracts(&self) -> Vec<String> {
887 self.contract_selected.lock().unwrap().clone()
888 }
889
890 pub fn log_line(&self, line: impl Into<String>) {
892 self.logs.lock().unwrap().push(line);
893 self.bump();
894 }
895
896 pub fn clear_logs(&self) {
898 self.logs.lock().unwrap().clear();
899 self.bump();
900 }
901
902 pub fn logs_snapshot(&self) -> (Vec<String>, u64) {
904 let r = self.logs.lock().unwrap();
905 (r.snapshot(), r.spilled())
906 }
907
908 pub fn logs_len(&self) -> usize {
910 self.logs.lock().unwrap().len()
911 }
912
913 pub fn set_snapshot(&self, snap: BusSnap) {
915 *self.snap.lock().unwrap() = Arc::new(snap);
916 self.bump();
917 }
918
919 pub fn snapshot(&self) -> Arc<BusSnap> {
921 Arc::clone(&self.snap.lock().unwrap())
922 }
923
924 pub fn rev(&self) -> u64 {
926 self.rev.load(Ordering::Acquire)
927 }
928
929 pub fn bump(&self) {
931 self.rev.fetch_add(1, Ordering::AcqRel);
932 }
933
934 pub fn request_stop(&self) {
936 self.stop.store(true, Ordering::Release);
937 }
938
939 pub fn is_stop(&self) -> bool {
941 self.stop.load(Ordering::Acquire)
942 }
943
944 pub fn push_cmd(&self, cmd: UiCmd) {
948 self.cmds.lock().unwrap().push_back(cmd);
949 self.bump();
950 }
951
952 pub fn drain_cmds(&self) -> Vec<UiCmd> {
954 self.cmds.lock().unwrap().drain(..).collect()
955 }
956
957 pub fn pending_confirm(&self) -> Option<String> {
959 self.pending_confirm.lock().unwrap().clone()
960 }
961
962 pub fn confirm_step(&self, name: &str) -> ConfirmStep {
964 let mut g = self.pending_confirm.lock().unwrap();
965 match g.take() {
966 Some(p) if p == name => ConfirmStep::Fired,
967 Some(_) => {
968 *g = Some(name.to_string());
969 ConfirmStep::Switched
970 }
971 None => {
972 *g = Some(name.to_string());
973 ConfirmStep::Armed
974 }
975 }
976 }
977
978 pub fn confirm_cancel(&self) {
980 *self.pending_confirm.lock().unwrap() = None;
981 self.bump();
982 }
983
984 pub fn market(&self) -> &MarketStore {
988 &self.market
989 }
990
991 pub fn set_trade_snap(&self, ts: TradeSnap) {
995 *self.trade_snap.lock().unwrap() = Arc::new(ts);
996 self.bump();
997 }
998
999 pub fn trade_snap(&self) -> Arc<TradeSnap> {
1001 self.trade_snap.lock().unwrap().clone()
1002 }
1003
1004 pub fn ctp_form(&self) -> CtpForm {
1008 self.ctp_form.lock().unwrap().clone()
1009 }
1010
1011 pub fn set_ctp_form(&self, f: CtpForm) {
1013 *self.ctp_form.lock().unwrap() = f;
1014 self.bump();
1015 }
1016
1017 pub fn row_selected(&self, table: &str, key: &str) -> bool {
1021 self.row_sel
1022 .lock()
1023 .unwrap()
1024 .get(table)
1025 .is_some_and(|k| k == key)
1026 }
1027
1028 pub fn select_row(&self, table: &str, key: &str) {
1030 self.row_sel
1031 .lock()
1032 .unwrap()
1033 .insert(table.to_string(), key.to_string());
1034 }
1035}