publicdotcom-cli 1.0.0__py3-none-any.whl
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.
- publicdotcom_cli/__init__.py +3 -0
- publicdotcom_cli/__main__.py +5 -0
- publicdotcom_cli/_generated/__init__.py +8 -0
- publicdotcom_cli/_generated/api/__init__.py +1 -0
- publicdotcom_cli/_generated/api/account_details/__init__.py +1 -0
- publicdotcom_cli/_generated/api/account_details/get_account_portfolio_v2.py +182 -0
- publicdotcom_cli/_generated/api/account_details/get_history.py +250 -0
- publicdotcom_cli/_generated/api/authorization/__init__.py +1 -0
- publicdotcom_cli/_generated/api/authorization/create_personal_access_token.py +230 -0
- publicdotcom_cli/_generated/api/instrument_details/__init__.py +1 -0
- publicdotcom_cli/_generated/api/instrument_details/get_all_instruments.py +303 -0
- publicdotcom_cli/_generated/api/instrument_details/get_instrument.py +170 -0
- publicdotcom_cli/_generated/api/list_accounts/__init__.py +1 -0
- publicdotcom_cli/_generated/api/list_accounts/get_accounts.py +172 -0
- publicdotcom_cli/_generated/api/market_data/__init__.py +1 -0
- publicdotcom_cli/_generated/api/market_data/get_option_chain.py +200 -0
- publicdotcom_cli/_generated/api/market_data/get_option_expirations.py +210 -0
- publicdotcom_cli/_generated/api/market_data/get_quotes.py +200 -0
- publicdotcom_cli/_generated/api/option_details/__init__.py +1 -0
- publicdotcom_cli/_generated/api/option_details/get_option_greeks.py +194 -0
- publicdotcom_cli/_generated/api/order_placement/__init__.py +1 -0
- publicdotcom_cli/_generated/api/order_placement/cancel_order.py +123 -0
- publicdotcom_cli/_generated/api/order_placement/get_order.py +206 -0
- publicdotcom_cli/_generated/api/order_placement/place_multileg_order.py +214 -0
- publicdotcom_cli/_generated/api/order_placement/place_order.py +222 -0
- publicdotcom_cli/_generated/api/order_placement/preflight_multi_leg.py +276 -0
- publicdotcom_cli/_generated/api/order_placement/preflight_single_leg.py +220 -0
- publicdotcom_cli/_generated/api/order_placement/replace_order.py +222 -0
- publicdotcom_cli/_generated/client.py +272 -0
- publicdotcom_cli/_generated/errors.py +16 -0
- publicdotcom_cli/_generated/models/__init__.py +417 -0
- publicdotcom_cli/_generated/models/com_hellopublic_holdingsystem_core_types_option_price_increment.py +71 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapiauthservice_api_personal_create_access_token_request.py +84 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapiauthservice_api_personal_create_access_token_response.py +61 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapiauthservice_domain_error_error_body.py +70 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings.py +161 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings_account_type.py +14 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings_brokerage_account_type.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings_options_level.py +12 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings_response.py +85 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_account_account_settings_trade_permissions.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_response_page.py +195 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction.py +263 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction_direction.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction_security_type.py +13 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction_sub_type.py +19 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_history_gateway_history_transaction_type.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_option_chain_request.py +85 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_option_chain_response.py +115 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_option_expirations_request.py +75 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_option_expirations_response.py +81 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_quote.py +272 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_quote_outcome.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_quote_request.py +84 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_gateway_quote_response.py +87 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_one_day_change.py +74 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_marketdata_quote_option_details.py +135 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_options_greek_response.py +113 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_options_greeks_response.py +86 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_options_option_greeks_type_0.py +117 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_cancel_replace_order_request.py +139 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_cancel_replace_order_request_order_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto.py +265 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto_fractional_trading.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto_option_spread_trading.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto_option_trading.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto_shorting_availability.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_dto_trading.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_instrument_response.py +87 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_multileg_order_request.py +141 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_multileg_order_request_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_request.py +231 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_request_equity_market_session.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_request_open_close_indicator.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_request_order_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_request_order_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_api_order_result.py +69 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_leg_instrument.py +73 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_leg_instrument_type.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order.py +334 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_instrument.py +73 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_instrument_type.py +15 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_leg.py +125 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_leg_open_close_indicator.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_leg_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_open_close_indicator.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_status.py +17 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_order_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_short_selling.py +127 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_short_selling_availability.py +10 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_gateway_short_selling_uptick_rule.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_instrumentdetails_api_instrument_details.py +67 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_instrumentdetails_api_instrument_details_bond.py +74 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_instrumentdetails_api_instrument_details_crypto.py +92 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_order_expiration.py +87 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_order_order_expiration_time_in_force.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gain_type_0.py +88 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_buying_power.py +77 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_cost_basis_type_0.py +167 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_account_v2.py +233 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_account_v2_account_type.py +14 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_equity_v2.py +100 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_equity_v2_type.py +14 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_instrument.py +93 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_instrument_type.py +14 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_portfolio_position.py +352 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_strategy.py +276 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_gateway_strategy_leg.py +78 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_portfolio_price_type_0.py +79 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_margin_impact.py +70 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_margin_requirement.py +70 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_option_details.py +91 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_option_details_type.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_option_rebate.py +79 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_price_increment.py +80 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_gateway_regulatory_fees.py +118 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_leg_response.py +167 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_leg_response_open_close_indicator.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_leg_response_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_multi_leg_request.py +148 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_multi_leg_request_order_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_multi_leg_response.py +313 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_request.py +225 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_request_equity_market_session.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_request_open_close_indicator.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_request_order_side.py +9 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_request_order_type.py +11 -0
- publicdotcom_cli/_generated/models/com_hellopublic_userapigateway_api_rest_preflight_preflight_single_leg_response.py +410 -0
- publicdotcom_cli/_generated/models/get_all_instruments_fractional_trading_filter_item.py +10 -0
- publicdotcom_cli/_generated/models/get_all_instruments_option_spread_trading_filter_item.py +10 -0
- publicdotcom_cli/_generated/models/get_all_instruments_option_trading_filter_item.py +10 -0
- publicdotcom_cli/_generated/models/get_all_instruments_trading_filter_item.py +10 -0
- publicdotcom_cli/_generated/models/get_all_instruments_type_filter_item.py +15 -0
- publicdotcom_cli/_generated/models/get_instrument_type.py +15 -0
- publicdotcom_cli/_generated/types.py +54 -0
- publicdotcom_cli/cli.py +761 -0
- publicdotcom_cli/client.py +83 -0
- publicdotcom_cli/config.py +222 -0
- publicdotcom_cli/output.py +77 -0
- publicdotcom_cli/payloads.py +36 -0
- publicdotcom_cli-1.0.0.dist-info/METADATA +256 -0
- publicdotcom_cli-1.0.0.dist-info/RECORD +146 -0
- publicdotcom_cli-1.0.0.dist-info/WHEEL +4 -0
- publicdotcom_cli-1.0.0.dist-info/entry_points.txt +2 -0
publicdotcom_cli/cli.py
ADDED
|
@@ -0,0 +1,761 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass
|
|
4
|
+
from getpass import getpass
|
|
5
|
+
from pathlib import Path
|
|
6
|
+
from typing import Annotated, Any
|
|
7
|
+
|
|
8
|
+
import typer
|
|
9
|
+
|
|
10
|
+
from publicdotcom_cli import __version__
|
|
11
|
+
from publicdotcom_cli.client import ApiClient, ApiError, MissingTokenError
|
|
12
|
+
from publicdotcom_cli.config import (
|
|
13
|
+
ACCOUNT_ID_ENV_VAR,
|
|
14
|
+
AUTO_REFRESH_ENV_VAR,
|
|
15
|
+
BASE_URL_ENV_VAR,
|
|
16
|
+
DEFAULT_BASE_URL,
|
|
17
|
+
SECRET_ENV_VAR,
|
|
18
|
+
TOKEN_ENV_VAR,
|
|
19
|
+
clear_default_account_id,
|
|
20
|
+
clear_personal_secret,
|
|
21
|
+
clear_token,
|
|
22
|
+
get_default_account_id,
|
|
23
|
+
get_personal_secret,
|
|
24
|
+
get_token,
|
|
25
|
+
mask_token,
|
|
26
|
+
set_default_account_id,
|
|
27
|
+
set_personal_secret,
|
|
28
|
+
set_token,
|
|
29
|
+
token_expires_soon,
|
|
30
|
+
)
|
|
31
|
+
from publicdotcom_cli.output import (
|
|
32
|
+
console,
|
|
33
|
+
exit_with_error,
|
|
34
|
+
print_accounts,
|
|
35
|
+
print_error,
|
|
36
|
+
print_json,
|
|
37
|
+
print_quotes,
|
|
38
|
+
)
|
|
39
|
+
from publicdotcom_cli.payloads import ensure_order_id, instrument, instruments, load_json_file
|
|
40
|
+
|
|
41
|
+
app = typer.Typer(help="CLI for the Public API.")
|
|
42
|
+
auth_app = typer.Typer(help="Authentication commands.")
|
|
43
|
+
accounts_app = typer.Typer(help="Account commands.")
|
|
44
|
+
portfolio_app = typer.Typer(help="Portfolio commands.")
|
|
45
|
+
history_app = typer.Typer(help="Account history commands.")
|
|
46
|
+
instruments_app = typer.Typer(help="Instrument lookup commands.")
|
|
47
|
+
market_app = typer.Typer(help="Market data commands.")
|
|
48
|
+
options_app = typer.Typer(help="Option details commands.")
|
|
49
|
+
order_app = typer.Typer(help="Order and preflight commands.")
|
|
50
|
+
|
|
51
|
+
app.add_typer(auth_app, name="auth")
|
|
52
|
+
app.add_typer(accounts_app, name="accounts")
|
|
53
|
+
app.add_typer(portfolio_app, name="portfolio")
|
|
54
|
+
app.add_typer(history_app, name="history")
|
|
55
|
+
app.add_typer(instruments_app, name="instruments")
|
|
56
|
+
app.add_typer(market_app, name="market")
|
|
57
|
+
app.add_typer(options_app, name="options")
|
|
58
|
+
app.add_typer(order_app, name="order")
|
|
59
|
+
|
|
60
|
+
|
|
61
|
+
@dataclass
|
|
62
|
+
class RuntimeConfig:
|
|
63
|
+
base_url: str
|
|
64
|
+
token: str | None
|
|
65
|
+
personal_secret: str | None
|
|
66
|
+
default_account_id: str | None
|
|
67
|
+
timeout: float
|
|
68
|
+
json_output: bool
|
|
69
|
+
auto_refresh: bool
|
|
70
|
+
refresh_validity_minutes: int
|
|
71
|
+
refresh_skew_seconds: int
|
|
72
|
+
|
|
73
|
+
|
|
74
|
+
def _runtime(ctx: typer.Context) -> RuntimeConfig:
|
|
75
|
+
runtime = ctx.find_root().obj
|
|
76
|
+
if not isinstance(runtime, RuntimeConfig):
|
|
77
|
+
raise RuntimeError("CLI runtime was not initialized")
|
|
78
|
+
return runtime
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
def _api(ctx: typer.Context) -> ApiClient:
|
|
82
|
+
runtime = _runtime(ctx)
|
|
83
|
+
return ApiClient(base_url=runtime.base_url, token=runtime.token, timeout=runtime.timeout)
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
def _resolve_account_id(ctx: typer.Context, account_id: str | None) -> str:
|
|
87
|
+
resolved = account_id or _runtime(ctx).default_account_id
|
|
88
|
+
if not resolved:
|
|
89
|
+
exit_with_error(
|
|
90
|
+
"No account ID provided. Pass --account-id, set PUBLIC_ACCOUNT_ID, "
|
|
91
|
+
"or run `public accounts set-default ACCOUNT_ID`."
|
|
92
|
+
)
|
|
93
|
+
return resolved
|
|
94
|
+
|
|
95
|
+
|
|
96
|
+
def _request_access_token(runtime: RuntimeConfig, secret: str) -> str:
|
|
97
|
+
client = ApiClient(base_url=runtime.base_url, token=None, timeout=runtime.timeout)
|
|
98
|
+
result = client.request(
|
|
99
|
+
"POST",
|
|
100
|
+
"/userapiauthservice/personal/access-tokens",
|
|
101
|
+
json_body={
|
|
102
|
+
"secret": secret,
|
|
103
|
+
"validityInMinutes": runtime.refresh_validity_minutes,
|
|
104
|
+
},
|
|
105
|
+
authenticated=False,
|
|
106
|
+
)
|
|
107
|
+
if not isinstance(result, dict) or not isinstance(result.get("accessToken"), str):
|
|
108
|
+
raise RuntimeError("Login response did not contain an accessToken.")
|
|
109
|
+
return result["accessToken"]
|
|
110
|
+
|
|
111
|
+
|
|
112
|
+
def _request_access_token_or_exit(runtime: RuntimeConfig, secret: str) -> str:
|
|
113
|
+
try:
|
|
114
|
+
return _request_access_token(runtime, secret)
|
|
115
|
+
except ApiError as exc:
|
|
116
|
+
print_error(str(exc))
|
|
117
|
+
if exc.body is not None:
|
|
118
|
+
print_json(exc.body)
|
|
119
|
+
raise typer.Exit(1) from exc
|
|
120
|
+
except RuntimeError as exc:
|
|
121
|
+
exit_with_error(str(exc))
|
|
122
|
+
|
|
123
|
+
|
|
124
|
+
def _refresh_token(ctx: typer.Context, *, force: bool = False) -> bool:
|
|
125
|
+
runtime = _runtime(ctx)
|
|
126
|
+
if not runtime.auto_refresh or not runtime.personal_secret:
|
|
127
|
+
return False
|
|
128
|
+
if not force and not token_expires_soon(
|
|
129
|
+
runtime.token, skew_seconds=runtime.refresh_skew_seconds
|
|
130
|
+
):
|
|
131
|
+
return False
|
|
132
|
+
|
|
133
|
+
try:
|
|
134
|
+
token = _request_access_token(runtime, runtime.personal_secret)
|
|
135
|
+
except ApiError as exc:
|
|
136
|
+
print_error("Automatic token refresh failed.")
|
|
137
|
+
print_error(str(exc))
|
|
138
|
+
if exc.body is not None:
|
|
139
|
+
print_json(exc.body)
|
|
140
|
+
raise typer.Exit(1) from exc
|
|
141
|
+
except RuntimeError as exc:
|
|
142
|
+
exit_with_error(str(exc))
|
|
143
|
+
|
|
144
|
+
runtime.token = token
|
|
145
|
+
set_token(token)
|
|
146
|
+
return True
|
|
147
|
+
|
|
148
|
+
|
|
149
|
+
def _print(ctx: typer.Context, data: Any, *, table: str | None = None) -> None:
|
|
150
|
+
runtime = _runtime(ctx)
|
|
151
|
+
if runtime.json_output:
|
|
152
|
+
print_json(data)
|
|
153
|
+
elif table == "accounts":
|
|
154
|
+
print_accounts(data)
|
|
155
|
+
elif table == "quotes":
|
|
156
|
+
print_quotes(data)
|
|
157
|
+
else:
|
|
158
|
+
print_json(data)
|
|
159
|
+
|
|
160
|
+
|
|
161
|
+
def _call(ctx: typer.Context, method: str, path: str, **kwargs: Any) -> Any:
|
|
162
|
+
authenticated = bool(kwargs.get("authenticated", True))
|
|
163
|
+
if authenticated:
|
|
164
|
+
_refresh_token(ctx)
|
|
165
|
+
|
|
166
|
+
try:
|
|
167
|
+
return _api(ctx).request(method, path, **kwargs)
|
|
168
|
+
except MissingTokenError as exc:
|
|
169
|
+
if authenticated and _refresh_token(ctx, force=True):
|
|
170
|
+
return _api(ctx).request(method, path, **kwargs)
|
|
171
|
+
exit_with_error(str(exc))
|
|
172
|
+
except ApiError as exc:
|
|
173
|
+
if authenticated and exc.status_code == 401 and _refresh_token(ctx, force=True):
|
|
174
|
+
return _api(ctx).request(method, path, **kwargs)
|
|
175
|
+
print_error(str(exc))
|
|
176
|
+
if exc.body is not None:
|
|
177
|
+
print_json(exc.body)
|
|
178
|
+
raise typer.Exit(1) from exc
|
|
179
|
+
|
|
180
|
+
|
|
181
|
+
ORDER_ACTION_WARNING = (
|
|
182
|
+
"Trading action: review the account, symbols, side, quantity, prices, "
|
|
183
|
+
"expiration, time-in-force, and full request payload before continuing."
|
|
184
|
+
)
|
|
185
|
+
|
|
186
|
+
|
|
187
|
+
def _confirm(action: str, yes: bool, *, warning: str | None = None) -> None:
|
|
188
|
+
if yes:
|
|
189
|
+
return
|
|
190
|
+
if warning:
|
|
191
|
+
console.print(f"[yellow]{warning}[/yellow]")
|
|
192
|
+
if not typer.confirm(action):
|
|
193
|
+
raise typer.Abort()
|
|
194
|
+
|
|
195
|
+
|
|
196
|
+
def _version_callback(value: bool) -> None:
|
|
197
|
+
if value:
|
|
198
|
+
console.print(f"publicdotcom-cli {__version__}")
|
|
199
|
+
raise typer.Exit()
|
|
200
|
+
|
|
201
|
+
|
|
202
|
+
@app.callback()
|
|
203
|
+
def root(
|
|
204
|
+
ctx: typer.Context,
|
|
205
|
+
version: Annotated[
|
|
206
|
+
bool | None,
|
|
207
|
+
typer.Option(
|
|
208
|
+
"--version",
|
|
209
|
+
callback=_version_callback,
|
|
210
|
+
is_eager=True,
|
|
211
|
+
help="Show the CLI version and exit.",
|
|
212
|
+
),
|
|
213
|
+
] = None,
|
|
214
|
+
base_url: Annotated[
|
|
215
|
+
str,
|
|
216
|
+
typer.Option(
|
|
217
|
+
"--base-url",
|
|
218
|
+
envvar=BASE_URL_ENV_VAR,
|
|
219
|
+
help="API base URL.",
|
|
220
|
+
),
|
|
221
|
+
] = DEFAULT_BASE_URL,
|
|
222
|
+
token: Annotated[
|
|
223
|
+
str | None,
|
|
224
|
+
typer.Option(
|
|
225
|
+
"--token",
|
|
226
|
+
envvar=TOKEN_ENV_VAR,
|
|
227
|
+
help="Access token. Prefer PUBLIC_ACCESS_TOKEN for automation.",
|
|
228
|
+
),
|
|
229
|
+
] = None,
|
|
230
|
+
timeout: Annotated[
|
|
231
|
+
float,
|
|
232
|
+
typer.Option("--timeout", min=1.0, help="HTTP timeout in seconds."),
|
|
233
|
+
] = 30.0,
|
|
234
|
+
auto_refresh: Annotated[
|
|
235
|
+
bool,
|
|
236
|
+
typer.Option(
|
|
237
|
+
"--auto-refresh/--no-auto-refresh",
|
|
238
|
+
envvar=AUTO_REFRESH_ENV_VAR,
|
|
239
|
+
help="Refresh access tokens from a stored or environment personal secret.",
|
|
240
|
+
),
|
|
241
|
+
] = True,
|
|
242
|
+
refresh_validity_minutes: Annotated[
|
|
243
|
+
int,
|
|
244
|
+
typer.Option(
|
|
245
|
+
"--refresh-validity-minutes",
|
|
246
|
+
min=5,
|
|
247
|
+
max=1440,
|
|
248
|
+
help="Lifetime for automatically refreshed access tokens.",
|
|
249
|
+
),
|
|
250
|
+
] = 60,
|
|
251
|
+
refresh_skew_seconds: Annotated[
|
|
252
|
+
int,
|
|
253
|
+
typer.Option(
|
|
254
|
+
"--refresh-skew-seconds",
|
|
255
|
+
min=0,
|
|
256
|
+
help="Refresh JWTs this many seconds before their exp timestamp.",
|
|
257
|
+
),
|
|
258
|
+
] = 60,
|
|
259
|
+
json_output: Annotated[
|
|
260
|
+
bool,
|
|
261
|
+
typer.Option("--json", help="Always print raw JSON output."),
|
|
262
|
+
] = False,
|
|
263
|
+
) -> None:
|
|
264
|
+
ctx.obj = RuntimeConfig(
|
|
265
|
+
base_url=base_url,
|
|
266
|
+
token=token or get_token(),
|
|
267
|
+
personal_secret=get_personal_secret(),
|
|
268
|
+
default_account_id=get_default_account_id(),
|
|
269
|
+
timeout=timeout,
|
|
270
|
+
json_output=json_output,
|
|
271
|
+
auto_refresh=auto_refresh,
|
|
272
|
+
refresh_validity_minutes=refresh_validity_minutes,
|
|
273
|
+
refresh_skew_seconds=refresh_skew_seconds,
|
|
274
|
+
)
|
|
275
|
+
|
|
276
|
+
|
|
277
|
+
@auth_app.command("login")
|
|
278
|
+
def auth_login(
|
|
279
|
+
ctx: typer.Context,
|
|
280
|
+
secret: Annotated[
|
|
281
|
+
str | None,
|
|
282
|
+
typer.Option("--secret", help="Personal secret. If omitted, you will be prompted."),
|
|
283
|
+
] = None,
|
|
284
|
+
validity_minutes: Annotated[
|
|
285
|
+
int,
|
|
286
|
+
typer.Option(
|
|
287
|
+
"--validity-minutes",
|
|
288
|
+
min=5,
|
|
289
|
+
max=1440,
|
|
290
|
+
help="Access token lifetime in minutes.",
|
|
291
|
+
),
|
|
292
|
+
] = 60,
|
|
293
|
+
print_token: Annotated[
|
|
294
|
+
bool,
|
|
295
|
+
typer.Option("--print-token", help="Print the access token after login."),
|
|
296
|
+
] = False,
|
|
297
|
+
store_secret: Annotated[
|
|
298
|
+
bool,
|
|
299
|
+
typer.Option(
|
|
300
|
+
"--store-secret",
|
|
301
|
+
help="Store the personal secret so future commands can refresh tokens automatically.",
|
|
302
|
+
),
|
|
303
|
+
] = False,
|
|
304
|
+
) -> None:
|
|
305
|
+
secret = secret or getpass("Personal secret: ")
|
|
306
|
+
runtime = _runtime(ctx)
|
|
307
|
+
runtime.refresh_validity_minutes = validity_minutes
|
|
308
|
+
token = _request_access_token_or_exit(runtime, secret)
|
|
309
|
+
runtime.token = token
|
|
310
|
+
location = set_token(token)
|
|
311
|
+
console.print(f"Access token stored in {location}.")
|
|
312
|
+
if store_secret:
|
|
313
|
+
secret_location = set_personal_secret(secret)
|
|
314
|
+
runtime.personal_secret = secret
|
|
315
|
+
console.print(f"Personal secret stored in {secret_location} for automatic refresh.")
|
|
316
|
+
if print_token:
|
|
317
|
+
console.print(token)
|
|
318
|
+
|
|
319
|
+
|
|
320
|
+
@auth_app.command("refresh")
|
|
321
|
+
def auth_refresh(
|
|
322
|
+
ctx: typer.Context,
|
|
323
|
+
secret: Annotated[
|
|
324
|
+
str | None,
|
|
325
|
+
typer.Option("--secret", help="Personal secret. Uses stored secret if omitted."),
|
|
326
|
+
] = None,
|
|
327
|
+
validity_minutes: Annotated[
|
|
328
|
+
int,
|
|
329
|
+
typer.Option(
|
|
330
|
+
"--validity-minutes",
|
|
331
|
+
min=5,
|
|
332
|
+
max=1440,
|
|
333
|
+
help="Access token lifetime in minutes.",
|
|
334
|
+
),
|
|
335
|
+
] = 60,
|
|
336
|
+
store_secret: Annotated[
|
|
337
|
+
bool,
|
|
338
|
+
typer.Option(
|
|
339
|
+
"--store-secret", help="Store the provided personal secret for future refresh."
|
|
340
|
+
),
|
|
341
|
+
] = False,
|
|
342
|
+
print_token: Annotated[
|
|
343
|
+
bool,
|
|
344
|
+
typer.Option("--print-token", help="Print the refreshed access token."),
|
|
345
|
+
] = False,
|
|
346
|
+
) -> None:
|
|
347
|
+
runtime = _runtime(ctx)
|
|
348
|
+
secret = secret or runtime.personal_secret or getpass("Personal secret: ")
|
|
349
|
+
runtime.refresh_validity_minutes = validity_minutes
|
|
350
|
+
token = _request_access_token_or_exit(runtime, secret)
|
|
351
|
+
runtime.token = token
|
|
352
|
+
location = set_token(token)
|
|
353
|
+
console.print(f"Access token refreshed and stored in {location}.")
|
|
354
|
+
if store_secret:
|
|
355
|
+
secret_location = set_personal_secret(secret)
|
|
356
|
+
runtime.personal_secret = secret
|
|
357
|
+
console.print(f"Personal secret stored in {secret_location} for automatic refresh.")
|
|
358
|
+
if print_token:
|
|
359
|
+
console.print(token)
|
|
360
|
+
|
|
361
|
+
|
|
362
|
+
@auth_app.command("status")
|
|
363
|
+
def auth_status(ctx: typer.Context) -> None:
|
|
364
|
+
runtime = _runtime(ctx)
|
|
365
|
+
console.print(f"Base URL: {runtime.base_url}")
|
|
366
|
+
console.print(f"Access token: {mask_token(runtime.token)}")
|
|
367
|
+
console.print(f"Personal secret: {mask_token(runtime.personal_secret)}")
|
|
368
|
+
console.print(f"Default account ID: {runtime.default_account_id or 'not set'}")
|
|
369
|
+
console.print(f"Auto refresh: {'enabled' if runtime.auto_refresh else 'disabled'}")
|
|
370
|
+
console.print(f"Secret env var: {SECRET_ENV_VAR}")
|
|
371
|
+
console.print(f"Account env var: {ACCOUNT_ID_ENV_VAR}")
|
|
372
|
+
|
|
373
|
+
|
|
374
|
+
@auth_app.command("logout")
|
|
375
|
+
def auth_logout(
|
|
376
|
+
clear_secret: Annotated[
|
|
377
|
+
bool,
|
|
378
|
+
typer.Option("--all", help="Also remove the stored personal secret."),
|
|
379
|
+
] = False,
|
|
380
|
+
) -> None:
|
|
381
|
+
clear_token()
|
|
382
|
+
console.print("Access token removed.")
|
|
383
|
+
if clear_secret:
|
|
384
|
+
clear_personal_secret()
|
|
385
|
+
console.print("Personal secret removed.")
|
|
386
|
+
|
|
387
|
+
|
|
388
|
+
@accounts_app.command("list")
|
|
389
|
+
def accounts_list(ctx: typer.Context) -> None:
|
|
390
|
+
result = _call(ctx, "GET", "/userapigateway/trading/account")
|
|
391
|
+
_print(ctx, result, table="accounts")
|
|
392
|
+
|
|
393
|
+
|
|
394
|
+
@accounts_app.command("set-default")
|
|
395
|
+
def accounts_set_default(
|
|
396
|
+
ctx: typer.Context,
|
|
397
|
+
account_id: Annotated[
|
|
398
|
+
str, typer.Argument(help="Account ID returned by `public accounts list`.")
|
|
399
|
+
],
|
|
400
|
+
) -> None:
|
|
401
|
+
runtime = _runtime(ctx)
|
|
402
|
+
runtime.default_account_id = account_id
|
|
403
|
+
location = set_default_account_id(account_id)
|
|
404
|
+
console.print(f"Default account ID set to {account_id} in {location}.")
|
|
405
|
+
|
|
406
|
+
|
|
407
|
+
@accounts_app.command("get-default")
|
|
408
|
+
def accounts_get_default(ctx: typer.Context) -> None:
|
|
409
|
+
runtime = _runtime(ctx)
|
|
410
|
+
if not runtime.default_account_id:
|
|
411
|
+
exit_with_error("No default account ID set. Run `public accounts set-default ACCOUNT_ID`.")
|
|
412
|
+
console.print(runtime.default_account_id)
|
|
413
|
+
|
|
414
|
+
|
|
415
|
+
@accounts_app.command("clear-default")
|
|
416
|
+
def accounts_clear_default(ctx: typer.Context) -> None:
|
|
417
|
+
runtime = _runtime(ctx)
|
|
418
|
+
runtime.default_account_id = None
|
|
419
|
+
clear_default_account_id()
|
|
420
|
+
console.print("Default account ID removed.")
|
|
421
|
+
|
|
422
|
+
|
|
423
|
+
@portfolio_app.command("show")
|
|
424
|
+
def portfolio_show(
|
|
425
|
+
ctx: typer.Context,
|
|
426
|
+
account_id: Annotated[
|
|
427
|
+
str | None,
|
|
428
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
429
|
+
] = None,
|
|
430
|
+
) -> None:
|
|
431
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
432
|
+
result = _call(ctx, "GET", f"/userapigateway/trading/{account_id}/portfolio/v2")
|
|
433
|
+
_print(ctx, result)
|
|
434
|
+
|
|
435
|
+
|
|
436
|
+
@history_app.command("list")
|
|
437
|
+
def history_list(
|
|
438
|
+
ctx: typer.Context,
|
|
439
|
+
account_id: Annotated[
|
|
440
|
+
str | None,
|
|
441
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
442
|
+
] = None,
|
|
443
|
+
start: Annotated[str | None, typer.Option("--start", help="ISO 8601 start timestamp.")] = None,
|
|
444
|
+
end: Annotated[str | None, typer.Option("--end", help="ISO 8601 end timestamp.")] = None,
|
|
445
|
+
page_size: Annotated[int | None, typer.Option("--page-size", min=1)] = None,
|
|
446
|
+
next_token: Annotated[str | None, typer.Option("--next-token")] = None,
|
|
447
|
+
) -> None:
|
|
448
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
449
|
+
result = _call(
|
|
450
|
+
ctx,
|
|
451
|
+
"GET",
|
|
452
|
+
f"/userapigateway/trading/{account_id}/history",
|
|
453
|
+
params={
|
|
454
|
+
"start": start,
|
|
455
|
+
"end": end,
|
|
456
|
+
"pageSize": page_size,
|
|
457
|
+
"nextToken": next_token,
|
|
458
|
+
},
|
|
459
|
+
)
|
|
460
|
+
_print(ctx, result)
|
|
461
|
+
|
|
462
|
+
|
|
463
|
+
@instruments_app.command("list")
|
|
464
|
+
def instruments_list(
|
|
465
|
+
ctx: typer.Context,
|
|
466
|
+
type_filter: Annotated[
|
|
467
|
+
list[str] | None,
|
|
468
|
+
typer.Option("--type-filter", help="Security type filter. Repeat for multiple."),
|
|
469
|
+
] = None,
|
|
470
|
+
trading_filter: Annotated[
|
|
471
|
+
list[str] | None,
|
|
472
|
+
typer.Option("--trading-filter", help="Trading status filter. Repeat for multiple."),
|
|
473
|
+
] = None,
|
|
474
|
+
fractional_trading_filter: Annotated[
|
|
475
|
+
list[str] | None,
|
|
476
|
+
typer.Option(
|
|
477
|
+
"--fractional-trading-filter",
|
|
478
|
+
help="Fractional trading status filter. Repeat for multiple.",
|
|
479
|
+
),
|
|
480
|
+
] = None,
|
|
481
|
+
option_trading_filter: Annotated[
|
|
482
|
+
list[str] | None,
|
|
483
|
+
typer.Option("--option-trading-filter", help="Option trading filter. Repeat for multiple."),
|
|
484
|
+
] = None,
|
|
485
|
+
option_spread_trading_filter: Annotated[
|
|
486
|
+
list[str] | None,
|
|
487
|
+
typer.Option(
|
|
488
|
+
"--option-spread-trading-filter",
|
|
489
|
+
help="Option spread trading filter. Repeat for multiple.",
|
|
490
|
+
),
|
|
491
|
+
] = None,
|
|
492
|
+
) -> None:
|
|
493
|
+
result = _call(
|
|
494
|
+
ctx,
|
|
495
|
+
"GET",
|
|
496
|
+
"/userapigateway/trading/instruments",
|
|
497
|
+
params={
|
|
498
|
+
"typeFilter": type_filter,
|
|
499
|
+
"tradingFilter": trading_filter,
|
|
500
|
+
"fractionalTradingFilter": fractional_trading_filter,
|
|
501
|
+
"optionTradingFilter": option_trading_filter,
|
|
502
|
+
"optionSpreadTradingFilter": option_spread_trading_filter,
|
|
503
|
+
},
|
|
504
|
+
)
|
|
505
|
+
_print(ctx, result)
|
|
506
|
+
|
|
507
|
+
|
|
508
|
+
@instruments_app.command("get")
|
|
509
|
+
def instrument_get(
|
|
510
|
+
ctx: typer.Context,
|
|
511
|
+
symbol: Annotated[str, typer.Argument()],
|
|
512
|
+
security_type: Annotated[str, typer.Argument(help="EQUITY, OPTION, CRYPTO, etc.")],
|
|
513
|
+
) -> None:
|
|
514
|
+
result = _call(
|
|
515
|
+
ctx,
|
|
516
|
+
"GET",
|
|
517
|
+
f"/userapigateway/trading/instruments/{symbol.upper()}/{security_type.upper()}",
|
|
518
|
+
)
|
|
519
|
+
_print(ctx, result)
|
|
520
|
+
|
|
521
|
+
|
|
522
|
+
@market_app.command("quotes")
|
|
523
|
+
def market_quotes(
|
|
524
|
+
ctx: typer.Context,
|
|
525
|
+
symbols: Annotated[list[str], typer.Argument(help="One or more symbols.")],
|
|
526
|
+
account_id: Annotated[
|
|
527
|
+
str | None,
|
|
528
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
529
|
+
] = None,
|
|
530
|
+
security_type: Annotated[
|
|
531
|
+
str,
|
|
532
|
+
typer.Option("--type", help="Instrument type for all symbols."),
|
|
533
|
+
] = "EQUITY",
|
|
534
|
+
) -> None:
|
|
535
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
536
|
+
result = _call(
|
|
537
|
+
ctx,
|
|
538
|
+
"POST",
|
|
539
|
+
f"/userapigateway/marketdata/{account_id}/quotes",
|
|
540
|
+
json_body={"instruments": instruments(symbols, security_type)},
|
|
541
|
+
)
|
|
542
|
+
_print(ctx, result, table="quotes")
|
|
543
|
+
|
|
544
|
+
|
|
545
|
+
@market_app.command("option-expirations")
|
|
546
|
+
def option_expirations(
|
|
547
|
+
ctx: typer.Context,
|
|
548
|
+
symbol: Annotated[str, typer.Argument()],
|
|
549
|
+
account_id: Annotated[
|
|
550
|
+
str | None,
|
|
551
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
552
|
+
] = None,
|
|
553
|
+
security_type: Annotated[
|
|
554
|
+
str,
|
|
555
|
+
typer.Option("--type", help="Underlying instrument type."),
|
|
556
|
+
] = "EQUITY",
|
|
557
|
+
) -> None:
|
|
558
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
559
|
+
result = _call(
|
|
560
|
+
ctx,
|
|
561
|
+
"POST",
|
|
562
|
+
f"/userapigateway/marketdata/{account_id}/option-expirations",
|
|
563
|
+
json_body={"instrument": instrument(symbol, security_type)},
|
|
564
|
+
)
|
|
565
|
+
_print(ctx, result)
|
|
566
|
+
|
|
567
|
+
|
|
568
|
+
@market_app.command("option-chain")
|
|
569
|
+
def option_chain(
|
|
570
|
+
ctx: typer.Context,
|
|
571
|
+
symbol: Annotated[str, typer.Argument()],
|
|
572
|
+
expiration_date: Annotated[str, typer.Argument(help="Expiration date as YYYY-MM-DD.")],
|
|
573
|
+
account_id: Annotated[
|
|
574
|
+
str | None,
|
|
575
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
576
|
+
] = None,
|
|
577
|
+
security_type: Annotated[
|
|
578
|
+
str,
|
|
579
|
+
typer.Option("--type", help="Underlying instrument type."),
|
|
580
|
+
] = "EQUITY",
|
|
581
|
+
) -> None:
|
|
582
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
583
|
+
result = _call(
|
|
584
|
+
ctx,
|
|
585
|
+
"POST",
|
|
586
|
+
f"/userapigateway/marketdata/{account_id}/option-chain",
|
|
587
|
+
json_body={
|
|
588
|
+
"instrument": instrument(symbol, security_type),
|
|
589
|
+
"expirationDate": expiration_date,
|
|
590
|
+
},
|
|
591
|
+
)
|
|
592
|
+
_print(ctx, result)
|
|
593
|
+
|
|
594
|
+
|
|
595
|
+
@options_app.command("greeks")
|
|
596
|
+
def option_greeks(
|
|
597
|
+
ctx: typer.Context,
|
|
598
|
+
osi_symbols: Annotated[list[str], typer.Argument(help="One or more OSI-normalized symbols.")],
|
|
599
|
+
account_id: Annotated[
|
|
600
|
+
str | None,
|
|
601
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
602
|
+
] = None,
|
|
603
|
+
) -> None:
|
|
604
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
605
|
+
result = _call(
|
|
606
|
+
ctx,
|
|
607
|
+
"GET",
|
|
608
|
+
f"/userapigateway/option-details/{account_id}/greeks",
|
|
609
|
+
params={"osiSymbols": osi_symbols},
|
|
610
|
+
)
|
|
611
|
+
_print(ctx, result)
|
|
612
|
+
|
|
613
|
+
|
|
614
|
+
@order_app.command("preflight-single")
|
|
615
|
+
def preflight_single(
|
|
616
|
+
ctx: typer.Context,
|
|
617
|
+
file: Annotated[Path, typer.Option("--file", "-f", exists=True, readable=True)],
|
|
618
|
+
account_id: Annotated[
|
|
619
|
+
str | None,
|
|
620
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
621
|
+
] = None,
|
|
622
|
+
) -> None:
|
|
623
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
624
|
+
body = load_json_file(file)
|
|
625
|
+
result = _call(
|
|
626
|
+
ctx,
|
|
627
|
+
"POST",
|
|
628
|
+
f"/userapigateway/trading/{account_id}/preflight/single-leg",
|
|
629
|
+
json_body=body,
|
|
630
|
+
)
|
|
631
|
+
_print(ctx, result)
|
|
632
|
+
|
|
633
|
+
|
|
634
|
+
@order_app.command("preflight-multi")
|
|
635
|
+
def preflight_multi(
|
|
636
|
+
ctx: typer.Context,
|
|
637
|
+
file: Annotated[Path, typer.Option("--file", "-f", exists=True, readable=True)],
|
|
638
|
+
account_id: Annotated[
|
|
639
|
+
str | None,
|
|
640
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
641
|
+
] = None,
|
|
642
|
+
) -> None:
|
|
643
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
644
|
+
body = load_json_file(file)
|
|
645
|
+
result = _call(
|
|
646
|
+
ctx,
|
|
647
|
+
"POST",
|
|
648
|
+
f"/userapigateway/trading/{account_id}/preflight/multi-leg",
|
|
649
|
+
json_body=body,
|
|
650
|
+
)
|
|
651
|
+
_print(ctx, result)
|
|
652
|
+
|
|
653
|
+
|
|
654
|
+
@order_app.command("place")
|
|
655
|
+
def order_place(
|
|
656
|
+
ctx: typer.Context,
|
|
657
|
+
file: Annotated[Path, typer.Option("--file", "-f", exists=True, readable=True)],
|
|
658
|
+
account_id: Annotated[
|
|
659
|
+
str | None,
|
|
660
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
661
|
+
] = None,
|
|
662
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation.")] = False,
|
|
663
|
+
) -> None:
|
|
664
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
665
|
+
body = load_json_file(file)
|
|
666
|
+
if not isinstance(body, dict):
|
|
667
|
+
exit_with_error("Order request JSON must be an object.")
|
|
668
|
+
order_id = ensure_order_id(body)
|
|
669
|
+
_confirm(f"Submit order {order_id}?", yes, warning=ORDER_ACTION_WARNING)
|
|
670
|
+
result = _call(
|
|
671
|
+
ctx,
|
|
672
|
+
"POST",
|
|
673
|
+
f"/userapigateway/trading/{account_id}/order",
|
|
674
|
+
json_body=body,
|
|
675
|
+
)
|
|
676
|
+
_print(ctx, result)
|
|
677
|
+
|
|
678
|
+
|
|
679
|
+
@order_app.command("replace")
|
|
680
|
+
def order_replace(
|
|
681
|
+
ctx: typer.Context,
|
|
682
|
+
file: Annotated[Path, typer.Option("--file", "-f", exists=True, readable=True)],
|
|
683
|
+
account_id: Annotated[
|
|
684
|
+
str | None,
|
|
685
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
686
|
+
] = None,
|
|
687
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation.")] = False,
|
|
688
|
+
) -> None:
|
|
689
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
690
|
+
body = load_json_file(file)
|
|
691
|
+
_confirm("Submit cancel-replace request?", yes, warning=ORDER_ACTION_WARNING)
|
|
692
|
+
result = _call(
|
|
693
|
+
ctx,
|
|
694
|
+
"PUT",
|
|
695
|
+
f"/userapigateway/trading/{account_id}/order",
|
|
696
|
+
json_body=body,
|
|
697
|
+
)
|
|
698
|
+
_print(ctx, result)
|
|
699
|
+
|
|
700
|
+
|
|
701
|
+
@order_app.command("place-multileg")
|
|
702
|
+
def order_place_multileg(
|
|
703
|
+
ctx: typer.Context,
|
|
704
|
+
file: Annotated[Path, typer.Option("--file", "-f", exists=True, readable=True)],
|
|
705
|
+
account_id: Annotated[
|
|
706
|
+
str | None,
|
|
707
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
708
|
+
] = None,
|
|
709
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation.")] = False,
|
|
710
|
+
) -> None:
|
|
711
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
712
|
+
body = load_json_file(file)
|
|
713
|
+
if not isinstance(body, dict):
|
|
714
|
+
exit_with_error("Multileg order request JSON must be an object.")
|
|
715
|
+
order_id = ensure_order_id(body)
|
|
716
|
+
_confirm(f"Submit multileg order {order_id}?", yes, warning=ORDER_ACTION_WARNING)
|
|
717
|
+
result = _call(
|
|
718
|
+
ctx,
|
|
719
|
+
"POST",
|
|
720
|
+
f"/userapigateway/trading/{account_id}/order/multileg",
|
|
721
|
+
json_body=body,
|
|
722
|
+
)
|
|
723
|
+
_print(ctx, result)
|
|
724
|
+
|
|
725
|
+
|
|
726
|
+
@order_app.command("get")
|
|
727
|
+
def order_get(
|
|
728
|
+
ctx: typer.Context,
|
|
729
|
+
order_id: Annotated[str, typer.Argument()],
|
|
730
|
+
account_id: Annotated[
|
|
731
|
+
str | None,
|
|
732
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
733
|
+
] = None,
|
|
734
|
+
) -> None:
|
|
735
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
736
|
+
result = _call(ctx, "GET", f"/userapigateway/trading/{account_id}/order/{order_id}")
|
|
737
|
+
_print(ctx, result)
|
|
738
|
+
|
|
739
|
+
|
|
740
|
+
@order_app.command("cancel")
|
|
741
|
+
def order_cancel(
|
|
742
|
+
ctx: typer.Context,
|
|
743
|
+
order_id: Annotated[str, typer.Argument()],
|
|
744
|
+
account_id: Annotated[
|
|
745
|
+
str | None,
|
|
746
|
+
typer.Option("--account-id", "-a", help="Account ID. Defaults to configured account."),
|
|
747
|
+
] = None,
|
|
748
|
+
yes: Annotated[bool, typer.Option("--yes", "-y", help="Skip confirmation.")] = False,
|
|
749
|
+
) -> None:
|
|
750
|
+
account_id = _resolve_account_id(ctx, account_id)
|
|
751
|
+
_confirm(
|
|
752
|
+
f"Cancel order {order_id}?",
|
|
753
|
+
yes,
|
|
754
|
+
warning="Trading action: cancellation requests may be asynchronous. Verify order status after submitting.",
|
|
755
|
+
)
|
|
756
|
+
result = _call(ctx, "DELETE", f"/userapigateway/trading/{account_id}/order/{order_id}")
|
|
757
|
+
_print(ctx, result if result is not None else {"cancelRequested": True})
|
|
758
|
+
|
|
759
|
+
|
|
760
|
+
def main() -> None:
|
|
761
|
+
app()
|