Skip to main content

futucli/cli/dispatch/
trade_write.rs

1use anyhow::Result;
2
3use super::super::commands::{
4    AccCashFlowArgs, CancelOrderArgs, HistoryDealsArgs, HistoryOrdersArgs, MarginRatioArgs,
5    MaxQtysArgs, ModifyOrderArgs, OrderFeeArgs, PlaceOrderArgs, ReconfirmOrderArgs,
6};
7use crate::cmd;
8use crate::output::OutputFormat;
9
10pub(super) struct CancelAllOrderDispatchArgs {
11    pub(super) acc_id: Option<u64>,
12    pub(super) card_num: Option<String>,
13    pub(super) env: String,
14    pub(super) market: Option<String>,
15    pub(super) jp_acc_type: Option<i32>,
16    pub(super) confirm: bool,
17}
18
19fn csv_list(value: Option<String>) -> Vec<String> {
20    value
21        .map(|s| {
22            s.split(',')
23                .map(|x| x.trim().to_string())
24                .filter(|x| !x.is_empty())
25                .collect()
26        })
27        .unwrap_or_default()
28}
29
30pub(super) async fn dispatch_place_order(
31    gateway: &str,
32    output: OutputFormat,
33    args: PlaceOrderArgs,
34) -> Result<()> {
35    let acc_id = cmd::account::resolve_account_locator(
36        gateway,
37        args.acc_id,
38        args.card_num.as_deref(),
39        "place-order",
40    )
41    .await?;
42    cmd::trade_ext::run_place_order(cmd::trade_ext::PlaceOrderCommand {
43        gateway,
44        env: &args.env,
45        acc_id,
46        market: &args.market,
47        side: &args.side,
48        order_type: &args.order_type,
49        code: &args.code,
50        qty: args.qty,
51        price: args.price,
52        time_in_force: args.time_in_force.as_deref(),
53        fill_outside_rth: args.fill_outside_rth,
54        session: args.session.as_deref(),
55        expire_time: args.expire_time.as_deref(),
56        jp_acc_type: args.jp_acc_type,
57        confirm: args.confirm,
58        idempotency_key: args.idempotency_key,
59        stop_price: args.stop_price,
60        trail_type: args.trail_type,
61        trail_value: args.trail_value,
62        trail_spread: args.trail_spread,
63        output,
64    })
65    .await
66}
67
68pub(super) async fn dispatch_modify_order(
69    gateway: &str,
70    output: OutputFormat,
71    args: ModifyOrderArgs,
72) -> Result<()> {
73    let acc_id = cmd::account::resolve_account_locator(
74        gateway,
75        args.acc_id,
76        args.card_num.as_deref(),
77        "modify-order",
78    )
79    .await?;
80    cmd::trade_ext::run_modify_order(cmd::trade_ext::ModifyOrderCommand {
81        gateway,
82        env: &args.env,
83        acc_id,
84        market: &args.market,
85        order_id: args.order_id,
86        op: &args.op,
87        qty: args.qty,
88        price: args.price,
89        jp_acc_type: args.jp_acc_type,
90        confirm: args.confirm,
91        idempotency_key: args.idempotency_key,
92        output,
93    })
94    .await
95}
96
97pub(super) async fn dispatch_cancel_order(
98    gateway: &str,
99    output: OutputFormat,
100    args: CancelOrderArgs,
101) -> Result<()> {
102    let acc_id = cmd::account::resolve_account_locator(
103        gateway,
104        args.acc_id,
105        args.card_num.as_deref(),
106        "cancel-order",
107    )
108    .await?;
109    cmd::trade_ext::run_cancel_order(cmd::trade_ext::CancelOrderCommand {
110        gateway,
111        env: &args.env,
112        acc_id,
113        market: &args.market,
114        order_id: args.order_id,
115        jp_acc_type: args.jp_acc_type,
116        confirm: args.confirm,
117        idempotency_key: args.idempotency_key,
118        output,
119    })
120    .await
121}
122
123pub(super) async fn dispatch_reconfirm_order(
124    gateway: &str,
125    output: OutputFormat,
126    args: ReconfirmOrderArgs,
127) -> Result<()> {
128    let acc_id = cmd::account::resolve_account_locator(
129        gateway,
130        args.acc_id,
131        args.card_num.as_deref(),
132        "reconfirm-order",
133    )
134    .await?;
135    cmd::trade_ext::run_reconfirm_order(cmd::trade_ext::ReconfirmOrderCommand {
136        gateway,
137        env: &args.env,
138        acc_id,
139        market: &args.market,
140        order_id: args.order_id,
141        reason: args.reason,
142        jp_acc_type: args.jp_acc_type,
143        confirm: args.confirm,
144        output,
145    })
146    .await
147}
148
149pub(super) async fn dispatch_history_orders(
150    gateway: &str,
151    output: OutputFormat,
152    args: HistoryOrdersArgs,
153) -> Result<()> {
154    let acc_id = cmd::account::resolve_account_locator(
155        gateway,
156        args.acc_id,
157        args.card_num.as_deref(),
158        "history-orders",
159    )
160    .await?;
161    cmd::trade_ext::run_history_orders(cmd::trade_ext::HistoryOrdersCommand {
162        gateway,
163        env: &args.env,
164        acc_id,
165        market: &args.market,
166        codes: csv_list(args.codes),
167        begin: args.begin,
168        end: args.end,
169        output,
170    })
171    .await
172}
173
174pub(super) async fn dispatch_history_deals(
175    gateway: &str,
176    output: OutputFormat,
177    args: HistoryDealsArgs,
178) -> Result<()> {
179    let acc_id = cmd::account::resolve_account_locator(
180        gateway,
181        args.acc_id,
182        args.card_num.as_deref(),
183        "history-deals",
184    )
185    .await?;
186    cmd::trade_ext::run_history_deals(cmd::trade_ext::HistoryDealsCommand {
187        gateway,
188        env: &args.env,
189        acc_id,
190        market: &args.market,
191        codes: csv_list(args.codes),
192        begin: args.begin,
193        end: args.end,
194        output,
195    })
196    .await
197}
198
199pub(super) async fn dispatch_max_qtys(
200    gateway: &str,
201    output: OutputFormat,
202    args: MaxQtysArgs,
203) -> Result<()> {
204    let acc_id = cmd::account::resolve_account_locator(
205        gateway,
206        args.acc_id,
207        args.card_num.as_deref(),
208        "max-trd-qtys",
209    )
210    .await?;
211    cmd::trade_ext::run_max_qtys(cmd::trade_ext::MaxQtysCommand {
212        gateway,
213        env: &args.env,
214        acc_id,
215        market: &args.market,
216        order_type: &args.order_type,
217        code: &args.code,
218        price: args.price,
219        jp_acc_type: args.jp_acc_type,
220        output,
221    })
222    .await
223}
224
225pub(super) async fn dispatch_margin_ratio(
226    gateway: &str,
227    output: OutputFormat,
228    args: MarginRatioArgs,
229) -> Result<()> {
230    let acc_id = cmd::account::resolve_account_locator(
231        gateway,
232        args.acc_id,
233        args.card_num.as_deref(),
234        "margin-ratio",
235    )
236    .await?;
237    let symbols = args.symbols.or(args.symbols_arg).ok_or_else(|| {
238        anyhow::anyhow!("margin-ratio: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
239    })?;
240    let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
241    cmd::trade_ext::run_margin_ratio(gateway, &args.env, acc_id, &args.market, &syms, output).await
242}
243
244pub(super) async fn dispatch_order_fee(
245    gateway: &str,
246    output: OutputFormat,
247    args: OrderFeeArgs,
248) -> Result<()> {
249    let acc_id = cmd::account::resolve_account_locator(
250        gateway,
251        args.acc_id,
252        args.card_num.as_deref(),
253        "order-fee",
254    )
255    .await?;
256    let ids: Vec<String> = args
257        .order_ids
258        .split(',')
259        .map(|s| s.trim().to_string())
260        .collect();
261    cmd::trade_ext::run_order_fee(gateway, &args.env, acc_id, &args.market, &ids, output).await
262}
263
264pub(super) async fn dispatch_cancel_all_order(
265    gateway: &str,
266    output: OutputFormat,
267    args: CancelAllOrderDispatchArgs,
268) -> Result<()> {
269    let acc_id = cmd::account::resolve_account_locator(
270        gateway,
271        args.acc_id,
272        args.card_num.as_deref(),
273        "cancel-all-order",
274    )
275    .await?;
276    cmd::trade_ext::run_cancel_all_order(
277        gateway,
278        acc_id,
279        &args.env,
280        args.market.as_deref(),
281        args.jp_acc_type,
282        args.confirm,
283        output,
284    )
285    .await
286}
287
288pub(super) async fn dispatch_acc_cash_flow(
289    gateway: &str,
290    output: OutputFormat,
291    args: AccCashFlowArgs,
292) -> Result<()> {
293    let acc_id = cmd::account::resolve_account_locator(
294        gateway,
295        args.acc_id.or(args.acc_id_arg),
296        args.card_num.as_deref(),
297        "acc-cash-flow",
298    )
299    .await?;
300    // date / date_range 通过 #[arg(conflicts_with)] 互斥
301    if let Some(range) = args.date_range {
302        // 解析 "YYYY-MM-DD..YYYY-MM-DD"
303        let (from_s, to_s) = range.split_once("..").ok_or_else(|| {
304            anyhow::anyhow!("--date-range 格式应为 `YYYY-MM-DD..YYYY-MM-DD`,当前:{range}")
305        })?;
306        let from = chrono::NaiveDate::parse_from_str(from_s, "%Y-%m-%d")
307            .map_err(|e| anyhow::anyhow!("start date parse: {e}"))?;
308        let to = chrono::NaiveDate::parse_from_str(to_s, "%Y-%m-%d")
309            .map_err(|e| anyhow::anyhow!("end date parse: {e}"))?;
310        cmd::trade_ext::run_acc_cash_flow_range(cmd::trade_ext::AccCashFlowRangeCommand {
311            gateway,
312            acc_id,
313            date_from: from,
314            date_to: to,
315            env: &args.env,
316            market: &args.market,
317            direction: args.direction,
318        })
319        .await
320    } else if let Some(d) = args.date {
321        cmd::trade_ext::run_acc_cash_flow(
322            gateway,
323            acc_id,
324            &d,
325            &args.env,
326            &args.market,
327            args.direction,
328            output,
329        )
330        .await
331    } else {
332        anyhow::bail!("需传 --date <YYYY-MM-DD> 或 --date-range <FROM..TO>")
333    }
334}