135 lines
3.4 KiB
Dart
135 lines
3.4 KiB
Dart
import 'dart:convert';
|
|
|
|
import 'package:postgres/postgres.dart';
|
|
|
|
/// Normalized market data row persisted for rule evaluation.
|
|
class MarketDataSnapshot {
|
|
MarketDataSnapshot({
|
|
required this.symbol,
|
|
required this.metric,
|
|
required this.asOf,
|
|
this.id,
|
|
this.assetClass = 'us_equity',
|
|
this.feed = 'iex',
|
|
this.price,
|
|
this.volume,
|
|
this.raw,
|
|
this.createdAt,
|
|
});
|
|
|
|
final int? id;
|
|
final String symbol;
|
|
final String assetClass;
|
|
final String feed;
|
|
final String metric;
|
|
final num? price;
|
|
final num? volume;
|
|
final DateTime asOf;
|
|
final Map<String, dynamic>? raw;
|
|
final DateTime? createdAt;
|
|
}
|
|
|
|
/// Postgres access for [market_data_snapshots].
|
|
class MarketDataDb {
|
|
MarketDataDb(this._connection);
|
|
|
|
final Connection _connection;
|
|
|
|
Future<MarketDataSnapshot> insertSnapshot({
|
|
required String symbol,
|
|
required String metric,
|
|
required DateTime asOf,
|
|
String assetClass = 'us_equity',
|
|
String feed = 'iex',
|
|
num? price,
|
|
num? volume,
|
|
Map<String, dynamic>? raw,
|
|
}) async {
|
|
final Result result = await _connection.execute(
|
|
Sql.named(
|
|
'''
|
|
INSERT INTO market_data_snapshots (
|
|
symbol, asset_class, feed, metric, price, volume, as_of, raw
|
|
) VALUES (
|
|
@symbol, @asset_class, @feed, @metric, @price, @volume, @as_of, @raw::jsonb
|
|
)
|
|
RETURNING id, symbol, asset_class, feed, metric, price, volume, as_of, raw, created_at
|
|
''',
|
|
),
|
|
parameters: <String, dynamic>{
|
|
'symbol': symbol,
|
|
'asset_class': assetClass,
|
|
'feed': feed,
|
|
'metric': metric,
|
|
'price': price,
|
|
'volume': volume,
|
|
'as_of': asOf.toUtc(),
|
|
'raw': raw == null ? null : jsonEncode(raw),
|
|
},
|
|
);
|
|
return _rowToSnapshot(result.first);
|
|
}
|
|
|
|
/// Newest snapshot for [symbol] and [metric] by [as_of].
|
|
Future<MarketDataSnapshot?> latestForSymbol(
|
|
String symbol,
|
|
String metric,
|
|
) async {
|
|
final Result result = await _connection.execute(
|
|
Sql.named(
|
|
'''
|
|
SELECT id, symbol, asset_class, feed, metric, price, volume, as_of, raw, created_at
|
|
FROM market_data_snapshots
|
|
WHERE symbol = @symbol AND metric = @metric
|
|
ORDER BY as_of DESC
|
|
LIMIT 1
|
|
''',
|
|
),
|
|
parameters: <String, dynamic>{
|
|
'symbol': symbol,
|
|
'metric': metric,
|
|
},
|
|
);
|
|
if (result.isEmpty) {
|
|
return null;
|
|
}
|
|
return _rowToSnapshot(result.first);
|
|
}
|
|
|
|
MarketDataSnapshot _rowToSnapshot(ResultRow row) {
|
|
final Object? rawValue = row[8];
|
|
Map<String, dynamic>? raw;
|
|
if (rawValue is Map<String, dynamic>) {
|
|
raw = rawValue;
|
|
} else if (rawValue != null) {
|
|
raw = jsonDecode(rawValue.toString()) as Map<String, dynamic>;
|
|
}
|
|
|
|
return MarketDataSnapshot(
|
|
id: (row[0]! as num).toInt(),
|
|
symbol: row[1]! as String,
|
|
assetClass: row[2]! as String,
|
|
feed: row[3]! as String,
|
|
metric: row[4]! as String,
|
|
price: _readOptionalNumeric(row[5]),
|
|
volume: _readOptionalNumeric(row[6]),
|
|
asOf: (row[7]! as DateTime).toUtc(),
|
|
raw: raw,
|
|
createdAt: (row[9]! as DateTime).toUtc(),
|
|
);
|
|
}
|
|
|
|
static num? _readOptionalNumeric(Object? value) {
|
|
if (value == null) {
|
|
return null;
|
|
}
|
|
if (value is num) {
|
|
return value;
|
|
}
|
|
if (value is String) {
|
|
return num.parse(value);
|
|
}
|
|
return num.parse(value.toString());
|
|
}
|
|
}
|