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, ProtoJsonArgs, ReconfirmOrderArgs,
6};
7use crate::cmd;
8use crate::output::OutputFormat;
9
10pub(super) struct CancelAllOrderDispatchArgs {
11    pub(super) acc_id: Option<u64>,
12    pub(super) acc_id_file: Option<std::path::PathBuf>,
13    pub(super) card_num: Option<String>,
14    pub(super) env: String,
15    pub(super) market: Option<String>,
16    pub(super) jp_acc_type: Option<i32>,
17    pub(super) confirm: bool,
18}
19
20pub(super) async fn dispatch_place_order_group(
21    gateway: &str,
22    output: OutputFormat,
23    args: ProtoJsonArgs,
24) -> Result<()> {
25    cmd::proto_json::run_place_order_group(gateway, &args.c2s_json, output).await
26}
27
28pub(super) async fn dispatch_modify_order_group(
29    gateway: &str,
30    output: OutputFormat,
31    args: ProtoJsonArgs,
32) -> Result<()> {
33    cmd::proto_json::run_modify_order_group(gateway, &args.c2s_json, output).await
34}
35
36pub(super) async fn dispatch_cancel_order_group(
37    gateway: &str,
38    output: OutputFormat,
39    args: ProtoJsonArgs,
40) -> Result<()> {
41    cmd::proto_json::run_cancel_order_group(gateway, &args.c2s_json, output).await
42}
43
44pub(super) async fn dispatch_delete_order_group(
45    gateway: &str,
46    output: OutputFormat,
47    args: ProtoJsonArgs,
48) -> Result<()> {
49    cmd::proto_json::run_delete_order_group(gateway, &args.c2s_json, output).await
50}
51
52pub(super) async fn dispatch_place_algo_order(
53    gateway: &str,
54    output: OutputFormat,
55    args: ProtoJsonArgs,
56) -> Result<()> {
57    cmd::proto_json::run_place_algo_order(gateway, &args.c2s_json, output).await
58}
59
60pub(super) async fn dispatch_modify_algo_order(
61    gateway: &str,
62    output: OutputFormat,
63    args: ProtoJsonArgs,
64) -> Result<()> {
65    cmd::proto_json::run_modify_algo_order(gateway, &args.c2s_json, output).await
66}
67
68pub(super) async fn dispatch_algo_order_logs(
69    gateway: &str,
70    output: OutputFormat,
71    args: ProtoJsonArgs,
72) -> Result<()> {
73    cmd::proto_json::run_algo_order_logs(gateway, &args.c2s_json, output).await
74}
75
76pub(super) async fn dispatch_reverse_position(
77    gateway: &str,
78    output: OutputFormat,
79    args: ProtoJsonArgs,
80) -> Result<()> {
81    cmd::proto_json::run_reverse_position(gateway, &args.c2s_json, output).await
82}
83
84pub(super) async fn dispatch_roll_position(
85    gateway: &str,
86    output: OutputFormat,
87    args: ProtoJsonArgs,
88) -> Result<()> {
89    cmd::proto_json::run_roll_position(gateway, &args.c2s_json, output).await
90}
91
92pub(super) async fn dispatch_clear_futures_positions(
93    gateway: &str,
94    output: OutputFormat,
95    args: ProtoJsonArgs,
96) -> Result<()> {
97    cmd::proto_json::run_clear_futures_positions(gateway, &args.c2s_json, output).await
98}
99
100pub(super) async fn dispatch_batch_close_positions(
101    gateway: &str,
102    output: OutputFormat,
103    args: ProtoJsonArgs,
104) -> Result<()> {
105    cmd::proto_json::run_batch_close_positions(gateway, &args.c2s_json, output).await
106}
107
108fn csv_list(value: Option<String>) -> Vec<String> {
109    value
110        .map(|s| {
111            s.split(',')
112                .map(|x| x.trim().to_string())
113                .filter(|x| !x.is_empty())
114                .collect()
115        })
116        .unwrap_or_default()
117}
118
119pub(super) async fn dispatch_place_order(
120    gateway: &str,
121    output: OutputFormat,
122    args: PlaceOrderArgs,
123) -> Result<()> {
124    let acc_id = cmd::account::resolve_account_locator(
125        gateway,
126        args.acc_id,
127        args.acc_id_file.as_deref(),
128        args.card_num.as_deref(),
129        "place-order",
130    )
131    .await?;
132    cmd::trade_ext::run_place_order(cmd::trade_ext::PlaceOrderCommand {
133        gateway,
134        env: &args.env,
135        acc_id,
136        market: &args.market,
137        side: &args.side,
138        order_type: &args.order_type,
139        code: &args.code,
140        qty: args.qty,
141        price: args.price,
142        amount: args.amount,
143        pred_side: args.pred_side,
144        time_in_force: args.time_in_force.as_deref(),
145        fill_outside_rth: args.fill_outside_rth,
146        session: args.session.as_deref(),
147        expire_time: args.expire_time.as_deref(),
148        jp_acc_type: args.jp_acc_type,
149        confirm: args.confirm,
150        idempotency_key: args.idempotency_key,
151        stop_price: args.stop_price,
152        trail_type: args.trail_type,
153        trail_value: args.trail_value,
154        trail_spread: args.trail_spread,
155        output,
156    })
157    .await
158}
159
160pub(super) async fn dispatch_modify_order(
161    gateway: &str,
162    output: OutputFormat,
163    args: ModifyOrderArgs,
164) -> Result<()> {
165    let acc_id = cmd::account::resolve_account_locator(
166        gateway,
167        args.acc_id,
168        args.acc_id_file.as_deref(),
169        args.card_num.as_deref(),
170        "modify-order",
171    )
172    .await?;
173    cmd::trade_ext::run_modify_order(cmd::trade_ext::ModifyOrderCommand {
174        gateway,
175        env: &args.env,
176        acc_id,
177        market: &args.market,
178        order_id: args.order_id,
179        op: &args.op,
180        qty: args.qty,
181        price: args.price,
182        jp_acc_type: args.jp_acc_type,
183        confirm: args.confirm,
184        idempotency_key: args.idempotency_key,
185        output,
186    })
187    .await
188}
189
190pub(super) async fn dispatch_cancel_order(
191    gateway: &str,
192    output: OutputFormat,
193    args: CancelOrderArgs,
194) -> Result<()> {
195    let acc_id = cmd::account::resolve_account_locator(
196        gateway,
197        args.acc_id,
198        args.acc_id_file.as_deref(),
199        args.card_num.as_deref(),
200        "cancel-order",
201    )
202    .await?;
203    cmd::trade_ext::run_cancel_order(cmd::trade_ext::CancelOrderCommand {
204        gateway,
205        env: &args.env,
206        acc_id,
207        market: &args.market,
208        order_id: args.order_id,
209        jp_acc_type: args.jp_acc_type,
210        confirm: args.confirm,
211        idempotency_key: args.idempotency_key,
212        output,
213    })
214    .await
215}
216
217pub(super) async fn dispatch_reconfirm_order(
218    gateway: &str,
219    output: OutputFormat,
220    args: ReconfirmOrderArgs,
221) -> Result<()> {
222    let acc_id = cmd::account::resolve_account_locator(
223        gateway,
224        args.acc_id,
225        args.acc_id_file.as_deref(),
226        args.card_num.as_deref(),
227        "reconfirm-order",
228    )
229    .await?;
230    cmd::trade_ext::run_reconfirm_order(cmd::trade_ext::ReconfirmOrderCommand {
231        gateway,
232        env: &args.env,
233        acc_id,
234        market: &args.market,
235        order_id: args.order_id,
236        reason: args.reason,
237        jp_acc_type: args.jp_acc_type,
238        confirm: args.confirm,
239        output,
240    })
241    .await
242}
243
244pub(super) async fn dispatch_history_orders(
245    gateway: &str,
246    output: OutputFormat,
247    args: HistoryOrdersArgs,
248) -> Result<()> {
249    let acc_id = cmd::account::resolve_account_locator(
250        gateway,
251        args.acc_id,
252        args.acc_id_file.as_deref(),
253        args.card_num.as_deref(),
254        "history-orders",
255    )
256    .await?;
257    cmd::trade_ext::run_history_orders(cmd::trade_ext::HistoryOrdersCommand {
258        gateway,
259        env: &args.env,
260        acc_id,
261        market: &args.market,
262        codes: csv_list(args.codes),
263        begin: args.begin,
264        end: args.end,
265        output,
266    })
267    .await
268}
269
270pub(super) async fn dispatch_history_deals(
271    gateway: &str,
272    output: OutputFormat,
273    args: HistoryDealsArgs,
274) -> Result<()> {
275    let acc_id = cmd::account::resolve_account_locator(
276        gateway,
277        args.acc_id,
278        args.acc_id_file.as_deref(),
279        args.card_num.as_deref(),
280        "history-deals",
281    )
282    .await?;
283    cmd::trade_ext::run_history_deals(cmd::trade_ext::HistoryDealsCommand {
284        gateway,
285        env: &args.env,
286        acc_id,
287        market: &args.market,
288        codes: csv_list(args.codes),
289        begin: args.begin,
290        end: args.end,
291        output,
292    })
293    .await
294}
295
296pub(super) async fn dispatch_max_qtys(
297    gateway: &str,
298    output: OutputFormat,
299    args: MaxQtysArgs,
300) -> Result<()> {
301    let acc_id = cmd::account::resolve_account_locator(
302        gateway,
303        args.acc_id,
304        args.acc_id_file.as_deref(),
305        args.card_num.as_deref(),
306        "max-trd-qtys",
307    )
308    .await?;
309    cmd::trade_ext::run_max_qtys(cmd::trade_ext::MaxQtysCommand {
310        gateway,
311        env: &args.env,
312        acc_id,
313        market: &args.market,
314        order_type: &args.order_type,
315        code: &args.code,
316        price: args.price,
317        jp_acc_type: args.jp_acc_type,
318        output,
319    })
320    .await
321}
322
323pub(super) async fn dispatch_margin_ratio(
324    gateway: &str,
325    output: OutputFormat,
326    args: MarginRatioArgs,
327) -> Result<()> {
328    let acc_id = cmd::account::resolve_account_locator(
329        gateway,
330        args.acc_id,
331        args.acc_id_file.as_deref(),
332        args.card_num.as_deref(),
333        "margin-ratio",
334    )
335    .await?;
336    let symbols = args.symbols.or(args.symbols_arg).ok_or_else(|| {
337        anyhow::anyhow!("margin-ratio: 需要位置参数 <SYMBOLS> 或 --code / --symbols")
338    })?;
339    let syms: Vec<String> = symbols.split(',').map(|s| s.trim().to_string()).collect();
340    cmd::trade_ext::run_margin_ratio(gateway, &args.env, acc_id, &args.market, &syms, output).await
341}
342
343pub(super) async fn dispatch_order_fee(
344    gateway: &str,
345    output: OutputFormat,
346    args: OrderFeeArgs,
347) -> Result<()> {
348    let acc_id = cmd::account::resolve_account_locator(
349        gateway,
350        args.acc_id,
351        args.acc_id_file.as_deref(),
352        args.card_num.as_deref(),
353        "order-fee",
354    )
355    .await?;
356    let ids: Vec<String> = args
357        .order_ids
358        .split(',')
359        .map(|s| s.trim().to_string())
360        .collect();
361    cmd::trade_ext::run_order_fee(gateway, &args.env, acc_id, &args.market, &ids, output).await
362}
363
364pub(super) async fn dispatch_cancel_all_order(
365    gateway: &str,
366    output: OutputFormat,
367    args: CancelAllOrderDispatchArgs,
368) -> Result<()> {
369    let acc_id = cmd::account::resolve_account_locator(
370        gateway,
371        args.acc_id,
372        args.acc_id_file.as_deref(),
373        args.card_num.as_deref(),
374        "cancel-all-order",
375    )
376    .await?;
377    cmd::trade_ext::run_cancel_all_order(
378        gateway,
379        acc_id,
380        &args.env,
381        args.market.as_deref(),
382        args.jp_acc_type,
383        args.confirm,
384        output,
385    )
386    .await
387}
388
389pub(super) async fn dispatch_acc_cash_flow(
390    gateway: &str,
391    output: OutputFormat,
392    args: AccCashFlowArgs,
393) -> Result<()> {
394    let acc_id = cmd::account::resolve_account_locator(
395        gateway,
396        args.acc_id.or(args.acc_id_arg),
397        args.acc_id_file.as_deref(),
398        args.card_num.as_deref(),
399        "acc-cash-flow",
400    )
401    .await?;
402    // date / date_range 通过 #[arg(conflicts_with)] 互斥
403    if let Some(range) = args.date_range {
404        // 解析 "YYYY-MM-DD..YYYY-MM-DD"
405        let (from_s, to_s) = range.split_once("..").ok_or_else(|| {
406            anyhow::anyhow!("--date-range 格式应为 `YYYY-MM-DD..YYYY-MM-DD`,当前:{range}")
407        })?;
408        let from = chrono::NaiveDate::parse_from_str(from_s, "%Y-%m-%d")
409            .map_err(|e| anyhow::anyhow!("start date parse: {e}"))?;
410        let to = chrono::NaiveDate::parse_from_str(to_s, "%Y-%m-%d")
411            .map_err(|e| anyhow::anyhow!("end date parse: {e}"))?;
412        cmd::trade_ext::run_acc_cash_flow_range(cmd::trade_ext::AccCashFlowRangeCommand {
413            gateway,
414            acc_id,
415            date_from: from,
416            date_to: to,
417            env: &args.env,
418            market: &args.market,
419            direction: args.direction,
420        })
421        .await
422    } else if let Some(d) = args.date {
423        cmd::trade_ext::run_acc_cash_flow(
424            gateway,
425            acc_id,
426            &d,
427            &args.env,
428            &args.market,
429            args.direction,
430            output,
431        )
432        .await
433    } else {
434        anyhow::bail!("需传 --date <YYYY-MM-DD> 或 --date-range <FROM..TO>")
435    }
436}