nexusquant-cli 0.1.0__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.0 → nexusquant_cli-0.1.2}/.gitignore +3 -0
- nexusquant_cli-0.1.2/CLAUDE.md +66 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/PKG-INFO +19 -13
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/README.md +18 -12
- nexusquant_cli-0.1.2/nexusquant_cli/__init__.py +1 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/auth_pkce.py +14 -5
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/strategy/cmd.py +29 -30
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/config.py +1 -1
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/pyproject.toml +1 -1
- nexusquant_cli-0.1.0/nexusquant_cli/__init__.py +0 -1
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/__main__.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/api_client.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/__init__.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/_util.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/auth/__init__.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/auth/cmd.py +0 -0
- {nexusquant_cli-0.1.0 → nexusquant_cli-0.1.2}/nexusquant_cli/commands/strategy/__init__.py +0 -0
- {nexusquant_cli-0.1.0 → 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,12 +66,14 @@ 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
|
-
--
|
|
68
|
-
--
|
|
71
|
+
--quantity 100 \
|
|
72
|
+
--order-type MARKET
|
|
69
73
|
```
|
|
70
74
|
|
|
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`.
|
|
76
|
+
|
|
71
77
|
Send a multi-route `signals` map:
|
|
72
78
|
|
|
73
79
|
```bash
|
|
@@ -109,8 +115,8 @@ nexusquant strategy sub config my_alpha_001
|
|
|
109
115
|
"ticker": "AAPL",
|
|
110
116
|
"time": "2026-04-26T19:30:00Z",
|
|
111
117
|
"price": 150.25,
|
|
112
|
-
"
|
|
113
|
-
"
|
|
118
|
+
"direction": "buy",
|
|
119
|
+
"order_type": "LIMIT",
|
|
114
120
|
"quantity": 100,
|
|
115
121
|
"metadata": {}
|
|
116
122
|
}
|
|
@@ -121,12 +127,12 @@ nexusquant strategy sub config my_alpha_001
|
|
|
121
127
|
|
|
122
128
|
Normal use does not require configuration. For staging or local development:
|
|
123
129
|
|
|
124
|
-
| Variable
|
|
125
|
-
|
|
|
126
|
-
| `NEXUSQUANT_API_ENDPOINT`
|
|
127
|
-
| `NEXUSQUANT_API_BASE_URL`
|
|
128
|
-
| `NEXUSQUANT_COGNITO_DOMAIN`
|
|
129
|
-
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id
|
|
130
|
-
| `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` |
|
|
131
137
|
|
|
132
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,12 +55,14 @@ 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
|
-
--
|
|
57
|
-
--
|
|
60
|
+
--quantity 100 \
|
|
61
|
+
--order-type MARKET
|
|
58
62
|
```
|
|
59
63
|
|
|
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`.
|
|
65
|
+
|
|
60
66
|
Send a multi-route `signals` map:
|
|
61
67
|
|
|
62
68
|
```bash
|
|
@@ -98,8 +104,8 @@ nexusquant strategy sub config my_alpha_001
|
|
|
98
104
|
"ticker": "AAPL",
|
|
99
105
|
"time": "2026-04-26T19:30:00Z",
|
|
100
106
|
"price": 150.25,
|
|
101
|
-
"
|
|
102
|
-
"
|
|
107
|
+
"direction": "buy",
|
|
108
|
+
"order_type": "LIMIT",
|
|
103
109
|
"quantity": 100,
|
|
104
110
|
"metadata": {}
|
|
105
111
|
}
|
|
@@ -110,12 +116,12 @@ nexusquant strategy sub config my_alpha_001
|
|
|
110
116
|
|
|
111
117
|
Normal use does not require configuration. For staging or local development:
|
|
112
118
|
|
|
113
|
-
| Variable
|
|
114
|
-
|
|
|
115
|
-
| `NEXUSQUANT_API_ENDPOINT`
|
|
116
|
-
| `NEXUSQUANT_API_BASE_URL`
|
|
117
|
-
| `NEXUSQUANT_COGNITO_DOMAIN`
|
|
118
|
-
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id
|
|
119
|
-
| `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` |
|
|
120
126
|
|
|
121
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,23 +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",
|
|
241
|
+
"--quantity",
|
|
242
|
+
help="Share quantity (positive integer). Required when sending a single signal.",
|
|
252
243
|
),
|
|
253
244
|
] = None,
|
|
254
|
-
|
|
255
|
-
|
|
245
|
+
order_type: Annotated[
|
|
246
|
+
str | None,
|
|
256
247
|
typer.Option(
|
|
257
|
-
"--
|
|
258
|
-
help=
|
|
248
|
+
"--order-type",
|
|
249
|
+
help='Broker order type: MARKET (default at API) or LIMIT (limit at --price). "NORMAL" is accepted as an alias for LIMIT.',
|
|
259
250
|
),
|
|
260
251
|
] = None,
|
|
261
252
|
metadata_json: Annotated[
|
|
@@ -273,7 +264,8 @@ def strategy_signal(
|
|
|
273
264
|
readable=True,
|
|
274
265
|
help=(
|
|
275
266
|
"UTF-8 JSON object for multi-route signals map. Keys are default or user_id; "
|
|
276
|
-
"values are signal objects. When provided,
|
|
267
|
+
"values are signal objects and may include order_type. When provided, "
|
|
268
|
+
"single-signal options are ignored."
|
|
277
269
|
),
|
|
278
270
|
),
|
|
279
271
|
] = None,
|
|
@@ -285,7 +277,10 @@ def strategy_signal(
|
|
|
285
277
|
``nexusquant strategy signal my_alpha --history --limit 20``
|
|
286
278
|
|
|
287
279
|
Single signal:
|
|
288
|
-
``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``
|
|
281
|
+
|
|
282
|
+
Limit signal:
|
|
283
|
+
``nexusquant strategy signal my_alpha --ticker AAPL --direction sell --price 150 --quantity 100 --order-type LIMIT``
|
|
289
284
|
|
|
290
285
|
Signals map:
|
|
291
286
|
``nexusquant strategy signal my_alpha --signals-file signals.json --strategy-name "My Alpha"``
|
|
@@ -296,9 +291,8 @@ def strategy_signal(
|
|
|
296
291
|
ticker,
|
|
297
292
|
direction,
|
|
298
293
|
price,
|
|
299
|
-
level,
|
|
300
294
|
quantity,
|
|
301
|
-
|
|
295
|
+
order_type,
|
|
302
296
|
signals_file,
|
|
303
297
|
)
|
|
304
298
|
)
|
|
@@ -333,7 +327,7 @@ def strategy_signal(
|
|
|
333
327
|
"--ticker": ticker,
|
|
334
328
|
"--direction": direction,
|
|
335
329
|
"--price": price,
|
|
336
|
-
"--
|
|
330
|
+
"--quantity": quantity,
|
|
337
331
|
}.items()
|
|
338
332
|
if value is None
|
|
339
333
|
]
|
|
@@ -342,19 +336,24 @@ def strategy_signal(
|
|
|
342
336
|
f"Sending a single signal requires: {', '.join(missing)}"
|
|
343
337
|
)
|
|
344
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
|
+
|
|
345
343
|
signal: dict[str, Any] = {
|
|
346
344
|
"ticker": ticker,
|
|
347
345
|
"time": time_value or _now_iso(),
|
|
348
346
|
"price": price,
|
|
349
|
-
"
|
|
350
|
-
"
|
|
347
|
+
"direction": direction_norm,
|
|
348
|
+
"quantity": int(quantity),
|
|
351
349
|
}
|
|
352
|
-
if
|
|
353
|
-
|
|
354
|
-
|
|
355
|
-
|
|
356
|
-
|
|
357
|
-
|
|
350
|
+
if order_type is not None:
|
|
351
|
+
normalized_order_type = order_type.strip().upper()
|
|
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).')
|
|
356
|
+
signal["order_type"] = normalized_order_type
|
|
358
357
|
metadata = load_json_option(metadata_json, label="metadata")
|
|
359
358
|
if metadata is not None:
|
|
360
359
|
signal["metadata"] = metadata
|
|
@@ -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
|