Metadata-Version: 2.5
Name: nexusquant-cli
Version: 0.2.1
Summary: NexusQuant strategy provider CLI
Requires-Python: >=3.10
Requires-Dist: httpx>=0.27
Requires-Dist: nexusquant-sdk>=0.2
Requires-Dist: platformdirs>=4.2
Requires-Dist: rich>=13.7
Requires-Dist: typer>=0.12
Description-Content-Type: text/markdown

# nexusquant-cli

Command line tool for NexusQuant strategy providers.

It supports browser login through Cognito, stores tokens locally, refreshes tokens, and calls the Nexus strategy provider APIs. Provider commands require a Cognito user with `custom:userType=strategyProvider` or `custom:userType=admin`.

## Install

```bash
python3 -m pip install -e .
```

```bash
pip install nexusquant-cli
```

## Login

```bash
nexusquant auth
nexusquant auth --status
nexusquant auth --refresh
nexusquant auth --logout
```

Tokens are stored in the current OS user's config directory, for example `~/Library/Application Support/nexusquant-cli/credentials.json` on macOS. The file is written with `0600` permissions when supported.

## Strategy Commands

Create or update a strategy:

```bash
nexusquant strategy create \
  --strategy-id my_alpha_001 \
  --name "My Alpha" \
  --schema-file schema.json \
  --output-unit SHARE_COUNT
```

List strategies registered by the current provider. Admin users see all strategies:

```bash
nexusquant strategy list
```

List signal history for one strategy:

```bash
nexusquant strategy signal my_alpha_001 --history --limit 20
```

Send a single signal:

```bash
nexusquant strategy signal my_alpha_001 \
  --strategy-name "My Alpha" \
  --ticker AAPL \
  --direction buy \
  --price 150.25 \
  --quantity 100 \
  --order-type MARKET
```

`--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`.

Send a multi-route `signals` map:

```bash
nexusquant strategy signal my_alpha_001 \
  --strategy-name "My Alpha" \
  --signals-file signals.json
```

Get non-PII subscriber config for a strategy:

```bash
nexusquant strategy sub config my_alpha_001
```

## Policy Commands

A SizingPolicy replaces the fixed `--quantity` on a signal with a **formula** that
decides the order quantity — and optionally a limit price — at order time. You
publish the formula once; each signal then carries only the parameter values it
needs. Values that depend on the individual subscriber's account are never sent by
you: they are bound inside that user's own container when the order is placed.

Requires `custom:userType=strategyProvider` or `admin`, the same as the strategy
commands.

### See which names a formula may use

The server registry is the authority — a name outside it cannot be published. Run
this before writing a formula:

```bash
nexusquant policy features
nexusquant policy features --scope shared    # names you send with each signal
nexusquant policy features --scope account   # names bound in the user's container
nexusquant policy features --json            # raw output for scripting
```

Account-scoped names carry a "suppliable" flag: a name that exists but that the
container cannot currently supply will pass your editor and fail at publish.

### Publish a formula

```bash
nexusquant policy publish --strategy-id my_alpha --ticker TQQQ --file tqqq.json
nexusquant policy publish --strategy-id my_alpha --ticker TQQQ --file tqqq.json --profile normal
```

`--file` holds the two things a policy is made of — the formulas keyed by output
slot, and the names you promise to supply with every signal:

```json
{
  "expr": {
    "f_depth":       "exp(-k_depth * depth / 10)",
    "buy.quantity":  "target_shares * f_depth * clamp(1 - pos_ratio, 0, 1)",
    "buy.price":     "ref_price * (1 - slip)",
    "sell.quantity": "held_shares * exit_frac"
  },
  "params": ["target_shares", "k_depth", "depth", "ref_price", "slip", "exit_frac"]
}
```

Output slots — the container picks the one matching the signal's direction:

| Slot | What the formula yields |
|---|---|
| `buy.quantity` / `sell.quantity` | **Share count** (floored) |
| `buy.price` / `sell.price` | **Limit price** — supplied ⇒ limit order, omitted ⇒ market order |

Intermediate names (`f_depth`) are fine, but an `expr` must define at least one
output slot, and an intermediate name may not contain a dot. `--ticker` is separate
from the file because it is not part of the artifact: the same formula pointed at
another symbol is the same formula.

⚠️ **`publish` IS the go-live action.** There is no shadow or canary step, and the
previous version on the same slot is deactivated. `--profile` defaults to `normal`.

Before sending anything, the CLI fetches the registry and checks locally that every
free name in your formula has someone to supply it — forgetting to list a name in
`params` is the most common mistake. `--skip-name-check` disables that fetch, and
then local success does not imply the server will accept.

### List and stop

```bash
nexusquant policy list my_alpha
nexusquant policy list my_alpha --ticker TQQQ --mode active   # --mode active / off

# Emergency stop for a live version — not a promotion step, since publish already went live
nexusquant policy set-mode --policy-id 'TQQQ/normal/…/abc123' --mode off
nexusquant policy set-mode --policy-id 'TQQQ/normal/…/abc123' --mode active
```

### Send a signal against a policy

Every declared parameter must be present, or the signal is rejected:

```bash
nexusquant policy send-signal \
  --strategy-id my_alpha --ticker TQQQ --price 51.2 --direction buy \
  -p target_shares=10 -p k_depth=0.5 -p depth=3 \
  -p ref_price=51.2 -p slip=0.002 -p exit_frac=0.25

# Or pass all parameter values as a JSON object
nexusquant policy send-signal --strategy-id my_alpha --ticker TQQQ \
  --price 51.2 --direction buy --params-file params.json

# Validate and print the payload without sending it
nexusquant policy send-signal ... --dry-run
```

| Flag | Meaning |
|---|---|
| `--param` / `-p` | One parameter value, `name=value`; repeat per declared name |
| `--params-file` | A JSON object with all parameter values instead |
| `--policy-id` | Defaults to the active policy on this `(strategy, ticker)` |
| `--quantity` | Used when the formula has no slot for this direction |
| `--order-type` | `MARKET` / `LIMIT` |
| `--strategy-name` | Defaults to the strategy id |
| `--no-fetch` | Skip fetching the policy — see below |
| `--dry-run` | Validate and print the payload, send nothing |

By default the CLI fetches the active policy first and uses it to validate and
coerce your values. Three checks, each mirroring the server:

| Situation | Result | Why not something else |
|---|---|---|
| A declared parameter is missing | Error | No defaults — a default turns "unknown" into "known" |
| A name you did not declare | Error | The formula cannot reference it; extra names mean the two sides disagree |
| An account-state name (`pos_ratio`, `held_shares`, …) | Error | Its value never leaves the execution plane |

`--no-fetch` skips the fetch, and then **only the account-state rule is checked** —
the rest is unknowable locally, so it is not pretended.

⚠️ **The server is always the authority.** Passing local validation does not mean
the server will accept — policy state and review status live there. This layer only
moves the obvious mistakes onto your machine, where you can see which field is
wrong instead of guessing from a rejection.

⚠️ **There is no publish-time bound on order size.** Parameters are names only,
with no declared domain, so nothing proves at publish time that a formula stays
under a limit. How large an order it can place is clamped at order time by the
subscriber's own `max_order_cash_usd`. Keep your formulas in a sane range yourself.

## JSON Inputs

`--schema-file` must contain the `strategy_schema` object accepted by `nexus-service`, for example:

```json
{
  "type": "object",
  "properties": {
    "window": {
      "type": "integer",
      "default": 14,
      "title": "Window",
      "source": "user"
    }
  },
  "required": []
}
```

`--signals-file` must contain a JSON object whose keys are `default` or user ids:

```json
{
  "default": {
    "ticker": "AAPL",
    "time": "2026-04-26T19:30:00Z",
    "price": 150.25,
    "direction": "buy",
    "order_type": "LIMIT",
    "quantity": 100,
    "metadata": {}
  }
}
```

## Environment Overrides

Normal use does not require configuration. For staging or local development:

| Variable                       | Meaning                                                                               |
| ------------------------------ | ------------------------------------------------------------------------------------- |
| `NEXUSQUANT_API_ENDPOINT`      | API endpoint host, default `https://api.nexusquant.co`; CLI calls `/api/...` under it |
| `NEXUSQUANT_API_BASE_URL`      | Optional full API base override, e.g. `https://api.nexusquant.co/api`                 |
| `NEXUSQUANT_COGNITO_DOMAIN`    | Cognito Hosted UI host, default `auth.lookatwallstreet.com`                           |
| `NEXUSQUANT_COGNITO_CLIENT_ID` | Cognito app client id                                                                 |
| `NEXUSQUANT_REDIRECT_URI`      | OAuth callback, default `http://127.0.0.1:8251/callback`                              |

There is also a hidden typo-compatible alias: `nexusquant startegy ...` maps to `nexusquant strategy ...`.
