import 'dart:convert'; import 'package:http/http.dart' as http; import 'alpaca_env.dart'; import 'alpaca_models.dart'; /// REST client for Alpaca Market Data API v2 (IEX feed on Basic plan). class AlpacaMarketDataClient { AlpacaMarketDataClient({ required AlpacaEnv env, http.Client? httpClient, }) : _env = env, _client = httpClient ?? http.Client(); final AlpacaEnv _env; final http.Client _client; Map get _headers => { 'APCA-API-KEY-ID': _env.apiKeyId, 'APCA-API-SECRET-KEY': _env.apiSecretKey, }; /// `GET /v2/stocks/{symbol}/trades/latest` Future getLatestTrade(String symbol) async { _env.requireCredentials(); final Uri uri = Uri.parse( '${_env.dataBaseUrl}/v2/stocks/${Uri.encodeComponent(symbol)}/trades/latest', ).replace(queryParameters: {'feed': _env.dataFeed}); final http.Response response = await _client.get(uri, headers: _headers); if (response.statusCode != 200) { throw AlpacaMarketDataException( 'getLatestTrade($symbol) failed: ${response.statusCode} ${response.body}', ); } return AlpacaLatestTradeResponse.fromJson( jsonDecode(response.body) as Map, ); } /// `GET /v2/stocks/bars` — batched symbols, daily bars newest last. Future getDailyBars( List symbols, { int limit = 2, }) async { _env.requireCredentials(); if (symbols.isEmpty) { return AlpacaBarsResponse(barsBySymbol: >{}); } final Uri uri = Uri.parse('${_env.dataBaseUrl}/v2/stocks/bars').replace( queryParameters: { 'symbols': symbols.join(','), 'timeframe': '1Day', 'limit': limit.toString(), 'feed': _env.dataFeed, }, ); final http.Response response = await _client.get(uri, headers: _headers); if (response.statusCode != 200) { throw AlpacaMarketDataException( 'getDailyBars failed: ${response.statusCode} ${response.body}', ); } return AlpacaBarsResponse.fromJson( jsonDecode(response.body) as Map, ); } void close() => _client.close(); } class AlpacaMarketDataException implements Exception { AlpacaMarketDataException(this.message); final String message; @override String toString() => message; }