futu_mcp/handlers/
trade.rs

1//! 交易域 handler(只读):accounts / funds / positions / orders / deals
2
3use std::sync::Arc;
4
5use anyhow::{bail, Result};
6use futu_net::client::FutuClient;
7use futu_trd::types::{TrdEnv, TrdHeader, TrdMarket};
8use serde::Serialize;
9
10fn parse_trd_market(s: &str) -> Result<TrdMarket> {
11    let m = match s.trim().to_ascii_uppercase().as_str() {
12        "HK" => TrdMarket::HK,
13        "US" => TrdMarket::US,
14        "CN" => TrdMarket::CN,
15        "HKCC" => TrdMarket::HKCC,
16        other => bail!("unknown trd market {other:?} (HK|US|CN|HKCC)"),
17    };
18    Ok(m)
19}
20
21fn parse_trd_env(s: &str) -> Result<TrdEnv> {
22    let e = match s.trim().to_ascii_lowercase().as_str() {
23        "simulate" | "sim" => TrdEnv::Simulate,
24        "real" => TrdEnv::Real,
25        other => bail!("unknown trd env {other:?} (real|simulate)"),
26    };
27    Ok(e)
28}
29
30fn build_header(env: &str, acc_id: u64, market: &str) -> Result<TrdHeader> {
31    Ok(TrdHeader {
32        trd_env: parse_trd_env(env)?,
33        acc_id,
34        trd_market: parse_trd_market(market)?,
35    })
36}
37
38#[derive(Serialize)]
39struct AccountOut {
40    acc_id: u64,
41    trd_env: i32,
42    env_label: &'static str,
43    trd_market_auth_list: Vec<i32>,
44}
45
46pub async fn list_accounts(client: &Arc<FutuClient>) -> Result<String> {
47    let accs = futu_trd::account::get_acc_list(client).await?;
48    let out: Vec<AccountOut> = accs
49        .iter()
50        .map(|a| AccountOut {
51            acc_id: a.acc_id,
52            trd_env: a.trd_env,
53            env_label: match a.trd_env {
54                0 => "simulate",
55                1 => "real",
56                _ => "unknown",
57            },
58            trd_market_auth_list: a.trd_market_auth_list.clone(),
59        })
60        .collect();
61    Ok(serde_json::to_string_pretty(&out)?)
62}
63
64#[derive(Serialize)]
65struct FundsOut {
66    power: f64,
67    total_assets: f64,
68    cash: f64,
69    market_val: f64,
70    frozen_cash: f64,
71    debt_cash: f64,
72    avl_withdrawal_cash: f64,
73}
74
75pub async fn get_funds(
76    client: &Arc<FutuClient>,
77    env: &str,
78    acc_id: u64,
79    market: &str,
80) -> Result<String> {
81    let header = build_header(env, acc_id, market)?;
82    let f = futu_trd::account::get_funds(client, &header).await?;
83    let out = FundsOut {
84        power: f.power,
85        total_assets: f.total_assets,
86        cash: f.cash,
87        market_val: f.market_val,
88        frozen_cash: f.frozen_cash,
89        debt_cash: f.debt_cash,
90        avl_withdrawal_cash: f.avl_withdrawal_cash,
91    };
92    Ok(serde_json::to_string_pretty(&out)?)
93}
94
95#[derive(Serialize)]
96struct PositionOut {
97    position_id: u64,
98    code: String,
99    name: String,
100    qty: f64,
101    can_sell_qty: f64,
102    price: f64,
103    cost_price: f64,
104    val: f64,
105    pl_val: f64,
106    pl_ratio: f64,
107}
108
109pub async fn get_positions(
110    client: &Arc<FutuClient>,
111    env: &str,
112    acc_id: u64,
113    market: &str,
114) -> Result<String> {
115    let header = build_header(env, acc_id, market)?;
116    let list = futu_trd::account::get_position_list(client, &header).await?;
117    let out: Vec<PositionOut> = list
118        .iter()
119        .map(|p| PositionOut {
120            position_id: p.position_id,
121            code: p.code.clone(),
122            name: p.name.clone(),
123            qty: p.qty,
124            can_sell_qty: p.can_sell_qty,
125            price: p.price,
126            cost_price: p.cost_price,
127            val: p.val,
128            pl_val: p.pl_val,
129            pl_ratio: p.pl_ratio,
130        })
131        .collect();
132    Ok(serde_json::to_string_pretty(&out)?)
133}
134
135#[derive(Serialize)]
136struct OrderOut {
137    order_id: u64,
138    order_id_ex: String,
139    trd_side: i32,
140    order_type: i32,
141    order_status: i32,
142    code: String,
143    name: String,
144    qty: f64,
145    price: f64,
146    fill_qty: f64,
147    fill_avg_price: f64,
148    create_time: String,
149    update_time: String,
150    last_err_msg: String,
151}
152
153pub async fn get_orders(
154    client: &Arc<FutuClient>,
155    env: &str,
156    acc_id: u64,
157    market: &str,
158) -> Result<String> {
159    let header = build_header(env, acc_id, market)?;
160    let list = futu_trd::query::get_order_list(client, &header).await?;
161    let out: Vec<OrderOut> = list
162        .iter()
163        .map(|o| OrderOut {
164            order_id: o.order_id,
165            order_id_ex: o.order_id_ex.clone(),
166            trd_side: o.trd_side,
167            order_type: o.order_type,
168            order_status: o.order_status,
169            code: o.code.clone(),
170            name: o.name.clone(),
171            qty: o.qty,
172            price: o.price,
173            fill_qty: o.fill_qty,
174            fill_avg_price: o.fill_avg_price,
175            create_time: o.create_time.clone(),
176            update_time: o.update_time.clone(),
177            last_err_msg: o.last_err_msg.clone(),
178        })
179        .collect();
180    Ok(serde_json::to_string_pretty(&out)?)
181}
182
183#[derive(Serialize)]
184struct DealOut {
185    fill_id: u64,
186    fill_id_ex: String,
187    order_id: u64,
188    trd_side: i32,
189    code: String,
190    name: String,
191    qty: f64,
192    price: f64,
193    create_time: String,
194}
195
196pub async fn get_deals(
197    client: &Arc<FutuClient>,
198    env: &str,
199    acc_id: u64,
200    market: &str,
201) -> Result<String> {
202    let header = build_header(env, acc_id, market)?;
203    let list = futu_trd::query::get_order_fill_list(client, &header).await?;
204    let out: Vec<DealOut> = list
205        .iter()
206        .map(|f| DealOut {
207            fill_id: f.fill_id,
208            fill_id_ex: f.fill_id_ex.clone(),
209            order_id: f.order_id,
210            trd_side: f.trd_side,
211            code: f.code.clone(),
212            name: f.name.clone(),
213            qty: f.qty,
214            price: f.price,
215            create_time: f.create_time.clone(),
216        })
217        .collect();
218    Ok(serde_json::to_string_pretty(&out)?)
219}