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.
@@ -4,3 +4,6 @@ __pycache__/
4
4
  *.egg-info/
5
5
  dist/
6
6
  build/
7
+ *.ipynb
8
+ .idea/
9
+ .DS_Store
@@ -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.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 bull \
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. Use `MARKET` for market orders or `NORMAL` for limit orders; `NORMAL` uses the signal `--price` as the limit reference. If omitted, the service defaults to `MARKET`.
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
- "level": 0.8,
116
- "direction": "bull",
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 | Meaning |
129
- | --- | --- |
130
- | `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
131
- | `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
132
- | `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
133
- | `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
134
- | `NEXUSQUANT_REDIRECT_URI` | OAuth callback, default `http://127.0.0.1:8251/callback` |
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 bull \
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. Use `MARKET` for market orders or `NORMAL` for limit orders; `NORMAL` uses the signal `--price` as the limit reference. If omitted, the service defaults to `MARKET`.
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
- "level": 0.8,
105
- "direction": "bull",
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 | Meaning |
118
- | --- | --- |
119
- | `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
120
- | `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
121
- | `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
122
- | `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
123
- | `NEXUSQUANT_REDIRECT_URI` | OAuth callback, default `http://127.0.0.1:8251/callback` |
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
- new_tokens = refresh_tokens(
218
- domain_host=cognito_domain_host(),
219
- client_id=cognito_client_id(),
220
- refresh_token=refresh_token,
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="Signal direction: bull or bear."),
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 for send and notional_usd sizing.",
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", help="Share quantity for shares sizing. Positive integer."
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 bull --price 150 --level 0.8 --quantity 100 --order-type MARKET``
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 bear --price 150 --level 0.8 --quantity 100 --order-type NORMAL``
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
- "--level": level,
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
- "level": level,
365
- "direction": direction,
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 not in {"MARKET", "NORMAL"}:
376
- raise typer.BadParameter("--order-type must be MARKET or NORMAL.")
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-cli", appauthor=False))
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"
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nexusquant-cli"
7
- version = "0.1.1"
7
+ version = "0.1.2"
8
8
  description = "NexusQuant strategy provider CLI"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -1 +0,0 @@
1
- __version__ = "0.1.0"