Skip to main content

longbridge/portfolio/
types.rs

1#![allow(missing_docs)]
2
3use rust_decimal::Decimal;
4use serde::{Deserialize, Serialize};
5use strum_macros::{Display, EnumString};
6
7use crate::utils::counter::deserialize_counter_id_as_symbol;
8
9// ── exchange_rate ─────────────────────────────────────────────────
10
11/// Response for [`crate::PortfolioContext::exchange_rate`]
12#[derive(Debug, Clone, Serialize, Deserialize)]
13pub struct ExchangeRates {
14    /// List of exchange rates
15    pub exchanges: Vec<ExchangeRate>,
16}
17
18/// One currency exchange rate
19#[derive(Debug, Clone, Serialize, Deserialize)]
20pub struct ExchangeRate {
21    /// Average rate (base_currency / other_currency)
22    pub average_rate: f64,
23    /// Base currency, e.g. `"USD"`
24    pub base_currency: String,
25    /// Bid rate
26    pub bid_rate: f64,
27    /// Offer rate
28    pub offer_rate: f64,
29    /// Other currency, e.g. `"HKD"`
30    pub other_currency: String,
31}
32
33// ── profit_analysis ───────────────────────────────────────────────
34
35/// Summary response for [`crate::PortfolioContext::profit_analysis`]
36///
37/// This is a combined response from two API endpoints:
38/// `/v1/portfolio/profit-analysis-summary` and
39/// `/v1/portfolio/profit-analysis-sublist`.
40#[derive(Debug, Clone, Serialize, Deserialize)]
41pub struct ProfitAnalysis {
42    /// Summary overview
43    pub summary: ProfitAnalysisSummary,
44    /// Per-security breakdown
45    pub sublist: ProfitAnalysisSublist,
46}
47
48/// Account-level P&L summary
49#[derive(Debug, Clone, Serialize, Deserialize)]
50pub struct ProfitAnalysisSummary {
51    /// Account currency
52    pub currency: String,
53    /// Current total asset value
54    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
55    pub current_total_asset: Option<Decimal>,
56    /// Query start date string
57    pub start_date: String,
58    /// Query end date string
59    pub end_date: String,
60    /// Start time (unix timestamp string)
61    pub start_time: String,
62    /// End time (unix timestamp string)
63    pub end_time: String,
64    /// Ending asset value
65    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
66    pub ending_asset_value: Option<Decimal>,
67    /// Initial asset value
68    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
69    pub initial_asset_value: Option<Decimal>,
70    /// Total invested amount
71    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
72    pub invest_amount: Option<Decimal>,
73    /// Whether any trades occurred
74    pub is_traded: bool,
75    /// Total profit/loss
76    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
77    pub sum_profit: Option<Decimal>,
78    /// Total profit/loss rate
79    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
80    pub sum_profit_rate: Option<Decimal>,
81    /// Per-asset-type breakdown
82    pub profits: ProfitSummaryBreakdown,
83}
84
85/// P&L breakdown by asset type
86#[derive(Debug, Clone, Serialize, Deserialize)]
87pub struct ProfitSummaryBreakdown {
88    /// Stock P&L
89    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
90    pub stock: Option<Decimal>,
91    /// Fund P&L
92    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
93    pub fund: Option<Decimal>,
94    /// Crypto P&L
95    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
96    pub crypto: Option<Decimal>,
97    /// Money market fund P&L
98    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
99    pub mmf: Option<Decimal>,
100    /// Other P&L
101    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
102    pub other: Option<Decimal>,
103    /// Cumulative transaction amount
104    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
105    pub cumulative_transaction_amount: Option<Decimal>,
106    /// Total number of orders
107    pub trade_order_num: String,
108    /// Total number of traded securities
109    pub trade_stock_num: String,
110    /// IPO P&L
111    #[serde(default, with = "crate::serde_utils::decimal_opt_str_is_none")]
112    pub ipo: Option<Decimal>,
113    /// IPO hits
114    pub ipo_hit: i32,
115    /// IPO subscriptions
116    pub ipo_subscription: i32,
117    /// Per-category summary info
118    pub summary_info: Vec<ProfitSummaryInfo>,
119}
120
121/// P&L summary for one asset category
122#[derive(Debug, Clone, Serialize, Deserialize)]
123pub struct ProfitSummaryInfo {
124    /// Asset type
125    pub asset_type: AssetType,
126    /// Security with the maximum profit
127    pub profit_max: String,
128    /// Name of the max-profit security
129    pub profit_max_name: String,
130    /// Security with the maximum loss
131    pub loss_max: String,
132    /// Name of the max-loss security
133    pub loss_max_name: String,
134}
135
136/// Per-security P&L breakdown
137#[derive(Debug, Clone, Default, Serialize, Deserialize)]
138pub struct ProfitAnalysisSublist {
139    /// Start time (unix timestamp string)
140    pub start: String,
141    /// End time (unix timestamp string)
142    pub end: String,
143    /// Start date string
144    pub start_date: String,
145    /// End date string
146    pub end_date: String,
147    /// Last updated time (unix timestamp string)
148    pub updated_at: String,
149    /// Last updated date string
150    pub updated_date: String,
151    /// Per-security items
152    pub items: Vec<ProfitAnalysisItem>,
153}
154
155/// P&L for one security
156#[derive(Debug, Clone, Serialize, Deserialize)]
157pub struct ProfitAnalysisItem {
158    /// Security name
159    pub name: String,
160    /// Market
161    pub market: String,
162    /// Whether still holding
163    pub is_holding: bool,
164    /// Profit/loss amount
165    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
166    pub profit: Option<Decimal>,
167    /// Profit/loss rate
168    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
169    pub profit_rate: Option<Decimal>,
170    /// Number of completed trades
171    pub clearance_times: i64,
172    /// Asset type
173    #[serde(rename = "type")]
174    pub item_type: AssetType,
175    /// Currency
176    pub currency: String,
177    /// Security symbol
178    #[serde(
179        rename = "counter_id",
180        deserialize_with = "deserialize_counter_id_as_symbol"
181    )]
182    pub symbol: String,
183    /// Holding period display string
184    #[serde(default)]
185    pub holding_period: String,
186    /// Ticker code
187    pub security_code: String,
188    /// ISIN (for funds)
189    pub isin: String,
190    /// Underlying stock P&L
191    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
192    pub underlying_profit: Option<Decimal>,
193    /// Derivatives P&L
194    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
195    pub derivatives_profit: Option<Decimal>,
196    /// P&L in order currency
197    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
198    pub order_profit: Option<Decimal>,
199}
200
201// ── profit_analysis_detail ────────────────────────────────────────
202
203/// Response for [`crate::PortfolioContext::profit_analysis_detail`]
204#[derive(Debug, Clone, Serialize, Deserialize)]
205pub struct ProfitAnalysisDetail {
206    /// Total profit/loss
207    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
208    pub profit: Option<Decimal>,
209    /// Underlying stock P&L details
210    pub underlying_details: ProfitDetails,
211    /// Derivative P&L details
212    pub derivative_pnl_details: ProfitDetails,
213    /// Security name
214    pub name: String,
215    /// Last updated time (unix timestamp string)
216    pub updated_at: String,
217    /// Last updated date string
218    pub updated_date: String,
219    /// Currency
220    pub currency: String,
221    /// Default detail tab: 0 = underlying, 1 = derivative
222    pub default_tag: i32,
223    /// Query start time (unix timestamp string)
224    pub start: String,
225    /// Query end time (unix timestamp string)
226    pub end: String,
227    /// Query start date string
228    pub start_date: String,
229    /// Query end date string
230    pub end_date: String,
231}
232
233/// Detailed P&L breakdown for one asset class
234#[derive(Debug, Clone, Serialize, Deserialize)]
235pub struct ProfitDetails {
236    /// Current holding market value
237    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
238    pub holding_value: Option<Decimal>,
239    /// Total profit/loss
240    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
241    pub profit: Option<Decimal>,
242    /// Cumulative credited amount
243    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
244    pub cumulative_credited_amount: Option<Decimal>,
245    /// Credit detail entries
246    pub credited_details: Vec<ProfitDetailEntry>,
247    /// Cumulative debited amount
248    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
249    pub cumulative_debited_amount: Option<Decimal>,
250    /// Debit detail entries
251    pub debited_details: Vec<ProfitDetailEntry>,
252    /// Cumulative fee amount
253    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
254    pub cumulative_fee_amount: Option<Decimal>,
255    /// Fee detail entries
256    pub fee_details: Vec<ProfitDetailEntry>,
257    /// Short position holding value
258    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
259    pub short_holding_value: Option<Decimal>,
260    /// Long position holding value
261    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
262    pub long_holding_value: Option<Decimal>,
263    /// Opening position market value at period start
264    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
265    pub holding_value_at_beginning: Option<Decimal>,
266    /// Closing position market value at period end
267    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
268    pub holding_value_at_ending: Option<Decimal>,
269}
270
271/// One P&L detail line item (credit, debit, or fee)
272#[derive(Debug, Clone, Serialize, Deserialize)]
273pub struct ProfitDetailEntry {
274    /// Description
275    pub describe: String,
276    /// Amount
277    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
278    pub amount: Option<Decimal>,
279}
280
281// ── profit_analysis_by_market ─────────────────────────────────────
282
283/// Response for [`crate::PortfolioContext::profit_analysis_by_market`]
284#[derive(Debug, Clone, Serialize, Deserialize)]
285pub struct ProfitAnalysisByMarket {
286    /// Total P&L across all returned items
287    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
288    pub profit: Option<Decimal>,
289    /// Whether more pages are available
290    #[serde(default)]
291    pub has_more: bool,
292    /// Per-security P&L items for the requested market/page
293    #[serde(default)]
294    pub stock_items: Vec<ProfitAnalysisByMarketItem>,
295}
296
297/// One security entry in a by-market P&L response
298#[derive(Debug, Clone, Serialize, Deserialize)]
299pub struct ProfitAnalysisByMarketItem {
300    /// Security symbol (ticker code)
301    pub code: String,
302    /// Security name
303    pub name: String,
304    /// Market, e.g. `"HK"`, `"US"`
305    pub market: String,
306    /// Profit/loss amount
307    #[serde(with = "crate::serde_utils::decimal_opt_str_is_none")]
308    pub profit: Option<Decimal>,
309}
310
311// ── profit_analysis_flows ─────────────────────────────────────────
312
313/// Response for [`crate::PortfolioContext::profit_analysis_flows`]
314#[derive(Debug, Clone, Serialize, Deserialize)]
315pub struct ProfitAnalysisFlows {
316    /// Paginated list of flow items
317    #[serde(default)]
318    pub flows_list: Vec<FlowItem>,
319    /// Whether there are more pages
320    #[serde(default)]
321    pub has_more: bool,
322}
323
324/// One profit-analysis flow record
325#[derive(Debug, Clone, Serialize, Deserialize)]
326pub struct FlowItem {
327    /// Execution date string, e.g. `"2024-01-15"`
328    #[serde(default)]
329    pub executed_date: String,
330    /// Execution timestamp (may be int or string)
331    #[serde(default)]
332    pub executed_timestamp: serde_json::Value,
333    /// Security code / ticker
334    #[serde(default)]
335    pub code: String,
336    /// Direction of the flow
337    pub direction: FlowDirection,
338    /// Executed quantity
339    #[serde(default, with = "crate::serde_utils::decimal_opt_str_is_none")]
340    pub executed_quantity: Option<Decimal>,
341    /// Executed price
342    #[serde(default, with = "crate::serde_utils::decimal_opt_str_is_none")]
343    pub executed_price: Option<Decimal>,
344    /// Executed cost
345    #[serde(default, with = "crate::serde_utils::decimal_opt_str_is_none")]
346    pub executed_cost: Option<Decimal>,
347    /// Human-readable description
348    #[serde(default)]
349    pub describe: String,
350}
351
352/// Flow direction
353#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
354pub enum FlowDirection {
355    /// Unknown
356    Unknown,
357    /// Buy
358    #[strum(serialize = "buy")]
359    Buy,
360    /// Sell
361    #[strum(serialize = "sell")]
362    Sell,
363}
364
365impl_default_for_enum_string!(FlowDirection);
366impl_serde_for_enum_string!(FlowDirection);
367
368/// Asset type
369#[derive(Debug, Copy, Clone, Hash, Eq, PartialEq, EnumString, Display)]
370pub enum AssetType {
371    /// Unknown
372    Unknown,
373    /// Stock
374    #[strum(serialize = "stock")]
375    Stock,
376    /// Fund
377    #[strum(serialize = "fund")]
378    Fund,
379    /// Crypto
380    #[strum(serialize = "crypto")]
381    Crypto,
382}
383
384impl_default_for_enum_string!(AssetType);
385impl_serde_for_enum_string!(AssetType);