82 lines
2.6 KiB
Dart
82 lines
2.6 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:http/http.dart' as http;
|
|
|
|
class StockPricePoint {
|
|
final DateTime date;
|
|
final double close;
|
|
|
|
const StockPricePoint({required this.date, required this.close});
|
|
}
|
|
|
|
class StockPriceService {
|
|
Future<List<StockPricePoint>> fetchPriceHistory(
|
|
String ticker, {
|
|
String range = '1mo',
|
|
String interval = '1d',
|
|
}) async {
|
|
final normalizedTicker = ticker.trim().toUpperCase();
|
|
final uri = Uri.parse(
|
|
'https://query1.finance.yahoo.com/v8/finance/chart/$normalizedTicker?interval=$interval&range=$range',
|
|
);
|
|
|
|
final response = await http.get(uri, headers: {
|
|
'User-Agent': 'Mozilla/5.0',
|
|
'Accept': 'application/json',
|
|
});
|
|
|
|
if (response.statusCode != 200) {
|
|
throw Exception('Failed to fetch price history for $normalizedTicker: ${response.statusCode}');
|
|
}
|
|
|
|
final json = jsonDecode(response.body) as Map<String, dynamic>;
|
|
final chart = json['chart'] as Map<String, dynamic>?;
|
|
final results = chart?['result'] as List?;
|
|
|
|
if (results == null || results.isEmpty) {
|
|
throw Exception('No price history returned for $normalizedTicker');
|
|
}
|
|
|
|
final result = results.first as Map<String, dynamic>;
|
|
final timestamps = (result['timestamp'] as List?) ?? const [];
|
|
final indicators = result['indicators'] as Map<String, dynamic>?;
|
|
final adjCloseSeries = ((indicators?['adjclose'] as List?) ?? const [])
|
|
.cast<Map<String, dynamic>?>();
|
|
final quoteSeries = ((indicators?['quote'] as List?) ?? const [])
|
|
.cast<Map<String, dynamic>?>();
|
|
|
|
final adjCloseEntry = adjCloseSeries.isNotEmpty ? adjCloseSeries.first : null;
|
|
final quoteEntry = quoteSeries.isNotEmpty ? quoteSeries.first : null;
|
|
final adjCloseValues = adjCloseEntry == null ? null : adjCloseEntry['adjclose'];
|
|
final quoteCloseValues = quoteEntry == null ? null : quoteEntry['close'];
|
|
final closes = (adjCloseValues as List?) ?? (quoteCloseValues as List?) ?? const [];
|
|
|
|
final points = <StockPricePoint>[];
|
|
final itemCount = timestamps.length < closes.length ? timestamps.length : closes.length;
|
|
|
|
for (int i = 0; i < itemCount; i++) {
|
|
final timestamp = timestamps[i];
|
|
final close = closes[i];
|
|
|
|
if (timestamp is! num || close is! num) {
|
|
continue;
|
|
}
|
|
|
|
points.add(
|
|
StockPricePoint(
|
|
date: DateTime.fromMillisecondsSinceEpoch(
|
|
timestamp.toInt() * 1000,
|
|
isUtc: true,
|
|
).toLocal(),
|
|
close: close.toDouble(),
|
|
),
|
|
);
|
|
}
|
|
|
|
if (points.isEmpty) {
|
|
throw Exception('No valid price points returned for $normalizedTicker');
|
|
}
|
|
|
|
return points;
|
|
}
|
|
}
|