cyberhybridhub/server/lib/trading/market_data_db.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());
}
}