cyberhybridhub/server/lib/trading/tradable_assets_sync.dart
2026-05-31 11:17:12 -05:00

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,
);
}
}