orionapi 2.27.0__tar.gz → 2.28.0__tar.gz
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
__version__ = "2.
|
|
1
|
+
__version__ = "2.28.0"
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
@@ -56,10 +56,16 @@ TRADE_INSTANCE_SUBTYPES = {
|
|
|
56
56
|
}
|
|
57
57
|
|
|
58
58
|
# TradingApplication mappings for the v2 /TradeOrder/Trades endpoints.
|
|
59
|
-
# Not published in the Swagger (which lists the ints with no names)
|
|
60
|
-
# 2026-07-28 from the API's own rejection messages
|
|
61
|
-
# not supported.")
|
|
59
|
+
# Not published in the Swagger (which lists the ints with no names). Names 0-10
|
|
60
|
+
# and 12-18 were recovered 2026-07-28 from the API's own rejection messages
|
|
61
|
+
# ("Trade Application <Name> is not supported."); 11 is QuickTrades, confirmed
|
|
62
|
+
# 2026-08-03 by Orion support and verified live (the string "QuickTrades" and the
|
|
63
|
+
# int 11 return identical results). Only 11 creates trade orders -- see
|
|
62
64
|
# EclipseV2.create_trades.
|
|
65
|
+
#
|
|
66
|
+
# These names are also accepted as *strings* in the ``application`` field, e.g.
|
|
67
|
+
# ``{"application": "QuickTrades"}``. A name outside this set is rejected with
|
|
68
|
+
# 400 "Error converting value ... to type 'OAS.Eclipse.Core.Enums.TradingApplication'".
|
|
63
69
|
TRADING_APPLICATIONS = {
|
|
64
70
|
0: "BuySell",
|
|
65
71
|
1: "CashNeeds",
|
|
@@ -72,7 +78,7 @@ TRADING_APPLICATIONS = {
|
|
|
72
78
|
8: "TaxLossHarvesting",
|
|
73
79
|
9: "TickerSwap",
|
|
74
80
|
10: "TradeToTargetPercent",
|
|
75
|
-
11: "
|
|
81
|
+
11: "QuickTrades",
|
|
76
82
|
12: "TacticalTrades",
|
|
77
83
|
13: "PartialShare",
|
|
78
84
|
14: "(unnamed, accepted but creates nothing)",
|
|
@@ -86,7 +92,12 @@ TRADING_APPLICATIONS = {
|
|
|
86
92
|
# POST /api/v2/TradeOrder/Trades. Every other value except 14 returns
|
|
87
93
|
# 400 "Trade Application <Name> is not supported."; 14 returns 200 with an
|
|
88
94
|
# empty ``tradeId`` list and creates nothing. Live-verified 2026-07-28.
|
|
89
|
-
|
|
95
|
+
TRADING_APPLICATION_QUICK_TRADES = 11
|
|
96
|
+
|
|
97
|
+
# Deprecated alias. 2.26.0 shipped this as TRADING_APPLICATION_MANUAL on the
|
|
98
|
+
# mistaken belief that 11 was named "ManualTrade"; the API rejects that name.
|
|
99
|
+
# Kept so 2.26.0/2.27.0 callers keep working -- the value is unchanged.
|
|
100
|
+
TRADING_APPLICATION_MANUAL = TRADING_APPLICATION_QUICK_TRADES
|
|
90
101
|
|
|
91
102
|
# Set-Aside Cash Constants (from Eclipse API)
|
|
92
103
|
CASH_TYPE_DOLLAR = 1
|
|
@@ -203,6 +214,17 @@ class NotFoundError(OrionAPIError):
|
|
|
203
214
|
pass
|
|
204
215
|
|
|
205
216
|
|
|
217
|
+
class AmbiguousAccountError(OrionAPIError):
|
|
218
|
+
"""Raised when an account search matches more than one account.
|
|
219
|
+
|
|
220
|
+
The message lists the candidate accounts (id, name, accountNumber) so the
|
|
221
|
+
caller can retry with an exact internal id, a full account number, or the
|
|
222
|
+
deliberate fuzzy workflow (``search_accounts_number_and_name``).
|
|
223
|
+
"""
|
|
224
|
+
|
|
225
|
+
pass
|
|
226
|
+
|
|
227
|
+
|
|
206
228
|
class RateLimiter:
|
|
207
229
|
"""Thread-safe rate limiter to prevent API abuse.
|
|
208
230
|
|
|
@@ -2348,30 +2370,71 @@ class EclipseBase(BaseAPI):
|
|
|
2348
2370
|
res = self.api_request(f"{self.base_url}/account/accounts/simple")
|
|
2349
2371
|
return res.json()
|
|
2350
2372
|
|
|
2351
|
-
def get_internal_account_id(self, search_param):
|
|
2373
|
+
def get_internal_account_id(self, search_param, fuzzy=False):
|
|
2352
2374
|
"""Get internal Eclipse account ID from a search parameter.
|
|
2353
2375
|
|
|
2354
|
-
Searches across id, accountName, accountNumber, and portfolioName
|
|
2355
|
-
|
|
2376
|
+
Searches across id, accountName, accountNumber, and portfolioName,
|
|
2377
|
+
then resolves in tiers:
|
|
2378
|
+
|
|
2379
|
+
1. **Exact match:** a result whose internal ``id`` or ``accountNumber``
|
|
2380
|
+
equals ``search_param`` is returned immediately. If an exact-id and
|
|
2381
|
+
an exact-account-number match point at *different* accounts, raises
|
|
2382
|
+
``AmbiguousAccountError`` rather than guessing.
|
|
2383
|
+
2. A single remaining candidate is returned.
|
|
2384
|
+
3. Multiple candidates raise ``AmbiguousAccountError`` listing them,
|
|
2385
|
+
unless ``fuzzy=True``, which returns the first match (the pre-2.28
|
|
2386
|
+
behavior).
|
|
2387
|
+
|
|
2388
|
+
Best use is to pass a full custodian account number. Callers that
|
|
2389
|
+
already hold the internal id should pass it via the
|
|
2390
|
+
``internal_account_id=`` keyword on scoped methods instead of
|
|
2391
|
+
resolving here. For deliberate partial-number + name resolution use
|
|
2392
|
+
:meth:`search_accounts_number_and_name`.
|
|
2356
2393
|
|
|
2357
2394
|
Args:
|
|
2358
2395
|
search_param: Account number, name, or ID to search for
|
|
2396
|
+
fuzzy: When True, silently return the first match when multiple
|
|
2397
|
+
candidates remain (default False)
|
|
2359
2398
|
|
|
2360
2399
|
Returns:
|
|
2361
2400
|
int: The internal Eclipse account ID
|
|
2362
2401
|
|
|
2363
2402
|
Raises:
|
|
2364
2403
|
NotFoundError: If no matching account is found
|
|
2365
|
-
|
|
2366
|
-
|
|
2367
|
-
Returns the first matching result, which may not be expected
|
|
2368
|
-
if multiple accounts match the search parameter.
|
|
2404
|
+
AmbiguousAccountError: If multiple accounts match and no exact
|
|
2405
|
+
match singles one out (and ``fuzzy`` is False)
|
|
2369
2406
|
"""
|
|
2370
2407
|
res = self.search_accounts(search_param)
|
|
2371
2408
|
logging.debug("search_accounts result: %s", res)
|
|
2372
2409
|
if not res:
|
|
2373
2410
|
raise NotFoundError(f"No account found for search: {search_param}")
|
|
2374
|
-
|
|
2411
|
+
|
|
2412
|
+
def _candidates(accounts):
|
|
2413
|
+
return [{k: a.get(k) for k in ("id", "name", "accountNumber")} for a in accounts]
|
|
2414
|
+
|
|
2415
|
+
search_str = str(search_param)
|
|
2416
|
+
id_exact = [a for a in res if str(a.get("id")) == search_str]
|
|
2417
|
+
number_exact = [a for a in res if a.get("accountNumber") == search_str]
|
|
2418
|
+
exact = id_exact + [a for a in number_exact if a not in id_exact]
|
|
2419
|
+
if len(exact) == 1:
|
|
2420
|
+
return exact[0]["id"]
|
|
2421
|
+
if len(exact) > 1:
|
|
2422
|
+
raise AmbiguousAccountError(
|
|
2423
|
+
f"Search '{search_param}' exactly matches both an internal id and "
|
|
2424
|
+
f"other account numbers; pass internal_account_id= or a full "
|
|
2425
|
+
f"account number. Candidates: {_candidates(exact)}"
|
|
2426
|
+
)
|
|
2427
|
+
|
|
2428
|
+
if len(res) == 1:
|
|
2429
|
+
return res[0]["id"]
|
|
2430
|
+
if fuzzy:
|
|
2431
|
+
return res[0]["id"]
|
|
2432
|
+
raise AmbiguousAccountError(
|
|
2433
|
+
f"Search '{search_param}' matched {len(res)} accounts with no exact "
|
|
2434
|
+
f"id/accountNumber match. Pass an exact identifier, use "
|
|
2435
|
+
f"search_accounts_number_and_name(), or pass fuzzy=True to accept "
|
|
2436
|
+
f"the first match. Candidates: {_candidates(res)}"
|
|
2437
|
+
)
|
|
2375
2438
|
|
|
2376
2439
|
def search_accounts(self, search_param):
|
|
2377
2440
|
"""Search for accounts by various criteria.
|
|
@@ -2467,7 +2530,7 @@ class EclipseV1(EclipseBase):
|
|
|
2467
2530
|
res = self.api_request(f"{self.base_url}/admin/authorization/user")
|
|
2468
2531
|
return res.json()["userLoginId"]
|
|
2469
2532
|
|
|
2470
|
-
def get_set_asides(self, account_id, active_only=False):
|
|
2533
|
+
def get_set_asides(self, account_id=None, active_only=False, *, internal_account_id=None):
|
|
2471
2534
|
"""Get set-aside cash settings for a specific account (v1 surface).
|
|
2472
2535
|
|
|
2473
2536
|
Returns the raw v1 records from ``GET /account/accounts/{id}/asidecash``.
|
|
@@ -2479,11 +2542,25 @@ class EclipseV1(EclipseBase):
|
|
|
2479
2542
|
account_id: Account ID, number, or name (resolved via search).
|
|
2480
2543
|
active_only: When True, keep only records where ``isActive`` is truthy.
|
|
2481
2544
|
Defaults to False (includes expired/inactive).
|
|
2545
|
+
internal_account_id: Eclipse internal account id, used verbatim with
|
|
2546
|
+
no search resolution — the deterministic path for callers that
|
|
2547
|
+
already hold the id. Mutually exclusive with ``account_id``.
|
|
2482
2548
|
|
|
2483
2549
|
Returns:
|
|
2484
2550
|
list: Raw v1 set-aside records for the account.
|
|
2485
|
-
|
|
2486
|
-
|
|
2551
|
+
|
|
2552
|
+
Raises:
|
|
2553
|
+
ValueError: If both or neither of ``account_id`` /
|
|
2554
|
+
``internal_account_id`` are given.
|
|
2555
|
+
"""
|
|
2556
|
+
if account_id is not None and internal_account_id is not None:
|
|
2557
|
+
raise ValueError("pass account_id (search) or internal_account_id, not both")
|
|
2558
|
+
if internal_account_id is not None:
|
|
2559
|
+
internal_id = int(internal_account_id)
|
|
2560
|
+
elif account_id is not None:
|
|
2561
|
+
internal_id = self.get_internal_account_id(account_id)
|
|
2562
|
+
else:
|
|
2563
|
+
raise ValueError("account_id or internal_account_id is required")
|
|
2487
2564
|
res = self.api_request(f"{self.base_url}/account/accounts/{internal_id}/asidecash")
|
|
2488
2565
|
records = res.json()
|
|
2489
2566
|
if active_only:
|
|
@@ -2492,8 +2569,8 @@ class EclipseV1(EclipseBase):
|
|
|
2492
2569
|
|
|
2493
2570
|
def create_set_aside(
|
|
2494
2571
|
self,
|
|
2495
|
-
account_number,
|
|
2496
|
-
amount,
|
|
2572
|
+
account_number=None,
|
|
2573
|
+
amount=None,
|
|
2497
2574
|
min_amount=0.0,
|
|
2498
2575
|
max_amount=0.0,
|
|
2499
2576
|
description=None,
|
|
@@ -2506,11 +2583,14 @@ class EclipseV1(EclipseBase):
|
|
|
2506
2583
|
deplete_over_time=False,
|
|
2507
2584
|
percent_calc_type=0,
|
|
2508
2585
|
sync=True,
|
|
2586
|
+
*,
|
|
2587
|
+
internal_account_id=None,
|
|
2509
2588
|
):
|
|
2510
2589
|
"""Create a set-aside cash reservation for an account.
|
|
2511
2590
|
|
|
2512
2591
|
Args:
|
|
2513
|
-
account_number: Full custodial account number
|
|
2592
|
+
account_number: Full custodial account number (resolved via search).
|
|
2593
|
+
Mutually exclusive with ``internal_account_id``.
|
|
2514
2594
|
amount: Cash amount to set aside
|
|
2515
2595
|
min_amount: Minimum cash amount (default 0.0)
|
|
2516
2596
|
max_amount: Maximum cash amount (default 0.0)
|
|
@@ -2527,11 +2607,27 @@ class EclipseV1(EclipseBase):
|
|
|
2527
2607
|
percent_calc_type: 0='Use Default/Managed Value', 1='Use Total Value',
|
|
2528
2608
|
2='Use Excluded Value' (default 0)
|
|
2529
2609
|
sync: Wait for analytics to complete (default True)
|
|
2610
|
+
internal_account_id: Eclipse internal account id, used verbatim with
|
|
2611
|
+
no search resolution — the deterministic path for callers that
|
|
2612
|
+
already hold the id. Mutually exclusive with ``account_number``.
|
|
2530
2613
|
|
|
2531
2614
|
Returns:
|
|
2532
2615
|
dict: Created set-aside details
|
|
2533
|
-
|
|
2534
|
-
|
|
2616
|
+
|
|
2617
|
+
Raises:
|
|
2618
|
+
ValueError: If both or neither of ``account_number`` /
|
|
2619
|
+
``internal_account_id`` are given, or ``amount`` is missing.
|
|
2620
|
+
"""
|
|
2621
|
+
if account_number is not None and internal_account_id is not None:
|
|
2622
|
+
raise ValueError("pass account_number (search) or internal_account_id, not both")
|
|
2623
|
+
if amount is None:
|
|
2624
|
+
raise ValueError("amount is required")
|
|
2625
|
+
if internal_account_id is not None:
|
|
2626
|
+
account_id = int(internal_account_id)
|
|
2627
|
+
elif account_number is not None:
|
|
2628
|
+
account_id = self.get_internal_account_id(account_number)
|
|
2629
|
+
else:
|
|
2630
|
+
raise ValueError("account_number or internal_account_id is required")
|
|
2535
2631
|
|
|
2536
2632
|
cash_type_map = {
|
|
2537
2633
|
# end point account/accounts/asideCashAmountType
|
|
@@ -3033,6 +3129,13 @@ class EclipseV1(EclipseBase):
|
|
|
3033
3129
|
:meth:`EclipseV2.create_trades` handles buys *and* sells and honours
|
|
3034
3130
|
share count and dollar amount exactly.
|
|
3035
3131
|
|
|
3132
|
+
Orion support's answer to the sell-500 case (2026-08-03) was to use
|
|
3133
|
+
the **v2** wrapper body (``application`` / ``trades[]`` / ``action`` /
|
|
3134
|
+
``tradeShares``). That body is not accepted here -- posting it to this
|
|
3135
|
+
v1 endpoint returns ``422 "Invalid trade order type id"`` for buys and
|
|
3136
|
+
sells alike -- so it is a redirect to the v2 surface, not a fix for
|
|
3137
|
+
this one. The v1 sell 500 remains unexplained upstream.
|
|
3138
|
+
|
|
3036
3139
|
The generic per-trade creator behind Eclipse's Quick Trade / journal flows,
|
|
3037
3140
|
as opposed to the ``/tradetool/`` generators (:meth:`cash_needs_trade`,
|
|
3038
3141
|
:meth:`rebalance_trade`, ...) which operate on whole portfolios.
|
|
@@ -6625,7 +6728,7 @@ class EclipseV2(EclipseBase):
|
|
|
6625
6728
|
"end_date": end_date,
|
|
6626
6729
|
}
|
|
6627
6730
|
|
|
6628
|
-
def get_set_asides(self, account_id=None, active_only=False):
|
|
6731
|
+
def get_set_asides(self, account_id=None, active_only=False, *, internal_account_id=None):
|
|
6629
6732
|
"""Get set-aside cash reservations, including the Eclipse set-aside id.
|
|
6630
6733
|
|
|
6631
6734
|
Uses the v2 batch endpoint, which accepts a list of internal account IDs
|
|
@@ -6637,15 +6740,27 @@ class EclipseV2(EclipseBase):
|
|
|
6637
6740
|
|
|
6638
6741
|
Args:
|
|
6639
6742
|
account_id: Account ID, number, or name to restrict to a single
|
|
6640
|
-
account
|
|
6641
|
-
|
|
6743
|
+
account (resolved via search). When neither this nor
|
|
6744
|
+
``internal_account_id`` is given, returns set-asides firm-wide
|
|
6745
|
+
by issuing one batch POST over all accounts.
|
|
6642
6746
|
active_only: When True, return only currently-active set-asides.
|
|
6643
6747
|
Defaults to False (includes expired/inactive).
|
|
6748
|
+
internal_account_id: Eclipse internal account id, used verbatim with
|
|
6749
|
+
no search resolution — the deterministic path for callers that
|
|
6750
|
+
already hold the id. Mutually exclusive with ``account_id``.
|
|
6644
6751
|
|
|
6645
6752
|
Returns:
|
|
6646
6753
|
list: Augmented set-aside records
|
|
6647
|
-
|
|
6648
|
-
|
|
6754
|
+
|
|
6755
|
+
Raises:
|
|
6756
|
+
ValueError: If both ``account_id`` and ``internal_account_id`` are
|
|
6757
|
+
given.
|
|
6758
|
+
"""
|
|
6759
|
+
if account_id is not None and internal_account_id is not None:
|
|
6760
|
+
raise ValueError("pass account_id (search) or internal_account_id, not both")
|
|
6761
|
+
if internal_account_id is not None:
|
|
6762
|
+
account_ids = [int(internal_account_id)]
|
|
6763
|
+
elif account_id is not None:
|
|
6649
6764
|
account_ids = [self.get_internal_account_id(account_id)]
|
|
6650
6765
|
else:
|
|
6651
6766
|
account_ids = [a["id"] for a in self.get_all_accounts()]
|
|
@@ -7328,7 +7443,7 @@ class EclipseV2(EclipseBase):
|
|
|
7328
7443
|
def validate_trades(
|
|
7329
7444
|
self,
|
|
7330
7445
|
trades,
|
|
7331
|
-
application=
|
|
7446
|
+
application=TRADING_APPLICATION_QUICK_TRADES,
|
|
7332
7447
|
trade_tool_selection=2,
|
|
7333
7448
|
trade_instance_type=5,
|
|
7334
7449
|
trade_instance_sub_type=11,
|
|
@@ -7355,10 +7470,13 @@ class EclipseV2(EclipseBase):
|
|
|
7355
7470
|
Size each with ``shares`` (share count) or ``amount`` (dollars) --
|
|
7356
7471
|
see :meth:`build_trade` for the full parameter table.
|
|
7357
7472
|
application: ``TradingApplication`` -- see :data:`TRADING_APPLICATIONS`.
|
|
7358
|
-
Defaults to 11 (
|
|
7359
|
-
:meth:`create_trades`.
|
|
7360
|
-
|
|
7361
|
-
|
|
7473
|
+
Defaults to 11 (``QuickTrades``), the only value that creates orders
|
|
7474
|
+
on :meth:`create_trades`. The enum name is also accepted as a string
|
|
7475
|
+
(``"QuickTrades"``) and behaves identically. This endpoint tolerates
|
|
7476
|
+
other values, but they are not equivalent: ``application=1``
|
|
7477
|
+
(CashNeeds) returns ``isValid: False`` with ``tradeAmount: 0`` rather
|
|
7478
|
+
than pricing the trade, so validating under one application and
|
|
7479
|
+
creating under another gives a meaningless pre-flight.
|
|
7362
7480
|
trade_tool_selection: 1=Portfolio, 2=Account, 3=Model, ... (default 2)
|
|
7363
7481
|
trade_instance_type: See :data:`TRADE_INSTANCE_TYPES` (default 5, Quick Trade)
|
|
7364
7482
|
trade_instance_sub_type: See :data:`TRADE_INSTANCE_SUBTYPES`
|
|
@@ -7389,7 +7507,7 @@ class EclipseV2(EclipseBase):
|
|
|
7389
7507
|
def create_trades(
|
|
7390
7508
|
self,
|
|
7391
7509
|
trades,
|
|
7392
|
-
application=
|
|
7510
|
+
application=TRADING_APPLICATION_QUICK_TRADES,
|
|
7393
7511
|
trade_tool_selection=2,
|
|
7394
7512
|
trade_instance_type=5,
|
|
7395
7513
|
trade_instance_sub_type=11,
|
|
@@ -7427,7 +7545,8 @@ class EclipseV2(EclipseBase):
|
|
|
7427
7545
|
Size each with ``shares`` (share count) or ``amount`` (dollars);
|
|
7428
7546
|
see :meth:`build_trade` for the full parameter table.
|
|
7429
7547
|
application: ``TradingApplication`` -- see :data:`TRADING_APPLICATIONS`.
|
|
7430
|
-
**Only 11 (
|
|
7548
|
+
**Only 11 (``QuickTrades``) creates orders**, and it is the default.
|
|
7549
|
+
The enum name is also accepted as a string (``"QuickTrades"``).
|
|
7431
7550
|
Every other value except 14 returns 400 "Trade Application <Name>
|
|
7432
7551
|
is not supported."; 14 returns 200 with an empty ``tradeId`` list
|
|
7433
7552
|
and creates nothing, so it is rejected here rather than allowed to
|
|
@@ -7455,11 +7574,11 @@ class EclipseV2(EclipseBase):
|
|
|
7455
7574
|
>>> e.create_trades([t])["tradeId"]
|
|
7456
7575
|
[72495]
|
|
7457
7576
|
"""
|
|
7458
|
-
if application !=
|
|
7577
|
+
if application != TRADING_APPLICATION_QUICK_TRADES:
|
|
7459
7578
|
name = TRADING_APPLICATIONS.get(application, "unknown")
|
|
7460
7579
|
raise ValueError(
|
|
7461
7580
|
f"application={application} ({name}) does not create trade orders on "
|
|
7462
|
-
f"this endpoint; only {
|
|
7581
|
+
f"this endpoint; only {TRADING_APPLICATION_QUICK_TRADES} (QuickTrades) does. "
|
|
7463
7582
|
"Other values return 400, and 14 returns 200 while creating nothing."
|
|
7464
7583
|
)
|
|
7465
7584
|
for i, t in enumerate(trades or []):
|
|
@@ -11548,13 +11667,19 @@ class Eclipse(EclipseBase):
|
|
|
11548
11667
|
|
|
11549
11668
|
# --- v2-preferred overrides (documented best-of-both choices) ---------------
|
|
11550
11669
|
|
|
11551
|
-
def get_set_asides(self, account_id=None, active_only=False):
|
|
11670
|
+
def get_set_asides(self, account_id=None, active_only=False, *, internal_account_id=None):
|
|
11552
11671
|
"""Set-asides via the v2 batch endpoint (best: firm-wide + set-aside id).
|
|
11553
11672
|
|
|
11554
11673
|
Delegates to :meth:`EclipseV2.get_set_asides`. Use ``self.v1.get_set_asides``
|
|
11555
|
-
for the raw per-account v1 form.
|
|
11674
|
+
for the raw per-account v1 form. Pass ``internal_account_id=`` when you
|
|
11675
|
+
already hold the Eclipse internal id — it is used verbatim, with no
|
|
11676
|
+
search resolution.
|
|
11556
11677
|
"""
|
|
11557
|
-
return self.v2.get_set_asides(
|
|
11678
|
+
return self.v2.get_set_asides(
|
|
11679
|
+
account_id=account_id,
|
|
11680
|
+
active_only=active_only,
|
|
11681
|
+
internal_account_id=internal_account_id,
|
|
11682
|
+
)
|
|
11558
11683
|
|
|
11559
11684
|
|
|
11560
11685
|
class EclipseAPI(Eclipse):
|
|
File without changes
|