Skip to content

Commit f2b80c8

Browse files
committed
Add data conversion helpers to OrderState
Adds two helper methods for converting `OrderState` values from default numbers-in-strings to either numbers directly or formatted strings. Now you can get the result of a WhatIf order and just run `orderStateWithNumbers = orderState.numeric()` instead of needing to manually run 13 different `float()` conversions plus checking for `UNSET_DOUBLE`. You can also run `orderState.formatted()` to convert the values to comma separated strings. Both of those methods take an optional paramter for number of digits to round the floats. Defaults to 2 digits since these are account monetary values, but you can use `orderState.numeric(digits=4)` etc if you want more precision.
1 parent 38cf54a commit f2b80c8

File tree

1 file changed

+50
-0
lines changed

1 file changed

+50
-0
lines changed

ib_async/order.py

Lines changed: 50 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
"""Order types used by Interactive Brokers."""
22

3+
import dataclasses
34
from dataclasses import dataclass, field
45
from typing import ClassVar, FrozenSet, List, NamedTuple
56

@@ -280,6 +281,55 @@ class OrderState:
280281
completedTime: str = ""
281282
completedStatus: str = ""
282283

284+
def transform(self, transformer):
285+
"""Convert the numeric values of this OrderState into a new OrderState transformed by 'using'"""
286+
return dataclasses.replace(
287+
self,
288+
initMarginBefore=transformer(self.initMarginBefore),
289+
maintMarginBefore=transformer(self.maintMarginBefore),
290+
equityWithLoanBefore=transformer(self.equityWithLoanBefore),
291+
initMarginChange=transformer(self.initMarginChange),
292+
maintMarginChange=transformer(self.maintMarginChange),
293+
equityWithLoanChange=transformer(self.equityWithLoanChange),
294+
initMarginAfter=transformer(self.initMarginAfter),
295+
maintMarginAfter=transformer(self.maintMarginAfter),
296+
equityWithLoanAfter=transformer(self.equityWithLoanAfter),
297+
commission=transformer(self.commission),
298+
minCommission=transformer(self.minCommission),
299+
maxCommission=transformer(self.maxCommission),
300+
)
301+
302+
def numeric(self, digits: int = 2):
303+
"""Return a new OrderState with the current values values to floats instead of strings as returned from IBKR directly."""
304+
305+
def floatOrNone(what, precision) -> float | None:
306+
"""Attempt to convert input to a float, but if we fail (value is just empty string) return None"""
307+
try:
308+
# convert
309+
floated = float(what)
310+
311+
# if the conversion is IBKR speak for "this value is not set" then give us None
312+
if floated == UNSET_DOUBLE:
313+
return None
314+
315+
# else, round to the requested precision
316+
return round(floated, precision)
317+
except:
318+
# initial conversion failed so just return None in its place
319+
return None
320+
321+
return self.transform(lambda x: floatOrNone(x, digits))
322+
323+
def formatted(self, digits: int = 2):
324+
"""Return a new OrderState with the current values as formatted strings."""
325+
return self.numeric(8).transform(
326+
# 300000.21 -> 300,000.21
327+
# 0.0 -> 0.00
328+
# 431.342000000001 -> 431.34
329+
# Note: we need 'is not None' here because 'x=0' is a valid numeric input too
330+
lambda x: f"{x:,.{digits}f}" if x is not None else None
331+
)
332+
283333

284334
@dataclass
285335
class OrderComboLeg:

0 commit comments

Comments
 (0)