RayMelius Claude Opus 4.6 commited on
Commit
9209065
Β·
1 Parent(s): bf84a36

Seed dashboard price chart with real OHLCV history from Yahoo Finance

Browse files

- Script: --seed-chart flag writes OHLCV candles into dashboard_history.db
- Dashboard: auto-seeds from ohlcv/*.json on first run if DB is empty
- Dashboard: /history/symbols endpoint for pre-populating chart dropdown
- Frontend: loads available symbols on page load for pre-session chart display
- Fix HISTORY_DB default path after shared_data -> shared/data merge
- 1W shows ~5 daily candles, 1M shows ~20 daily candles with real prices

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>

dashboard/dashboard.py CHANGED
@@ -220,7 +220,7 @@ def _generate_and_broadcast():
220
  print(f"[Dashboard/LLM] LLM failed: {source}")
221
 
222
  # ── OHLCV History ──────────────────────────────────────────────────────────────
223
- HISTORY_DB = os.getenv("HISTORY_DB", "/app/data/dashboard_history.db")
224
  BUCKET_SIZE = 60 # 1-minute candles
225
 
226
  PERIOD_SECONDS = {
@@ -248,9 +248,42 @@ def init_history_db():
248
  )
249
  """)
250
  conn.commit()
 
 
 
 
 
 
251
  conn.close()
252
 
253
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
254
  def record_trade(symbol, price, qty, ts):
255
  if not symbol or price <= 0:
256
  return
@@ -770,6 +803,18 @@ def session_status():
770
 
771
  # ── History endpoint ───────────────────────────────────────────────────────────
772
 
 
 
 
 
 
 
 
 
 
 
 
 
773
  @app.route("/history/<symbol>")
774
  def history(symbol):
775
  period = request.args.get("period", "1d")
 
220
  print(f"[Dashboard/LLM] LLM failed: {source}")
221
 
222
  # ── OHLCV History ──────────────────────────────────────────────────────────────
223
+ HISTORY_DB = os.getenv("HISTORY_DB", "/app/shared/data/dashboard_history.db")
224
  BUCKET_SIZE = 60 # 1-minute candles
225
 
226
  PERIOD_SECONDS = {
 
248
  )
249
  """)
250
  conn.commit()
251
+
252
+ # Auto-seed from OHLCV JSON files if DB is empty
253
+ row_count = conn.execute("SELECT COUNT(*) FROM ohlcv").fetchone()[0]
254
+ if row_count == 0:
255
+ _seed_history_from_ohlcv(conn)
256
+
257
  conn.close()
258
 
259
 
260
+ def _seed_history_from_ohlcv(conn):
261
+ """Seed dashboard_history.db from shared/data/ohlcv/*.json on first run."""
262
+ import glob as globmod
263
+ ohlcv_dir = os.path.join(os.path.dirname(HISTORY_DB), "ohlcv")
264
+ if not os.path.isdir(ohlcv_dir):
265
+ return
266
+ count = 0
267
+ for path in sorted(globmod.glob(os.path.join(ohlcv_dir, "*.json"))):
268
+ sym = os.path.splitext(os.path.basename(path))[0]
269
+ try:
270
+ with open(path) as f:
271
+ bars = json.load(f)
272
+ for bar in bars:
273
+ from datetime import datetime as _dt
274
+ dt = _dt.strptime(bar["date"], "%Y-%m-%d").replace(hour=10)
275
+ bucket = int(dt.timestamp() // 60) * 60
276
+ conn.execute(
277
+ "INSERT OR IGNORE INTO ohlcv VALUES (?,?,?,?,?,?,?)",
278
+ (sym, bucket, bar["open"], bar["high"], bar["low"], bar["close"], bar["volume"]),
279
+ )
280
+ count += 1
281
+ except Exception as e:
282
+ print(f"[History] Error seeding {sym}: {e}")
283
+ conn.commit()
284
+ print(f"[History] Auto-seeded {count} candles from OHLCV files")
285
+
286
+
287
  def record_trade(symbol, price, qty, ts):
288
  if not symbol or price <= 0:
289
  return
 
803
 
804
  # ── History endpoint ───────────────────────────────────────────────────────────
805
 
806
+ @app.route("/history/symbols")
807
+ def history_symbols():
808
+ """Return list of symbols that have historical OHLCV data."""
809
+ try:
810
+ conn = sqlite3.connect(HISTORY_DB)
811
+ rows = conn.execute("SELECT DISTINCT symbol FROM ohlcv ORDER BY symbol").fetchall()
812
+ conn.close()
813
+ return jsonify({"symbols": [r[0] for r in rows]})
814
+ except Exception:
815
+ return jsonify({"symbols": []})
816
+
817
+
818
  @app.route("/history/<symbol>")
819
  def history(symbol):
820
  period = request.args.get("period", "1d")
dashboard/templates/index.html CHANGED
@@ -1617,6 +1617,26 @@
1617
  connectSSE();
1618
  loadAIConfig();
1619
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1620
  // Refresh order book panel every 3 seconds
1621
  setInterval(() => {
1622
  const sym = document.getElementById("bbo-symbol-select").value;
 
1617
  connectSSE();
1618
  loadAIConfig();
1619
 
1620
+ // Pre-populate history symbol dropdown from DB (shows chart before session starts)
1621
+ try {
1622
+ const resp = await fetch("/history/symbols");
1623
+ const data = await resp.json();
1624
+ const histSel = document.getElementById("history-symbol");
1625
+ const existing = new Set(Array.from(histSel.options).map(o => o.value));
1626
+ (data.symbols || []).forEach(sym => {
1627
+ if (!existing.has(sym)) {
1628
+ const opt = document.createElement("option");
1629
+ opt.value = sym; opt.textContent = sym;
1630
+ histSel.appendChild(opt);
1631
+ }
1632
+ });
1633
+ // Default to 1W view if there's historical data and no live session
1634
+ if (data.symbols && data.symbols.length > 0 && !document.getElementById("history-period").value) {
1635
+ document.getElementById("history-period").value = "1w";
1636
+ }
1637
+ loadHistory();
1638
+ } catch(e) { /* ignore */ }
1639
+
1640
  // Refresh order book panel every 3 seconds
1641
  setInterval(() => {
1642
  const sym = document.getElementById("bbo-symbol-select").value;
scripts/update_securities_prices.py CHANGED
@@ -8,12 +8,14 @@ Usage:
8
  python scripts/update_securities_prices.py --history 10 # also print 10-day history
9
  python scripts/update_securities_prices.py --reset-start # reset start_price to oldest close
10
  python scripts/update_securities_prices.py --ohlcv 60 # export 60 days of OHLCV for RL agent
 
11
 
12
  Requires: pip install yfinance
13
  """
14
  import argparse
15
  import json
16
  import os
 
17
  import sys
18
  from datetime import datetime
19
 
@@ -45,6 +47,7 @@ TICKER_MAP = {
45
 
46
  SECURITIES_FILE = os.path.join(os.path.dirname(__file__), "..", "shared", "data", "securities.txt")
47
  OHLCV_DIR = os.path.join(os.path.dirname(__file__), "..", "shared", "data", "ohlcv")
 
48
 
49
  # Tickers that trade in USD and need EUR conversion
50
  USD_TICKERS = {"AMZN", "TSLA", "NVDA", "GOOGL", "AAPL"}
@@ -206,6 +209,45 @@ def export_ohlcv(results):
206
  print(f"Exported OHLCV for {exported} symbols to {OHLCV_DIR}")
207
 
208
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
209
  def main():
210
  parser = argparse.ArgumentParser(description="Update securities.txt with real ATHEX prices")
211
  parser.add_argument("--history", type=int, default=0,
@@ -218,6 +260,8 @@ def main():
218
  help="Output results as JSON")
219
  parser.add_argument("--ohlcv", type=int, default=0,
220
  help="Export N days of OHLCV data for RL agent (default: 0, disabled)")
 
 
221
  args = parser.parse_args()
222
 
223
  days = max(args.history, args.ohlcv, 10)
@@ -236,6 +280,9 @@ def main():
236
  if args.ohlcv > 0:
237
  print(f"\n=== Exporting OHLCV for RL Agent ({days} bars) ===")
238
  export_ohlcv(results)
 
 
 
239
  else:
240
  print("(dry run β€” no files updated)")
241
 
 
8
  python scripts/update_securities_prices.py --history 10 # also print 10-day history
9
  python scripts/update_securities_prices.py --reset-start # reset start_price to oldest close
10
  python scripts/update_securities_prices.py --ohlcv 60 # export 60 days of OHLCV for RL agent
11
+ python scripts/update_securities_prices.py --seed-chart # seed dashboard price chart DB
12
 
13
  Requires: pip install yfinance
14
  """
15
  import argparse
16
  import json
17
  import os
18
+ import sqlite3
19
  import sys
20
  from datetime import datetime
21
 
 
47
 
48
  SECURITIES_FILE = os.path.join(os.path.dirname(__file__), "..", "shared", "data", "securities.txt")
49
  OHLCV_DIR = os.path.join(os.path.dirname(__file__), "..", "shared", "data", "ohlcv")
50
+ HISTORY_DB = os.path.join(os.path.dirname(__file__), "..", "shared", "data", "dashboard_history.db")
51
 
52
  # Tickers that trade in USD and need EUR conversion
53
  USD_TICKERS = {"AMZN", "TSLA", "NVDA", "GOOGL", "AAPL"}
 
209
  print(f"Exported OHLCV for {exported} symbols to {OHLCV_DIR}")
210
 
211
 
212
+ def seed_dashboard_db(results):
213
+ """Insert OHLCV data into dashboard_history.db for the price chart.
214
+
215
+ Each daily bar is inserted as a single candle at 10:00 market open
216
+ of that trading day (bucket = Unix timestamp rounded to 60s).
217
+ """
218
+ conn = sqlite3.connect(HISTORY_DB)
219
+ conn.execute("""
220
+ CREATE TABLE IF NOT EXISTS ohlcv (
221
+ symbol TEXT,
222
+ bucket INTEGER,
223
+ open REAL,
224
+ high REAL,
225
+ low REAL,
226
+ close REAL,
227
+ volume INTEGER,
228
+ PRIMARY KEY (symbol, bucket)
229
+ )
230
+ """)
231
+
232
+ inserted = 0
233
+ for sym, data in results.items():
234
+ if data is None or "ohlcv" not in data:
235
+ continue
236
+ for bar in data["ohlcv"]:
237
+ # Convert date string to Unix timestamp at 10:00 local time
238
+ dt = datetime.strptime(bar["date"], "%Y-%m-%d").replace(hour=10, minute=0)
239
+ bucket = int(dt.timestamp() // 60) * 60
240
+ conn.execute(
241
+ "INSERT OR REPLACE INTO ohlcv VALUES (?,?,?,?,?,?,?)",
242
+ (sym, bucket, bar["open"], bar["high"], bar["low"], bar["close"], bar["volume"]),
243
+ )
244
+ inserted += 1
245
+
246
+ conn.commit()
247
+ conn.close()
248
+ print(f"Seeded {inserted} candles into {HISTORY_DB}")
249
+
250
+
251
  def main():
252
  parser = argparse.ArgumentParser(description="Update securities.txt with real ATHEX prices")
253
  parser.add_argument("--history", type=int, default=0,
 
260
  help="Output results as JSON")
261
  parser.add_argument("--ohlcv", type=int, default=0,
262
  help="Export N days of OHLCV data for RL agent (default: 0, disabled)")
263
+ parser.add_argument("--seed-chart", action="store_true",
264
+ help="Seed dashboard_history.db with fetched OHLCV for price chart display")
265
  args = parser.parse_args()
266
 
267
  days = max(args.history, args.ohlcv, 10)
 
280
  if args.ohlcv > 0:
281
  print(f"\n=== Exporting OHLCV for RL Agent ({days} bars) ===")
282
  export_ohlcv(results)
283
+ if args.seed_chart:
284
+ print(f"\n=== Seeding Dashboard Price Chart ===")
285
+ seed_dashboard_db(results)
286
  else:
287
  print("(dry run β€” no files updated)")
288