The ICICI Breeze API token expires daily, exact time undocumented by ICICI. You must authenticate to generate a new token.
See the README for the step-by-step Daily Session Flow (browser login -> Telegram /refresh_session command).
To deeply backfill history (e.g. 3 years) for all tickers currently in your holdings and watchlist, run the manual script:
docker exec portfoliopi-bot python -m scripts.backfill_historyThis will fetch 1095 calendar days in 90-day chunks, respecting rate limits.
Verification:
To ensure the backfill succeeded without silent API truncation, check the actual depth landed. Run this against the Supabase database (e.g. via its SQL Editor, or psql, or a quick one-off script):
SELECT stock_code, COUNT(*) as days_cached, MIN(date) as earliest, MAX(date) as latest FROM ohlcv_cache GROUP BY stock_code ORDER BY days_cached ASC;If most tickers show ~700+ days cached (roughly 3 years minus non-trading days), it worked.
A nightly backup of every table is taken at 11:30 PM IST via core/scheduler.py's db_backup_job, which streams each table out with Postgres's COPY command into a gzip-compressed CSV (data/backups/<table>_<YYYYMMDD>.csv.gz), retained for 7 days. This exists as a supplementary safety net in addition to whatever backup/PITR your Supabase project tier provides — check your Supabase project's Database → Backups settings for the platform-level restore options first, since those are usually the faster/more complete path.
To restore a table from one of these CSV backups (example: ohlcv_cache):
- Decompress:
gunzip -k data/backups/ohlcv_cache_YYYYMMDD.csv.gz - In Supabase's SQL Editor or via
psql, optionally back up the current (broken) table first:CREATE TABLE ohlcv_cache_broken AS TABLE ohlcv_cache; - Truncate and reload:
TRUNCATE ohlcv_cache; \copy ohlcv_cache FROM 'ohlcv_cache_YYYYMMDD.csv' WITH (FORMAT csv, HEADER true);(the\copyvariant runs client-side viapsql; use Supabase's Table Editor "Import data from CSV" feature if you don't have a directpsqlconnection.) - Fix the identity sequence if the table has one:
SELECT setval(pg_get_serial_sequence('ohlcv_cache','id'), COALESCE((SELECT MAX(id) FROM ohlcv_cache),0)+1, false);
PortfolioPi splits its data operations into two pipelines to balance accuracy and uptime:
- Breeze Ground-Truth Sync (
run_breeze_sync):- Trigger: Daily cron (8:00 AM IST) and manual "Refresh Now" dashboard requests.
- Uptime dependence: Requires a fresh Breeze session token (updated daily).
- Scope: Fetches Demat/Portfolio holdings, updates
quantityandaverage_price, and performs an incremental daily OHLCV backfill from Breeze.
- Market Data Refresh (
run_market_data_refresh):- Trigger: Every 60 minutes, 24/7.
- Uptime dependence: Unauthenticated yfinance API. Runs even if Breeze session token is expired or missing.
- Scope: Fetches recent quotes and daily candles from
yfinance, updatescurrent_priceinholdings_snapshot, appends new daily OHLCV rows toohlcv_cache, runs technical indicator generation, and computes buy/sell verdicts.
When a new ticker is added to the watchlist (via dashboard or Telegram):
- A backfill request is queued in the
backfill_requeststable. - The background scheduler checks this queue every 20 seconds.
- It fetches 3 years (1,095 days) of daily OHLCV candles via
yfinanceto seed the cache immediately. - Screener signals and Stage verdicts become active immediately instead of waiting for a daily sync cycle.
- Bot container shows unhealthy: The Docker healthcheck ensures a data refresh pipeline has succeeded in the last 24 hours by checking
job_heartbeats. Check the "Session Status" dashboard page or run/statusin Telegram. If no jobs ran, your session token may be expired. - Session token expired: Follow the Daily Session Refresh flow (Telegram
/refresh_sessioncommand). - Dashboard shows stale data: The dashboard relies on background jobs. If you click a "Refresh" button on the dashboard, it queues a request in
refresh_requests. The bot container polls this table every 60 seconds. If data isn't updating, check the bot container logs (docker logs portfoliopi-bot) to confirm the polling job is running and processing the request.