nexusquant-cli 0.1.1__tar.gz → 0.1.2__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.
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/.gitignore +3 -0
- nexusquant_cli-0.1.2/CLAUDE.md +66 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/PKG-INFO +16 -14
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/README.md +15 -13
- nexusquant_cli-0.1.2/nexusquant_cli/__init__.py +1 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/auth_pkce.py +14 -5
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/strategy/cmd.py +18 -39
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/config.py +1 -1
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/pyproject.toml +1 -1
- nexusquant_cli-0.1.1/nexusquant_cli/__init__.py +0 -1
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/__main__.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/api_client.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/__init__.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/_util.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/auth/__init__.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/auth/cmd.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/strategy/__init__.py +0 -0
- {nexusquant_cli-0.1.1 → nexusquant_cli-0.1.2}/nexusquant_cli/main.py +0 -0
|
@@ -0,0 +1,66 @@
|
|
|
1
|
+
# nexusquant-cli
|
|
2
|
+
|
|
3
|
+
Python CLI for Nexus strategy providers. Handles Cognito PKCE browser-based login, token storage/refresh, and wraps the Nexus provider API (strategies, signals, containers).
|
|
4
|
+
|
|
5
|
+
## What this repo does
|
|
6
|
+
|
|
7
|
+
Command-line tool that strategy providers (and admins) use to manage their strategies and submit signals without going through the web UI. Authenticates via Cognito browser login, stores tokens locally, and calls the same REST API as the frontend.
|
|
8
|
+
|
|
9
|
+
## Code structure
|
|
10
|
+
|
|
11
|
+
```
|
|
12
|
+
nexusquant_cli/
|
|
13
|
+
main.py # CLI entry point (Click group)
|
|
14
|
+
api_client.py # HTTP client wrapping nexus-service REST API
|
|
15
|
+
auth_pkce.py # Cognito PKCE auth flow (opens browser, listens for callback)
|
|
16
|
+
config.py # Token storage path, API base URL
|
|
17
|
+
commands/ # One file per command group (auth, strategy, signal, ...)
|
|
18
|
+
__main__.py # Allows `python -m nexusquant_cli`
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
## Install
|
|
22
|
+
|
|
23
|
+
```bash
|
|
24
|
+
# Development (editable)
|
|
25
|
+
pip install -e .
|
|
26
|
+
|
|
27
|
+
# From PyPI
|
|
28
|
+
pip install nexusquant-cli
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
## Usage
|
|
32
|
+
|
|
33
|
+
```bash
|
|
34
|
+
# Authenticate (opens browser for Cognito login)
|
|
35
|
+
nexusquant auth
|
|
36
|
+
nexusquant auth --status
|
|
37
|
+
nexusquant auth --refresh
|
|
38
|
+
nexusquant auth --logout
|
|
39
|
+
|
|
40
|
+
# Strategy management
|
|
41
|
+
nexusquant strategy create --strategy-id my_alpha --name "My Alpha" --schema-file schema.json
|
|
42
|
+
nexusquant strategy list
|
|
43
|
+
nexusquant strategy update --strategy-id my_alpha ...
|
|
44
|
+
nexusquant strategy delete --strategy-id my_alpha
|
|
45
|
+
|
|
46
|
+
# Signal submission
|
|
47
|
+
nexusquant signal submit --strategy-id my_alpha --file signals.json
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Tokens are stored at `~/Library/Application Support/nexusquant-cli/credentials.json` on macOS (respects XDG on Linux). File is written with `0600` permissions.
|
|
51
|
+
|
|
52
|
+
## Deploy / publish
|
|
53
|
+
|
|
54
|
+
```bash
|
|
55
|
+
# Build distribution
|
|
56
|
+
python -m build
|
|
57
|
+
|
|
58
|
+
# Publish to PyPI
|
|
59
|
+
twine upload dist/*
|
|
60
|
+
```
|
|
61
|
+
|
|
62
|
+
## Configuration
|
|
63
|
+
|
|
64
|
+
API base URL is set in `nexusquant_cli/config.py`. Change `API_BASE_URL` to point at a different environment (staging vs production).
|
|
65
|
+
|
|
66
|
+
Required Cognito role: `custom:userType=strategyProvider` or `custom:userType=admin`. Standard users cannot use provider commands.
|
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
Metadata-Version: 2.4
|
|
2
2
|
Name: nexusquant-cli
|
|
3
|
-
Version: 0.1.
|
|
3
|
+
Version: 0.1.2
|
|
4
4
|
Summary: NexusQuant strategy provider CLI
|
|
5
5
|
Requires-Python: >=3.10
|
|
6
6
|
Requires-Dist: httpx>=0.27
|
|
@@ -21,6 +21,10 @@ It supports browser login through Cognito, stores tokens locally, refreshes toke
|
|
|
21
21
|
python3 -m pip install -e .
|
|
22
22
|
```
|
|
23
23
|
|
|
24
|
+
```bash
|
|
25
|
+
pip install nexusquant-cli
|
|
26
|
+
```
|
|
27
|
+
|
|
24
28
|
## Login
|
|
25
29
|
|
|
26
30
|
```bash
|
|
@@ -62,14 +66,13 @@ Send a single signal:
|
|
|
62
66
|
nexusquant strategy signal my_alpha_001 \
|
|
63
67
|
--strategy-name "My Alpha" \
|
|
64
68
|
--ticker AAPL \
|
|
65
|
-
--direction
|
|
69
|
+
--direction buy \
|
|
66
70
|
--price 150.25 \
|
|
67
|
-
--level 0.8 \
|
|
68
71
|
--quantity 100 \
|
|
69
72
|
--order-type MARKET
|
|
70
73
|
```
|
|
71
74
|
|
|
72
|
-
`--order-type` is a signal-level setting
|
|
75
|
+
`--order-type` is a signal-level setting: `MARKET` or `LIMIT` (limit uses `--price`). Legacy `NORMAL` is accepted by the CLI as an alias for `LIMIT`. If omitted, the API defaults to `MARKET`.
|
|
73
76
|
|
|
74
77
|
Send a multi-route `signals` map:
|
|
75
78
|
|
|
@@ -112,9 +115,8 @@ nexusquant strategy sub config my_alpha_001
|
|
|
112
115
|
"ticker": "AAPL",
|
|
113
116
|
"time": "2026-04-26T19:30:00Z",
|
|
114
117
|
"price": 150.25,
|
|
115
|
-
"
|
|
116
|
-
"
|
|
117
|
-
"order_type": "NORMAL",
|
|
118
|
+
"direction": "buy",
|
|
119
|
+
"order_type": "LIMIT",
|
|
118
120
|
"quantity": 100,
|
|
119
121
|
"metadata": {}
|
|
120
122
|
}
|
|
@@ -125,12 +127,12 @@ nexusquant strategy sub config my_alpha_001
|
|
|
125
127
|
|
|
126
128
|
Normal use does not require configuration. For staging or local development:
|
|
127
129
|
|
|
128
|
-
| Variable
|
|
129
|
-
|
|
|
130
|
-
| `NEXUSQUANT_API_ENDPOINT`
|
|
131
|
-
| `NEXUSQUANT_API_BASE_URL`
|
|
132
|
-
| `NEXUSQUANT_COGNITO_DOMAIN`
|
|
133
|
-
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id
|
|
134
|
-
| `NEXUSQUANT_REDIRECT_URI`
|
|
130
|
+
| Variable | Meaning |
|
|
131
|
+
| ------------------------------ | ------------------------------------------------------------------------------------- |
|
|
132
|
+
| `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
|
|
133
|
+
| `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
|
|
134
|
+
| `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
|
|
135
|
+
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
|
|
136
|
+
| `NEXUSQUANT_REDIRECT_URI` | OAuth callback, default `http://127.0.0.1:8251/callback` |
|
|
135
137
|
|
|
136
138
|
There is also a hidden typo-compatible alias: `nexusquant startegy ...` maps to `nexusquant strategy ...`.
|
|
@@ -10,6 +10,10 @@ It supports browser login through Cognito, stores tokens locally, refreshes toke
|
|
|
10
10
|
python3 -m pip install -e .
|
|
11
11
|
```
|
|
12
12
|
|
|
13
|
+
```bash
|
|
14
|
+
pip install nexusquant-cli
|
|
15
|
+
```
|
|
16
|
+
|
|
13
17
|
## Login
|
|
14
18
|
|
|
15
19
|
```bash
|
|
@@ -51,14 +55,13 @@ Send a single signal:
|
|
|
51
55
|
nexusquant strategy signal my_alpha_001 \
|
|
52
56
|
--strategy-name "My Alpha" \
|
|
53
57
|
--ticker AAPL \
|
|
54
|
-
--direction
|
|
58
|
+
--direction buy \
|
|
55
59
|
--price 150.25 \
|
|
56
|
-
--level 0.8 \
|
|
57
60
|
--quantity 100 \
|
|
58
61
|
--order-type MARKET
|
|
59
62
|
```
|
|
60
63
|
|
|
61
|
-
`--order-type` is a signal-level setting
|
|
64
|
+
`--order-type` is a signal-level setting: `MARKET` or `LIMIT` (limit uses `--price`). Legacy `NORMAL` is accepted by the CLI as an alias for `LIMIT`. If omitted, the API defaults to `MARKET`.
|
|
62
65
|
|
|
63
66
|
Send a multi-route `signals` map:
|
|
64
67
|
|
|
@@ -101,9 +104,8 @@ nexusquant strategy sub config my_alpha_001
|
|
|
101
104
|
"ticker": "AAPL",
|
|
102
105
|
"time": "2026-04-26T19:30:00Z",
|
|
103
106
|
"price": 150.25,
|
|
104
|
-
"
|
|
105
|
-
"
|
|
106
|
-
"order_type": "NORMAL",
|
|
107
|
+
"direction": "buy",
|
|
108
|
+
"order_type": "LIMIT",
|
|
107
109
|
"quantity": 100,
|
|
108
110
|
"metadata": {}
|
|
109
111
|
}
|
|
@@ -114,12 +116,12 @@ nexusquant strategy sub config my_alpha_001
|
|
|
114
116
|
|
|
115
117
|
Normal use does not require configuration. For staging or local development:
|
|
116
118
|
|
|
117
|
-
| Variable
|
|
118
|
-
|
|
|
119
|
-
| `NEXUSQUANT_API_ENDPOINT`
|
|
120
|
-
| `NEXUSQUANT_API_BASE_URL`
|
|
121
|
-
| `NEXUSQUANT_COGNITO_DOMAIN`
|
|
122
|
-
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id
|
|
123
|
-
| `NEXUSQUANT_REDIRECT_URI`
|
|
119
|
+
| Variable | Meaning |
|
|
120
|
+
| ------------------------------ | ------------------------------------------------------------------------------------- |
|
|
121
|
+
| `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
|
|
122
|
+
| `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
|
|
123
|
+
| `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
|
|
124
|
+
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
|
|
125
|
+
| `NEXUSQUANT_REDIRECT_URI` | OAuth callback, default `http://127.0.0.1:8251/callback` |
|
|
124
126
|
|
|
125
127
|
There is also a hidden typo-compatible alias: `nexusquant startegy ...` maps to `nexusquant strategy ...`.
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
__version__ = "0.1.1"
|
|
@@ -22,6 +22,7 @@ from typing import Any
|
|
|
22
22
|
import httpx
|
|
23
23
|
|
|
24
24
|
from nexusquant_cli.config import (
|
|
25
|
+
clear_credentials,
|
|
25
26
|
cognito_client_id,
|
|
26
27
|
cognito_domain_host,
|
|
27
28
|
load_credentials,
|
|
@@ -214,11 +215,19 @@ def ensure_fresh_id_token(*, force_refresh: bool = False) -> str:
|
|
|
214
215
|
if not refresh_token:
|
|
215
216
|
return str(creds["id_token"])
|
|
216
217
|
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
218
|
+
try:
|
|
219
|
+
new_tokens = refresh_tokens(
|
|
220
|
+
domain_host=cognito_domain_host(),
|
|
221
|
+
client_id=cognito_client_id(),
|
|
222
|
+
refresh_token=refresh_token,
|
|
223
|
+
)
|
|
224
|
+
except RuntimeError as exc:
|
|
225
|
+
if "invalid_grant" in str(exc):
|
|
226
|
+
clear_credentials()
|
|
227
|
+
raise RuntimeError(
|
|
228
|
+
"Refresh token expired or revoked. Please run: nexusquant auth"
|
|
229
|
+
) from exc
|
|
230
|
+
raise
|
|
222
231
|
merged = {
|
|
223
232
|
"id_token": new_tokens.get("id_token") or creds["id_token"],
|
|
224
233
|
"access_token": new_tokens.get("access_token") or creds.get("access_token"),
|
|
@@ -219,19 +219,15 @@ def strategy_signal(
|
|
|
219
219
|
] = None,
|
|
220
220
|
direction: Annotated[
|
|
221
221
|
str | None,
|
|
222
|
-
typer.Option("--direction", help=
|
|
222
|
+
typer.Option("--direction", help='Signal direction: "buy" or "sell".'),
|
|
223
223
|
] = None,
|
|
224
224
|
price: Annotated[
|
|
225
225
|
float | None,
|
|
226
226
|
typer.Option(
|
|
227
227
|
"--price",
|
|
228
|
-
help="Reference price. Required
|
|
228
|
+
help="Reference price. Required when sending; LIMIT orders use this as limit price.",
|
|
229
229
|
),
|
|
230
230
|
] = None,
|
|
231
|
-
level: Annotated[
|
|
232
|
-
float | None,
|
|
233
|
-
typer.Option("--level", help="Signal strength/level, e.g. 0.8."),
|
|
234
|
-
] = None,
|
|
235
231
|
time_value: Annotated[
|
|
236
232
|
str | None,
|
|
237
233
|
typer.Option(
|
|
@@ -239,33 +235,18 @@ def strategy_signal(
|
|
|
239
235
|
help="Signal timestamp in ISO-8601. Defaults to current UTC time when sending.",
|
|
240
236
|
),
|
|
241
237
|
] = None,
|
|
242
|
-
sizing_kind: Annotated[
|
|
243
|
-
str | None,
|
|
244
|
-
typer.Option(
|
|
245
|
-
"--sizing-kind", help="Optional explicit sizing: shares or notional_usd."
|
|
246
|
-
),
|
|
247
|
-
] = None,
|
|
248
238
|
quantity: Annotated[
|
|
249
239
|
int | None,
|
|
250
240
|
typer.Option(
|
|
251
|
-
"--quantity",
|
|
252
|
-
|
|
253
|
-
] = None,
|
|
254
|
-
notional_usd: Annotated[
|
|
255
|
-
float | None,
|
|
256
|
-
typer.Option(
|
|
257
|
-
"--notional-usd",
|
|
258
|
-
help="USD notional for notional_usd sizing. Positive number.",
|
|
241
|
+
"--quantity",
|
|
242
|
+
help="Share quantity (positive integer). Required when sending a single signal.",
|
|
259
243
|
),
|
|
260
244
|
] = None,
|
|
261
245
|
order_type: Annotated[
|
|
262
246
|
str | None,
|
|
263
247
|
typer.Option(
|
|
264
248
|
"--order-type",
|
|
265
|
-
help=(
|
|
266
|
-
"Signal-level broker order type: MARKET (default) or NORMAL "
|
|
267
|
-
"(limit order; uses --price)."
|
|
268
|
-
),
|
|
249
|
+
help='Broker order type: MARKET (default at API) or LIMIT (limit at --price). "NORMAL" is accepted as an alias for LIMIT.',
|
|
269
250
|
),
|
|
270
251
|
] = None,
|
|
271
252
|
metadata_json: Annotated[
|
|
@@ -296,10 +277,10 @@ def strategy_signal(
|
|
|
296
277
|
``nexusquant strategy signal my_alpha --history --limit 20``
|
|
297
278
|
|
|
298
279
|
Single signal:
|
|
299
|
-
``nexusquant strategy signal my_alpha --ticker AAPL --direction
|
|
280
|
+
``nexusquant strategy signal my_alpha --ticker AAPL --direction buy --price 150 --quantity 100 --order-type MARKET``
|
|
300
281
|
|
|
301
282
|
Limit signal:
|
|
302
|
-
``nexusquant strategy signal my_alpha --ticker AAPL --direction
|
|
283
|
+
``nexusquant strategy signal my_alpha --ticker AAPL --direction sell --price 150 --quantity 100 --order-type LIMIT``
|
|
303
284
|
|
|
304
285
|
Signals map:
|
|
305
286
|
``nexusquant strategy signal my_alpha --signals-file signals.json --strategy-name "My Alpha"``
|
|
@@ -310,9 +291,7 @@ def strategy_signal(
|
|
|
310
291
|
ticker,
|
|
311
292
|
direction,
|
|
312
293
|
price,
|
|
313
|
-
level,
|
|
314
294
|
quantity,
|
|
315
|
-
notional_usd,
|
|
316
295
|
order_type,
|
|
317
296
|
signals_file,
|
|
318
297
|
)
|
|
@@ -348,7 +327,7 @@ def strategy_signal(
|
|
|
348
327
|
"--ticker": ticker,
|
|
349
328
|
"--direction": direction,
|
|
350
329
|
"--price": price,
|
|
351
|
-
"--
|
|
330
|
+
"--quantity": quantity,
|
|
352
331
|
}.items()
|
|
353
332
|
if value is None
|
|
354
333
|
]
|
|
@@ -357,23 +336,23 @@ def strategy_signal(
|
|
|
357
336
|
f"Sending a single signal requires: {', '.join(missing)}"
|
|
358
337
|
)
|
|
359
338
|
|
|
339
|
+
direction_norm = direction.strip().lower()
|
|
340
|
+
if direction_norm not in ("buy", "sell"):
|
|
341
|
+
raise typer.BadParameter('--direction must be "buy" or "sell".')
|
|
342
|
+
|
|
360
343
|
signal: dict[str, Any] = {
|
|
361
344
|
"ticker": ticker,
|
|
362
345
|
"time": time_value or _now_iso(),
|
|
363
346
|
"price": price,
|
|
364
|
-
"
|
|
365
|
-
"
|
|
347
|
+
"direction": direction_norm,
|
|
348
|
+
"quantity": int(quantity),
|
|
366
349
|
}
|
|
367
|
-
if sizing_kind is not None:
|
|
368
|
-
signal["sizing_kind"] = sizing_kind
|
|
369
|
-
if quantity is not None:
|
|
370
|
-
signal["quantity"] = quantity
|
|
371
|
-
if notional_usd is not None:
|
|
372
|
-
signal["notional_usd"] = notional_usd
|
|
373
350
|
if order_type is not None:
|
|
374
351
|
normalized_order_type = order_type.strip().upper()
|
|
375
|
-
if normalized_order_type
|
|
376
|
-
|
|
352
|
+
if normalized_order_type == "NORMAL":
|
|
353
|
+
normalized_order_type = "LIMIT"
|
|
354
|
+
if normalized_order_type not in {"MARKET", "LIMIT"}:
|
|
355
|
+
raise typer.BadParameter('--order-type must be MARKET, LIMIT, or legacy alias NORMAL (=LIMIT).')
|
|
377
356
|
signal["order_type"] = normalized_order_type
|
|
378
357
|
metadata = load_json_option(metadata_json, label="metadata")
|
|
379
358
|
if metadata is not None:
|
|
@@ -7,7 +7,7 @@ from typing import Any
|
|
|
7
7
|
|
|
8
8
|
from platformdirs import user_config_dir
|
|
9
9
|
|
|
10
|
-
CONFIG_DIR = Path(user_config_dir("nexusquant-
|
|
10
|
+
CONFIG_DIR = Path(user_config_dir("nexusquant-sdk", appauthor=False))
|
|
11
11
|
CREDENTIALS_PATH = CONFIG_DIR / "credentials.json"
|
|
12
12
|
|
|
13
13
|
DEFAULT_API_ENDPOINT = "https://api.nexusquant.co"
|
|
@@ -1 +0,0 @@
|
|
|
1
|
-
__version__ = "0.1.0"
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|