capital-manager — Implementazione per file
Struttura directory
capital-manager/
├── server.js # Bootstrap
├── routes.allocation.js # Router REST /allocation
├── modules/
│ ├── main.js # CapitalManager (BaseService)
│ ├── allocation/
│ │ ├── decisionEngine.js # Formula reservedCashPct + computeAllocationDecision
│ │ └── exposureCalculator.js # Open orders reserved notional
│ ├── adapters/
│ │ ├── ibkrBridge.js # HTTP client → ibkr-bridge
│ │ └── liquidityManager.js # HTTP client → liquidity-manager
│ ├── store/
│ │ └── reservationsStore.js # CRUD prenotazioni Redis
│ └── utils/
│ ├── clamp.js # clamp(value, min, max)
│ └── hash.js # generateReservationId()
├── Dockerfile
├── package.json
└── release.json
Ruolo dei file
server.js
Bootstrap minimale via createMicroserviceServer. Passa buildAllocationRouter come factory function — serverFactory la chiama con { logger, getService } dopo l'init del servizio.
createMicroserviceServer({
ServiceClass: capitalManagerService,
microservice: "capital-manager",
defaultPort: 3010,
routes: [
{ path: "/allocation", router: buildAllocationRouter, protected: true },
],
});
routes.allocation.js
Router factory. Non contiene logica di business: delega tutto a getService() (istanza di CapitalManager). Gestisce: validazione parametri request, status code HTTP, serializzazione response.
modules/main.js — CapitalManager
Estende BaseService. Metodi pubblici:
computeQuote(params)— orchestra fetch + formula + rispostacreateReservation(params)— delega a storereleaseReservation(reservationId, userId, reason)— delega a storelistReservations(userId)— delega a store
modules/allocation/decisionEngine.js
Funzioni pure, nessuna dipendenza I/O:
computeReservedCashPct(liquidity)→{ reservedCashPct, reasons, usedFallback }computeAllocationDecision({ cashAvailable, openOrdersReserved, reservationsReserved, liquidity, symbol, market, logger })→{ ok, decision, error }
modules/allocation/exposureCalculator.js
Funzione pura:
computeOpenOrdersReserved(openOrders, priceHint)→number
Somma i notional degli ordini BUY aperti (lmtPrice × totalQuantity). Accetta priceHint come fallback se un ordine non ha prezzo.
modules/adapters/ibkrBridge.js
HTTP client leggero (no axios, usa http/https Node built-in):
fetchAccountSummary()→{ cashAvailable, netLiq, buyingPower, currency }fetchPositions()→ArrayfetchOpenOrders()→Array
Legge URL e path da env vars. Timeout configurabile via IBKR_ADAPTER_TIMEOUT_MS.
modules/adapters/liquidityManager.js
HTTP client leggero:
fetchLiquidityScore(market)→{ score, riskRegime, volatility, confidence }
Normalizza riskRegime anche da forme alternative ("ON" → viene passato così come viene; la formula verifica "OFF" vs qualsiasi altro valore).
modules/store/reservationsStore.js
Accede a Redis tramite bus (istanza RedisBus di BaseService, bus.pub per SCAN):
createReservation(params, bus)— idempotente suclientRequestIdreleaseReservation(reservationId, userId, bus)→booleanlistReservations(userId, bus)→ArraygetTotalReservedAmount(userId, bus)→number
Usa scanIterator (node-redis v4) per il listing, evita KEYS * bloccante.
modules/utils/clamp.js
clamp(value, min, max) → Math.min(Math.max(value, min), max)
modules/utils/hash.js
generateReservationId() → "res_{timestamp_base36}_{4bytes_hex}"