nexusquant-cli 0.6.0__tar.gz → 0.6.1__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.
Files changed (36) hide show
  1. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/PKG-INFO +1 -1
  2. nexusquant_cli-0.6.1/nexusquant_cli/__init__.py +1 -0
  3. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/data/signal_codes.json +10 -0
  4. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/investor.py +16 -8
  5. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/signal_explain.py +11 -0
  6. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/pyproject.toml +1 -1
  7. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/tests/test_signal_explain.py +19 -0
  8. nexusquant_cli-0.6.0/nexusquant_cli/__init__.py +0 -1
  9. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/.gitignore +0 -0
  10. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/CLAUDE.md +0 -0
  11. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/README.md +0 -0
  12. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/docs/investor-operations.md +0 -0
  13. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/__main__.py +0 -0
  14. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/api_client.py +0 -0
  15. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/auth_pkce.py +0 -0
  16. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/__init__.py +0 -0
  17. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/_util.py +0 -0
  18. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/auth/__init__.py +0 -0
  19. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/auth/cmd.py +0 -0
  20. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/policy/__init__.py +0 -0
  21. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/policy/cmd.py +0 -0
  22. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/strategy/__init__.py +0 -0
  23. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/commands/strategy/cmd.py +0 -0
  24. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/config.py +0 -0
  25. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/embedded.py +0 -0
  26. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/identity.py +0 -0
  27. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/main.py +0 -0
  28. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/nexus.py +0 -0
  29. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/__init__.py +0 -0
  30. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/_agent.py +0 -0
  31. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/admin.py +0 -0
  32. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/auth.py +0 -0
  33. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/nexusquant_cli/roles/provider.py +0 -0
  34. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/scripts/gen_signal_codes.cjs +0 -0
  35. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/tests/__init__.py +0 -0
  36. {nexusquant_cli-0.6.0 → nexusquant_cli-0.6.1}/tests/test_nexus_cli.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.5
2
2
  Name: nexusquant-cli
3
- Version: 0.6.0
3
+ Version: 0.6.1
4
4
  Summary: NexusQuant CLI for users, strategy providers and admins
5
5
  Requires-Python: >=3.10
6
6
  Requires-Dist: httpx>=0.27
@@ -0,0 +1 @@
1
+ __version__ = "0.6.1"
@@ -139,6 +139,16 @@
139
139
  "en": "The broker didn't respond in time, so the order was not sent (not filled). If this keeps happening, please check the broker connection in My strategies."
140
140
  }
141
141
  },
142
+ "in_doubt_absent": {
143
+ "label": {
144
+ "zh": "未执行 · 券商查无此单",
145
+ "en": "Not executed · Broker has no such order"
146
+ },
147
+ "detail": {
148
+ "zh": "下单结果当时未能确认,之后向券商核实,券商没有这笔订单(未成交)。本次信号不会补下。",
149
+ "en": "The result of this order couldn't be confirmed at the time. The broker later confirmed it has no such order (not filled). This signal will not be placed again."
150
+ }
151
+ },
142
152
  "broker_not_connected": {
143
153
  "label": {
144
154
  "zh": "未执行 · 券商未连接",
@@ -344,15 +344,11 @@ def list_signals(
344
344
  for col in ("time", "strategy", "ticker", "side", "price", "status", "your result"):
345
345
  table.add_column(col, overflow="fold")
346
346
  for s in logs:
347
- res = s.get("viewer_broker_result") or {}
348
- if res.get("error"):
349
- detail = f"[red]{res.get('error')}[/red]"
350
- elif res.get("skipped"):
351
- detail = f"[yellow]skipped[/yellow] {res.get('risk_code') or ''}"
352
- elif res.get("is_filled"):
353
- detail = "[green]filled[/green]"
347
+ results = s.get("viewer_broker_results") or []
348
+ if len({r.get("account_tail") or "" for r in results}) > 1:
349
+ detail = "\n".join(f"{r.get('account_tail')} {_result_detail(s, r)}" for r in results)
354
350
  else:
355
- detail = str(s.get("viewer_no_result_reason") or "")
351
+ detail = _result_detail(s, s.get("viewer_broker_result") or {})
356
352
  table.add_row(
357
353
  str(s.get("signal_time") or s.get("received_at"))[:19],
358
354
  str(s.get("strategy_id")), str(s.get("ticker")), str(s.get("direction")),
@@ -366,6 +362,16 @@ def list_signals(
366
362
  )
367
363
 
368
364
 
365
+ def _result_detail(signal: dict, res: dict) -> str:
366
+ if res.get("error"):
367
+ return f"[red]{res.get('error')}[/red]"
368
+ if res.get("skipped"):
369
+ return f"[yellow]skipped[/yellow] {res.get('risk_code') or ''}"
370
+ if res.get("is_filled"):
371
+ return "[green]filled[/green]"
372
+ return str(signal.get("viewer_no_result_reason") or "")
373
+
374
+
369
375
  @read_app.command("signal", help="One signal you received, and why it did or didn't place an order.")
370
376
  def read_signal(
371
377
  signal_id: Annotated[str, typer.Argument(help="Signal id (from `list signals`).")],
@@ -397,6 +403,8 @@ def read_signal(
397
403
  console.print(f"[dim]{key}.{sub}[/dim] {txt.get('zh') if isinstance(txt, dict) else txt}")
398
404
  if ex.get("broker_message"):
399
405
  console.print(f"[dim]broker[/dim] {ex['broker_message']}")
406
+ for acct in ex.get("accounts", []):
407
+ console.print(f"account {acct['account']} {acct['outcome']}")
400
408
 
401
409
 
402
410
  @read_app.command("stats", help="Your signal counts and broker success rate.")
@@ -52,6 +52,17 @@ def _upper_lookup(table: dict[str, Any], code: str | None) -> Any:
52
52
 
53
53
 
54
54
  def explain(row: dict[str, Any]) -> dict[str, Any]:
55
+ """The account the backend picked (a failed one, if any failed), plus every account when there are several."""
56
+ out = _explain_one(row)
57
+ results = row.get("viewer_broker_results") or []
58
+ if len({r.get("account_tail") or "" for r in results}) > 1:
59
+ out["accounts"] = [
60
+ {"account": r.get("account_tail"), **_explain_one({**row, "viewer_broker_result": r})} for r in results
61
+ ]
62
+ return out
63
+
64
+
65
+ def _explain_one(row: dict[str, Any]) -> dict[str, Any]:
55
66
  g = glossary()
56
67
  vbr = row.get("viewer_broker_result") or None
57
68
  listening = row.get("viewer_was_listening_at_signal_time")
@@ -4,7 +4,7 @@ build-backend = "hatchling.build"
4
4
 
5
5
  [project]
6
6
  name = "nexusquant-cli"
7
- version = "0.6.0"
7
+ version = "0.6.1"
8
8
  description = "NexusQuant CLI for users, strategy providers and admins"
9
9
  readme = "README.md"
10
10
  requires-python = ">=3.10"
@@ -162,3 +162,22 @@ def test_embedded_runs_admin_read(http: Recorder, monkeypatch, capsys):
162
162
  config.set_token_override(None)
163
163
  out = capsys.readouterr().out
164
164
  assert rc == 0 and '"u@x.test"' in out
165
+
166
+
167
+ def test_two_accounts_are_listed_separately():
168
+ filled = {"success": True, "is_filled": True, "account_tail": "****0001"}
169
+ failed = {"success": False, "error": "x", "account_tail": "****0002"}
170
+ out = explain(row(viewer_broker_result=failed, viewer_broker_results=[filled, failed]))
171
+ assert out["outcome"] == "failed"
172
+ assert [(a["account"], a["outcome"]) for a in out["accounts"]] == [("****0001", "filled"), ("****0002", "failed")]
173
+
174
+
175
+ def test_same_account_twice_is_not_listed():
176
+ one = {"success": True, "is_filled": True, "account_tail": "****0001"}
177
+ out = explain(row(viewer_broker_result=one, viewer_broker_results=[one, {**one}]))
178
+ assert "accounts" not in out
179
+
180
+
181
+ def test_one_result_has_no_accounts_key():
182
+ out = explain(row(viewer_broker_result={"success": True, "is_filled": True}))
183
+ assert "accounts" not in out
@@ -1 +0,0 @@
1
- __version__ = "0.6.0"
File without changes
File without changes