Skip to main content

comdirect_rest_api/brokerage/
types.rs

1use serde::{Deserialize, Serialize};
2use serde_json::Value;
3use std::collections::BTreeMap;
4
5/// Common paging object returned by brokerage endpoints.
6#[derive(Debug, Serialize, Deserialize, Clone, Default)]
7pub struct Paging {
8    pub index: Option<u32>,
9    pub matches: Option<u32>,
10}
11
12/// API response for `/brokerage/clients/user/v3/depots`.
13#[derive(Debug, Serialize, Deserialize, Clone, Default)]
14pub struct DepotsResponse {
15    pub paging: Option<Paging>,
16    #[serde(default)]
17    pub values: Vec<Depot>,
18}
19
20/// Depot metadata object.
21#[derive(Debug, Serialize, Deserialize, Clone, Default)]
22#[serde(rename_all = "camelCase")]
23pub struct Depot {
24    pub depot_id: Option<String>,
25    pub depot_display_id: Option<String>,
26    pub client_id: Option<String>,
27    pub depot_type: Option<String>,
28    pub default_settlement_account_id: Option<String>,
29    #[serde(default)]
30    pub settlement_account_ids: Vec<String>,
31    pub target_market: Option<String>,
32}
33
34/// API response for `/brokerage/v3/depots/{depot_id}/positions`.
35#[derive(Debug, Serialize, Deserialize, Clone, Default)]
36pub struct DepotPositionsResponse {
37    pub paging: Option<Paging>,
38    pub aggregated: Option<DepotPositionsAggregated>,
39    #[serde(default)]
40    pub values: Vec<Position>,
41}
42
43/// Aggregated valuation metadata over all positions of one depot.
44#[derive(Debug, Serialize, Deserialize, Clone, Default)]
45#[serde(rename_all = "camelCase")]
46pub struct DepotPositionsAggregated {
47    pub depot: Option<Depot>,
48    pub prev_day_value: Option<Amount>,
49    pub current_value: Option<Amount>,
50    pub purchase_value: Option<Amount>,
51    pub profit_loss_purchase_abs: Option<Amount>,
52    pub profit_loss_purchase_rel: Option<String>,
53    pub profit_loss_prev_day_abs: Option<Amount>,
54    pub profit_loss_prev_day_rel: Option<String>,
55    #[serde(flatten)]
56    pub extra: BTreeMap<String, Value>,
57}
58
59/// One depot position entry.
60#[derive(Debug, Serialize, Deserialize, Clone, Default)]
61#[serde(rename_all = "camelCase")]
62pub struct Position {
63    pub depot_id: Option<String>,
64    pub position_id: Option<String>,
65    pub wkn: Option<String>,
66    pub custody_type: Option<String>,
67    pub position_type: Option<String>,
68    pub has_purchase_price: Option<bool>,
69    pub quantity: Option<Amount>,
70    pub available_quantity: Option<Amount>,
71    pub current_price: Option<CurrentPrice>,
72    pub purchase_price: Option<Amount>,
73    pub prev_day_price: Option<CurrentPrice>,
74    pub current_value: Option<Amount>,
75    pub purchase_value: Option<Amount>,
76    pub purchase_value_editable: Option<bool>,
77    pub profit_loss_purchase_abs: Option<Amount>,
78    pub profit_loss_purchase_rel: Option<String>,
79    pub profit_loss_prev_day_abs: Option<Amount>,
80    pub profit_loss_prev_day_rel: Option<String>,
81    pub profit_loss_prev_day_total_abs: Option<Amount>,
82    pub version: Option<String>,
83    pub hedgeability: Option<String>,
84    pub available_quantity_to_hedge: Option<Amount>,
85    pub current_price_determinable: Option<bool>,
86    pub has_intra_day_executed_order: Option<bool>,
87    pub purchase_possible: Option<bool>,
88    pub sell_possible: Option<bool>,
89    #[serde(flatten)]
90    pub extra: BTreeMap<String, Value>,
91}
92
93/// Price object including venue information.
94#[derive(Debug, Serialize, Deserialize, Clone, Default)]
95#[serde(rename_all = "camelCase")]
96pub struct CurrentPrice {
97    pub price: Amount,
98    pub price_date_time: Option<String>,
99    pub venue: Option<Venue>,
100}
101
102/// Amount object used by brokerage endpoints.
103#[derive(Debug, Serialize, Deserialize, Clone, Default)]
104pub struct Amount {
105    pub value: String,
106    pub unit: Option<String>,
107}
108
109/// Exchange venue metadata.
110#[derive(Debug, Serialize, Deserialize, Clone, Default)]
111pub struct Venue {
112    pub name: Option<String>,
113    pub venue_id: Option<String>,
114    pub country: Option<String>,
115    #[serde(rename = "type")]
116    pub venue_type: Option<String>,
117}
118
119/// API response for `/brokerage/v1/instruments/{wkn}`.
120#[derive(Debug, Serialize, Deserialize, Clone, Default)]
121pub struct InstrumentResponse {
122    pub paging: Option<Paging>,
123    #[serde(default)]
124    pub values: Vec<Instrument>,
125}
126
127/// Instrument metadata object.
128#[derive(Debug, Serialize, Deserialize, Clone, Default)]
129#[serde(rename_all = "camelCase")]
130pub struct Instrument {
131    pub instrument_id: Option<String>,
132    pub wkn: Option<String>,
133    pub isin: Option<String>,
134    pub mnemonic: Option<String>,
135    pub name: Option<String>,
136    pub short_name: Option<String>,
137    pub issuer: Option<String>,
138    pub static_data: Option<InstrumentStaticData>,
139}
140
141/// Static data section of an instrument payload.
142#[derive(Debug, Serialize, Deserialize, Clone, Default)]
143#[serde(rename_all = "camelCase")]
144pub struct InstrumentStaticData {
145    pub notation: Option<String>,
146    pub currency: Option<String>,
147    pub settlement_currency: Option<String>,
148    pub instrument_type: Option<String>,
149    pub priips_relevant: Option<bool>,
150    pub kid_available: Option<bool>,
151    pub shipping_waiver_required: Option<bool>,
152    pub fund_redemption_limited: Option<bool>,
153    pub savings_plan_eligibility: Option<String>,
154}
155
156#[cfg(test)]
157mod tests {
158    use super::{DepotPositionsResponse, DepotsResponse, InstrumentResponse};
159
160    #[test]
161    fn depots_deserializes_real_shape() {
162        let raw = r#"{"paging":{"index":0,"matches":1},"values":[{"depotId":"d1","depotDisplayId":"299","clientId":"c1"}]}"#;
163        let parsed: DepotsResponse = serde_json::from_str(raw).unwrap();
164        assert_eq!(parsed.values.len(), 1);
165        assert_eq!(parsed.values[0].depot_id.as_deref(), Some("d1"));
166    }
167
168    #[test]
169    fn positions_deserializes_aggregated_and_venue() {
170        let raw = r#"{"paging":{"index":0,"matches":1},"aggregated":{"currentValue":{"value":"642.450","unit":"EUR"}},"values":[{"depotId":"d1","positionId":"p1","wkn":"A1JKSU","quantity":{"value":"1.567","unit":"XXX"},"currentPrice":{"price":{"value":"50.6","unit":"EUR"},"priceDateTime":"2026-03-06T17:35:49+01","venue":{"name":"Xetra","venueId":"v1","country":"DE","type":"EXCHANGE"}}}]}"#;
171        let parsed: DepotPositionsResponse = serde_json::from_str(raw).unwrap();
172        assert_eq!(parsed.values.len(), 1);
173        assert_eq!(
174            parsed.values[0]
175                .current_price
176                .as_ref()
177                .and_then(|p| p.venue.as_ref())
178                .and_then(|v| v.venue_type.as_deref()),
179            Some("EXCHANGE")
180        );
181    }
182
183    #[test]
184    fn instrument_deserializes_static_data() {
185        let raw = r#"{"paging":{"index":0,"matches":1},"values":[{"instrumentId":"i1","wkn":"A1JKSU","isin":"IE00B6YX5M31","name":"ETF","staticData":{"instrumentType":"ETF","kidAvailable":true}}]}"#;
186        let parsed: InstrumentResponse = serde_json::from_str(raw).unwrap();
187        assert_eq!(parsed.values.len(), 1);
188        assert_eq!(parsed.values[0].wkn.as_deref(), Some("A1JKSU"));
189    }
190}