ibkr_flex_statement/
open_position.rs1use super::currency::Currency;
2use super::time_utils;
3use crate::asset_category::AssetCategory;
4use crate::node_utils::NodeWrapper;
5use crate::statement_section::StatementSection;
6use anyhow::Result;
7
8#[derive(Debug, PartialEq)]
9pub enum PositionSide {
10 Long,
11 Short,
12}
13
14#[derive(Debug, PartialEq)]
15pub struct OpenPosition {
16 pub account_id: String,
17 pub asset_category: AssetCategory,
18 pub conid: u32,
19 pub cost_basis_price: f64,
20 pub fifo_pnl_unrealized: f64,
21 pub currency: Currency,
22 pub listing_exchange: String,
23 pub mark_price: f64,
24 pub open_quantity: f64,
25 pub position_value: f64,
26 pub timestamp_eod_ms: i64,
27 pub ticker: String,
28 pub side: PositionSide,
29}
30
31impl<'a> TryFrom<&'a str> for PositionSide {
32 type Error = anyhow::Error;
33
34 fn try_from(s: &'a str) -> Result<Self> {
35 match s {
36 "Long" => Ok(Self::Long),
37 "Short" => Ok(Self::Short),
38 _ => Err(anyhow::Error::msg(format!("unknown position side {}", s))),
39 }
40 }
41}
42
43impl StatementSection for OpenPosition {
44 fn from_node(node: &NodeWrapper) -> Result<OpenPosition> {
45 Ok(OpenPosition {
46 account_id: node.get_attribute("accountId")?,
47 asset_category: AssetCategory::try_from(node.node.attribute("assetCategory").unwrap())?,
48 conid: node.parse_attribute("conid")?,
49 cost_basis_price: node.parse_attribute("costBasisPrice")?,
50 currency: Currency::try_from(node.node.attribute("currency").unwrap())?,
51 fifo_pnl_unrealized: node.parse_attribute("fifoPnlUnrealized")?,
52 listing_exchange: node.get_attribute("listingExchange")?,
53 mark_price: node.parse_attribute("markPrice")?,
54 open_quantity: node.parse_attribute("position")?,
55 position_value: node.parse_attribute("positionValue")?,
56 side: PositionSide::try_from(node.node.attribute("side").unwrap())?,
57 ticker: node.get_attribute("symbol")?,
58 timestamp_eod_ms: time_utils::trading_eod_after_hours_timestamp_ms(
59 node.node.attribute("reportDate").unwrap(),
60 )?,
61 })
62 }
63}
64
65#[cfg(test)]
66mod tests {
67 use super::*;
68 use crate::Parser;
69 use crate::currency::Currency;
70 use anyhow::Result;
71
72 const PARTIAL_STATEMENT_EXAMPLE: &str = r##"
73 <FlexQueryResponse queryName="example-query" type="AF">
74 <FlexStatements count="1">
75 <FlexStatement accountId="U1234567" fromDate="2025-04-25" toDate="2025-04-25" period="LastBusinessDay" whenGenerated="2025-04-26;13:34:28 EDT">
76 <AccountInformation accountId="U1234567" accountType="Individual" customerType="Individual" accountCapabilities="Portfolio Margin" tradingPermissions="Stocks,Options,Warrants,Forex,Futures,Crypto Currencies,Mutual Funds,Fully Paid Stock Loan" />
77 <OpenPositions>
78 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="GRPN" conid="426480582" listingExchange="NASDAQ" reportDate="2025-04-25" position="3000" markPrice="19.89" positionValue="59670" openPrice="20.153441225" costBasisPrice="20.153441225" percentOfNAV="1.63" fifoPnlUnrealized="-790.323674" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
79 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="META" conid="107113386" listingExchange="NASDAQ" reportDate="2025-04-25" position="800" markPrice="547.27" positionValue="437816" openPrice="542.020354354" costBasisPrice="542.020354354" percentOfNAV="11.95" fifoPnlUnrealized="4199.716517" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
80 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="NFLX" conid="15124833" listingExchange="NASDAQ" reportDate="2025-04-25" position="400" markPrice="1101.53" positionValue="440612" openPrice="1056.32548211" costBasisPrice="1056.32548211" percentOfNAV="12.02" fifoPnlUnrealized="18081.807156" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
81 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="PLTR" conid="444857009" listingExchange="NASDAQ" reportDate="2025-04-25" position="3100" markPrice="112.78" positionValue="349618" openPrice="104.761973398" costBasisPrice="104.761973398" percentOfNAV="9.54" fifoPnlUnrealized="24855.882465" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
82 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="TQQQ" conid="72539702" listingExchange="NASDAQ" reportDate="2025-04-25" position="34100" markPrice="53.86" positionValue="1836626" openPrice="53.776784497" costBasisPrice="53.776784497" percentOfNAV="50.12" fifoPnlUnrealized="2837.648645" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
83 <OpenPosition accountId="U1234567" currency="USD" assetCategory="STK" symbol="TSLA" conid="76792991" listingExchange="NASDAQ" reportDate="2025-04-25" position="1500" markPrice="284.95" positionValue="427425" openPrice="262.984320092" costBasisPrice="262.984320092" percentOfNAV="11.66" fifoPnlUnrealized="32948.519862" side="Long" openDateTime="" holdingPeriodDateTime="" accruedInt="" commodityType="" />
84 <OpenPosition
85 accountId="U1234567"
86 currency="USD"
87 assetCategory="STK"
88 symbol="TTWO"
89 conid="6478131"
90 listingExchange="NASDAQ"
91 reportDate="2025-04-25"
92 position="500"
93 markPrice="225.38"
94 positionValue="112690"
95 openPrice="217.200032892"
96 costBasisPrice="217.200032892"
97 percentOfNAV="3.08"
98 fifoPnlUnrealized="4089.983554"
99 side="Long"
100 openDateTime=""
101 holdingPeriodDateTime=""
102 accruedInt=""
103 commodityType="" />
104 </OpenPositions>
105 </FlexStatement>
106 </FlexStatements>
107 </FlexQueryResponse>
108 "##;
109
110 #[test]
111 fn open_positions_parse() -> Result<()> {
112 let statements = Parser::new()?.parse_flex_query_response(PARTIAL_STATEMENT_EXAMPLE)?;
113 assert_eq!(statements.len(), 1);
114 let result = &statements[0];
115
116 assert_eq!(result.open_positions.len(), 7);
118
119 assert_eq!(
120 result.open_positions[6],
121 OpenPosition {
122 account_id: "U1234567".to_string(),
123 asset_category: AssetCategory::Stock,
124 conid: 6478131,
125 cost_basis_price: 217.200032892,
126 fifo_pnl_unrealized: 4089.983554,
127 currency: Currency::USD,
128 listing_exchange: "NASDAQ".to_string(),
129 mark_price: 225.38,
130 open_quantity: 500.0,
131 position_value: 112690.0,
132 timestamp_eod_ms: result.open_positions[6].timestamp_eod_ms,
133 ticker: "TTWO".to_string(),
134 side: PositionSide::Long
135 }
136 );
137 Ok(())
138 }
139}