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.
@@ -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.0
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 bull \
69
+ --direction buy \
66
70
  --price 150.25 \
67
- --level 0.8 \
68
- --quantity 100
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
- "level": 0.8,
113
- "direction": "bull",
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 | Meaning |
125
- | --- | --- |
126
- | `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
127
- | `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
128
- | `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
129
- | `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
130
- | `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` |
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 bull \
58
+ --direction buy \
55
59
  --price 150.25 \
56
- --level 0.8 \
57
- --quantity 100
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
- "level": 0.8,
102
- "direction": "bull",
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 | Meaning |
114
- | --- | --- |
115
- | `NEXUSQUANT_API_ENDPOINT` | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
116
- | `NEXUSQUANT_API_BASE_URL` | Optional full API base override, e.g. `https://api.nexusquant.co/api` |
117
- | `NEXUSQUANT_COGNITO_DOMAIN` | Cognito Hosted UI host, default `auth.lookatwallstreet.com` |
118
- | `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id |
119
- | `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` |
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
- 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,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", help="Share quantity for shares sizing. Positive integer."
241
+ "--quantity",
242
+ help="Share quantity (positive integer). Required when sending a single signal.",
252
243
  ),
253
244
  ] = None,
254
- notional_usd: Annotated[
255
- float | None,
245
+ order_type: Annotated[
246
+ str | None,
256
247
  typer.Option(
257
- "--notional-usd",
258
- help="USD notional for notional_usd sizing. Positive number.",
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, single-signal options are ignored."
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 bull --price 150 --level 0.8 --quantity 100``
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
- notional_usd,
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
- "--level": level,
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
- "level": level,
350
- "direction": direction,
347
+ "direction": direction_norm,
348
+ "quantity": int(quantity),
351
349
  }
352
- if sizing_kind is not None:
353
- signal["sizing_kind"] = sizing_kind
354
- if quantity is not None:
355
- signal["quantity"] = quantity
356
- if notional_usd is not None:
357
- signal["notional_usd"] = notional_usd
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-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.0"
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"