orionapi 2.0.0__tar.gz → 2.1.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.1.0"
|
|
2
2
|
|
|
3
3
|
import logging
|
|
4
4
|
import re
|
|
@@ -247,12 +247,15 @@ class BaseAPI:
|
|
|
247
247
|
|
|
248
248
|
return data
|
|
249
249
|
|
|
250
|
-
def api_request(self, url, req_func=
|
|
250
|
+
def api_request(self, url, req_func=None, timeout=30, **kwargs):
|
|
251
251
|
"""Make an authenticated API request with error handling.
|
|
252
252
|
|
|
253
253
|
Args:
|
|
254
254
|
url: The API endpoint URL
|
|
255
|
-
req_func: The requests function to use (get, post, put, delete)
|
|
255
|
+
req_func: The requests function to use (get, post, put, delete).
|
|
256
|
+
Defaults to ``requests.get``. Resolved at call time (not bound as a
|
|
257
|
+
default argument) so that patching ``requests.get`` in tests takes
|
|
258
|
+
effect for GET methods.
|
|
256
259
|
timeout: Request timeout in seconds (default 30)
|
|
257
260
|
**kwargs: Additional arguments passed to the request
|
|
258
261
|
|
|
@@ -264,6 +267,9 @@ class BaseAPI:
|
|
|
264
267
|
NotFoundError: On 404 responses
|
|
265
268
|
OrionAPIError: On other 4xx/5xx responses
|
|
266
269
|
"""
|
|
270
|
+
if req_func is None:
|
|
271
|
+
req_func = requests.get
|
|
272
|
+
|
|
267
273
|
# Apply rate limiting
|
|
268
274
|
self._rate_limiter.wait()
|
|
269
275
|
|
|
@@ -2479,6 +2485,54 @@ class EclipseV1(EclipseBase):
|
|
|
2479
2485
|
res = self.api_request(f"{self.base_url}/account/accounts/{internal_id}")
|
|
2480
2486
|
return res.json()
|
|
2481
2487
|
|
|
2488
|
+
def get_account_simple(self, account_id):
|
|
2489
|
+
"""Get a lightweight account record by internal ID.
|
|
2490
|
+
|
|
2491
|
+
Hits ``/account/accounts/simple/{id}`` — a lighter payload than the full
|
|
2492
|
+
record returned by :meth:`get_account_details`.
|
|
2493
|
+
|
|
2494
|
+
Args:
|
|
2495
|
+
account_id: Internal Eclipse account ID
|
|
2496
|
+
|
|
2497
|
+
Returns:
|
|
2498
|
+
dict: Lightweight account record
|
|
2499
|
+
"""
|
|
2500
|
+
res = self.api_request(f"{self.base_url}/account/accounts/simple/{account_id}")
|
|
2501
|
+
return res.json()
|
|
2502
|
+
|
|
2503
|
+
def get_account_holdings_detail(self, account_id):
|
|
2504
|
+
"""Get holdings for an account via the account-path holdings endpoint.
|
|
2505
|
+
|
|
2506
|
+
Hits ``/account/accounts/{id}/holdings`` — a different path and shape than
|
|
2507
|
+
:meth:`get_account_holdings` (which uses ``/holding/holdings/simple``).
|
|
2508
|
+
|
|
2509
|
+
Args:
|
|
2510
|
+
account_id: Internal Eclipse account ID
|
|
2511
|
+
|
|
2512
|
+
Returns:
|
|
2513
|
+
list: Holding dicts as returned by the account-path endpoint
|
|
2514
|
+
"""
|
|
2515
|
+
res = self.api_request(f"{self.base_url}/account/accounts/{account_id}/holdings")
|
|
2516
|
+
return res.json()
|
|
2517
|
+
|
|
2518
|
+
def get_out_of_tolerance_accounts(self, model_id, asset_id, asset_type="class"):
|
|
2519
|
+
"""Get accounts out of tolerance for a model asset.
|
|
2520
|
+
|
|
2521
|
+
Args:
|
|
2522
|
+
model_id: Model ID (note: the API uses this value in the account path
|
|
2523
|
+
segment — kept as-is per the documented endpoint quirk)
|
|
2524
|
+
asset_id: Asset ID
|
|
2525
|
+
asset_type: Asset type (default "class")
|
|
2526
|
+
|
|
2527
|
+
Returns:
|
|
2528
|
+
list: Out-of-tolerance account dicts
|
|
2529
|
+
"""
|
|
2530
|
+
res = self.api_request(
|
|
2531
|
+
f"{self.base_url}/account/accounts/{model_id}/outOfTolerance/{asset_id}",
|
|
2532
|
+
params={"assetType": asset_type},
|
|
2533
|
+
)
|
|
2534
|
+
return res.json()
|
|
2535
|
+
|
|
2482
2536
|
def get_all_account_details(self):
|
|
2483
2537
|
"""Get detailed information for all accounts.
|
|
2484
2538
|
|
|
@@ -2508,12 +2562,20 @@ class EclipseV1(EclipseBase):
|
|
|
2508
2562
|
res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}")
|
|
2509
2563
|
return res.json()
|
|
2510
2564
|
|
|
2511
|
-
def get_portfolio_accounts(self, portfolio_id):
|
|
2565
|
+
def get_portfolio_accounts(self, portfolio_id, simple=False):
|
|
2512
2566
|
"""Get list of accounts for a portfolio.
|
|
2513
2567
|
|
|
2568
|
+
Args:
|
|
2569
|
+
portfolio_id: Portfolio ID
|
|
2570
|
+
simple: When True, hit the lightweight ``/accounts/simple`` path
|
|
2571
|
+
(default False returns the full account records)
|
|
2572
|
+
|
|
2514
2573
|
Returns list of accounts with cash targets, sleeve settings, and values.
|
|
2515
2574
|
"""
|
|
2516
|
-
|
|
2575
|
+
path = f"/portfolio/portfolios/{portfolio_id}/accounts"
|
|
2576
|
+
if simple:
|
|
2577
|
+
path += "/simple"
|
|
2578
|
+
res = self.api_request(f"{self.base_url}{path}")
|
|
2517
2579
|
return res.json()
|
|
2518
2580
|
|
|
2519
2581
|
def get_model_tolerance(self, portfolio_id, account_id, account_type="Normal"):
|
|
@@ -2532,18 +2594,21 @@ class EclipseV1(EclipseBase):
|
|
|
2532
2594
|
)
|
|
2533
2595
|
return res.json()
|
|
2534
2596
|
|
|
2535
|
-
def get_all_portfolios(self, include_value=True, search=None):
|
|
2597
|
+
def get_all_portfolios(self, include_value=True, search=None, top=None):
|
|
2536
2598
|
"""Get list of all portfolios.
|
|
2537
2599
|
|
|
2538
2600
|
Args:
|
|
2539
2601
|
include_value: Include holding values (default True)
|
|
2540
2602
|
search: Optional search string
|
|
2603
|
+
top: Optional max number of results (maps to ``$top``)
|
|
2541
2604
|
|
|
2542
2605
|
Returns list of portfolios with basic info and optionally values.
|
|
2543
2606
|
"""
|
|
2544
2607
|
params = {"includevalue": str(include_value).lower()}
|
|
2545
2608
|
if search:
|
|
2546
2609
|
params["search"] = search
|
|
2610
|
+
if top is not None:
|
|
2611
|
+
params["$top"] = top
|
|
2547
2612
|
res = self.api_request(f"{self.base_url}/portfolio/portfolios/simple", params=params)
|
|
2548
2613
|
return res.json()
|
|
2549
2614
|
|
|
@@ -2577,6 +2642,33 @@ class EclipseV1(EclipseBase):
|
|
|
2577
2642
|
res = self.api_request(f"{self.base_url}/holding/holdings/simple", params=params)
|
|
2578
2643
|
return res.json()
|
|
2579
2644
|
|
|
2645
|
+
def get_portfolio_holdings_detail(self, portfolio_id):
|
|
2646
|
+
"""Get holdings for a portfolio via the portfolio-path holdings endpoint.
|
|
2647
|
+
|
|
2648
|
+
Hits ``/portfolio/portfolios/{id}/holdings`` — a different path and shape
|
|
2649
|
+
than :meth:`get_portfolio_holdings` (which uses ``/holding/holdings/simple``).
|
|
2650
|
+
|
|
2651
|
+
Args:
|
|
2652
|
+
portfolio_id: Portfolio ID
|
|
2653
|
+
|
|
2654
|
+
Returns:
|
|
2655
|
+
list: Holding dicts as returned by the portfolio-path endpoint
|
|
2656
|
+
"""
|
|
2657
|
+
res = self.api_request(f"{self.base_url}/portfolio/portfolios/{portfolio_id}/holdings")
|
|
2658
|
+
return res.json()
|
|
2659
|
+
|
|
2660
|
+
def get_taxlots(self, holding_id):
|
|
2661
|
+
"""Get tax lots for a holding.
|
|
2662
|
+
|
|
2663
|
+
Args:
|
|
2664
|
+
holding_id: Holding ID
|
|
2665
|
+
|
|
2666
|
+
Returns:
|
|
2667
|
+
list: Tax-lot dicts (acquisition date, cost basis, quantity, etc.)
|
|
2668
|
+
"""
|
|
2669
|
+
res = self.api_request(f"{self.base_url}/holding/holdings/{holding_id}/taxlots")
|
|
2670
|
+
return res.json()
|
|
2671
|
+
|
|
2580
2672
|
def run_analytics(self):
|
|
2581
2673
|
"""Run analytics for portfolios that need it.
|
|
2582
2674
|
|
|
@@ -2644,6 +2736,32 @@ class EclipseV1(EclipseBase):
|
|
|
2644
2736
|
"""
|
|
2645
2737
|
return self.api_request(f"{self.base_url}/tradeorder/trades?isPending=true").json()
|
|
2646
2738
|
|
|
2739
|
+
def get_trades(self, portfolio_id=None, top=None, is_pending=None):
|
|
2740
|
+
"""Get trade orders with optional portfolio / paging / pending filters.
|
|
2741
|
+
|
|
2742
|
+
Hits the same ``/tradeorder/trades`` endpoint as :meth:`get_orders` /
|
|
2743
|
+
:meth:`get_orders_pending`, but lets the caller filter by portfolio and cap
|
|
2744
|
+
the result count.
|
|
2745
|
+
|
|
2746
|
+
Args:
|
|
2747
|
+
portfolio_id: Optional portfolio ID filter (maps to ``portfolioId``)
|
|
2748
|
+
top: Optional max number of results (maps to ``$top``)
|
|
2749
|
+
is_pending: Optional bool; when set, filter by pending status
|
|
2750
|
+
(maps to ``isPending``)
|
|
2751
|
+
|
|
2752
|
+
Returns:
|
|
2753
|
+
list: Trade order dicts
|
|
2754
|
+
"""
|
|
2755
|
+
params = {}
|
|
2756
|
+
if portfolio_id is not None:
|
|
2757
|
+
params["portfolioId"] = portfolio_id
|
|
2758
|
+
if top is not None:
|
|
2759
|
+
params["$top"] = top
|
|
2760
|
+
if is_pending is not None:
|
|
2761
|
+
params["isPending"] = str(is_pending).lower()
|
|
2762
|
+
res = self.api_request(f"{self.base_url}/tradeorder/trades", params=params)
|
|
2763
|
+
return res.json()
|
|
2764
|
+
|
|
2647
2765
|
def cash_needs_trade(
|
|
2648
2766
|
self,
|
|
2649
2767
|
portfolio_ids,
|
|
@@ -2692,6 +2810,9 @@ class EclipseV1(EclipseBase):
|
|
|
2692
2810
|
reason="",
|
|
2693
2811
|
is_excel_import=False,
|
|
2694
2812
|
sync=True,
|
|
2813
|
+
selected_method_id=None,
|
|
2814
|
+
spend_full_amount=None,
|
|
2815
|
+
filter_type=None,
|
|
2695
2816
|
):
|
|
2696
2817
|
"""Generate Spend Cash trade for portfolios.
|
|
2697
2818
|
|
|
@@ -2703,6 +2824,12 @@ class EclipseV1(EclipseBase):
|
|
|
2703
2824
|
is_excel_import: Whether this is from an Excel import (default False)
|
|
2704
2825
|
sync: Wait for analytics to complete (default True)
|
|
2705
2826
|
|
|
2827
|
+
Args (additional):
|
|
2828
|
+
selected_method_id: Optional spend-cash calculation method id
|
|
2829
|
+
(see ``get_spend_cash_methods``); added to the body only when provided
|
|
2830
|
+
spend_full_amount: Optional bool; added to the body only when provided
|
|
2831
|
+
filter_type: Optional filter type; added to the body only when provided
|
|
2832
|
+
|
|
2706
2833
|
Returns:
|
|
2707
2834
|
dict with 'issues', 'success', and 'instanceId' fields
|
|
2708
2835
|
"""
|
|
@@ -2716,6 +2843,12 @@ class EclipseV1(EclipseBase):
|
|
|
2716
2843
|
"reason": reason,
|
|
2717
2844
|
"isExcelImport": is_excel_import,
|
|
2718
2845
|
}
|
|
2846
|
+
if selected_method_id is not None:
|
|
2847
|
+
payload["selectedMethodId"] = selected_method_id
|
|
2848
|
+
if spend_full_amount is not None:
|
|
2849
|
+
payload["spendFullAmount"] = spend_full_amount
|
|
2850
|
+
if filter_type is not None:
|
|
2851
|
+
payload["filterType"] = filter_type
|
|
2719
2852
|
|
|
2720
2853
|
res = self.api_request(
|
|
2721
2854
|
f"{self.base_url}/tradetool/spendcash/action/generatetrade", requests.post, json=payload
|
|
@@ -2724,6 +2857,148 @@ class EclipseV1(EclipseBase):
|
|
|
2724
2857
|
self._maybe_wait_for_analytics(sync)
|
|
2725
2858
|
return result
|
|
2726
2859
|
|
|
2860
|
+
def get_raise_cash_methods(self):
|
|
2861
|
+
"""Get the available raise-cash calculation methods.
|
|
2862
|
+
|
|
2863
|
+
Returns:
|
|
2864
|
+
list: Calculation-method dicts (id, name, etc.)
|
|
2865
|
+
"""
|
|
2866
|
+
res = self.api_request(f"{self.base_url}/tradetool/raisecash/calculation_methods")
|
|
2867
|
+
return res.json()
|
|
2868
|
+
|
|
2869
|
+
def get_spend_cash_methods(self):
|
|
2870
|
+
"""Get the available spend-cash calculation methods.
|
|
2871
|
+
|
|
2872
|
+
Returns:
|
|
2873
|
+
list: Calculation-method dicts (id, name, etc.)
|
|
2874
|
+
"""
|
|
2875
|
+
res = self.api_request(f"{self.base_url}/tradetool/spendcash/calculation_methods")
|
|
2876
|
+
return res.json()
|
|
2877
|
+
|
|
2878
|
+
@staticmethod
|
|
2879
|
+
def _tlh_id_body(portfolio_ids, account_ids):
|
|
2880
|
+
"""Build a tax-loss-harvesting request body, omitting empty id lists."""
|
|
2881
|
+
body = {}
|
|
2882
|
+
if portfolio_ids is not None:
|
|
2883
|
+
body["portfolioIds"] = portfolio_ids
|
|
2884
|
+
if account_ids is not None:
|
|
2885
|
+
body["accountIds"] = account_ids
|
|
2886
|
+
return body
|
|
2887
|
+
|
|
2888
|
+
def get_tlh_securities(self, portfolio_ids=None, account_ids=None):
|
|
2889
|
+
"""Get tax-loss-harvesting candidate securities (preview only).
|
|
2890
|
+
|
|
2891
|
+
Args:
|
|
2892
|
+
portfolio_ids: Optional list of portfolio IDs
|
|
2893
|
+
account_ids: Optional list of account IDs
|
|
2894
|
+
|
|
2895
|
+
Returns:
|
|
2896
|
+
list: Candidate security dicts
|
|
2897
|
+
"""
|
|
2898
|
+
res = self.api_request(
|
|
2899
|
+
f"{self.base_url}/tradetool/taxLossHarvesting/securities",
|
|
2900
|
+
requests.post,
|
|
2901
|
+
json=self._tlh_id_body(portfolio_ids, account_ids),
|
|
2902
|
+
)
|
|
2903
|
+
return res.json()
|
|
2904
|
+
|
|
2905
|
+
def check_tlh_gain_loss(self, portfolio_ids=None, account_ids=None):
|
|
2906
|
+
"""Check projected gain/loss for tax-loss harvesting (preview only).
|
|
2907
|
+
|
|
2908
|
+
Args:
|
|
2909
|
+
portfolio_ids: Optional list of portfolio IDs
|
|
2910
|
+
account_ids: Optional list of account IDs
|
|
2911
|
+
|
|
2912
|
+
Returns:
|
|
2913
|
+
dict: Projected gain/loss summary
|
|
2914
|
+
"""
|
|
2915
|
+
res = self.api_request(
|
|
2916
|
+
f"{self.base_url}/tradetool/taxLossHarvesting/action/checkGainLoss",
|
|
2917
|
+
requests.post,
|
|
2918
|
+
json=self._tlh_id_body(portfolio_ids, account_ids),
|
|
2919
|
+
)
|
|
2920
|
+
return res.json()
|
|
2921
|
+
|
|
2922
|
+
def tlh_trade(self, portfolio_ids=None, account_ids=None, is_view_only=True, sync=False):
|
|
2923
|
+
"""Generate a tax-loss-harvesting trade (preview/staging by default).
|
|
2924
|
+
|
|
2925
|
+
Args:
|
|
2926
|
+
portfolio_ids: Optional list of portfolio IDs
|
|
2927
|
+
account_ids: Optional list of account IDs
|
|
2928
|
+
is_view_only: If True (default), preview trades without executing
|
|
2929
|
+
sync: Wait for analytics to complete (default False)
|
|
2930
|
+
|
|
2931
|
+
Returns:
|
|
2932
|
+
dict: Generated trade preview (instanceId, trades, etc.)
|
|
2933
|
+
"""
|
|
2934
|
+
payload = self._tlh_id_body(portfolio_ids, account_ids)
|
|
2935
|
+
payload["isViewOnly"] = is_view_only
|
|
2936
|
+
res = self.api_request(
|
|
2937
|
+
f"{self.base_url}/tradetool/taxLossHarvesting/action/generateTrade",
|
|
2938
|
+
requests.post,
|
|
2939
|
+
json=payload,
|
|
2940
|
+
)
|
|
2941
|
+
result = res.json()
|
|
2942
|
+
self._maybe_wait_for_analytics(sync)
|
|
2943
|
+
return result
|
|
2944
|
+
|
|
2945
|
+
def rebalance_trade(
|
|
2946
|
+
self,
|
|
2947
|
+
portfolio_ids=None,
|
|
2948
|
+
account_ids=None,
|
|
2949
|
+
filter_type=None,
|
|
2950
|
+
is_view_only=True,
|
|
2951
|
+
max_gain_amount=0,
|
|
2952
|
+
minimum_trade_amount=0,
|
|
2953
|
+
minimum_trade_amount_type="$",
|
|
2954
|
+
allow_wash_sale=False,
|
|
2955
|
+
rounding=0,
|
|
2956
|
+
sync=False,
|
|
2957
|
+
):
|
|
2958
|
+
"""Generate a rebalance trade (preview/staging by default).
|
|
2959
|
+
|
|
2960
|
+
Args:
|
|
2961
|
+
portfolio_ids: Optional list of portfolio IDs
|
|
2962
|
+
account_ids: Optional list of account IDs
|
|
2963
|
+
filter_type: Optional filter type (maps to ``filterType``)
|
|
2964
|
+
is_view_only: If True (default), preview trades without executing
|
|
2965
|
+
max_gain_amount: Max gain amount (default 0)
|
|
2966
|
+
minimum_trade_amount: Minimum trade amount (default 0)
|
|
2967
|
+
minimum_trade_amount_type: Minimum trade amount type, "$" or "%" (default "$")
|
|
2968
|
+
allow_wash_sale: Whether to allow wash sales (default False)
|
|
2969
|
+
rounding: Rounding setting (default 0)
|
|
2970
|
+
sync: Wait for analytics to complete (default False)
|
|
2971
|
+
|
|
2972
|
+
Returns:
|
|
2973
|
+
dict: Generated trade preview (instanceId, trades, etc.)
|
|
2974
|
+
"""
|
|
2975
|
+
payload = {
|
|
2976
|
+
"filterType": filter_type,
|
|
2977
|
+
"isViewOnly": is_view_only,
|
|
2978
|
+
"isExcelImport": False,
|
|
2979
|
+
"maxGainAmount": max_gain_amount,
|
|
2980
|
+
"minimumTradeAmount": {
|
|
2981
|
+
"amount": minimum_trade_amount,
|
|
2982
|
+
"type": minimum_trade_amount_type,
|
|
2983
|
+
},
|
|
2984
|
+
"allowWashSale": allow_wash_sale,
|
|
2985
|
+
"allowShortTermGain": None,
|
|
2986
|
+
"priorityRanking": [],
|
|
2987
|
+
"rounding": rounding,
|
|
2988
|
+
}
|
|
2989
|
+
if portfolio_ids is not None:
|
|
2990
|
+
payload["portfolioIds"] = portfolio_ids
|
|
2991
|
+
if account_ids is not None:
|
|
2992
|
+
payload["accountIds"] = account_ids
|
|
2993
|
+
res = self.api_request(
|
|
2994
|
+
f"{self.base_url}/tradetool/rebalancer/action/generatetrade",
|
|
2995
|
+
requests.post,
|
|
2996
|
+
json=payload,
|
|
2997
|
+
)
|
|
2998
|
+
result = res.json()
|
|
2999
|
+
self._maybe_wait_for_analytics(sync)
|
|
3000
|
+
return result
|
|
3001
|
+
|
|
2727
3002
|
def get_closed_trades(self):
|
|
2728
3003
|
"""Get all closed/executed trade orders.
|
|
2729
3004
|
|
|
@@ -2732,21 +3007,27 @@ class EclipseV1(EclipseBase):
|
|
|
2732
3007
|
"""
|
|
2733
3008
|
return self.api_request(f"{self.base_url}/tradeorder/closedtrades").json()
|
|
2734
3009
|
|
|
2735
|
-
def get_trade_instances(self, start_date, end_date):
|
|
3010
|
+
def get_trade_instances(self, start_date, end_date, normalize=True):
|
|
2736
3011
|
"""Get trade instances (batches of trades) within a date range.
|
|
2737
3012
|
|
|
2738
3013
|
Args:
|
|
2739
3014
|
start_date: Start date (YYYY-MM-DD format)
|
|
2740
3015
|
end_date: End date (YYYY-MM-DD format)
|
|
3016
|
+
normalize: When True (default), map the numeric tradeInstanceType /
|
|
3017
|
+
tradeInstanceSubType IDs to friendly names. When False, return the
|
|
3018
|
+
raw response unchanged (matching the API output).
|
|
2741
3019
|
|
|
2742
3020
|
Returns:
|
|
2743
3021
|
list: List of trade instance dicts with id, orderCount, executeStatus,
|
|
2744
|
-
tradeInstanceType
|
|
3022
|
+
tradeInstanceType, tradeInstanceSubType, etc.
|
|
2745
3023
|
"""
|
|
2746
3024
|
instances = self.api_request(
|
|
2747
3025
|
f"{self.base_url}/tradeorder/instances?startDate={start_date}&endDate={end_date}"
|
|
2748
3026
|
).json()
|
|
2749
3027
|
|
|
3028
|
+
if not normalize:
|
|
3029
|
+
return instances
|
|
3030
|
+
|
|
2750
3031
|
# Convert type/subtype IDs to friendly names
|
|
2751
3032
|
for inst in instances:
|
|
2752
3033
|
type_id = inst.get("tradeInstanceType")
|
|
@@ -2760,13 +3041,22 @@ class EclipseV1(EclipseBase):
|
|
|
2760
3041
|
|
|
2761
3042
|
# Model Maintenance
|
|
2762
3043
|
|
|
2763
|
-
def get_all_models(self):
|
|
3044
|
+
def get_all_models(self, name=None, top=None):
|
|
2764
3045
|
"""Get all investment models.
|
|
2765
3046
|
|
|
3047
|
+
Args:
|
|
3048
|
+
name: Optional name filter
|
|
3049
|
+
top: Optional max number of results (maps to ``$top``)
|
|
3050
|
+
|
|
2766
3051
|
Returns:
|
|
2767
3052
|
list: List of model dicts with id, name, status, etc.
|
|
2768
3053
|
"""
|
|
2769
|
-
|
|
3054
|
+
params = {}
|
|
3055
|
+
if name is not None:
|
|
3056
|
+
params["name"] = name
|
|
3057
|
+
if top is not None:
|
|
3058
|
+
params["$top"] = top
|
|
3059
|
+
res = self.api_request(f"{self.base_url}/modeling/models", params=params)
|
|
2770
3060
|
return res.json()
|
|
2771
3061
|
|
|
2772
3062
|
def get_model(self, id):
|
|
@@ -2781,20 +3071,116 @@ class EclipseV1(EclipseBase):
|
|
|
2781
3071
|
res = self.api_request(f"{self.base_url}/modeling/models/{id}")
|
|
2782
3072
|
return res.json()
|
|
2783
3073
|
|
|
2784
|
-
def get_model_allocations(self, id):
|
|
2785
|
-
"""Get
|
|
3074
|
+
def get_model_allocations(self, id, aggregate=True):
|
|
3075
|
+
"""Get allocations for a model.
|
|
2786
3076
|
|
|
2787
3077
|
Args:
|
|
2788
3078
|
id: Model ID
|
|
3079
|
+
aggregate: When True (default), aggregate allocations across the model
|
|
3080
|
+
tree (``aggregateAllocations=true``); when False, return per-node
|
|
3081
|
+
allocations.
|
|
2789
3082
|
|
|
2790
3083
|
Returns:
|
|
2791
3084
|
list: List of allocation dicts with target percentages
|
|
2792
3085
|
"""
|
|
2793
3086
|
res = self.api_request(
|
|
2794
|
-
f"{self.base_url}/modeling/models/{id}/allocations
|
|
3087
|
+
f"{self.base_url}/modeling/models/{id}/allocations",
|
|
3088
|
+
params={"aggregateAllocations": str(aggregate).lower()},
|
|
3089
|
+
)
|
|
3090
|
+
return res.json()
|
|
3091
|
+
|
|
3092
|
+
def get_model_nodes(self, model_id):
|
|
3093
|
+
"""Get the node tree for a model.
|
|
3094
|
+
|
|
3095
|
+
Args:
|
|
3096
|
+
model_id: Model ID
|
|
3097
|
+
|
|
3098
|
+
Returns:
|
|
3099
|
+
dict: Node tree with ``levels``, ``modelId`` and ``preSelectedNodeId``
|
|
3100
|
+
"""
|
|
3101
|
+
res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/Model/nodes")
|
|
3102
|
+
return res.json()
|
|
3103
|
+
|
|
3104
|
+
def get_model_portfolios(self, model_id):
|
|
3105
|
+
"""Get portfolios assigned to a model.
|
|
3106
|
+
|
|
3107
|
+
Args:
|
|
3108
|
+
model_id: Model ID
|
|
3109
|
+
|
|
3110
|
+
Returns:
|
|
3111
|
+
list: Portfolio dicts assigned to the model
|
|
3112
|
+
"""
|
|
3113
|
+
res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/portfolios")
|
|
3114
|
+
return res.json()
|
|
3115
|
+
|
|
3116
|
+
def get_model_pending(self, model_id):
|
|
3117
|
+
"""Get pending changes for a model.
|
|
3118
|
+
|
|
3119
|
+
Args:
|
|
3120
|
+
model_id: Model ID
|
|
3121
|
+
|
|
3122
|
+
Returns:
|
|
3123
|
+
dict: Pending-change details
|
|
3124
|
+
"""
|
|
3125
|
+
res = self.api_request(f"{self.base_url}/modeling/models/{model_id}/pending")
|
|
3126
|
+
return res.json()
|
|
3127
|
+
|
|
3128
|
+
def get_model_analysis(self, model_id, asset_type="securityset"):
|
|
3129
|
+
"""Get model analysis for a model.
|
|
3130
|
+
|
|
3131
|
+
Args:
|
|
3132
|
+
model_id: Model ID
|
|
3133
|
+
asset_type: Asset type to analyze (default "securityset")
|
|
3134
|
+
|
|
3135
|
+
Returns:
|
|
3136
|
+
dict: Model analysis results
|
|
3137
|
+
"""
|
|
3138
|
+
res = self.api_request(
|
|
3139
|
+
f"{self.base_url}/modeling/models/{model_id}/modelAnalysis",
|
|
3140
|
+
params={
|
|
3141
|
+
"assetType": asset_type,
|
|
3142
|
+
"isIncludeTradeBlockAccount": 0,
|
|
3143
|
+
"isExcludeAsset": 0,
|
|
3144
|
+
},
|
|
2795
3145
|
)
|
|
2796
3146
|
return res.json()
|
|
2797
3147
|
|
|
3148
|
+
def get_model_status(self):
|
|
3149
|
+
"""Get the list of model statuses.
|
|
3150
|
+
|
|
3151
|
+
Returns:
|
|
3152
|
+
list: Model-status dicts
|
|
3153
|
+
"""
|
|
3154
|
+
res = self.api_request(f"{self.base_url}/modeling/models/modelStatus")
|
|
3155
|
+
return res.json()
|
|
3156
|
+
|
|
3157
|
+
def get_model_types(self):
|
|
3158
|
+
"""Get the list of model types.
|
|
3159
|
+
|
|
3160
|
+
Returns:
|
|
3161
|
+
list: Model-type dicts
|
|
3162
|
+
"""
|
|
3163
|
+
res = self.api_request(f"{self.base_url}/modeling/models/modelTypes")
|
|
3164
|
+
return res.json()
|
|
3165
|
+
|
|
3166
|
+
def get_submodels(self, model_type=None, search=None):
|
|
3167
|
+
"""Get submodels, optionally filtered by type and name.
|
|
3168
|
+
|
|
3169
|
+
Args:
|
|
3170
|
+
model_type: Optional model-type filter (maps to ``modelType``)
|
|
3171
|
+
search: Optional name filter (maps to ``name``)
|
|
3172
|
+
|
|
3173
|
+
Returns:
|
|
3174
|
+
list: Submodel dicts
|
|
3175
|
+
"""
|
|
3176
|
+
params = {}
|
|
3177
|
+
if model_type is not None:
|
|
3178
|
+
params["modelType"] = model_type
|
|
3179
|
+
if search is not None:
|
|
3180
|
+
params["name"] = search
|
|
3181
|
+
res = self.api_request(f"{self.base_url}/modeling/models/submodels", params=params)
|
|
3182
|
+
return res.json()
|
|
3183
|
+
|
|
2798
3184
|
def get_all_security_sets(self):
|
|
2799
3185
|
"""Get all security sets.
|
|
2800
3186
|
|
|
@@ -2816,6 +3202,41 @@ class EclipseV1(EclipseBase):
|
|
|
2816
3202
|
res = self.api_request(f"{self.base_url}/security/securityset/details/{id}")
|
|
2817
3203
|
return res.json()
|
|
2818
3204
|
|
|
3205
|
+
def get_security_set_summary(self, id):
|
|
3206
|
+
"""Get a security set by ID via the summary endpoint.
|
|
3207
|
+
|
|
3208
|
+
Hits ``/security/securityset/{id}`` — a different path than
|
|
3209
|
+
:meth:`get_security_set` (which uses ``/security/securityset/details/{id}``).
|
|
3210
|
+
|
|
3211
|
+
Args:
|
|
3212
|
+
id: Security set ID
|
|
3213
|
+
|
|
3214
|
+
Returns:
|
|
3215
|
+
dict: Security set summary
|
|
3216
|
+
"""
|
|
3217
|
+
res = self.api_request(f"{self.base_url}/security/securityset/{id}")
|
|
3218
|
+
return res.json()
|
|
3219
|
+
|
|
3220
|
+
def get_security_set_details(self):
|
|
3221
|
+
"""Get the full security-set detail list.
|
|
3222
|
+
|
|
3223
|
+
Hits ``/security/securityset/detail`` (documented as a large list across all
|
|
3224
|
+
sets).
|
|
3225
|
+
|
|
3226
|
+
.. note::
|
|
3227
|
+
As of 2026-05 this endpoint returns ``400 'id is not numeric string'`` on
|
|
3228
|
+
live Eclipse — the server routes the ``detail`` segment into the
|
|
3229
|
+
``/security/securityset/{id}`` param route instead. Kept faithful to the
|
|
3230
|
+
documented endpoint (and the Eclipse MCP, which calls the same path), but
|
|
3231
|
+
the upstream route appears broken. Use :meth:`get_all_security_sets` plus
|
|
3232
|
+
:meth:`get_security_set` per id as a working alternative.
|
|
3233
|
+
|
|
3234
|
+
Returns:
|
|
3235
|
+
list: Security-set detail dicts
|
|
3236
|
+
"""
|
|
3237
|
+
res = self.api_request(f"{self.base_url}/security/securityset/detail")
|
|
3238
|
+
return res.json()
|
|
3239
|
+
|
|
2819
3240
|
def add_model(
|
|
2820
3241
|
self,
|
|
2821
3242
|
name,
|