cyberhybridhub/server/lib/trading/market_history_query.dart
nathanAndrinoid a7c370b743
Some checks failed
Admin portal tests / test (push) Has been cancelled
Fix database connection pooling and UI question filtering
- Replaced single Postgres Connection with Pool across the server to prevent runTx locking
- Added _filterForEnabledSources to client's _fetchUnansweredForPanel to strictly enforce local configuration

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-06-05 22:04:06 +00:00

140 lines
4.0 KiB
Dart
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

import 'dart:math';
import 'package:postgres/postgres.dart';
import 'market_data_db.dart' show MarketDataDb;
import 'market_history_bar_placeholder.dart';
import 'market_history_config.dart';
import 'market_history_session_slot.dart';
import 'tradable_assets_db.dart';
/// One symbol's rolling-window 4-hour bar summary for the guessing game.
class WeeklyMover {
WeeklyMover({
required this.symbol,
required this.openClose,
required this.currentClose,
required this.days,
});
final String symbol;
final num openClose;
final num currentClose;
/// Number of 4-hour bars in the window (≥ [MarketHistoryConfig.minBarsForGuess]).
final int days;
}
/// Read-side queries over 4-hour history bars for question rules.
class MarketHistoryQuery {
MarketHistoryQuery({
required Pool connection,
TradableAssetsDb? tradableAssetsDb,
}) : _connection = connection,
_tradableAssetsDb = tradableAssetsDb ?? TradableAssetsDb(connection);
final Pool _connection;
final TradableAssetsDb _tradableAssetsDb;
/// Symbols with ≥ [minBars] 4-hour closes in [`asOf` − window, `asOf`), whose
/// newest bar is not older than [maxStalenessDays] before [asOf].
///
/// When [random] is set, eligible rows are sorted by symbol then shuffled for
/// a stable pick order across runs with the same seed.
Future<List<WeeklyMover>> weeklyMovers({
required DateTime asOf,
int minBars = MarketHistoryConfig.minBarsForGuess,
int windowDays = MarketHistoryConfig.windowDays,
int maxStalenessDays = 2,
Random? random,
}) async {
final DateTime until = asOf.toUtc();
final DateTime since =
MarketHistorySessionSlot.windowFirstSlotStart(until, windowDays);
final DateTime freshSince =
until.subtract(Duration(days: maxStalenessDays));
final List<String> active =
await _tradableAssetsDb.listActiveTradableSymbols();
if (active.isEmpty) {
return <WeeklyMover>[];
}
final Result rows = await _connection.execute(
Sql.named(
'''
WITH bars AS (
SELECT symbol, as_of, price
FROM market_data_snapshots
WHERE metric = 'bar'
AND timeframe = @timeframe
AND as_of >= @since
AND as_of < @until
AND symbol = ANY(@symbols)
AND ${MarketHistoryBarPlaceholder.sqlExcludePlaceholders}
AND price IS NOT NULL
),
agg AS (
SELECT
symbol,
COUNT(*)::int AS bar_count,
MIN(as_of) AS oldest_as_of,
MAX(as_of) AS newest_as_of
FROM bars
GROUP BY symbol
HAVING COUNT(*) >= @min_bars
AND MAX(as_of) >= @fresh_since
)
SELECT
a.symbol,
a.bar_count,
(
SELECT b.price
FROM bars b
WHERE b.symbol = a.symbol AND b.as_of = a.oldest_as_of
LIMIT 1
) AS open_close,
(
SELECT b.price
FROM bars b
WHERE b.symbol = a.symbol AND b.as_of = a.newest_as_of
LIMIT 1
) AS current_close
FROM agg a
ORDER BY a.symbol ASC
''',
),
parameters: <String, dynamic>{
'since': since,
'until': until,
'symbols': active,
'timeframe': MarketHistoryConfig.barTimeframe,
'min_bars': minBars,
'fresh_since': freshSince,
},
);
final List<WeeklyMover> movers = <WeeklyMover>[];
for (final ResultRow row in rows) {
final num? openClose = MarketDataDb.readOptionalNumeric(row[2]);
final num? currentClose = MarketDataDb.readOptionalNumeric(row[3]);
if (openClose == null || currentClose == null) {
continue;
}
movers.add(
WeeklyMover(
symbol: row[0]! as String,
openClose: openClose,
currentClose: currentClose,
days: (row[1]! as num).toInt(),
),
);
}
if (random != null && movers.length > 1) {
movers.shuffle(random);
}
return movers;
}
}