83 lines
2.4 KiB
Dart
83 lines
2.4 KiB
Dart
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<String, String> get _headers => <String, String>{
|
|
'APCA-API-KEY-ID': _env.apiKeyId,
|
|
'APCA-API-SECRET-KEY': _env.apiSecretKey,
|
|
};
|
|
|
|
/// `GET /v2/stocks/{symbol}/trades/latest`
|
|
Future<AlpacaLatestTradeResponse> getLatestTrade(String symbol) async {
|
|
_env.requireCredentials();
|
|
final Uri uri = Uri.parse(
|
|
'${_env.dataBaseUrl}/v2/stocks/${Uri.encodeComponent(symbol)}/trades/latest',
|
|
).replace(queryParameters: <String, String>{'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<String, dynamic>,
|
|
);
|
|
}
|
|
|
|
/// `GET /v2/stocks/bars` — batched symbols, daily bars newest last.
|
|
Future<AlpacaBarsResponse> getDailyBars(
|
|
List<String> symbols, {
|
|
int limit = 2,
|
|
}) async {
|
|
_env.requireCredentials();
|
|
if (symbols.isEmpty) {
|
|
return AlpacaBarsResponse(barsBySymbol: <String, List<AlpacaBar>>{});
|
|
}
|
|
|
|
final Uri uri = Uri.parse('${_env.dataBaseUrl}/v2/stocks/bars').replace(
|
|
queryParameters: <String, String>{
|
|
'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<String, dynamic>,
|
|
);
|
|
}
|
|
|
|
void close() => _client.close();
|
|
}
|
|
|
|
class AlpacaMarketDataException implements Exception {
|
|
AlpacaMarketDataException(this.message);
|
|
|
|
final String message;
|
|
|
|
@override
|
|
String toString() => message;
|
|
}
|