|
1 | 1 | """Order types used by Interactive Brokers."""
|
2 | 2 |
|
| 3 | +import dataclasses |
3 | 4 | from dataclasses import dataclass, field
|
4 | 5 | from typing import ClassVar, FrozenSet, List, NamedTuple
|
5 | 6 |
|
@@ -280,6 +281,55 @@ class OrderState:
|
280 | 281 | completedTime: str = ""
|
281 | 282 | completedStatus: str = ""
|
282 | 283 |
|
| 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 | + |
283 | 333 |
|
284 | 334 | @dataclass
|
285 | 335 | class OrderComboLeg:
|
|
0 commit comments