Compare commits
5 Commits
de561b88b1
...
tools/pi_w
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
519565965e | ||
|
|
5607bfcc2c | ||
|
|
722ead5384 | ||
|
|
88eaf20363 | ||
|
|
d9284b7b75 |
@@ -382,6 +382,7 @@ EXAMPLES:
|
|||||||
parser.add_argument("--sessions", help="Comma-separated list of sessions")
|
parser.add_argument("--sessions", help="Comma-separated list of sessions")
|
||||||
parser.add_argument("--msg", default=f"Operator says CONTINUE. {AGENT_NICK} here, saying hi!", help="Wake message")
|
parser.add_argument("--msg", default=f"Operator says CONTINUE. {AGENT_NICK} here, saying hi!", help="Wake message")
|
||||||
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
parser.add_argument("--debug", action="store_true", help="Enable debug logging")
|
||||||
|
parser.add_argument("--dry-run", action="store_true", help="Show what would be done without executing")
|
||||||
parser.set_defaults(mode="install")
|
parser.set_defaults(mode="install")
|
||||||
return parser
|
return parser
|
||||||
|
|
||||||
@@ -417,6 +418,12 @@ def main() -> int:
|
|||||||
logger.info(f"Message: {args.msg}")
|
logger.info(f"Message: {args.msg}")
|
||||||
if args.mode == "succession":
|
if args.mode == "succession":
|
||||||
logger.info(f"Count: {args.count}")
|
logger.info(f"Count: {args.count}")
|
||||||
|
if args.dry_run:
|
||||||
|
logger.info("DRY RUN: no actions will be executed")
|
||||||
|
|
||||||
|
if args.dry_run:
|
||||||
|
logger.info("DRY RUN: would execute mode '%s' with sessions=%s, interval=%s, message='%s'", args.mode, sessions, args.interval, args.msg)
|
||||||
|
return 0
|
||||||
|
|
||||||
try:
|
try:
|
||||||
if args.mode == "install":
|
if args.mode == "install":
|
||||||
|
|||||||
51
pi_wake_agent.skill.json
Normal file
51
pi_wake_agent.skill.json
Normal file
@@ -0,0 +1,51 @@
|
|||||||
|
{
|
||||||
|
"name": "pi_wake_agent",
|
||||||
|
"description": "Multi-agent wake-up timer with self-cron/daemon/succession modes. Sends doorbell injections via zellij and durable messages via h5i bus.",
|
||||||
|
"parameters": {
|
||||||
|
"type": "object",
|
||||||
|
"properties": {
|
||||||
|
"mode": {
|
||||||
|
"type": "string",
|
||||||
|
"enum": ["install", "once", "daemon", "succession", "run", "remove", "list", "status", "validate"],
|
||||||
|
"description": "Operation mode"
|
||||||
|
},
|
||||||
|
"interval": {
|
||||||
|
"type": "string",
|
||||||
|
"pattern": "^\\d+[hms]$",
|
||||||
|
"default": "1h",
|
||||||
|
"description": "Interval duration. Formats: 30m, 1h, 90m, 2h, 10s"
|
||||||
|
},
|
||||||
|
"session": {
|
||||||
|
"type": "array",
|
||||||
|
"items": { "type": "string" },
|
||||||
|
"description": "Zellij session name(s). Repeatable."
|
||||||
|
},
|
||||||
|
"sessions": {
|
||||||
|
"type": "string",
|
||||||
|
"description": "Comma-separated list of sessions"
|
||||||
|
},
|
||||||
|
"message": {
|
||||||
|
"type": "string",
|
||||||
|
"default": "Operator says CONTINUE. pi_nvnemo here, saying hi!",
|
||||||
|
"description": "Wake message sent to agent(s)"
|
||||||
|
},
|
||||||
|
"count": {
|
||||||
|
"type": "integer",
|
||||||
|
"minimum": 1,
|
||||||
|
"default": 1,
|
||||||
|
"description": "Number of runs for succession mode"
|
||||||
|
},
|
||||||
|
"dry_run": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Show what would be done without executing"
|
||||||
|
},
|
||||||
|
"debug": {
|
||||||
|
"type": "boolean",
|
||||||
|
"default": false,
|
||||||
|
"description": "Enable debug logging"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"required": ["mode"]
|
||||||
|
}
|
||||||
|
}
|
||||||
242
pi_wake_agent.skill.yaml
Normal file
242
pi_wake_agent.skill.yaml
Normal file
@@ -0,0 +1,242 @@
|
|||||||
|
# pi_wake_agent Skill Definition
|
||||||
|
# Universal Agent Skill Format (YAML)
|
||||||
|
# Compatible with: Anthropic, LangChain, OpenAI Functions, Custom Agents
|
||||||
|
|
||||||
|
skill:
|
||||||
|
name: "pi_wake_agent"
|
||||||
|
version: "1.1.0"
|
||||||
|
description: |
|
||||||
|
Multi-agent wake-up timer with self-cron/daemon/succession modes.
|
||||||
|
Sends doorbell injections via zellij and durable messages via h5i bus.
|
||||||
|
Designed for the DOLPHIN fleet (pi_nvnemo, cmd, mimo, codex, etc.).
|
||||||
|
|
||||||
|
author: "pi_nvnemo"
|
||||||
|
repository: "https://github.com/dolphinng5/pi_wake_agent"
|
||||||
|
branch: "tools/pi_wake_agent"
|
||||||
|
license: "MIT"
|
||||||
|
|
||||||
|
# ─── Installation ─────────────────────────────────────────────────────
|
||||||
|
installation:
|
||||||
|
method: "script"
|
||||||
|
path: "/mnt/dolphinng5_predict/pi_wake_agent.py"
|
||||||
|
requirements:
|
||||||
|
- python >= 3.8
|
||||||
|
- zellij (for terminal injections)
|
||||||
|
- h5i (for bus messaging, optional)
|
||||||
|
test_command: "python3 -m pytest test_pi_wake_agent.py -v"
|
||||||
|
|
||||||
|
# ─── Capabilities ─────────────────────────────────────────────────────
|
||||||
|
capabilities:
|
||||||
|
- name: "install_cron"
|
||||||
|
description: "Install recurring wake-up timer via system cron"
|
||||||
|
modes: ["install"]
|
||||||
|
- name: "once"
|
||||||
|
description: "One-shot wake after interval (no cron)"
|
||||||
|
modes: ["once"]
|
||||||
|
- name: "daemon"
|
||||||
|
description: "Long-lived process, no cron needed"
|
||||||
|
modes: ["daemon"]
|
||||||
|
- name: "succession"
|
||||||
|
description: "Run N times at interval, then self-clean (remove cron)"
|
||||||
|
modes: ["succession"]
|
||||||
|
- name: "run_wake"
|
||||||
|
description: "Internal: execute wake action (called by cron)"
|
||||||
|
modes: ["run"]
|
||||||
|
- name: "remove_cron"
|
||||||
|
description: "Remove cron timer entry"
|
||||||
|
modes: ["remove"]
|
||||||
|
- name: "list_cron"
|
||||||
|
description: "List active cron timers"
|
||||||
|
modes: ["list"]
|
||||||
|
- name: "status"
|
||||||
|
description: "Show cron + one-shot timer status"
|
||||||
|
modes: ["status"]
|
||||||
|
- name: "validate_sessions"
|
||||||
|
description: "Validate zellij sessions exist"
|
||||||
|
modes: ["validate"]
|
||||||
|
|
||||||
|
# ─── Parameters ───────────────────────────────────────────────────────
|
||||||
|
parameters:
|
||||||
|
mode:
|
||||||
|
type: "string"
|
||||||
|
enum: ["install", "once", "daemon", "succession", "run", "run", "remove", "list", "status", "validate"]
|
||||||
|
required: true
|
||||||
|
default: "install"
|
||||||
|
description: "Operation mode"
|
||||||
|
|
||||||
|
interval:
|
||||||
|
type: "string"
|
||||||
|
pattern: "^\\d+[hms]$"
|
||||||
|
default: "1h"
|
||||||
|
description: "Interval duration. Formats: 30m, 1h, 90m, 2h, 10s"
|
||||||
|
examples: ["1h", "30m", "90m", "2h", "10s"]
|
||||||
|
|
||||||
|
session:
|
||||||
|
type: "array"
|
||||||
|
items:
|
||||||
|
type: "string"
|
||||||
|
description: "Zellij session name(s). Repeatable flag."
|
||||||
|
examples: [["cc_UV_dev0_Fb"], ["cc_UV_dev0_Fb", "cc_UV_dev1_48"]]
|
||||||
|
|
||||||
|
sessions:
|
||||||
|
type: "string"
|
||||||
|
description: "Comma-separated list of sessions (alternative to --session)"
|
||||||
|
examples: ["cc_UV_dev0_Fb,cc_UV_dev1_48"]
|
||||||
|
|
||||||
|
message:
|
||||||
|
type: "string"
|
||||||
|
default: "Operator says CONTINUE. pi_nvnemo here, saying hi!"
|
||||||
|
description: "Wake message sent to agent(s)"
|
||||||
|
|
||||||
|
count:
|
||||||
|
type: "integer"
|
||||||
|
minimum: 1
|
||||||
|
default: 1
|
||||||
|
description: "Number of runs for succession mode"
|
||||||
|
|
||||||
|
dry_run:
|
||||||
|
type: "boolean"
|
||||||
|
default: false
|
||||||
|
description: "Show what would be done without executing"
|
||||||
|
|
||||||
|
debug:
|
||||||
|
type: "boolean"
|
||||||
|
default: false
|
||||||
|
description: "Enable debug logging"
|
||||||
|
|
||||||
|
json:
|
||||||
|
type: "boolean"
|
||||||
|
default: false
|
||||||
|
description: "Output JSON for machine parsing"
|
||||||
|
|
||||||
|
config:
|
||||||
|
type: "string"
|
||||||
|
description: "Path to config file (JSON/YAML)"
|
||||||
|
|
||||||
|
# ─── Returns ──────────────────────────────────────────────────────────
|
||||||
|
returns:
|
||||||
|
type: "object"
|
||||||
|
properties:
|
||||||
|
exit_code:
|
||||||
|
type: "integer"
|
||||||
|
description: "0 = success, non-zero = error"
|
||||||
|
cron_entry:
|
||||||
|
type: "string"
|
||||||
|
description: "Installed cron line (for install mode)"
|
||||||
|
log_file:
|
||||||
|
type: "string"
|
||||||
|
value: "/tmp/pi_wake_agent.log"
|
||||||
|
message_sent:
|
||||||
|
type: "boolean"
|
||||||
|
description: "Whether wake message was sent"
|
||||||
|
|
||||||
|
# ─── Side Effects ─────────────────────────────────────────────────────
|
||||||
|
side_effects:
|
||||||
|
- "Modifies system crontab (install/remove modes)"
|
||||||
|
- "Injects text into zellij sessions (zellij write-chars)"
|
||||||
|
- "Sends h5i bus message to Fable (fire-and-forget)"
|
||||||
|
- "Writes to /tmp/pi_wake_agent.log (rotated at 10MB)"
|
||||||
|
- "Creates PID files in /tmp/pi_wake_agent_*.pid (once mode)"
|
||||||
|
|
||||||
|
# ─── h5i Bus Protocol ─────────────────────────────────────────────────
|
||||||
|
bus_protocol:
|
||||||
|
agent_id: "pi_nvnemo"
|
||||||
|
target: "Fable"
|
||||||
|
message_format: "[pi_nvnemo via zellij] {message} Run: h5i-bus msg inbox"
|
||||||
|
keypresses: 5
|
||||||
|
keypress_delay_ms: 1000
|
||||||
|
bus_message: "{message} (timer wakeup)"
|
||||||
|
timeout_ms: 5000
|
||||||
|
fire_and_forget: true
|
||||||
|
silently_ignore_failures: true
|
||||||
|
|
||||||
|
# ─── Zellij Integration ───────────────────────────────────────────────
|
||||||
|
zellij:
|
||||||
|
command_check: "zellij list-sessions"
|
||||||
|
injection_method: "zellij --session {session} action write-chars"
|
||||||
|
enter_keypress: "zellij --session {session} action write 13"
|
||||||
|
ansi_strip: true
|
||||||
|
session_exists_check: true
|
||||||
|
|
||||||
|
# ─── Cron Format ──────────────────────────────────────────────────────
|
||||||
|
cron:
|
||||||
|
comment_format: "pi_wake_agent:{sessions}:{interval}"
|
||||||
|
session_delimiter: ","
|
||||||
|
command_template: "cd /mnt/dolphinng5_predict && export H5I_AGENT=pi_nvnemo && /mnt/dolphinng5_predict/pi_wake_agent.py --run --sessions '{sessions}' --msg '{message}'"
|
||||||
|
|
||||||
|
# ─── Logging ──────────────────────────────────────────────────────────
|
||||||
|
logging:
|
||||||
|
file: "/tmp/pi_wake_agent.log"
|
||||||
|
rotation_mb: 10
|
||||||
|
max_files: 5
|
||||||
|
format: "[YYYY-MM-DD HH:MM:SS] [LEVEL] message"
|
||||||
|
levels: ["DEBUG", "INFO", "WARN", "ERROR"]
|
||||||
|
|
||||||
|
# ─── Examples ─────────────────────────────────────────────────────────
|
||||||
|
examples:
|
||||||
|
- name: "Recurring 1-hour wake for one session"
|
||||||
|
command: "pi_wake_agent.py --install --interval 1h --session cc_UV_dev0_Fb"
|
||||||
|
- name: "Multi-session 30-minute wake"
|
||||||
|
command: 'pi_wake_agent.py --install --interval 30m --sessions "cc_UV_dev0_Fb,cc_UV_dev1_48" --msg "Wake up!"'
|
||||||
|
- name: "One-shot in 2 hours"
|
||||||
|
command: 'pi_wake_agent.py --once --interval 2h --session cc_UV_dev0_Fb --msg "Time\'s up!"'
|
||||||
|
- name: "Run 3 times at 1-hour intervals, then self-clean"
|
||||||
|
command: 'pi_wake_agent.py --succession --count 3 --interval 1h --session cc_UV_dev0_Fb --msg "Scheduled wake"'
|
||||||
|
- name: "Daemon mode (long-lived process, no cron)"
|
||||||
|
command: 'pi_wake_agent.py --daemon --interval 1h --session cc_UV_dev0_Fb'
|
||||||
|
- name: "Remove timer"
|
||||||
|
command: 'pi_wake_agent.py --remove --session cc_UV_dev0_Fb --interval 1h'
|
||||||
|
- name: "List / status"
|
||||||
|
command: "pi_wake_agent.py --list"
|
||||||
|
command: "pi_wake_agent.py --status"
|
||||||
|
- name: "Validate sessions exist"
|
||||||
|
command: 'pi_wake_agent.py --validate --session cc_UV_dev0_Fb'
|
||||||
|
- name: "Dry run (show what would happen)"
|
||||||
|
command: 'pi_wake_agent.py --install --interval 1h --session test --msg "test" --dry-run'
|
||||||
|
|
||||||
|
# ─── Error Handling ───────────────────────────────────────────────────
|
||||||
|
error_handling:
|
||||||
|
- condition: "missing_session"
|
||||||
|
action: "return_error"
|
||||||
|
message: "--session or --sessions required"
|
||||||
|
exit_code: 1
|
||||||
|
- condition: "invalid_interval"
|
||||||
|
action: "return_error"
|
||||||
|
message: "Invalid interval format. Use like 1h, 30m, 90m, 2h"
|
||||||
|
exit_code: 1
|
||||||
|
- condition: "invalid_count"
|
||||||
|
action: "return_error"
|
||||||
|
message: "--count must be >= 1 for succession mode"
|
||||||
|
exit_code: 1
|
||||||
|
- condition: "cron_failure"
|
||||||
|
action: "log_warning_continue"
|
||||||
|
description: "Cron install/remove logs warning but continues"
|
||||||
|
- condition: "zellij_not_found"
|
||||||
|
action: "log_warning_continue"
|
||||||
|
description: "Session injection fails silently, bus message still sent"
|
||||||
|
- condition: "h5i_timeout"
|
||||||
|
action: "silent_ignore"
|
||||||
|
description: "h5i bus message failures are silently ignored (fire-and-forget)"
|
||||||
|
|
||||||
|
# ─── Testing ──────────────────────────────────────────────────────────
|
||||||
|
testing:
|
||||||
|
test_file: "test_pi_wake_agent.py"
|
||||||
|
test_count: 38
|
||||||
|
coverage:
|
||||||
|
- unit_interval_parsing
|
||||||
|
- unit_cron_comments
|
||||||
|
- unit_session_parsing
|
||||||
|
- integration_install_remove_list
|
||||||
|
- integration_once_short
|
||||||
|
- integration_succession_short
|
||||||
|
- edge_cases_invalid_intervals
|
||||||
|
- edge_cases_missing_sessions
|
||||||
|
- edge_cases_invalid_counts
|
||||||
|
run_command: "python3 -m pytest test_pi_wake_agent.py -v"
|
||||||
|
|
||||||
|
# ─── Metadata ─────────────────────────────────────────────────────────
|
||||||
|
metadata:
|
||||||
|
created: "2026-07-08"
|
||||||
|
updated: "2026-07-08"
|
||||||
|
maintainer: "pi_nvnemo"
|
||||||
|
tags: ["wake-timer", "cron", "zellij", "h5i", "multi-agent", "doorbell", "self-cleaning"]
|
||||||
105
prod/docs/BEADS_PASS_TRACKER_EVALUATION.md
Normal file
105
prod/docs/BEADS_PASS_TRACKER_EVALUATION.md
Normal file
@@ -0,0 +1,105 @@
|
|||||||
|
# BEADS as PASS Tracker — Evaluation & Recommendation
|
||||||
|
|
||||||
|
**Author:** pi_nvnemo (UV Overseer)
|
||||||
|
**Date:** 2026-07-08
|
||||||
|
**Context:** UV_OVERSEER_CHARTER__PI.md §6 task — evaluate beads vs bus+doc for PASS board
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Current State
|
||||||
|
|
||||||
|
| Tracker | Status |
|
||||||
|
|---------|--------|
|
||||||
|
| **h5i bus** | Active — dispatch, ACK, status updates |
|
||||||
|
| **Status doc** | Not yet created (charter says "track PASSes on the bus + a short status doc") |
|
||||||
|
| **beads** | Installed, `.beads/` exists at repo root (prefix `dp`), 1 existing PRODGREEN issue |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## What Beads Gives Us Over Bus+Doc
|
||||||
|
|
||||||
|
| Capability | h5i Bus + Doc | Beads |
|
||||||
|
|------------|---------------|-------|
|
||||||
|
| **Dependency graph** | Manual (doc) | Native (`br dep add`, `br graph`) |
|
||||||
|
| **Task hierarchy** | Flat (doc sections) | Epic → child beads (parent/child) |
|
||||||
|
| **State machine** | Manual (doc) | Enforced (open → in_progress → closed) |
|
||||||
|
| **Acceptance criteria** | Doc prose | Structured fields (`acceptance_criteria`, `test_command`) |
|
||||||
|
| **Audit trail** | Bus history + doc edits | Immutable JSONL + SQL + `br audit` |
|
||||||
|
| **Handoff protocol** | Informal | Formal (`br audit --message`, `br ready`) |
|
||||||
|
| **Multi-agent isolation** | Bus channels | Separate workspace per refactor stream |
|
||||||
|
| **Low-skill agent onboarding** | Ad-hoc | Bounded task template + dependency chain |
|
||||||
|
| **Query/Filter** | grep/awk | `br ready`, `br list`, `br status`, SQL |
|
||||||
|
| **Backup/Sync** | Git + manual | `br sync`, `br backup` |
|
||||||
|
|
||||||
|
**Verdict:** Beads adds **structured task management** that the bus+doc lacks — critical for multi-PASS dependency chains (PASS-P → PASS-A → PASS-S → PASS-B/X).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## PASS → Bead Mapping
|
||||||
|
|
||||||
|
| PASS | Bead Type | Suggested ID | Parent |
|
||||||
|
|------|-----------|--------------|--------|
|
||||||
|
| **PASS-P** (Pulse Landing) | Epic | `UV_PASS-P` | — |
|
||||||
|
| ├─ Copy `prod/uv_pulse_host/` | Task | `UV_PASS-P.1` | `UV_PASS-P` |
|
||||||
|
| ├─ Add `.gitignore` (exclude `target/`) | Task | `UV_PASS-P.2` | `UV_PASS-P` |
|
||||||
|
| ├─ Cert conveyor commit | Task | `UV_PASS-P.3` | `UV_PASS-P` |
|
||||||
|
| ├─ Soak DARK + TUI heartbeat | Task | `UV_PASS-P.4` | `UV_PASS-P` |
|
||||||
|
| **PASS-A** (Account Region) | Epic | `UV_PASS-A` | — |
|
||||||
|
| ├─ Phase 0: Contracts + in-mem | Task | `UV_PASS-A.1` | `UV_PASS-A` |
|
||||||
|
| ├─ Phase 1: Real shm + hardened reader | Task | `UV_PASS-A.2` | `UV_PASS-A` |
|
||||||
|
| ├─ Phase 2: ASEx publish | Task | `UV_PASS-A.3` | `UV_PASS-A` |
|
||||||
|
| ├─ Phase 3: Capital provider | Task | `UV_PASS-A.4` | `UV_PASS-A` |
|
||||||
|
| **PASS-S** (Sizing Seam) | Epic | `UV_PASS-S` | — |
|
||||||
|
| **PASS-B** (Host Brain) | Epic | `UV_PASS-B` | — |
|
||||||
|
| **PASS-X** (Tick Exits) | Epic | `UV_PASS-X` | — |
|
||||||
|
|
||||||
|
**Dependency Chain:**
|
||||||
|
```
|
||||||
|
UV_PASS-P → UV_PASS-A → UV_PASS-S → UV_PASS-B → UV_PASS-X
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Smallest Viable Setup
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# 1. Create dedicated UV workspace (isolate from PRODGREEN)
|
||||||
|
mkdir -p /mnt/dolphinng5_predict/uv/.beads
|
||||||
|
export BEADS_DIR=/mnt/dolphinng5_predict/uv/.beads
|
||||||
|
|
||||||
|
# 2. Initialize
|
||||||
|
br where # confirms workspace
|
||||||
|
|
||||||
|
# 3. Create PASS-P epic + children
|
||||||
|
br create --title "PASS-P: Pulse Landing" --type epic --id UV_PASS-P
|
||||||
|
br create --title "Copy prod/uv_pulse_host/ from /mnt/vp-PASS9" --parent UV_PASS-P --type task --acceptance "Directory copied, target/ excluded" --test "ls prod/uv_pulse_host/ && ! ls prod/uv_pulse_host/target/" --id UV_PASS-P.1
|
||||||
|
br create --title "Add .gitignore excluding target/" --parent UV_PASS-P --type task --acceptance "target/ ignored by git" --test "git check-ignore prod/uv_pulse_host/target/" --id UV_PASS-P.2
|
||||||
|
br create --title "Cert conveyor commit + integrator review" --parent UV_PASS-P --type task --acceptance "Commit on main, integrator signed" --test "git log --oneline -1 prod/uv_pulse_host/" --id UV_PASS-P.3
|
||||||
|
br create --title "Soak DARK + TUI heartbeat + STALE" --parent UV_PASS-P --type task --acceptance "TUI renders live rate/AGE/STALE, RSS flat ≥4h" --test "TUI smoke test + log review" --id UV_PASS-P.4
|
||||||
|
|
||||||
|
# 4. Link to bus for dispatch notifications
|
||||||
|
# (beads = source of truth; bus = real-time signal)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recommendation
|
||||||
|
|
||||||
|
**ADOPT BEADS for PASS tracking** with the following protocol:
|
||||||
|
|
||||||
|
1. **Beads = Source of Truth** — all PASS state, dependencies, acceptance criteria, audit trail
|
||||||
|
2. **h5i Bus = Real-time Signal** — dispatch, ACK, status pings, escalation (what we already do)
|
||||||
|
3. **Status Doc = Snapshot** — auto-generated from beads weekly or on demand (`br status > PASS_BOARD.md`)
|
||||||
|
|
||||||
|
**Migration Path:**
|
||||||
|
- Week 1: Create UV workspace, populate PASS-P + PASS-A epics/children
|
||||||
|
- Week 1: Run dual-track (beads + bus) — validate no drift
|
||||||
|
- Week 2: Deprecate manual status doc; auto-generate from `br status`
|
||||||
|
|
||||||
|
**Why not bus+doc alone?** The PASS chain has 5 epics with 15+ children, strict dependencies, and must survive agent rotation. Beads enforces what the charter demands: "keep a live board; chase stalls; escalate blocked specs."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Action
|
||||||
|
|
||||||
|
If approved: I'll initialize `/mnt/dolphinng5_predict/uv/.beads`, populate PASS-P epic + children, and link dispatch messages to bead IDs.
|
||||||
@@ -0,0 +1,659 @@
|
|||||||
|
# PINK Forensics — Dual Leverage Architecture (2026 Search Results)
|
||||||
|
|
||||||
|
**Date:** 2026-07-06
|
||||||
|
**Agent:** pi_nvnemo
|
||||||
|
**Trigger:** Operator request — locate the authoritative dual-leverage spec
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Executive Summary
|
||||||
|
|
||||||
|
The DOLPHIN system implements a **strict dual-leverage architecture** separating two distinct leverage concepts that must NEVER be conflated:
|
||||||
|
|
||||||
|
| Layer | Name | Range | Purpose | Set By |
|
||||||
|
|-------|------|-------|---------|--------|
|
||||||
|
| **Internal** | **Conviction Leverage** (our_leverage) | 0.5 – 9.0 (fractional) | Sizes QUANTITY: `notional = capital × 0.20 × conviction`, `qty = notional / entry_price` | Strategy / sizer (`esf_alpha_orchestrator`, `AlphaBetSizer`) |
|
||||||
|
| **Venue** | **Exchange Leverage** (xlev) | 1 – 3 (integer) | Controls MARGIN: `margin = notional / exchange_lev` sent to BingX API | Venue boundary mapper (`prod/bingx/leverage.py`) |
|
||||||
|
|
||||||
|
**PnL is ALWAYS leverage-free**: `qty × Δprice` (side-signed). Exchange leverage only affects collateral lockup.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Authoritative Source Files (Bit-Identity Required)
|
||||||
|
|
||||||
|
### 1. `prod/bingx/leverage.py` — **THE SINGLE SOURCE OF TRUTH** (83 lines, no callers)
|
||||||
|
|
||||||
|
```python
|
||||||
|
CONVICTION_MIN = 0.5
|
||||||
|
CONVICTION_MAX = 9.0
|
||||||
|
EXCHANGE_LEV_MIN = 1
|
||||||
|
EXCHANGE_LEV_MAX = 3
|
||||||
|
LEVERAGE_MAPPING_RULE = "round_half_even_linear_0.5_to_9.0_to_1_to_exchange_cap"
|
||||||
|
|
||||||
|
def map_internal_conviction_to_exchange_leverage_target(internal, *, exchange_min, exchange_max) -> float:
|
||||||
|
# clamp internal to [0.5, 9.0]
|
||||||
|
# linear: exchange_min + (internal - 0.5)/(9.0 - 0.5) * (exchange_max - exchange_min)
|
||||||
|
# returns FLOAT target (pre-round)
|
||||||
|
|
||||||
|
def normalize_bingx_leverage_value(leverage, *, exchange_min, exchange_max) -> int:
|
||||||
|
# ROUND_HALF_EVEN (banker's: 1.5→2, 2.5→2, 3.5→4) + clamp to [exchange_min, exchange_max]
|
||||||
|
|
||||||
|
def map_internal_conviction_to_exchange_leverage(internal, *, exchange_min, exchange_max) -> int:
|
||||||
|
# = normalize_bingx_leverage_value(map_..._target(internal), ...)
|
||||||
|
# FINAL integer sent to BingX API
|
||||||
|
```
|
||||||
|
|
||||||
|
### 2. `prod/clean_arch/runtime/pink_direct.py:_hz_publish()` (line ~909)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def _hz_publish(self, slot_dict: dict, acc: dict) -> None:
|
||||||
|
"""Fire-and-forget Hz write after any kernel state change.
|
||||||
|
|
||||||
|
Computes system leverage (our_leverage = notional/capital) for the Hz
|
||||||
|
snapshot — PINK/BLUE dual-leverage invariant: system leverage reflects real
|
||||||
|
margin utilisation; exchange leverage (1-3x cap) is set at BingX API level.
|
||||||
|
"""
|
||||||
|
size = float(slot_dict.get("size") or 0.0)
|
||||||
|
ep = float(slot_dict.get("entry_price") or 0.0)
|
||||||
|
capital = float(acc.get("capital") or 0.0)
|
||||||
|
our_leverage = (size * ep / capital) if capital > 1e-10 else 0.0
|
||||||
|
self.hz_state_writer.write_engine_snapshot(
|
||||||
|
slot_dict, acc,
|
||||||
|
posture=self._last_posture,
|
||||||
|
our_leverage=our_leverage, # <-- CONVICTION leverage published to Hz
|
||||||
|
scan_number=self._last_scan_number,
|
||||||
|
vel_div=self._last_vel_div,
|
||||||
|
vol_ok=self._last_vol_ok,
|
||||||
|
)
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Spec Documents (Chronological)
|
||||||
|
|
||||||
|
### A. `prod/docs/FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md` (2025-04-24)
|
||||||
|
**Origin story** — CRITICAL bug: exchange leverage was hardcoded to 1x, ignoring per-trade fractional leverage.
|
||||||
|
- "The system correctly separates leverage into two roles"
|
||||||
|
- Fractional leverage → affects quantity (how many contracts)
|
||||||
|
- Exchange leverage → affects margin (how much collateral)
|
||||||
|
- Fix: CEIL rounding for exchange leverage (`ceil(fractional_lev)` clamped to [1,9])
|
||||||
|
|
||||||
|
### B. `prod/docs/PINK_ACCOUNTING_EXEC_FIX.md` (2026-06-11)
|
||||||
|
**Forensic incident** — FET short settled at +$164 but kernel booked −$5,990.90.
|
||||||
|
**HARD INVARIANT (§0):**
|
||||||
|
> **Dual leverage**: `slot.size` = exchange quantity; `slot.leverage` = exchange leverage (1–3x cap, set at BingX API); *our*-leverage (conviction) = `size × entry_price / capital`, computed **only** at `pink_direct._hz_publish` (line ~911). PnL is therefore **leverage-free**: `qty × Δprice`, side-signed. Do not touch the conviction→exchange mapping (`round_half_even_linear_0.5_to_9.0_to_1_to_exchange_cap`) or `target_size` computation.
|
||||||
|
|
||||||
|
### C. `prod/docs/VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md` (2026-06-15)
|
||||||
|
**VIOLET L3 wrapper spec** — "WRAP, DON'T REIMPLEMENT"
|
||||||
|
- V-TYPES boundary: `ConvictionLeverage` (Annotated float) → `ExchangeLeverage` (Annotated int ≥1)
|
||||||
|
- `VioletExchangeLeverage` class wraps `prod/bingx/leverage.py` functions exactly
|
||||||
|
- Gate: MC bit-identity @ N≥1e6 vs real `leverage.py` output
|
||||||
|
- Zero shared-file edits; bit-identity is the contract
|
||||||
|
|
||||||
|
### D. `prod/docs/VIOLET_V3_FINDINGS.md` §2 (2026-06-15)
|
||||||
|
> **DUAL-LEVERAGE:** conviction leverage sizes the QUANTITY (internal); exchange leverage mapped at venue boundary via `prod/bingx/leverage.py` `map_internal_conviction_to_exchange_leverage_target` (round_half_even linear 0.5–9.0 → 1..cap; PINK/VIOLET use max-3× **linear** translator).
|
||||||
|
|
||||||
|
### E. `prod/docs/PRODGREEN_TUI_AND_LEVERAGE_OBSERVABILITY_SPEC.md` (2026)
|
||||||
|
**TUI display labels:**
|
||||||
|
- `cm:` for conviction multiplier
|
||||||
|
- `xlev:` for exchange leverage
|
||||||
|
- `lev:` legacy (visually secondary)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Terms / Vocabulary
|
||||||
|
|
||||||
|
| Term | Meaning | Where Defined |
|
||||||
|
|------|---------|---------------|
|
||||||
|
| `conviction leverage` / `our_leverage` | Internal fractional [0.5, 9.0], sizes quantity | `pink_direct.py:_hz_publish` |
|
||||||
|
| `exchange leverage` / `xlev` | Integer [1,3] sent to BingX API | `leverage.py`, `pink_direct.py` |
|
||||||
|
| `dual-leverage doctrine` | The separation principle | `PINK_ACCOUNTING_EXEC_FIX.md` §0 |
|
||||||
|
| `round_half_even` | Banker's rounding (x.5 → even) | `leverage.py`, `VIOLET_SUB_SPEC__L3` |
|
||||||
|
| `map_internal_conviction_to_exchange_leverage` | The mapper function | `leverage.py` |
|
||||||
|
| `target_exchange_leverage` | Float pre-round value | `VIOLET_SUB_SPEC__L3` |
|
||||||
|
| `exchange_leverage` | Final int sent to venue | `VIOLET_SUB_SPEC__L3` |
|
||||||
|
| `notional` | `capital × 0.20 × conviction` | `esf_alpha_orchestrator.py` |
|
||||||
|
| `base_fraction` | 0.20 (constant in BLUE) | `VIOLET_V3_FINDINGS.md` §2 |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Execution Flow (PINK → BingX)
|
||||||
|
|
||||||
|
```
|
||||||
|
1. BLUE/VIOLET sizer computes conviction ∈ [0.5, 9.0]
|
||||||
|
2. notional = capital × 0.20 × conviction
|
||||||
|
3. quantity = notional / entry_price
|
||||||
|
4. At venue boundary (pink_direct / execution.py):
|
||||||
|
target = map_internal_conviction_to_exchange_leverage_target(conviction) # float
|
||||||
|
xlev = normalize_bingx_leverage_value(target) # int [1,3]
|
||||||
|
5. BingX API: POST /leverage {"symbol": "...", "side": "BOTH", "leverage": xlev}
|
||||||
|
6. Margin locked = notional / xlev
|
||||||
|
7. PnL calculation: qty × (exit_price - entry_price) [NO leverage factor]
|
||||||
|
8. Hz snapshot publishes: our_leverage = (size × entry_price) / capital
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VIOLET Integration Points
|
||||||
|
|
||||||
|
| Component | Role | File |
|
||||||
|
|-----------|------|------|
|
||||||
|
| `VioletExchangeLeverage` | V-TYPES wrapper, bit-identity gated | `prod/clean_arch/violet/exchange_leverage.py` |
|
||||||
|
| `TradeabilityProjection` | L1→L3 projector (conviction → xlev + margin) | `prod/clean_arch/violet/tradeability.py` (Task 6) |
|
||||||
|
| `ShadowDecision` | L1 output carrying `conviction_leverage` | `decision_engine.py` |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mutation Litmus (What Breaks If Conflated)
|
||||||
|
|
||||||
|
| Mutation | Expected Test Failure |
|
||||||
|
|----------|----------------------|
|
||||||
|
| Use `exchange_leverage` in PnL calc | `test_pink_ditav2_accounting_invariants.py` — realized PnL 3× inflated |
|
||||||
|
| Use `conviction` as BingX leverage | Margin rejection or over-leverage (BingX max 3× for PINK) |
|
||||||
|
| Round-half-up instead of half-even | `VIOLET_SUB_SPEC__L3` gate: 2.5→3 instead of 2, bit-identity fails |
|
||||||
|
| Drop the clamp to [1,3] | BingX API rejects leverage >3 for PINK symbols |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Files to Audit (Per Search)
|
||||||
|
|
||||||
|
- `prod/clean_arch/runtime/pink_direct.py` — `_hz_publish`, `_exec_submit`, intent leverage flow
|
||||||
|
- `prod/bingx/execution.py` — `_ensure_leverage`, `_normalize_bingx_leverage_value` (legacy CEIL, not ROUND_HALF_EVEN)
|
||||||
|
- `prod/clean_arch/violet/exchange_leverage.py` — VIOLET L3 wrapper
|
||||||
|
- `prod/clean_arch/violet/tradeability.py` — L3 projector (if built)
|
||||||
|
- `esf_alpha_orchestrator.py` — 5-factor conviction composition (base × DC × ACB × OB × EsoF)
|
||||||
|
- `alpha_wrappers.py` — VIOLET V-TYPES for `ConvictionLeverage`
|
||||||
|
- `prod/tests/test_pink_ditav2_accounting_invariants.py` — Accounting tests
|
||||||
|
- `prod/tests/test_violet_exchange_leverage.py` — VIOLET L3 gate tests
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Operator Directives (Binding)
|
||||||
|
|
||||||
|
1. **NEVER reimplement `leverage.py` logic** — wrap it (VIOLET L3 spec, non-negotiable)
|
||||||
|
2. **PnL is leverage-free** — `qty × Δprice` only (PINK_ACCOUNTING_EXEC_FIX.md HARD INVARIANT)
|
||||||
|
3. **Bit-identity gate** — VIOLET output must `==` `prod/bingx/leverage.py` output exactly (MC N≥1e6)
|
||||||
|
4. **ROUND_HALF_EVEN** — not round-half-up, not CEIL, not floor (banker's rounding)
|
||||||
|
5. **Conviction sizes qty; exchange lev sizes margin** — the two paths are orthogonal after notional
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search Provenance
|
||||||
|
|
||||||
|
Found via: `grep -r "dual.leverage\|our.*leverage.*exchange\|conviction.*multiplier\|map_internal_conviction_to_exchange" /mnt/dolphinng5_predict/prod/docs --include="*.md"`
|
||||||
|
|
||||||
|
Key hits: `PINK_ACCOUNTING_EXEC_FIX.md`, `VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md`, `FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md`, `VIOLET_V3_FINDINGS.md`, `PRODGREEN_TUI_AND_LEVERAGE_OBSERVABILITY_SPEC.md`, `INDEX_REVIEW_alpha_engine.md`, `UV_TASK_T19_UV_CLOCK_HOST.md`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Next Search Vectors (Operator Guidance)
|
||||||
|
|
||||||
|
- Search `esf_alpha_orchestrator.py` for 5-factor conviction composition
|
||||||
|
- Search `alpha_wrappers.py` for V-TYPES `ConvictionLeverage` definition
|
||||||
|
- Search `prod/bingx/execution.py` for legacy CEIL vs ROUND_HALF_EVEN divergence
|
||||||
|
- Trace `dolphin_actor.py` tag `lev:X.XX` → execution path
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Additional Findings (Extended Search)
|
||||||
|
|
||||||
|
### 1. `prod/bingx/sizing_mode.py` — Sizing Mode Contract
|
||||||
|
- Three modes: `engine` (default, no BingX payload), `testnet`, `live_market`
|
||||||
|
- `build_split_sizing_payload()` emits BingX-ready sizing with `exchange_leverage_cap`
|
||||||
|
- Delegates to `prod.utils.trade_sizing_bridge.build_engine_ready_sizing()`
|
||||||
|
|
||||||
|
### 2. `prod/utils/trade_sizing_bridge.py` — Engine-Ready Sizing Translation
|
||||||
|
**Core function:** `size_trade_from_sizing_lev()` — the complete translation pipeline:
|
||||||
|
|
||||||
|
```python
|
||||||
|
# Input: sizing_lev (conviction), capital, mark_price, etc.
|
||||||
|
# Output: TradeSizingResult with:
|
||||||
|
# - internal_leverage: cubic-convex conviction ∈ [0.5, 9.0]
|
||||||
|
# - exchange_leverage_target: float (pre-round, linear map)
|
||||||
|
# - exchange_leverage: int (ROUND_HALF_EVEN + clamp to [1, exchange_cap])
|
||||||
|
# - effective_notional: min(venue_cap, margin_budget × exchange_leverage)
|
||||||
|
# - quantity: floor(effective_notional / mark_price / step_size) × step_size
|
||||||
|
# - margin_to_capital, notional_to_capital ratios
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key constants:**
|
||||||
|
- `DEFAULT_BINGX_EXCHANGE_LEVERAGE_CAP = 3`
|
||||||
|
- `DEFAULT_MIN_INTERNAL_LEVERAGE = 0.5`
|
||||||
|
- `DEFAULT_MAX_INTERNAL_LEVERAGE = 9.0`
|
||||||
|
- `DEFAULT_LEVERAGE_CONVEXITY = 3.0` (cubic!)
|
||||||
|
- `DEFAULT_MARGIN_BUDGET_FRACTION = 0.20`
|
||||||
|
|
||||||
|
**Convexity note:** The "cubic" in "max-3× cubic translator" refers to the **conviction sizing curve** (`strength_cubic = clamp(...)³`), NOT the exchange leverage mapping. The exchange mapping is **linear** with **ROUND_HALF_EVEN**.
|
||||||
|
|
||||||
|
### 3. `prod/clean_arch/adapters/bingx_direct.py` — DITAv2 Venue Adapter
|
||||||
|
- Uses `map_internal_conviction_to_exchange_leverage()` from `prod.bingx.leverage`
|
||||||
|
- Default `exchange_leverage_cap = 3`
|
||||||
|
- Applies leverage per-symbol via cache `_configured_leverage`
|
||||||
|
|
||||||
|
### 4. `prod/clean_arch/dita_v2/blue_parity.py` — BLUE Parity Wrapper
|
||||||
|
**DUAL-LEVERAGE INVARIANT (docstring):**
|
||||||
|
> "the fractional leverage produced here is STRATEGY conviction — it sizes the quantity. At-exchange leverage is derived from it at the venue boundary via map_internal_conviction_to_exchange_leverage() (linear [0.5, 9.0] → [1, cap], bankers rounding, security cap)."
|
||||||
|
|
||||||
|
### 5. `prod/clean_arch/dita_v2/test_blue_parity.py` — Parity Tests
|
||||||
|
**TestConvictionToExchangeLeverage class validates:**
|
||||||
|
```python
|
||||||
|
m(0.5) == 1 # conviction floor → exchange floor
|
||||||
|
m(9.0) == 3 # conviction ceiling → exchange cap (3)
|
||||||
|
m(4.75) == 2 # exact midpoint [0.5, 9.0] → target 2.0 → round_half_even(2.0) = 2
|
||||||
|
m(0.1) == 1 # clamped below conviction floor
|
||||||
|
m(50.0) == 3 # clamped above conviction ceiling
|
||||||
|
# monotonic: {1, 2, 3} across conviction range
|
||||||
|
```
|
||||||
|
|
||||||
|
### 6. `prod/docs/NAUTILUS_DOLPHIN_SPEC.md` — Sizing Formula
|
||||||
|
```
|
||||||
|
leverage = min_leverage + (max_leverage - min_leverage) × (signal_strength)^leverage_convexity
|
||||||
|
# leverage_convexity = 3.0 → CUBIC
|
||||||
|
strength_cubic = clamp((threshold - vel_div) / (threshold - extreme), 0, 1) ** 3
|
||||||
|
```
|
||||||
|
|
||||||
|
### 7. `prod/docs/SYSTEM_BIBLE_v7.md` §38.5 (margin-sizing addendum)
|
||||||
|
> "internal sizing leverage and BingX exchange leverage are separate layers. Exchange leverage controls the required margin; strategy leverage controls sizing intent."
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Leverage Flow (End-to-End)
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ BLUE STRATEGY (esf_alpha_orchestrator) │
|
||||||
|
│ signal_strength = clamp((|vel_div| - threshold) / (extreme - threshold)) │
|
||||||
|
│ strength_cubic = signal_strength ** 3.0 ← CUBIC CONVEXITY │
|
||||||
|
│ raw_leverage = base × DC_boost × ACB_regime × OB_consensus × EsoF_haircut │
|
||||||
|
│ clamped to [0.5, 9.0] │
|
||||||
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
||||||
|
│ conviction ∈ [0.5, 9.0]
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ PINK / VIOLET VENUE BOUNDARY │
|
||||||
|
│ target = map_internal_conviction_to_exchange_leverage_target(conviction) │
|
||||||
|
│ = 1.0 + (conviction - 0.5) / 8.5 × (3.0 - 1.0) ← LINEAR │
|
||||||
|
│ ∈ [1.0, 3.0] (float) │
|
||||||
|
│ xlev = normalize_bingx_leverage_value(target) │
|
||||||
|
│ = ROUND_HALF_EVEN(target) clamped to [1, 3] ← BANKER'S ROUNDING │
|
||||||
|
│ ∈ {1, 2, 3} (int) │
|
||||||
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
||||||
|
│ exchange_leverage ∈ {1, 2, 3}
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ BINGX EXECUTION │
|
||||||
|
│ POST /trade/leverage {"symbol": "...", "side": "BOTH", "leverage": xlev} │
|
||||||
|
│ margin = notional / xlev │
|
||||||
|
└──────────────────────────────────┬──────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ ACCOUNTING (PnL) │
|
||||||
|
│ qty = notional / entry_price │
|
||||||
|
│ PnL = qty × (exit_price - entry_price) ← LEVERAGE-FREE │
|
||||||
|
│ our_leverage = (size × entry_price) / capital ← PUBLISHED TO Hz │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Critical Distinction: CEIL vs ROUND_HALF_EVEN
|
||||||
|
|
||||||
|
| Context | Rounding | Source |
|
||||||
|
|---------|----------|--------|
|
||||||
|
| **Old execution.py fix (2025-04-24)** | `ceil(fractional_lev)` | `FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md` |
|
||||||
|
| **Current production `leverage.py`** | `ROUND_HALF_EVEN` (banker's) | `prod/bingx/leverage.py` |
|
||||||
|
| **VIOLET L3 wrapper** | `ROUND_HALF_EVEN` (bit-identical gate) | `VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md` |
|
||||||
|
|
||||||
|
**The CEIL fix was superseded** by the cleaner `leverage.py` module with banker's rounding. The production code now uses `prod/bingx/leverage.py` exclusively.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ROUND_HALF_EVEN Boundary Cases (Tested)
|
||||||
|
|
||||||
|
| Conviction | Target (float) | ROUND_HALF_EVEN | Final xlev |
|
||||||
|
|------------|----------------|-----------------|------------|
|
||||||
|
| 0.5 | 1.0 | 1 | 1 |
|
||||||
|
| ~2.82 | 1.5 | 2 | 2 |
|
||||||
|
| 4.75 | 2.0 | 2 | 2 |
|
||||||
|
| ~6.68 | 2.5 | 2 | 2 ← BANKER'S: 2.5 → 2 |
|
||||||
|
| 9.0 | 3.0 | 3 | 3 |
|
||||||
|
|
||||||
|
The "max-3× cubic translator" phrase in VIOLET docs refers to:
|
||||||
|
- **Cubic** = conviction sizing curve (strength³)
|
||||||
|
- **3×** = exchange leverage cap (1–3)
|
||||||
|
- **Translator** = the linear + ROUND_HALF_EVEN mapper
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Source Code Inventory (All Leverage-Related)
|
||||||
|
|
||||||
|
| File | Role |
|
||||||
|
|------|------|
|
||||||
|
| `prod/bingx/leverage.py` | **SOURCE OF TRUTH** — pure functions, 83 lines, no callers |
|
||||||
|
| `prod/bingx/config.py` | `exchange_leverage_cap: PositiveInt = 3` default |
|
||||||
|
| `prod/bingx/execution.py` | Venue client, wraps leverage.py functions |
|
||||||
|
| `prod/bingx/sizing_mode.py` | Sizing mode contract (engine/testnet/live) |
|
||||||
|
| `prod/utils/trade_sizing_bridge.py` | Full sizing pipeline with margin math |
|
||||||
|
| `prod/clean_arch/adapters/bingx_direct.py` | DITAv2 venue adapter |
|
||||||
|
| `prod/clean_arch/dita_v2/blue_parity.py` | BLUE parity wrapper (docstrings the invariant) |
|
||||||
|
| `prod/clean_arch/dita_v2/test_blue_parity.py` | Parity tests including dual-leverage |
|
||||||
|
| `prod/clean_arch/runtime/pink_direct.py` | PINK runtime, `_hz_publish` computes `our_leverage` |
|
||||||
|
| `prod/clean_arch/violet/exchange_leverage.py` | VIOLET L3 typed wrapper (bit-identity gated) |
|
||||||
|
| `prod/clean_arch/violet/test_violet_exchange_leverage.py` | VIOLET L3 gate tests (N≥1e6 MC) |
|
||||||
|
| `prod/clean_arch/violet/exec_intent.py` | VIOLET PASS4 DARK intent projection |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mutation Litmus (What Breaks If Wrong)
|
||||||
|
|
||||||
|
| Mutation | Test That Catches It |
|
||||||
|
|----------|---------------------|
|
||||||
|
| Use `exchange_leverage` in PnL formula | `test_pink_ditav2_accounting_invariants.py` |
|
||||||
|
| Use `conviction` directly as BingX leverage | Margin rejection (BingX max 3× for PINK) |
|
||||||
|
| ROUND_HALF_UP instead of ROUND_HALF_EVEN | `test_violet_exchange_leverage.py::test_round_half_even_boundary_cases` (2.5→3 fails) |
|
||||||
|
| Drop clamp to [1,3] | BingX API rejects leverage >3 |
|
||||||
|
| Conflate the two leverage concepts | `PINK_ACCOUNTING_EXEC_FIX.md` HARD INVARIANT violation |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search Provenance (Complete)
|
||||||
|
|
||||||
|
```
|
||||||
|
# Primary searches
|
||||||
|
grep -r "dual.leverage\|map_internal_conviction_to_exchange" prod/docs --include="*.md"
|
||||||
|
grep -r "CONVICTION_MIN\|EXCHANGE_LEV_MAX\|LEVERAGE_MAPPING_RULE" prod --include="*.py"
|
||||||
|
grep -r "round_half_even\|ROUND_HALF_EVEN" prod --include="*.py"
|
||||||
|
grep -r "exchange_leverage_cap" prod --include="*.py"
|
||||||
|
|
||||||
|
# Key files examined
|
||||||
|
prod/bingx/leverage.py ← SOURCE OF TRUTH
|
||||||
|
prod/bingx/config.py ← Default cap = 3
|
||||||
|
prod/bingx/execution.py ← Venue client
|
||||||
|
prod/bingx/sizing_mode.py ← Mode contract
|
||||||
|
prod/utils/trade_sizing_bridge.py ← Full pipeline
|
||||||
|
prod/clean_arch/adapters/bingx_direct.py ← DITAv2 adapter
|
||||||
|
prod/clean_arch/dita_v2/blue_parity.py ← BLUE parity + invariant docstring
|
||||||
|
prod/clean_arch/dita_v2/test_blue_parity.py ← Parity tests
|
||||||
|
prod/clean_arch/runtime/pink_direct.py ← PINK runtime, _hz_publish
|
||||||
|
prod/clean_arch/violet/exchange_leverage.py ← VIOLET L3 wrapper
|
||||||
|
prod/clean_arch/violet/test_violet_exchange_leverage.py ← VIOLET gate tests
|
||||||
|
prod/clean_arch/violet/exec_intent.py ← VIOLET PASS4 intent
|
||||||
|
|
||||||
|
# Spec docs
|
||||||
|
prod/docs/FRACTIONAL_LEVERAGE_TO_BINGX_FIX.md ← Origin story (CEIL fix)
|
||||||
|
prod/docs/PINK_ACCOUNTING_EXEC_FIX.md ← Forensic HARD INVARIANT
|
||||||
|
prod/docs/VIOLET_SUB_SPEC__L3_EXCHANGE_LEVERAGE.md ← VIOLET L3 spec
|
||||||
|
prod/docs/VIOLET_V3_FINDINGS.md ← V3 findings
|
||||||
|
prod/docs/BINGX_MARGIN_SIZING_RULE.md ← Operational rule
|
||||||
|
prod/docs/SYSTEM_BIBLE_v7.md ← §38.5 margin-sizing addendum
|
||||||
|
prod/docs/NAUTILUS_DOLPHIN_SPEC.md ← Cubic sizing formula
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## CRITICAL CORRECTION: Actual PINK Runtime Was DITA v1 (NOT DITAv2)
|
||||||
|
|
||||||
|
**The running PINK system that traded on BingX VST used `prod/clean_arch/dita/` (DITA v1), NOT `prod/clean_arch/dita_v2/`.**
|
||||||
|
|
||||||
|
DITAv2 (`prod/clean_arch/dita_v2/`) was a later rewrite that preserved the same dual-leverage invariant but was NOT the system that ran live.
|
||||||
|
|
||||||
|
### Actual Running PINK Stack (DITA v1)
|
||||||
|
|
||||||
|
| Layer | File | Role |
|
||||||
|
|-------|------|------|
|
||||||
|
| **Launcher** | `prod/launch_dolphin_pink.py` (baseline in `prod/refactor_snapshots_20260527_222130/`) | Wired DITA v1 + BingX direct adapter |
|
||||||
|
| **Decision** | `prod/clean_arch/dita/decision.py` | `DecisionEngine` — computes `leverage` (conviction) + `our_leverage` (notional/capital) |
|
||||||
|
| **Intent** | `prod/clean_arch/dita/intent.py` | `IntentEngine` — passes `leverage` from decision to `Intent` |
|
||||||
|
| **Trade FSM** | `prod/clean_arch/dita/trade.py` | `TradeExecutor` — `TradePosition.leverage` = conviction from intent |
|
||||||
|
| **Account** | `prod/clean_arch/dita/account.py` | `AccountProjection` — `snapshot.leverage` = `open_notional / capital` (**our_leverage**) |
|
||||||
|
| **Venue Adapter** | `prod/clean_arch/adapters/bingx_direct.py` | `submit_intent()` — **dual-leverage translation happens HERE** |
|
||||||
|
| **TP Curve** | `prod/clean_arch/tp_curve.py` | `compute_our_leverage(notional, capital)` — used for TP tightening |
|
||||||
|
|
||||||
|
### Dual-Leverage Translation in Production Code
|
||||||
|
|
||||||
|
**`prod/clean_arch/adapters/bingx_direct.py:submit_intent()` (lines 599-606):**
|
||||||
|
```python
|
||||||
|
# intent.leverage is the STRATEGY conviction (fractional, 0.5–9.0) and
|
||||||
|
# already sized the quantity. At-exchange leverage is derived from it
|
||||||
|
# via the linear conviction map → integer [1, cap], bankers rounding.
|
||||||
|
leverage = map_internal_conviction_to_exchange_leverage(
|
||||||
|
float(intent.leverage or self._config.default_leverage),
|
||||||
|
exchange_max=self._config.exchange_leverage_cap, # = 3
|
||||||
|
)
|
||||||
|
await self._ensure_leverage(symbol, leverage) # POST to BingX /trade/leverage
|
||||||
|
```
|
||||||
|
|
||||||
|
**`prod/clean_arch/tp_curve.py`:**
|
||||||
|
```python
|
||||||
|
def compute_our_leverage(*, notional, capital) -> float:
|
||||||
|
"""Return the current system leverage implied by sizing, NOT exchange leverage."""
|
||||||
|
return abs(notional) / capital # our_leverage = notional/capital
|
||||||
|
```
|
||||||
|
|
||||||
|
**`prod/clean_arch/dita/decision.py`:**
|
||||||
|
```python
|
||||||
|
our_leverage = compute_our_leverage(notional=target_exposure, capital=context.capital)
|
||||||
|
# ... passed in Decision.metadata["our_leverage"] for TP curve
|
||||||
|
tp_effective_pct = compute_soft_tp_pct(tp_base_pct, our_leverage)
|
||||||
|
```
|
||||||
|
|
||||||
|
### Three Leverage Concepts in the Live System
|
||||||
|
|
||||||
|
| Name | Variable | Range | Computed Where | Purpose |
|
||||||
|
|------|----------|-------|----------------|---------|
|
||||||
|
| **Conviction** | `intent.leverage`, `Decision.leverage` | 0.5–9.0 | Sizer (cubic-convex) | Sizes QUANTITY |
|
||||||
|
| **Exchange** | `leverage` (BingX API) | 1–3 (int) | `map_internal_conviction_to_exchange_leverage()` | Controls MARGIN = notional/exchange_lev |
|
||||||
|
| **Our/System** | `our_leverage` | 0.0–~1.8 | `compute_our_leverage(notional, capital)` | TP curve tightening, Hz publishing |
|
||||||
|
|
||||||
|
### DITAv2 Migration Note
|
||||||
|
`prod/clean_arch/dita_v2/` was a **later rewrite** that re-implemented the same architecture with a Rust kernel (`ExecutionKernel`). It preserved the dual-leverage invariant (documented in `PINK_ACCOUNTING_EXEC_FIX.md` §0 and `blue_parity.py` docstring) but the live PINK system that actually traded used **DITA v1**.
|
||||||
|
|
||||||
|
### Files That Were Actually Running Live
|
||||||
|
- `prod/launch_dolphin_pink.py` (the launcher)
|
||||||
|
- `prod/clean_arch/runtime/pink_direct.py` (the runtime — uses DITA v1 components)
|
||||||
|
- `prod/clean_arch/dita/` (decision, intent, trade, account)
|
||||||
|
- `prod/clean_arch/adapters/bingx_direct.py` (venue adapter with dual-leverage translation)
|
||||||
|
- `prod/clean_arch/tp_curve.py` (leverage-conditioned TP)
|
||||||
|
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## VIOLET Contracts — Dual-Leverage in Data Types
|
||||||
|
|
||||||
|
### `prod/clean_arch/violet/alpha_wrappers.py` — `SizeDecision` (PASS3a)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class SizeDecision(StrictModel):
|
||||||
|
"""Bet-sizer output. notional_fraction = fraction * conviction_leverage
|
||||||
|
is the realized notional/capital (== the recorded our_leverage); it is
|
||||||
|
the conviction side of the dual-leverage and is exchange-agnostic."""
|
||||||
|
|
||||||
|
fraction: Fraction
|
||||||
|
conviction_leverage: ConvictionLeverage # ∈ [0.5, 9.0] — internal sizing
|
||||||
|
notional_fraction: float = Field(ge=0.0) # == our_leverage = fraction × conviction_leverage
|
||||||
|
bucket_idx: int
|
||||||
|
strength_score: float
|
||||||
|
signal_bucket: str
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key invariant:** `notional_fraction = fraction × conviction_leverage` — this IS the recorded `our_leverage` (system leverage = notional/capital).
|
||||||
|
|
||||||
|
### `prod/clean_arch/violet/decision_engine.py` — `ShadowDecision` (PASS3c)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ShadowDecision(StrictModel):
|
||||||
|
"""One muted decision — what BLUE *would* do this scan. Never executed."""
|
||||||
|
|
||||||
|
ts_ns: int
|
||||||
|
scan_number: int
|
||||||
|
asset: Symbol
|
||||||
|
side: str
|
||||||
|
vel_div: float
|
||||||
|
fraction: float # base_fraction (0.20)
|
||||||
|
conviction_leverage: float # ∈ [0.5, 9.0] — full BLUE conviction (5-factor)
|
||||||
|
notional_fraction: float # == our_leverage = fraction × conviction_leverage
|
||||||
|
target_exposure: float # = capital × notional_fraction
|
||||||
|
ars_score: float
|
||||||
|
bucket_idx: int
|
||||||
|
actuated: bool
|
||||||
|
# 5-factor breakdown (V3.4):
|
||||||
|
base_leverage: Optional[float] # base cubic from AlphaBetSizer
|
||||||
|
dc_lev_mult: Optional[float] # DC confirmation boost
|
||||||
|
regime_size_mult: Optional[float] # ACB boost × meta × MC_scale (the "steepener")
|
||||||
|
market_ob_mult: Optional[float] # OB consensus 0.85–1.20
|
||||||
|
esof_size_mult: Optional[float] # EsoF haircut [0, 1]
|
||||||
|
```
|
||||||
|
|
||||||
|
**Key points:**
|
||||||
|
- `conviction_leverage` = full 5-factor BLUE conviction (base × DC × ACB-regime × OB × EsoF)
|
||||||
|
- `notional_fraction` = `fraction × conviction_leverage` = `our_leverage` (system leverage)
|
||||||
|
- `target_exposure` = `capital × notional_fraction` = notional
|
||||||
|
- Exchange leverage is **L3 only** — never in L1 decision
|
||||||
|
|
||||||
|
### `prod/clean_arch/violet/contracts_v3.py` — `ExecIntent` (PASS4)
|
||||||
|
|
||||||
|
```python
|
||||||
|
class ExecIntent(StrictModel):
|
||||||
|
"""DARK would-be order intent. Data only; never sent to a venue here."""
|
||||||
|
|
||||||
|
asset: Symbol
|
||||||
|
side: Literal["SHORT", "LONG"]
|
||||||
|
qty: Qty
|
||||||
|
exchange_leverage: Annotated[int, Field(ge=1)] # ← L3: exchange leverage
|
||||||
|
maker_policy: str
|
||||||
|
target_notional: float
|
||||||
|
ts_ns: MonoNs
|
||||||
|
reason: Literal["ENTRY", "EXIT"]
|
||||||
|
```
|
||||||
|
|
||||||
|
### `prod/clean_arch/violet/exec_intent.py` — L1→L3 Projection (PASS4 Task 17)
|
||||||
|
|
||||||
|
```python
|
||||||
|
def to_exec_intent(
|
||||||
|
decision: ShadowDecision,
|
||||||
|
*,
|
||||||
|
capital: float,
|
||||||
|
reference_price: float,
|
||||||
|
maker_policy: str = "maker_both",
|
||||||
|
) -> ExecIntent:
|
||||||
|
# target_notional = capital × notional_fraction (our_leverage side)
|
||||||
|
target_notional = capital * decision.notional_fraction
|
||||||
|
qty = target_notional / reference_price
|
||||||
|
|
||||||
|
# L3: conviction → exchange leverage via prod/bingx/leverage.py
|
||||||
|
exchange = _exchange_leverage_for(decision.conviction_leverage)
|
||||||
|
|
||||||
|
return ExecIntent(
|
||||||
|
asset=decision.asset,
|
||||||
|
side=decision.side,
|
||||||
|
qty=qty,
|
||||||
|
exchange_leverage=exchange,
|
||||||
|
maker_policy=maker_policy,
|
||||||
|
target_notional=target_notional,
|
||||||
|
ts_ns=decision.ts_ns,
|
||||||
|
reason="ENTRY",
|
||||||
|
)
|
||||||
|
|
||||||
|
def _exchange_leverage_for(conviction_leverage: float) -> int:
|
||||||
|
# Wraps VioletExchangeLeverage (bit-identical to prod/bingx/leverage.py)
|
||||||
|
return VioletExchangeLeverage().to_exchange(conviction_leverage).exchange_leverage
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Complete Dual-Leverage Architecture Across All Systems
|
||||||
|
|
||||||
|
```
|
||||||
|
┌─────────────────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ BLUE (nautilus_event_trader.py) │
|
||||||
|
│ esf_alpha_orchestrator: 5-factor conviction (base × DC × ACB-regime × OB × EsoF)│
|
||||||
|
│ our_leverage = compute_our_leverage(notional, capital) # for TP curve │
|
||||||
|
│ target_notional = capital × 0.20 × conviction_leverage │
|
||||||
|
└─────────────────────────────────────────────────────────────────────────────────┘
|
||||||
|
│
|
||||||
|
┌─────────────────┼─────────────────┐
|
||||||
|
▼ ▼ ▼
|
||||||
|
┌──────────────────┐ ┌──────────────────┐ ┌──────────────────┐
|
||||||
|
│ PINK │ │ PRODGREEN │ │ VIOLET │
|
||||||
|
│ (DITA v1 live) │ │ (BLUE mirror) │ │ (shadow/UV) │
|
||||||
|
└──────────────────┘ └──────────────────┘ └──────────────────┘
|
||||||
|
│ │ │
|
||||||
|
┌──────────┴──────────┐ │ ┌──────────┴──────────┐
|
||||||
|
▼ ▼ ▼ ▼ ▼
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ decision.py │ │ decision.py │ │ alpha_wrap │ │decision_eng │
|
||||||
|
│ DecisionEng │ │ DecisionEng │ │ SizeDecision│ │ ShadowDec │
|
||||||
|
│ leverage= │ │ leverage= │ │ conviction_ │ │conviction_ │
|
||||||
|
│ conviction │ │ conviction │ │ leverage │ │leverage │
|
||||||
|
└──────┬──────┘ └──────┬──────┘ └──────┬──────┘ └──────┬──────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ VENUE BOUNDARY (dual-leverage translation) │
|
||||||
|
│ │
|
||||||
|
│ PINK: prod/clean_arch/adapters/bingx_direct.py:submit_intent() │
|
||||||
|
│ leverage = map_internal_conviction_to_exchange_leverage( │
|
||||||
|
│ intent.leverage, exchange_max=3) │
|
||||||
|
│ │
|
||||||
|
│ VIOLET: prod/clean_arch/violet/exec_intent.py:to_exec_intent() │
|
||||||
|
│ exchange = VioletExchangeLeverage().to_exchange( │
|
||||||
|
│ decision.conviction_leverage).exchange_leverage │
|
||||||
|
│ │
|
||||||
|
│ BLUE: prod/bingx/execution.py:_ensure_leverage() │
|
||||||
|
│ leverage = map_internal_conviction_to_exchange_leverage( │
|
||||||
|
│ sizing_lev, exchange_max=config.exchange_leverage_cap)│
|
||||||
|
│ │
|
||||||
|
│ ALL use: prod/bingx/leverage.py (SOURCE OF TRUTH) │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
┌─────────────┐ ┌─────────────┐ ┌─────────────┐ ┌─────────────┐
|
||||||
|
│ BingX API │ │ BingX API │ │ BingX API │ │ BingX API │
|
||||||
|
│ /trade/ │ │ /trade/ │ │ /trade/ │ │ /trade/ │
|
||||||
|
│ leverage │ │ leverage │ │ leverage │ │ leverage │
|
||||||
|
│ (int 1-3) │ │ (int 1-3) │ │ (int 1-3) │ │ (int 1-3) │
|
||||||
|
└─────────────┘ └─────────────┘ └─────────────┘ └─────────────┘
|
||||||
|
│ │ │ │
|
||||||
|
▼ ▼ ▼ ▼
|
||||||
|
┌─────────────────────────────────────────────────────────────────────┐
|
||||||
|
│ ACCOUNTING (leverage-free) │
|
||||||
|
│ │
|
||||||
|
│ PnL = qty × (exit_price - entry_price) [side-signed] │
|
||||||
|
│ our_leverage = (size × entry_price) / capital [Hz publishing] │
|
||||||
|
│ margin = notional / exchange_leverage │
|
||||||
|
│ │
|
||||||
|
│ HARD INVARIANT (PINK_ACCOUNTING_EXEC_FIX.md §0): │
|
||||||
|
│ "slot.size = exchange quantity; slot.leverage = exchange leverage │
|
||||||
|
│ (1-3x cap, set at BingX API); our_leverage (conviction) = │
|
||||||
|
│ size × entry_price / capital, computed ONLY at _hz_publish. │
|
||||||
|
│ PnL is therefore LEVERAGE-FREE: qty × Δprice, side-signed." │
|
||||||
|
└─────────────────────────────────────────────────────────────────────┘
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Mutation Litmus — Complete
|
||||||
|
|
||||||
|
| Mutation | Where It Breaks | Catching Test |
|
||||||
|
|----------|----------------|---------------|
|
||||||
|
| Use `exchange_leverage` in PnL formula | `prod/clean_arch/dita/trade.py:apply_fill()` | `test_pink_ditav2_accounting_invariants.py` |
|
||||||
|
| Use `conviction_leverage` as BingX leverage | `prod/clean_arch/adapters/bingx_direct.py:submit_intent()` | BingX API rejects >3× for PINK |
|
||||||
|
| ROUND_HALF_UP instead of ROUND_HALF_EVEN | `prod/bingx/leverage.py:normalize_bingx_leverage_value()` | `test_violet_exchange_leverage.py::test_round_half_even_boundary_cases` (2.5→2) |
|
||||||
|
| Drop clamp to [1,3] | `prod/bingx/leverage.py:_clamp_exchange_bounds()` | BingX API rejects leverage >3 |
|
||||||
|
| Conflate `our_leverage` with `exchange_leverage` | Any accounting code | `PINK_ACCOUNTING_EXEC_FIX.md` HARD INVARIANT violation |
|
||||||
|
| Skip dual-leverage in VIOLET L3 | `prod/clean_arch/violet/exec_intent.py:_exchange_leverage_for()` | `test_violet_exchange_leverage.py::test_gate_exchange_leverage_bit_identity` (N≥1e6) |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Search Complete — All Systems Mapped
|
||||||
|
|
||||||
|
| System | Decision/Sizing | Intent | Venue Translation | Accounting |
|
||||||
|
|--------|----------------|--------|-------------------|------------|
|
||||||
|
| **BLUE** | `esf_alpha_orchestrator` (5-factor) | `nautilus_event_trader.py` | `prod/bingx/execution.py` | `compute_our_leverage()` for TP |
|
||||||
|
| **PINK (live)** | `prod/clean_arch/dita/decision.py` | `prod/clean_arch/dita/intent.py` | `prod/clean_arch/adapters/bingx_direct.py` | `AccountProjection.leverage = our_leverage` |
|
||||||
|
| **PINK (DITAv2)** | `prod/clean_arch/dita_v2/blue_parity.py` | `prod/clean_arch/dita/intent.py` | `prod/clean_arch/adapters/bingx_direct.py` | `AccountProjection.leverage = our_leverage` |
|
||||||
|
| **PRODGREEN** | Same as BLUE | Same | `prod/bingx/execution.py` | Same |
|
||||||
|
| **VIOLET (shadow)** | `prod/clean_arch/violet/decision_engine.py` | `prod/clean_arch/violet/exec_intent.py` | `prod/clean_arch/violet/exchange_leverage.py` | `CapitalState.capital` anchor |
|
||||||
|
|
||||||
|
**All paths converge on `prod/bingx/leverage.py` — the single source of truth for conviction→exchange mapping.**
|
||||||
165
prod/docs/PI_WAKE_AGENT_TOOL.md
Normal file
165
prod/docs/PI_WAKE_AGENT_TOOL.md
Normal file
@@ -0,0 +1,165 @@
|
|||||||
|
# pi_wake_agent.py — Multi-Agent Wake Timer
|
||||||
|
|
||||||
|
**Location:** `/mnt/dolphinng5_predict/pi_wake_agent.py`
|
||||||
|
**Branch:** `tools/pi_wake_agent`
|
||||||
|
**Status:** v1.0 — 38 tests passing
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Overview
|
||||||
|
|
||||||
|
Reusable multi-agent wake-up timer with self-cron/daemon/succession modes. Designed for the DOLPHIN fleet (pi_nvnemo, cmd, mimo, codex, etc.) to send doorbell injections via zellij and durable messages via h5i bus.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Installation
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/dolphinng5_predict
|
||||||
|
python3 pi_wake_agent.py --install --interval 30m --session pi_test --msg "Wake up!"
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Modes
|
||||||
|
|
||||||
|
| Mode | Flag | Description |
|
||||||
|
|------|------|-------------|
|
||||||
|
| Install cron | `--install` | Recurring wake via system cron |
|
||||||
|
| One-shot | `--once` | Single wake after interval (no cron) |
|
||||||
|
| Daemon | `--daemon` | Long-lived process, no cron |
|
||||||
|
| Succession | `--succession` | Run N times at interval, then self-clean |
|
||||||
|
| Run (internal) | `--run` | Called by cron, executes wake |
|
||||||
|
| Remove | `--remove` | Remove cron entry |
|
||||||
|
| List | `--list` | Show active cron entries |
|
||||||
|
| Status | `--status` | Show cron + one-shot timers |
|
||||||
|
| Validate | `--validate` | Check zellij sessions exist |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Options
|
||||||
|
|
||||||
|
| Option | Description |
|
||||||
|
|--------|-------------|
|
||||||
|
| `--interval DURATION` | Default: 1h. Formats: 30m, 1h, 90m, 2h, 10s |
|
||||||
|
| `--session SESSION` | Zellij session name (repeatable) |
|
||||||
|
| `--sessions "A,B,C"` | Comma-separated list |
|
||||||
|
| `--msg "MESSAGE"` | Wake message (default: "Operator says CONTINUE. Pi here!") |
|
||||||
|
| `--count N` | Number of runs for `--succession` |
|
||||||
|
| `--dry-run` | Show what would be done without executing |
|
||||||
|
| `--help` | Show help |
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Examples
|
||||||
|
|
||||||
|
```bash
|
||||||
|
# Recurring 1-hour wake for one session
|
||||||
|
pi_wake_agent.py --install --interval 1h --session cc_UV_dev0_Fb
|
||||||
|
|
||||||
|
# Multi-session 30-minute wake
|
||||||
|
pi_wake_agent.py --install --interval 30m --sessions "cc_UV_dev0_Fb,cc_UV_dev1_48" --msg "Wake up!"
|
||||||
|
|
||||||
|
# One-shot in 2 hours
|
||||||
|
pi_wake_agent.py --once --interval 2h --session cc_UV_dev0_Fb --msg "Time's up!"
|
||||||
|
|
||||||
|
# Run 3 times at 1-hour intervals, then self-clean
|
||||||
|
pi_wake_agent.py --succession --count 3 --interval 1h --session cc_UV_dev0_Fb --msg "Scheduled wake"
|
||||||
|
|
||||||
|
# Daemon mode (long-lived, no cron)
|
||||||
|
pi_wake_agent.py --daemon --interval 1h --session cc_UV_dev0_Fb
|
||||||
|
|
||||||
|
# Remove timer
|
||||||
|
pi_wake_agent.py --remove --session cc_UV_dev0_Fb --interval 1h
|
||||||
|
|
||||||
|
# List / status
|
||||||
|
pi_wake_agent.py --list
|
||||||
|
pi_wake_agent.py --status
|
||||||
|
|
||||||
|
# Validate sessions exist
|
||||||
|
pi_wake_agent.py --validate --session cc_UV_dev0_Fb
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Key Features
|
||||||
|
|
||||||
|
### Non-Blocking h5i Bus
|
||||||
|
Messages sent to Fable via `h5i msg send` are **fire-and-forget**:
|
||||||
|
- Runs in background thread
|
||||||
|
- 5-second timeout
|
||||||
|
- Silently ignores failures
|
||||||
|
- Never blocks the wake cycle
|
||||||
|
|
||||||
|
### Self-Cleaning Succession
|
||||||
|
```bash
|
||||||
|
pi_wake_agent.py --succession --count 3 --interval 1h --session S
|
||||||
|
```
|
||||||
|
Runs exactly 3 times at 1-hour intervals, then removes its own cron entry.
|
||||||
|
|
||||||
|
### Multi-Session
|
||||||
|
```bash
|
||||||
|
# Repeatable --session
|
||||||
|
pi_wake_agent.py --install --interval 1h --session s1 --session s2
|
||||||
|
|
||||||
|
# Comma-separated --sessions
|
||||||
|
pi_wake_agent.py --install --interval 1h --sessions "s1,s2,s3"
|
||||||
|
```
|
||||||
|
|
||||||
|
### Daemon Mode
|
||||||
|
Runs indefinitely as a long-lived process (no cron needed):
|
||||||
|
```bash
|
||||||
|
pi_wake_agent.py --daemon --interval 1h --session S
|
||||||
|
```
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## h5i Bus Protocol Compliance
|
||||||
|
|
||||||
|
Every wake injection:
|
||||||
|
1. Identifies as `[pi_nvnemo via zellij]`
|
||||||
|
2. Includes `Run: h5i-bus msg inbox` directive
|
||||||
|
3. Sends 5× ENTER keypresses (1s delay) for reliable submission
|
||||||
|
4. Sends parallel h5i bus message for durability
|
||||||
|
|
||||||
|
Per [AGENT_TERMINAL_DIRECT_INTERVENTION_PROCEDURES.md](AGENT_TERMINAL_DIRECT_INTERVENTION_PROCEDURES.md).
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Testing
|
||||||
|
|
||||||
|
```bash
|
||||||
|
cd /mnt/dolphinng5_predict
|
||||||
|
python3 -m pytest test_pi_wake_agent.py -v
|
||||||
|
```
|
||||||
|
**38 tests passing** covering:
|
||||||
|
- Unit tests: interval parsing, cron comments, session parsing
|
||||||
|
- Integration: install/remove/list, one-shot, succession, run mode
|
||||||
|
- Edge cases: invalid intervals, missing sessions, invalid counts
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Cron Entry Format
|
||||||
|
|
||||||
|
```
|
||||||
|
*/30 * * * * cd /mnt/dolphinng5_predict && export H5I_AGENT=pi_nvnemo && /mnt/dolphinng5_predict/pi_wake_agent.py --run --sessions 'pi_test' --msg '...' # pi_wake_agent:pi_test:30m
|
||||||
|
```
|
||||||
|
|
||||||
|
Comment format: `pi_wake_agent:<sessions>:<interval>`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Logs
|
||||||
|
|
||||||
|
- **File:** `/tmp/pi_wake_agent.log`
|
||||||
|
- **Rotation:** 10MB max, 5 files
|
||||||
|
- **Format:** `[YYYY-MM-DD HH:MM:SS] [LEVEL] message`
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Related Files
|
||||||
|
|
||||||
|
- `/mnt/dolphinng5_predict/pi_wake_agent.py` — Main script
|
||||||
|
- `/mnt/dolphinng5_predict/test_pi_wake_agent.py` — Test suite (38 tests)
|
||||||
|
- `/mnt/dolphinng5_predict/prod/docs/AGENT_TERMINAL_DIRECT_INTERVENTION_PROCEDURES.md` — Protocol
|
||||||
|
- Branch: `tools/pi_wake_agent`
|
||||||
126
prod/docs/VIOLET_STUDY_SPEC__BASE_FRACTION_SIZING.md
Normal file
126
prod/docs/VIOLET_STUDY_SPEC__BASE_FRACTION_SIZING.md
Normal file
@@ -0,0 +1,126 @@
|
|||||||
|
# VIOLET Study Spec — Base-Fraction / Capital-Utilization Sizing Study
|
||||||
|
|
||||||
|
**Status:** TODO (research spec, written 2026-06-13). Gated AFTER the regime-robustness
|
||||||
|
study (#1). Feeds VIOLET V3 Layer-3 sizing mechanics and any base-fraction change to
|
||||||
|
the live PINK/BLUE `AlphaBetSizer`.
|
||||||
|
|
||||||
|
**Owner intent:** the [[blue_margin_envelope_study]] proved BLUE's capital is badly
|
||||||
|
*under-utilized* (median trade ties up ~3.4% of wallet at 2× exchange leverage; 100% of
|
||||||
|
trades feasible at 2×; max realized `our_leverage` = notional/capital ≈ 1.81). The ROI
|
||||||
|
lever is the **base fraction** (currently `base_fraction = 0.20` in `AlphaBetSizer`),
|
||||||
|
NOT exchange leverage. Question this study answers: **how far above 0.20 can base
|
||||||
|
fraction be pushed for more ROI, risk-bounded, and where do hard constraints bind?**
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 0. Doctrine / non-negotiables
|
||||||
|
|
||||||
|
- **ROI is driven by `notional/capital` = `base_fraction × conviction_leverage`**, not by
|
||||||
|
exchange leverage. Exchange leverage (PINK/VIOLET max-3× **linear** translator) is a
|
||||||
|
margin-efficiency knob only. Confirmed empirically:
|
||||||
|
`notional = capital × 0.20 × leverage`, `leverage` = cubic-convex conviction ∈ [0.5, 9].
|
||||||
|
- **The edge is regime-concentrated** (≈95% of clean edge in choppy-bearish; bull is the
|
||||||
|
separate EFSM long-reversal algo's domain). Therefore sizing-up amplifies exposure to
|
||||||
|
the worst observed regime AND to the untested-by-this-strategy tails. This study MUST
|
||||||
|
output a fraction recommendation **conditioned on the regime-robustness result (#1)**,
|
||||||
|
not a raw-ROI maximizer.
|
||||||
|
- **Counterfactual honesty:** resizing past trades assumes the *same trades would have
|
||||||
|
filled at the larger size*. That assumption degrades with size (market impact). The
|
||||||
|
study MUST estimate and discount for slippage/impact, not assume linear scaling.
|
||||||
|
|
||||||
|
## 1. The hard constraint that binds first — the 3× translator ceiling
|
||||||
|
|
||||||
|
`our_leverage = base_fraction × conviction`, max conviction = 9.0. To finance a position
|
||||||
|
the exchange leverage must satisfy `exch_lev ≥ our_leverage`. PINK/VIOLET's translator
|
||||||
|
caps exchange leverage at **3×**. Therefore the **maximum financeable base fraction**
|
||||||
|
before the cap binds on the highest-conviction trades is:
|
||||||
|
|
||||||
|
```
|
||||||
|
base_fraction_max ≈ 3.0 / 9.0 ≈ 0.333 (i.e. our_leverage_max = 0.333 × 9 = 3.0 = cap)
|
||||||
|
```
|
||||||
|
|
||||||
|
- At `f = 0.20`: max our_leverage 1.8 → 2× suffices, comfortable.
|
||||||
|
- At `f ≈ 0.333`: max our_leverage 3.0 → exactly the 3× cap (no buffer on max-conviction
|
||||||
|
trades).
|
||||||
|
- At `f > 0.333`: highest-conviction trades CANNOT be financed at 3× → they clip
|
||||||
|
(under-size) or require raising the translator cap (a separate margin-risk decision).
|
||||||
|
|
||||||
|
**Deliverable 1:** the exact binding curve `f → fraction of trades that clip at 3× cap`,
|
||||||
|
using the real conviction distribution (most trades are low-conviction, so the cap may
|
||||||
|
bind on very few trades well above 0.333 — quantify it, don't assume the 0.333 worst case
|
||||||
|
dominates).
|
||||||
|
|
||||||
|
## 2. Method
|
||||||
|
|
||||||
|
Operate on the **clean deduped trade set** (one row per `trade_id`; drop `HIBERNATE_HALT`
|
||||||
|
and `bars_held = 0`; see [[blue_margin_envelope_study]] for the cleaning that yields
|
||||||
|
+$47k / 2121 trades). Required per-trade fields: `pnl`, `pnl_pct`, `entry_price`,
|
||||||
|
`quantity`, `capital_before`, `leverage` (conviction), `our_leverage`, regime hash tags
|
||||||
|
(join to `maras_fingerprint.composite_hash`), and execution-quality (slippage) from
|
||||||
|
`trade_execution_quality` / `execution_quality_json`.
|
||||||
|
|
||||||
|
### 2a. Counterfactual resize grid
|
||||||
|
For `f ∈ {0.20, 0.25, 0.30, 0.333, 0.40, 0.50}` (and finer near the optimum):
|
||||||
|
- Per trade, resized notional scales by `f / 0.20`; **`pnl_pct` is size-invariant**, so
|
||||||
|
resized `$pnl = pnl_pct × resized_notional` **before** slippage discount.
|
||||||
|
- Apply the §2c slippage discount.
|
||||||
|
- Apply the §1 cap clip: if `f × conviction > 3.0`, clip notional to `3.0 × capital`.
|
||||||
|
|
||||||
|
### 2b. Path-dependent equity reconstruction
|
||||||
|
Replay trades in time order, compounding each resized `$pnl` onto a running capital base
|
||||||
|
(bigger size → bigger swings → different compounding path; do NOT just sum). Seed from the
|
||||||
|
real starting capital of the tracked window. Produce per-`f`:
|
||||||
|
- final capital, CAGR
|
||||||
|
- **max drawdown**, Calmar/MAR (CAGR ÷ maxDD), longest-underwater days
|
||||||
|
- Sharpe, Sortino, downside deviation
|
||||||
|
- risk-of-ruin estimate
|
||||||
|
|
||||||
|
### 2c. Slippage / market-impact model (critical — do NOT skip)
|
||||||
|
The largest real-world degrader. From the maker-fill telemetry estimate whether larger
|
||||||
|
notionals get worse fills / more requotes / more taker fallback:
|
||||||
|
- regress realized fill slippage (and maker→taker fallback rate) against order notional
|
||||||
|
/ notional-vs-ADV where available
|
||||||
|
- build a `slippage_bps(notional)` discount applied in §2a
|
||||||
|
- if data is insufficient, state so and use a conservative parametric impact assumption
|
||||||
|
(document it); flag the result as impact-uncertain
|
||||||
|
|
||||||
|
### 2d. Kelly / fractional-Kelly anchor
|
||||||
|
Estimate the growth-optimal fraction from the empirical win-rate + payoff distribution.
|
||||||
|
Recommend **fractional Kelly (¼–½)** given the edge is **non-stationary and
|
||||||
|
regime-conditional** — full Kelly assumes a stationary edge we have explicitly shown does
|
||||||
|
not hold. Compare the Kelly-implied fraction to the §1 cap ceiling and the §2b
|
||||||
|
drawdown-optimal fraction.
|
||||||
|
|
||||||
|
### 2e. Regime-conditioned drawdown (the binding test)
|
||||||
|
Re-run §2b conditioned on the regime **hash** buckets from #1 (NOT the MARAS label — the
|
||||||
|
label is held untrusted; sub-regimes within choppy-bearish are expected). The binding
|
||||||
|
drawdown is the **worst-hash-bucket** drawdown, not the aggregate. Add a **stress
|
||||||
|
scenario**: inject a hypothetical adverse excursion sized to the worst plausible
|
||||||
|
unsampled-regime loss and report each `f`'s survival.
|
||||||
|
|
||||||
|
## 3. Deliverables
|
||||||
|
|
||||||
|
1. Table: `f` × {final capital, CAGR, maxDD, Calmar, Sharpe, ruin-prob, %trades-clipped-at-3×}.
|
||||||
|
2. The §1 cap-binding curve.
|
||||||
|
3. The §2c slippage discount model + its effect on the optimum.
|
||||||
|
4. A **recommended base fraction** (or a conviction-conditioned fraction *schedule*),
|
||||||
|
with the explicit risk statement: how much extra ROI, at what extra drawdown, under
|
||||||
|
what regime assumption.
|
||||||
|
5. Machine-readable report → `prod/VIOLET_dev/reports/base_fraction_study_<ts>.json`;
|
||||||
|
1-page FINDINGS alongside.
|
||||||
|
|
||||||
|
## 4. Caveats to carry into every conclusion
|
||||||
|
|
||||||
|
- Non-stationary, regime-concentrated edge — the optimum is conditional, not universal.
|
||||||
|
- Counterfactual resizing assumes fillability at scale (mitigated by §2c, never eliminated).
|
||||||
|
- Single-slot (no concurrency) — confirmed; if that ever changes, margin math changes.
|
||||||
|
- The clean set still may carry minor residual pollution; corroborate against the
|
||||||
|
corrected-capital trajectory as in the parent study.
|
||||||
|
- Do not let raw-ROI maximization override drawdown/ruin constraints. The under-utilized
|
||||||
|
capital is an *opportunity bounded by regime risk*, not free money.
|
||||||
|
|
||||||
|
## 5. Related
|
||||||
|
|
||||||
|
[[blue_margin_envelope_study]] · [[violet_v3_alpha_doctrine]] ·
|
||||||
|
`prod/bingx/leverage.py` (translator) · `nautilus_dolphin/nautilus/alpha_bet_sizer.py`
|
||||||
|
(base_fraction) · `prod/clean_arch/dita_v2/blue_parity.py` (PINK wrapper, note 8 vs 9 drift).
|
||||||
@@ -48,7 +48,7 @@ Self-consistent at row level vs recorded `dolphin.trade_events`:
|
|||||||
- **DUAL-LEVERAGE:** conviction leverage sizes the QUANTITY (internal); exchange leverage
|
- **DUAL-LEVERAGE:** conviction leverage sizes the QUANTITY (internal); exchange leverage
|
||||||
mapped at the venue boundary via `prod/bingx/leverage.py`
|
mapped at the venue boundary via `prod/bingx/leverage.py`
|
||||||
`map_internal_conviction_to_exchange_leverage_target` (round_half_even linear
|
`map_internal_conviction_to_exchange_leverage_target` (round_half_even linear
|
||||||
0.5–9.0 → 1..cap; PINK/VIOLET use a max-3× cubic translator).
|
0.5–9.0 → 1..cap; PINK/VIOLET use a max-3× **linear** translator).
|
||||||
|
|
||||||
## 3. blue_parity drift (doctrine validated by evidence)
|
## 3. blue_parity drift (doctrine validated by evidence)
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user