78 lines
2.4 KiB
Dart
78 lines
2.4 KiB
Dart
import 'package:postgres/postgres.dart';
|
|
|
|
import '../alpaca/alpaca_assets_client.dart';
|
|
import '../alpaca/alpaca_models.dart';
|
|
import 'sync_run_recorder.dart';
|
|
import 'tradable_assets_db.dart';
|
|
|
|
/// Outcome of a single [TradableAssetsSync.runOnce] invocation.
|
|
///
|
|
/// On the happy path [error] is `null` and [rowsWritten] is the number of
|
|
/// asset rows upserted. On a failure path [error] holds the upstream
|
|
/// message and [rowsWritten] is `0` — the exception itself is never
|
|
/// rethrown to callers (the scheduler in §5 must keep going).
|
|
class TradableAssetsSyncResult {
|
|
TradableAssetsSyncResult({
|
|
required this.rowsWritten,
|
|
required this.startedAt,
|
|
required this.finishedAt,
|
|
this.error,
|
|
});
|
|
|
|
final int rowsWritten;
|
|
final DateTime startedAt;
|
|
final DateTime finishedAt;
|
|
final String? error;
|
|
|
|
bool get succeeded => error == null;
|
|
}
|
|
|
|
/// Daily refresh of the `tradable_assets` cache from Alpaca.
|
|
///
|
|
/// One pass:
|
|
/// 1. [SyncRunRecorder] inserts a `market_data_sync_runs` row with
|
|
/// `kind='universe'`.
|
|
/// 2. Pull `/v2/assets?status=active&asset_class=us_equity`.
|
|
/// 3. Upsert into `tradable_assets`, marking missing symbols inactive.
|
|
/// 4. Recorder closes the row with `finished_at` + `rows_written` (or
|
|
/// `error` on failure).
|
|
///
|
|
/// The scheduler in §5 runs this once per `MARKET_UNIVERSE_REFRESH_HOURS`.
|
|
class TradableAssetsSync {
|
|
TradableAssetsSync({
|
|
required AlpacaAssetsClient assetsClient,
|
|
required TradableAssetsDb assetsDb,
|
|
required Connection connection,
|
|
}) : _assetsClient = assetsClient,
|
|
_assetsDb = assetsDb,
|
|
_recorder = SyncRunRecorder(connection);
|
|
|
|
final AlpacaAssetsClient _assetsClient;
|
|
final TradableAssetsDb _assetsDb;
|
|
final SyncRunRecorder _recorder;
|
|
|
|
static const String kind = 'universe';
|
|
|
|
Future<TradableAssetsSyncResult> runOnce({DateTime? now}) async {
|
|
final DateTime started = (now ?? DateTime.now()).toUtc();
|
|
|
|
final SyncRunOutcome outcome = await _recorder.record(
|
|
kind,
|
|
() async {
|
|
final List<AlpacaAsset> assets =
|
|
await _assetsClient.listActiveTradable();
|
|
await _assetsDb.upsertAll(assets, now: started);
|
|
return SyncRunCounts(rowsWritten: assets.length);
|
|
},
|
|
now: started,
|
|
);
|
|
|
|
return TradableAssetsSyncResult(
|
|
rowsWritten: outcome.rowsWritten,
|
|
startedAt: outcome.startedAt,
|
|
finishedAt: outcome.finishedAt,
|
|
error: outcome.error,
|
|
);
|
|
}
|
|
}
|