# Multi-Venue Live Bridge — Architecture & Protocol Reference

> **Purpose**: Connect real trading venues (NinjaTrader 8, TradingView) to the DBN platform so every live trade flows into DBNCore for behavioral scoring, Lock evaluation, journal sync, and performance analytics — without DBN ever placing or modifying orders.

---

## 1. Architecture Overview

```
┌──────────────────┐      ┌──────────────────┐
│  NinjaTrader 8   │      │   TradingView    │
│  (Desktop App)   │      │   (Cloud)        │
│                  │      │                  │
│  ATI File/DLL    │      │  Alert Webhooks  │
│  ↓ events        │      │  POST JSON       │
└────────┬─────────┘      └────────┬─────────┘
         │                         │
         ▼                         ▼
┌──────────────────┐      ┌──────────────────┐
│ Desktop Companion│      │ (direct POST)    │
│ (reads ATI,      │      │                  │
│  forwards JSON)  │      │                  │
└────────┬─────────┘      └────────┘─────────┘
         │                         │
         ▼                         ▼
┌──────────────────────────────────────────────┐
│         Cloudflare Worker (dbn-worker.js)     │
│                                              │
│  POST /bridge/event                          │
│    ├─ Validate pairing token (NT) or         │
│    │  passphrase (TV)                        │
│    ├─ Normalize to DBN trade schema          │
│    ├─ Write to D1 bridge_trades              │
│    └─ Return { ok: true, tradeId }           │
│                                              │
│  POST /bridge/pair      (JWT auth)           │
│  GET  /bridge/status    (JWT auth)           │
│  DELETE /bridge/revoke  (JWT auth)           │
└──────────────────────────────────────────────┘
         │
         ▼
┌──────────────────────────────────────────────┐
│  Client (browser)                            │
│  dbn-core.js  liveBridge.ingestTrade()       │
│    ├─ Creates DBNCore trade record           │
│    │  source: 'ninjatrader-live' | 'tv-live' │
│    ├─ Post-hoc Lock evaluation               │
│    ├─ Rating update                          │
│    ├─ Deviations scan                        │
│    ├─ Journal + Psychology auto-sync         │
│    └─ Emits 'bridge:trade' event             │
└──────────────────────────────────────────────┘
```

### Design Principles
1. **Read-only integration**: DBN never places, modifies, or cancels orders. It only observes.
2. **Post-hoc Lock**: Since trades execute outside DBN, Lock evaluates what it *would* have said, building a comparison dataset over time.
3. **Multi-venue generic**: The Worker endpoint accepts a `venue` field. Adding a new venue means adding a normalizer function, not new routes.
4. **Pairing token auth**: Desktop companion authenticates via a one-time token generated in account.html. TradingView uses a passphrase embedded in the webhook URL.

---

## 2. NinjaTrader 8 ATI Protocol

### 2.1 Interfaces

NT8 provides three ATI interfaces:

| Interface | Mechanism | Latency | Use Case |
|-----------|-----------|---------|----------|
| **File (OIF)** | Drop files in `incoming/` folder | ~50ms | Most reliable, recommended |
| **DLL** | `NinjaTrader.Client.dll` (.NET) | ~10ms | Programmatic, tighter coupling |
| **Email** | TradeStation-compatible emails | High | Legacy, not recommended |

### 2.2 OIF Command Format

Semicolon-delimited strings placed in `My Documents\NinjaTrader 8\incoming\`:

```
PLACE;Account;Instrument;Action;Qty;OrderType;LimitPrice;StopPrice;TIF;OCOID;OrderID;Strategy;StrategyID
```

**Actions**: `BUY`, `SELL`, `BUYTOCOVER`, `SELLSHORT`
**Order Types**: `MARKET`, `LIMIT`, `STOP`, `STOPLIMIT`
**TIF**: `GTC`, `GTD`, `DAY`, `IOC`

### 2.3 DLL Functions (NinjaTrader.Client)

```csharp
// Connection
int Connected(int showMessage)          // 0=connected, -1=not
int SetUp(string host, int port)        // Default: localhost:36973

// Read state
int MarketPosition(string instrument, string account)
// Returns: 0=Flat, >0=Long qty, <0=Short qty

string Orders(string account)
// Returns: pipe-separated order IDs

// Position events (polled)
double AvgEntryPrice(string instrument, string account)
double UnrealizedPnL(string instrument, string account)
```

### 2.4 Event Flow (Companion → Worker)

The desktop companion monitors NT8 state changes and forwards events:

```
NT8 State Change → Companion detects → POST /bridge/event
```

**Detection methods** (companion polls every 500ms):
1. `MarketPosition()` changes from 0 → non-zero = **Trade Open**
2. `MarketPosition()` changes from non-zero → 0 = **Trade Close**
3. `MarketPosition()` sign flip = **Reverse** (close + open)
4. `AvgEntryPrice()` changes while position exists = **Scale in/out**

---

## 3. TradingView Webhook Protocol

### 3.1 Webhook Setup

TradingView Pro/Pro+/Premium users can set webhook URLs on alerts:
- URL: `https://dbn-api.<domain>.workers.dev/bridge/event`
- Method: POST
- Content-Type: application/json

### 3.2 Alert Message Format

TradingView sends whatever JSON the user puts in the alert message body. DBN recommends this template:

```json
{
  "passphrase": "{{user_passphrase}}",
  "venue": "tradingview",
  "action": "{{strategy.order.action}}",
  "symbol": "{{ticker}}",
  "price": {{close}},
  "qty": {{strategy.order.contracts}},
  "sl": {{strategy.order.stop}},
  "tp": {{strategy.order.limit}},
  "time": "{{time}}"
}
```

**Built-in placeholders**:
- `{{strategy.order.action}}` → `buy`, `sell`, `closelong`, `closeshort`
- `{{ticker}}` → `EURUSD`, `ES1!`, etc.
- `{{close}}` → Current price
- `{{strategy.order.contracts}}` → Quantity
- `{{time}}` → Alert trigger time (ISO)
- `{{strategy.order.stop}}` / `{{strategy.order.limit}}` → SL/TP if set in strategy

### 3.3 Passphrase Auth

Instead of a pairing token (which requires a companion app), TradingView uses a passphrase:
1. User generates a passphrase in account.html Live Bridge tab
2. Passphrase is stored hashed in D1 `bridge_tokens` table with `venue='tradingview'`
3. Each webhook POST includes the passphrase in the JSON body
4. Worker validates passphrase against stored hash

---

## 4. Event Normalization — DBNCore Trade Schema

### 4.1 Incoming Event Format (Worker receives)

```json
{
  "token": "bt_xxxx...",
  "venue": "ninjatrader" | "tradingview",
  "action": "open" | "close" | "update",
  "symbol": "ES 06-26" | "EURUSD",
  "direction": "long" | "short",
  "qty": 1,
  "price": 5432.50,
  "sl": 5420.00,
  "tp": 5460.00,
  "timestamp": "2026-04-18T14:32:00Z",
  "venue_order_id": "NT-12345",
  "passphrase": "abc123"
}
```

### 4.2 Instrument Mapping

| Venue Symbol | DBN Pair | Asset Class |
|-------------|----------|-------------|
| ES 06-26, ES1! | ES | Futures |
| NQ 06-26, NQ1! | NQ | Futures |
| YM 06-26, YM1! | YM | Futures |
| CL 06-26, CL1! | CL | Futures |
| GC 06-26, GC1! | GC | Futures |
| EURUSD, EUR/USD | EURUSD | Forex |
| GBPUSD, GBP/USD | GBPUSD | Forex |
| USDJPY, USD/JPY | USDJPY | Forex |
| GBPJPY, GBP/JPY | GBPJPY | Forex |
| XAUUSD, GOLD | XAUUSD | Metals |
| BTCUSD, BTC/USD | BTCUSD | Crypto |

Normalization function strips contract months, slashes, and maps aliases.

### 4.3 Output: DBNCore Trade Object

When a trade is **closed**, the Worker pairs the close event with the open event and the client-side `liveBridge.ingestTrade()` creates:

```javascript
{
  id: crypto.randomUUID(),
  source: 'ninjatrader-live' | 'tv-live',
  pair: 'ES',                    // Normalized symbol
  direction: 'BUY',              // BUY | SELL
  entryPrice: 5432.50,
  exitPrice: 5445.25,
  entryTime: '2026-04-18T14:32:00Z',
  exitTime: '2026-04-18T15:10:00Z',
  lotSize: 1,
  sl: 5420.00,
  tp: 5460.00,
  pnl: 637.50,                   // Calculated from price diff × multiplier
  pips: 12.75,                   // Points for futures, pips for forex
  rr: 1.92,                      // Risk:reward ratio
  setup: null,                   // Not available from venue
  session: 'ny',                 // Derived from entry time UTC
  timeframe: null,               // Not available from venue
  regime: null,                  // Not available from venue
  emotions: { before: null, after: null },
  adherence: null,               // User can fill in journal post-hoc
  tags: ['live', 'ninjatrader', 'futures'],
  autoTags: [],                  // No ICT analyzer on live trades
  notes: '',
  gateUsed: false,
  slMoved: false,
  mae: null,                     // Not available without tick data
  mfe: null,
  lockEvaluation: {              // Post-hoc Lock eval
    score: 62,
    verdict: 'amber',
    cellKey: 'unknown:ny:unknown',
    postHoc: true                // Distinguishes from real-time Lock
  },
  overrideNote: null,
  venueOrderId: 'NT-12345',     // Venue-specific reference
  venue: 'ninjatrader'
}
```

---

## 5. Post-Hoc Lock Evaluation

Since trades happen outside DBN, Lock can't gate entry. Instead:

1. On every closed live trade, `liveBridge.ingestTrade()` calls `DBNCore.lock.evaluate()` with best-available context:
   - **Setup**: `'unknown'` (no ICT analyzer on live trades) — unless user manually tags later
   - **Session**: Derived from entry time UTC (Asian/London/NY)
   - **Market state**: `'unknown'` (no candle data) — unless workspace sim is running
2. Lock evaluation is stored on the trade as `lockEvaluation` with `postHoc: true` flag
3. Over time, this builds a comparison dataset:
   - **Lock-aligned trades**: Trades where Lock would have said green/amber → track win rate
   - **Lock-opposing trades**: Trades where Lock would have said red → track win rate
   - If Lock-aligned WR significantly > Lock-opposing WR, Lock has predictive value for this trader's live execution

### 5.1 Lock Alignment Tracking

```javascript
// In dbn-core.js liveBridge namespace
getLockAlignmentStats() {
  const liveTrades = trades.filter(t => t.source?.includes('-live') && t.lockEvaluation);
  const aligned = liveTrades.filter(t => t.lockEvaluation.verdict !== 'red');
  const opposing = liveTrades.filter(t => t.lockEvaluation.verdict === 'red');
  return {
    aligned: { count: aligned.length, winRate: wins(aligned)/aligned.length },
    opposing: { count: opposing.length, winRate: wins(opposing)/opposing.length },
    edge: alignedWR - opposingWR,  // Positive = Lock has predictive value
    sampleSize: liveTrades.length,
    significant: liveTrades.length >= 30  // Minimum for statistical relevance
  };
}
```

---

## 6. Pairing Token System

### 6.1 Flow

1. User opens account.html → "Live Bridge" tab
2. Clicks "Generate Token" for NinjaTrader or "Generate Passphrase" for TradingView
3. Worker creates a token/passphrase:
   - **NinjaTrader**: `bt_` + 32 random hex chars, stored PBKDF2-hashed in D1
   - **TradingView**: 6-word passphrase (human-readable), stored PBKDF2-hashed in D1
4. Token/passphrase shown ONCE in UI with copy button
5. User pastes into companion app (NT) or TradingView alert message (TV)
6. Each `/bridge/event` POST includes token/passphrase → Worker validates against hash → maps to user_id

### 6.2 Token Table Schema

```sql
CREATE TABLE bridge_tokens (
  id TEXT PRIMARY KEY,           -- UUID
  user_id TEXT NOT NULL,         -- FK to users
  venue TEXT NOT NULL,           -- 'ninjatrader' | 'tradingview'
  token_hash TEXT NOT NULL,      -- PBKDF2 hash of token/passphrase
  label TEXT DEFAULT '',         -- User-friendly label ("My NT8 Desktop")
  created_at TEXT NOT NULL,      -- ISO timestamp
  last_used_at TEXT,             -- Last successful auth
  revoked_at TEXT,               -- NULL if active
  FOREIGN KEY (user_id) REFERENCES users(id)
);
```

### 6.3 Security
- Tokens are PBKDF2-SHA256 hashed (same as passwords in dbn-worker.js)
- Tokens can be revoked from account.html
- Max 5 active tokens per user per venue
- Tokens auto-expire after 90 days of inactivity (last_used_at check)

---

## 7. Desktop Companion Specification

> **Scope**: This section is a SPEC ONLY. The companion app is a separate deliverable.

### 7.1 Purpose

A lightweight desktop application that:
1. Reads NinjaTrader 8 position state via the DLL interface (NinjaTrader.Client.dll)
2. Detects trade open/close events by polling `MarketPosition()` and `AvgEntryPrice()`
3. Forwards events as JSON to the DBN Worker

### 7.2 Technology Options

| Option | Pros | Cons |
|--------|------|------|
| **Python + ctypes** | Simple, fast to build, small binary via PyInstaller | Requires .NET interop (pythonnet) |
| **C# Console App** | Native .NET, direct DLL access | Requires .NET runtime |
| **Electron** | Cross-platform UI, easy HTTP | Heavy for a polling service |

**Recommendation**: C# Console App — smallest footprint, native DLL access, can run as Windows service.

### 7.3 Polling Loop

```
every 500ms:
  for each monitored_account:
    for each monitored_instrument:
      pos = MarketPosition(instrument, account)
      entry = AvgEntryPrice(instrument, account)
      pnl = UnrealizedPnL(instrument, account)

      if prev_pos == 0 && pos != 0:
        → emit OPEN event (direction from sign, price from entry)
      if prev_pos != 0 && pos == 0:
        → emit CLOSE event (exit price from last known price)
      if sign(prev_pos) != sign(pos) && pos != 0:
        → emit CLOSE + OPEN (reverse)

      prev_state[key] = { pos, entry, pnl }
```

### 7.4 Configuration File

```json
{
  "worker_url": "https://dbn-api.adrian-djents.workers.dev",
  "token": "bt_a1b2c3d4...",
  "accounts": ["Sim101"],
  "instruments": ["ES 06-26", "NQ 06-26", "EURUSD"],
  "poll_interval_ms": 500,
  "retry_max": 5,
  "retry_backoff_ms": 2000
}
```

### 7.5 Event Payload (Companion → Worker)

```json
{
  "token": "bt_a1b2c3d4...",
  "venue": "ninjatrader",
  "action": "open",
  "symbol": "ES 06-26",
  "direction": "long",
  "qty": 2,
  "price": 5432.50,
  "sl": null,
  "tp": null,
  "timestamp": "2026-04-18T14:32:00.000Z",
  "venue_order_id": null
}
```

### 7.6 Retry & Offline Queue

- Failed POSTs queued to local file (`~/.dbn-bridge/queue.json`)
- Exponential backoff: 2s, 4s, 8s, 16s, 32s max
- Queue processed FIFO on next successful connection
- Max queue size: 500 events (oldest evicted)

---

## 8. D1 Tables

### bridge_tokens
Stores hashed pairing tokens for venue authentication. See §6.2.

### bridge_trades
Raw event log — every event from every venue, before client-side processing:

```sql
CREATE TABLE bridge_trades (
  id TEXT PRIMARY KEY,
  user_id TEXT NOT NULL,
  venue TEXT NOT NULL,
  action TEXT NOT NULL,           -- 'open' | 'close' | 'update'
  symbol TEXT NOT NULL,
  direction TEXT,
  qty REAL,
  price REAL,
  sl REAL,
  tp REAL,
  pnl REAL,
  timestamp TEXT NOT NULL,
  venue_order_id TEXT,
  token_id TEXT,                  -- FK to bridge_tokens
  created_at TEXT NOT NULL,
  FOREIGN KEY (user_id) REFERENCES users(id)
);
```

---

## 9. API Reference

### POST /bridge/pair
Generate a new pairing token.

**Auth**: JWT (logged-in user)
**Body**:
```json
{ "venue": "ninjatrader" | "tradingview", "label": "My NT8 Desktop" }
```
**Response**:
```json
{ "ok": true, "token": "bt_a1b2c3d4..." , "tokenId": "uuid" }
```
For TradingView, response includes `passphrase` instead of `token`.

### POST /bridge/event
Receive a trade event from a venue.

**Auth**: Token in body (`token` field for NT, `passphrase` for TV)
**Body**: See §4.1
**Response**:
```json
{ "ok": true, "eventId": "uuid", "action": "open" }
```

### GET /bridge/status
Get bridge connection status for the authenticated user.

**Auth**: JWT
**Response**:
```json
{
  "venues": {
    "ninjatrader": { "active": true, "lastEvent": "2026-04-18T15:10:00Z", "todayTrades": 4 },
    "tradingview": { "active": false, "lastEvent": null, "todayTrades": 0 }
  },
  "tokens": [
    { "id": "uuid", "venue": "ninjatrader", "label": "My NT8", "lastUsed": "...", "active": true }
  ]
}
```

### DELETE /bridge/revoke
Revoke a pairing token.

**Auth**: JWT
**Body**: `{ "tokenId": "uuid" }`
**Response**: `{ "ok": true }`

---

## 10. Gate Criterion

**Primary**: ≥50 NinjaTrader/TradingView-bridged users daily active within 30 days of rollout.

**Secondary**: Lock-aligned live-trade win rate > Lock-opposing win rate by ≥3% at Day 60 (validates Lock's predictive value on real execution).

**Falsification**: If bridged user count plateaus below 20 DAU at Day 30, the companion app UX is the bottleneck — prioritize TradingView-only (no companion needed) and revisit NT companion.
