# Delta Terminal — Backend Blueprint (Self-Hosted)

The Lovable frontend in `src/` targets a self-hosted Node.js + MongoDB backend.
This document is the complete reference: schemas, WebSocket architecture,
business logic, and security. Deploy on any Docker/VM host.

---

## 1. Stack

| Layer          | Choice                                                       |
| -------------- | ------------------------------------------------------------ |
| Runtime        | Node.js 20 LTS (or Python 3.11 alternative — noted inline)   |
| API            | Fastify (Node) or FastAPI (Python)                           |
| Realtime IN    | Delta Exchange WebSocket (`wss://socket.india.delta.exchange`) |
| Realtime OUT   | Socket.IO / native WS to browser                             |
| Data store     | MongoDB 7 (Atlas or self-hosted) + Redis 7 (pub/sub + cache) |
| TA engine      | `technicalindicators` (JS) or `TA-Lib` + `pandas-ta` (Py)    |
| Reactive layer | RxJS (Node) or Celery + Redis Streams (Py)                   |
| Auth           | JWT (RS256) + bcrypt password hashing                        |
| Secrets        | AES-256-GCM for API keys at rest                             |
| Orders         | Delta REST `POST /v2/orders` (HMAC-SHA256 signed)            |

---

## 2. MongoDB Schemas (JSON Schema validators)

### `users`
```json
{
  "$jsonSchema": {
    "bsonType": "object",
    "required": ["email", "password_hash", "status", "created_at"],
    "properties": {
      "email":         { "bsonType": "string", "pattern": "^.+@.+$" },
      "password_hash": { "bsonType": "string" },
      "status":        { "enum": ["active", "inactive"] },
      "created_at":    { "bsonType": "date" }
    }
  }
}
```
Indexes: `{ email: 1 }` unique.

### `user_profiles`
```json
{
  "user_id":          "ObjectId",
  "delta_api_key":    "String (AES-GCM ciphertext, base64)",
  "delta_api_secret": "String (AES-GCM ciphertext, base64)",
  "risk_profile": {
    "max_risk_per_trade_pct": "Number",
    "default_leverage":       "Number",
    "sizing_type":            "'FIXED_AMOUNT' | 'PERCENTAGE'",
    "sizing_value":           "Number"
  },
  "updated_at": "Date"
}
```
Indexes: `{ user_id: 1 }` unique.

### `active_trades`
```json
{
  "user_id":               "ObjectId",
  "coin_symbol":           "String",
  "position_side":         "'LONG' | 'SHORT'",
  "order_sizing_type":     "'FIXED_AMOUNT' | 'PERCENTAGE'",
  "order_size_value":      "Number",
  "leverage":              "Number",
  "entry_price":           "Number",
  "current_status":        "'WAITING_TRIGGER'|'OPEN'|'COMPLETED'|'LIQUIDATED'",
  "stop_loss":             "Number",
  "take_profit":           "Number",
  "max_wait_time_minutes": "Number",
  "delta_order_id":        "String",
  "opened_at":             "Date",
  "closed_at":             "Date | null",
  "timestamp":             "Date"
}
```
Indexes: `{ user_id: 1, current_status: 1 }`, `{ coin_symbol: 1 }`, `{ opened_at: -1 }`.

### `trade_analytics`
```json
{
  "user_id":                 "ObjectId",
  "trade_id":                "ObjectId",
  "realized_pnl":            "Number",
  "is_profitable":           "Boolean",
  "risk_percentage_used":    "Number",
  "account_balance_snapshot":"Number",
  "execution_date":          "Date"
}
```
Indexes: `{ user_id: 1, execution_date: -1 }`.

### `ticks` (high-frequency log; capped collection)
```js
db.createCollection("ticks", { capped: true, size: 5 * 1024 ** 3, max: 50_000_000 });
```
`{ symbol, price, ts }` — used for backfilling indicators and audit.

---

## 3. Architecture — Multi-Coin WebSocket Fan-Out

```text
                      ┌──────────────────────────┐
                      │   Delta Exchange WS      │
                      │  wss://socket.india...   │
                      └──────────────┬───────────┘
                                     │ ticker@BTCUSDT, @ETHUSDT, ... (batched sub)
                                     ▼
              ┌────────────────────────────────────────┐
              │  MarketDataService (single connection) │
              │  - reconnect w/ exponential backoff    │
              │  - heartbeat every 30s                 │
              │  - RxJS Subject<Tick> per symbol       │
              └───┬──────────────┬───────────────┬─────┘
                  │              │               │
                  ▼              ▼               ▼
        ┌──────────────┐ ┌───────────────┐ ┌─────────────────┐
        │ IndicatorSvc │ │ SignalEngine  │ │ TradeManager    │
        │ RSI/MACD/EMA │ │ Long/Short?   │ │ SL/TP/timeout   │
        │ /ATR/BB      │ │ per strategy  │ │ places orders   │
        └───────┬──────┘ └───────┬───────┘ └────────┬────────┘
                │                │                  │
                ▼                ▼                  ▼
        ┌──────────────────────────────────────────────────┐
        │ Redis pub/sub  (channels: ticks, signals, trade) │
        └──────────────────────┬───────────────────────────┘
                               ▼
                    ┌────────────────────┐
                    │ Socket.IO gateway  │──►  Browser (this app)
                    └────────────────────┘
```

One upstream WS per process, N downstream browser subs. Horizontal scale: put
`MarketDataService` on a leader instance, Redis fans out to any number of
Socket.IO nodes.

---

## 4. Business Logic (Node.js, drop-in modules)

### 4.1 Trade sizing — Fixed vs Percentage
```js
// backend/src/logic/sizing.js
export function computeOrderSize({ sizingType, sizingValue, accountBalance, entryPrice, leverage }) {
  if (sizingType !== "FIXED_AMOUNT" && sizingType !== "PERCENTAGE") {
    throw new Error("Invalid sizingType");
  }
  const notionalUSD = sizingType === "FIXED_AMOUNT"
    ? Number(sizingValue)
    : (Number(sizingValue) / 100) * Number(accountBalance);

  if (notionalUSD <= 0) throw new Error("Non-positive notional");

  const contractQty = (notionalUSD * leverage) / entryPrice;
  const marginUsed  = notionalUSD;                       // 1x margin in USD terms
  return { notionalUSD, contractQty, marginUsed };
}
```

### 4.2 Stop-loss / take-profit check (called per tick)
```js
// backend/src/logic/riskCheck.js
export function evaluateExit(trade, markPrice, nowMs) {
  const { position_side, stop_loss, take_profit, opened_at, max_wait_time_minutes } = trade;

  if (position_side === "LONG") {
    if (markPrice <= stop_loss)   return { close: true, reason: "STOP_LOSS" };
    if (markPrice >= take_profit) return { close: true, reason: "TAKE_PROFIT" };
  } else {
    if (markPrice >= stop_loss)   return { close: true, reason: "STOP_LOSS" };
    if (markPrice <= take_profit) return { close: true, reason: "TAKE_PROFIT" };
  }

  const elapsedMin = (nowMs - new Date(opened_at).getTime()) / 60_000;
  if (elapsedMin >= max_wait_time_minutes) {
    return { close: true, reason: "WAIT_TIMEOUT" };
  }
  return { close: false };
}
```

### 4.3 Realized PnL
```js
export function realizedPnL({ side, entry, exit, contractQty }) {
  const raw = (exit - entry) * contractQty;
  return side === "LONG" ? raw : -raw;
}
```

### 4.4 Reactive signal loop (RxJS)
```js
// backend/src/engine/signalEngine.js
import { combineLatest, interval } from "rxjs";
import { map, distinctUntilChanged } from "rxjs/operators";
import { RSI, MACD, EMA } from "technicalindicators";

export function attachSignals(tickStream$) {
  return tickStream$.pipe(
    map((tick) => {
      const closes = tick.window;                       // last N closes
      const rsi  = RSI.calculate({ values: closes, period: 14 }).at(-1);
      const macd = MACD.calculate({ values: closes, fastPeriod: 12, slowPeriod: 26, signalPeriod: 9,
                                    SimpleMAOscillator: false, SimpleMASignal: false }).at(-1);
      const ema9  = EMA.calculate({ values: closes, period: 9  }).at(-1);
      const ema21 = EMA.calculate({ values: closes, period: 21 }).at(-1);
      let signal = "NEUTRAL";
      if (ema9 > ema21 && rsi < 70 && macd?.histogram > 0) signal = "LONG";
      if (ema9 < ema21 && rsi > 30 && macd?.histogram < 0) signal = "SHORT";
      return { symbol: tick.symbol, signal, rsi, ema9, ema21 };
    }),
    distinctUntilChanged((a, b) => a.signal === b.signal),
  );
}
```

### 4.5 Delta Exchange order placement (HMAC-signed)
```js
// backend/src/delta/client.js
import crypto from "node:crypto";

const BASE = "https://api.india.delta.exchange";

export async function placeOrder({ apiKey, apiSecret, symbol, side, size, orderType = "market_order", leverage }) {
  const timestamp = Math.floor(Date.now() / 1000).toString();
  const method = "POST";
  const path = "/v2/orders";
  const body = JSON.stringify({ product_symbol: symbol, side, size, order_type: orderType, leverage });

  const prehash = method + timestamp + path + body;
  const signature = crypto.createHmac("sha256", apiSecret).update(prehash).digest("hex");

  const res = await fetch(BASE + path, {
    method,
    headers: {
      "api-key": apiKey,
      "signature": signature,
      "timestamp": timestamp,
      "Content-Type": "application/json",
    },
    body,
  });
  if (!res.ok) throw new Error(`Delta order failed: ${res.status} ${await res.text()}`);
  return res.json();
}

export async function closePosition(creds, symbol) {
  // Delta supports reduce-only market order to flatten
  return placeOrder({ ...creds, symbol, side: "sell", size: 0, orderType: "market_order" });
}
```

### 4.6 Manual close endpoint (called by the frontend button)
```js
// backend/src/routes/trades.js
app.post("/api/trades/:id/close", { preHandler: [authJWT] }, async (req, reply) => {
  const trade = await Trades.findOne({ _id: req.params.id, user_id: req.user.id });
  if (!trade) return reply.code(404).send({ error: "not_found" });

  const profile = await Profiles.findOne({ user_id: req.user.id });
  const creds = decryptCreds(profile);

  const resp = await closePosition(creds, trade.coin_symbol);
  const exit = Number(resp.result.average_fill_price);
  const pnl  = realizedPnL({ side: trade.position_side, entry: trade.entry_price, exit, contractQty: trade.contract_qty });

  await Trades.updateOne({ _id: trade._id }, { $set: { current_status: "COMPLETED", closed_at: new Date() } });
  await Analytics.insertOne({ user_id: req.user.id, trade_id: trade._id, realized_pnl: pnl,
                              is_profitable: pnl > 0, risk_percentage_used: trade.risk_pct,
                              account_balance_snapshot: profile.balance, execution_date: new Date() });
  return { ok: true, pnl };
});
```

---

## 5. API surface consumed by the frontend

Set `VITE_API_BASE_URL` in the Lovable project to your backend URL.

| Method | Path                        | Purpose                       |
| ------ | --------------------------- | ----------------------------- |
| POST   | `/api/auth/register`        | Create user                   |
| POST   | `/api/auth/login`           | Returns JWT                   |
| GET    | `/api/me/profile`           | Read profile + risk config    |
| PUT    | `/api/me/profile`           | Update API keys + sizing      |
| GET    | `/api/markets/screener`     | Multi-coin snapshot + signals |
| WS     | `/ws`                       | Live ticks + PnL updates      |
| GET    | `/api/trades/active`        | Open positions                |
| POST   | `/api/trades/:id/close`     | Manual emergency close        |
| GET    | `/api/reports/nav?range=…`  | Daily/Weekly/Monthly NAV      |

---

## 6. Security checklist

- Password hashing: bcrypt cost 12+.
- JWT: RS256, 15 min access + 7 day refresh, httpOnly cookie.
- API-key encryption: AES-256-GCM with a per-deployment key from KMS/`.env`.
- Delta API scope: **Trade + Read only** — never Withdraw.
- Rate limiting: 60 req/min per user on trade endpoints (Fastify rate-limit).
- Audit log every order into `trade_analytics` before returning to client.
- CORS: allow-list the deployed frontend origin only.
- Never log request bodies containing secrets.

---

## 7. Deployment sketch (docker-compose)

```yaml
services:
  api:
    build: ./backend
    environment:
      MONGODB_URI: mongodb://mongo:27017/delta
      REDIS_URL: redis://redis:6379
      JWT_PRIVATE_KEY: ${JWT_PRIVATE_KEY}
      ENC_KEY_BASE64:  ${ENC_KEY_BASE64}
    ports: ["4000:4000"]
    depends_on: [mongo, redis]
  mongo:
    image: mongo:7
    volumes: [mongo:/data/db]
  redis:
    image: redis:7-alpine
volumes: { mongo: {} }
```

Point the Lovable app at `https://api.your-domain.com/api` via
`VITE_API_BASE_URL` and you're wired end-to-end.
