/// Alpaca v2 latest-trade response: `{ "symbol", "trade" }`. class AlpacaLatestTradeResponse { AlpacaLatestTradeResponse({required this.symbol, required this.trade}); final String symbol; final AlpacaTrade trade; factory AlpacaLatestTradeResponse.fromJson(Map json) { return AlpacaLatestTradeResponse( symbol: json['symbol']! as String, trade: AlpacaTrade.fromJson( Map.from(json['trade'] as Map), ), ); } } /// Single trade tick from market data API. class AlpacaTrade { AlpacaTrade({ required this.timestamp, required this.price, required this.size, this.exchange, this.tape, }); final DateTime timestamp; final num price; final num size; final String? exchange; final String? tape; factory AlpacaTrade.fromJson(Map json) { return AlpacaTrade( timestamp: DateTime.parse(json['t']! as String).toUtc(), price: json['p'] as num, size: json['s'] as num, exchange: json['x'] as String?, tape: json['z'] as String?, ); } } /// Daily (or intraday) OHLCV bar. class AlpacaBar { AlpacaBar({ required this.timestamp, required this.open, required this.high, required this.low, required this.close, required this.volume, }); final DateTime timestamp; final num open; final num high; final num low; final num close; final num volume; factory AlpacaBar.fromJson(Map json) { return AlpacaBar( timestamp: DateTime.parse(json['t']! as String).toUtc(), open: json['o'] as num, high: json['h'] as num, low: json['l'] as num, close: json['c'] as num, volume: json['v'] as num, ); } } /// Multi-symbol bars response: `{ "bars": { "SPY": [ ... ] } }`. class AlpacaBarsResponse { AlpacaBarsResponse({required this.barsBySymbol}); final Map> barsBySymbol; factory AlpacaBarsResponse.fromJson(Map json) { final Map rawBars = Map.from(json['bars'] as Map? ?? {}); final Map> parsed = >{}; for (final MapEntry entry in rawBars.entries) { final List list = entry.value as List? ?? []; parsed[entry.key] = list .whereType() .map((Map m) => AlpacaBar.fromJson(Map.from(m))) .toList(); } return AlpacaBarsResponse(barsBySymbol: parsed); } AlpacaBar? latestBar(String symbol) { final List? bars = barsBySymbol[symbol]; if (bars == null || bars.isEmpty) { return null; } return bars.last; } /// Prior daily bar when [limit] ≥ 2 (used for `prev_close` metric). AlpacaBar? previousDailyBar(String symbol) { final List? bars = barsBySymbol[symbol]; if (bars == null || bars.length < 2) { return null; } return bars[bars.length - 2]; } } /// Parsed `POST /v2/orders` (or `GET /v2/orders/by_client_order_id`) response. /// /// Captures the fields the trade actuator uses to persist a `trade_orders` /// row: Alpaca order id, client order id, status, symbol, side, type, /// notional/qty, fill price, and timestamps. class AlpacaOrderResponse { AlpacaOrderResponse({ required this.id, required this.clientOrderId, required this.symbol, required this.side, required this.type, required this.status, this.notional, this.qty, this.filledQty, this.filledAvgPrice, this.submittedAt, this.filledAt, this.raw, }); final String id; final String clientOrderId; final String symbol; final String side; final String type; final String status; final num? notional; final num? qty; final num? filledQty; final num? filledAvgPrice; final DateTime? submittedAt; final DateTime? filledAt; final Map? raw; factory AlpacaOrderResponse.fromJson(Map json) { return AlpacaOrderResponse( id: json['id']! as String, clientOrderId: json['client_order_id']! as String, symbol: json['symbol']! as String, side: json['side']! as String, type: json['type']! as String, status: json['status']! as String, notional: _readOptionalNum(json['notional']), qty: _readOptionalNum(json['qty']), filledQty: _readOptionalNum(json['filled_qty']), filledAvgPrice: _readOptionalNum(json['filled_avg_price']), submittedAt: _readOptionalDateTime(json['submitted_at']), filledAt: _readOptionalDateTime(json['filled_at']), raw: json, ); } static num? _readOptionalNum(Object? value) { if (value == null) return null; if (value is num) return value; if (value is String) { if (value.isEmpty) return null; return num.tryParse(value); } return null; } static DateTime? _readOptionalDateTime(Object? value) { if (value == null) return null; if (value is DateTime) return value.toUtc(); if (value is String) { if (value.isEmpty) return null; return DateTime.tryParse(value)?.toUtc(); } return null; } } /// Body for `POST /v2/orders` (market by notional). class AlpacaOrderRequest { AlpacaOrderRequest({ required this.symbol, required this.side, required this.type, required this.timeInForce, required this.clientOrderId, this.notional, this.qty, }); final String symbol; final String side; final String type; final String timeInForce; final String clientOrderId; final num? notional; final num? qty; Map toJson() { return { 'symbol': symbol, 'side': side, 'type': type, 'time_in_force': timeInForce, 'client_order_id': clientOrderId, if (notional != null) 'notional': notional, if (qty != null) 'qty': qty, }; } }