SPB Git

Repository Public

perp-exec.ts 2.8 KB 43 lines typescript

1// The PAPER implementation of `PerpExecutor` (perp-engine.ts): the agent's own derivatives paper account, through the existing2// paper service — risk engine first (`checkOrder` with the policy derived from the owner's limits), then the paper engine3// against the live book. Nothing here signs, calls the signer or reaches a venue's trading endpoint.4//5// A live executor (not in this delivery) will implement the same three methods over a venue account whose orders are signed6// under a signer-enforced derivatives mandate (`MANDATE_LIVE`); the engine will not change.7import type { DecStr, OrderRequest, Position } from '@spw/derivatives';8import { UserError } from '../config.ts';9import { sql } from '../db.ts';10import { OrderBodySchema, type PaperService } from '../derivatives/paper.ts';11import type { ExecAccount, PerpExecutor, PlaceResult } from './perp-engine.ts';1213/** risk events of the account the engine reacts to */14const WATCHED = new Set(['liquidation', 'margin_warning']);1516export class PaperPerpExecutor implements PerpExecutor {17  readonly kind = 'paper' as const;18  private readonly paper: PaperService;19  private readonly accountId: string;20  constructor(paper: PaperService, accountId: string) { this.paper = paper; this.accountId = accountId; }2122  async account(sinceEventId = 0): Promise<ExecAccount> {23    const g = await this.paper.get(this.accountId);24    const rows = await sql<{ id: string; ts: Date; kind: string; code: string | null; message: string }[]>`select id, ts, kind, code, message from deriv_risk_events where account_id = ${this.accountId} and id > ${sinceEventId} order by id`;25    return { state: g.state, policy: g.policy, events: rows.filter((e) => WATCHED.has(e.kind)).map((e) => ({ id: Number(e.id), ts: e.ts.getTime(), kind: e.kind, code: e.code, message: e.message })) };26  }2728  async positions(): Promise<Position[]> { return (await this.paper.get(this.accountId)).positions; }2930  async place(order: OrderRequest, leverage: DecStr): Promise<PlaceResult> {31    const body = OrderBodySchema.parse({ instrument: order.instrument, side: order.side, type: order.type, size: order.size, price: order.price, triggerPrice: order.triggerPrice, reduceOnly: order.reduceOnly, tif: order.tif, leverage });32    try {33      const r = await this.paper.order(this.accountId, body, order.clientId);34      await sql`update deriv_orders set origin = 'agent' where account_id = ${this.accountId} and client_id = ${order.clientId}`;35      return { status: 'placed', order: r.order, fills: r.fills, idempotent: r.idempotent };36    } catch (e) {37      // 422 = the risk engine (or the service's own order rules) said no: a refusal, recorded by the service as a risk event38      if (e instanceof UserError && e.status === 422) return { status: 'refused', code: e.code, reason: e.message };39      throw e;40    }41  }42}43
Files main
Menu

Theme