Spaces:
Running
Running
Sync from GitHub (tests passed)
Browse files- app/data_manager.py +34 -10
- app/settings.py +3 -3
- worker/tasks.py +1 -3
app/data_manager.py
CHANGED
|
@@ -464,6 +464,10 @@ def ingest_prices(session: Session) -> dict:
|
|
| 464 |
"""
|
| 465 |
Ingest price data for all configured symbols.
|
| 466 |
|
|
|
|
|
|
|
|
|
|
|
|
|
| 467 |
Returns:
|
| 468 |
Dict with stats per symbol
|
| 469 |
"""
|
|
@@ -479,24 +483,44 @@ def ingest_prices(session: Session) -> dict:
|
|
| 479 |
|
| 480 |
stats = {}
|
| 481 |
|
| 482 |
-
#
|
| 483 |
end_date = datetime.now(timezone.utc)
|
| 484 |
-
|
|
|
|
|
|
|
|
|
|
| 485 |
|
| 486 |
for i, symbol in enumerate(symbols):
|
| 487 |
-
logger.info(f"Fetching prices for {symbol}...")
|
| 488 |
-
|
| 489 |
try:
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 490 |
# Fetch with retry mechanism
|
| 491 |
df = fetch_symbol_with_retry(symbol, start_date, end_date)
|
| 492 |
|
| 493 |
if df is None or df.empty:
|
| 494 |
logger.warning(f"No data returned for {symbol}")
|
| 495 |
-
stats[symbol] = {"imported": 0, "
|
| 496 |
continue
|
| 497 |
|
| 498 |
imported = 0
|
| 499 |
-
|
| 500 |
|
| 501 |
for date_idx, row in df.iterrows():
|
| 502 |
try:
|
|
@@ -537,7 +561,7 @@ def ingest_prices(session: Session) -> dict:
|
|
| 537 |
if result.rowcount > 0:
|
| 538 |
imported += 1
|
| 539 |
else:
|
| 540 |
-
|
| 541 |
|
| 542 |
except Exception as e:
|
| 543 |
logger.debug(f"Error processing price bar: {e}")
|
|
@@ -545,8 +569,8 @@ def ingest_prices(session: Session) -> dict:
|
|
| 545 |
|
| 546 |
session.commit()
|
| 547 |
|
| 548 |
-
stats[symbol] = {"imported": imported, "
|
| 549 |
-
logger.info(f"{symbol}: {imported} bars imported, {
|
| 550 |
|
| 551 |
# Add delay between symbols to avoid rate limiting
|
| 552 |
if i < len(symbols) - 1:
|
|
@@ -554,7 +578,7 @@ def ingest_prices(session: Session) -> dict:
|
|
| 554 |
|
| 555 |
except Exception as e:
|
| 556 |
logger.error(f"Failed to fetch {symbol}: {e}")
|
| 557 |
-
stats[symbol] = {"imported": 0, "
|
| 558 |
|
| 559 |
return stats
|
| 560 |
|
|
|
|
| 464 |
"""
|
| 465 |
Ingest price data for all configured symbols.
|
| 466 |
|
| 467 |
+
Uses INCREMENTAL fetching: checks latest bar date per symbol in DB
|
| 468 |
+
and only fetches from that point forward (plus 3-day overlap for corrections).
|
| 469 |
+
Falls back to full lookback if no existing data found for a symbol.
|
| 470 |
+
|
| 471 |
Returns:
|
| 472 |
Dict with stats per symbol
|
| 473 |
"""
|
|
|
|
| 483 |
|
| 484 |
stats = {}
|
| 485 |
|
| 486 |
+
# Full lookback range (used only for first-time fetches)
|
| 487 |
end_date = datetime.now(timezone.utc)
|
| 488 |
+
full_start_date = end_date - timedelta(days=settings.lookback_days)
|
| 489 |
+
|
| 490 |
+
# Overlap buffer: re-fetch last 3 days to catch any corrections/adjustments
|
| 491 |
+
OVERLAP_DAYS = 3
|
| 492 |
|
| 493 |
for i, symbol in enumerate(symbols):
|
|
|
|
|
|
|
| 494 |
try:
|
| 495 |
+
# Check latest bar in DB for incremental fetch
|
| 496 |
+
latest_bar = session.query(PriceBar.date).filter(
|
| 497 |
+
PriceBar.symbol == symbol
|
| 498 |
+
).order_by(PriceBar.date.desc()).first()
|
| 499 |
+
|
| 500 |
+
if latest_bar and latest_bar.date:
|
| 501 |
+
# Incremental: fetch from (latest - overlap) to now
|
| 502 |
+
latest_date = latest_bar.date
|
| 503 |
+
if latest_date.tzinfo is None:
|
| 504 |
+
latest_date = latest_date.replace(tzinfo=timezone.utc)
|
| 505 |
+
start_date = latest_date - timedelta(days=OVERLAP_DAYS)
|
| 506 |
+
mode = "incremental"
|
| 507 |
+
else:
|
| 508 |
+
# First time: full lookback
|
| 509 |
+
start_date = full_start_date
|
| 510 |
+
mode = "full"
|
| 511 |
+
|
| 512 |
+
logger.info(f"Fetching prices for {symbol} ({mode})...")
|
| 513 |
+
|
| 514 |
# Fetch with retry mechanism
|
| 515 |
df = fetch_symbol_with_retry(symbol, start_date, end_date)
|
| 516 |
|
| 517 |
if df is None or df.empty:
|
| 518 |
logger.warning(f"No data returned for {symbol}")
|
| 519 |
+
stats[symbol] = {"imported": 0, "updated": 0, "error": "no_data"}
|
| 520 |
continue
|
| 521 |
|
| 522 |
imported = 0
|
| 523 |
+
updated = 0
|
| 524 |
|
| 525 |
for date_idx, row in df.iterrows():
|
| 526 |
try:
|
|
|
|
| 561 |
if result.rowcount > 0:
|
| 562 |
imported += 1
|
| 563 |
else:
|
| 564 |
+
updated += 1
|
| 565 |
|
| 566 |
except Exception as e:
|
| 567 |
logger.debug(f"Error processing price bar: {e}")
|
|
|
|
| 569 |
|
| 570 |
session.commit()
|
| 571 |
|
| 572 |
+
stats[symbol] = {"imported": imported, "updated": updated, "mode": mode}
|
| 573 |
+
logger.info(f"{symbol}: {imported} bars imported, {updated} unchanged ({mode}, {len(df)} fetched)")
|
| 574 |
|
| 575 |
# Add delay between symbols to avoid rate limiting
|
| 576 |
if i < len(symbols) - 1:
|
|
|
|
| 578 |
|
| 579 |
except Exception as e:
|
| 580 |
logger.error(f"Failed to fetch {symbol}: {e}")
|
| 581 |
+
stats[symbol] = {"imported": 0, "updated": 0, "error": str(e)}
|
| 582 |
|
| 583 |
return stats
|
| 584 |
|
app/settings.py
CHANGED
|
@@ -78,10 +78,10 @@ class Settings(BaseSettings):
|
|
| 78 |
openrouter_api_key: Optional[str] = None
|
| 79 |
# Deprecated - kept for backward compatibility
|
| 80 |
openrouter_model: str = "arcee-ai/trinity-large-preview:free"
|
| 81 |
-
# New primary config
|
| 82 |
-
openrouter_model_scoring: str = "
|
| 83 |
openrouter_model_scoring_fast: Optional[str] = None
|
| 84 |
-
openrouter_model_scoring_reliable: Optional[str] =
|
| 85 |
openrouter_model_commentary: str = "arcee-ai/trinity-large-preview:free"
|
| 86 |
openrouter_rpm: int = 18
|
| 87 |
openrouter_max_retries: int = 3
|
|
|
|
| 78 |
openrouter_api_key: Optional[str] = None
|
| 79 |
# Deprecated - kept for backward compatibility
|
| 80 |
openrouter_model: str = "arcee-ai/trinity-large-preview:free"
|
| 81 |
+
# New primary config - Nemotron supports structured output (response_format/json_schema)
|
| 82 |
+
openrouter_model_scoring: str = "nvidia/nemotron-3-nano-30b-a3b:free"
|
| 83 |
openrouter_model_scoring_fast: Optional[str] = None
|
| 84 |
+
openrouter_model_scoring_reliable: Optional[str] = "google/gemma-3-27b-it:free"
|
| 85 |
openrouter_model_commentary: str = "arcee-ai/trinity-large-preview:free"
|
| 86 |
openrouter_rpm: int = 18
|
| 87 |
openrouter_max_retries: int = 3
|
worker/tasks.py
CHANGED
|
@@ -421,13 +421,11 @@ async def _execute_pipeline_stages_v2(
|
|
| 421 |
# -------------------------------------------------------------------------
|
| 422 |
logger.info(f"[run_id={run_id}] Stage 3: Sentiment aggregation")
|
| 423 |
try:
|
| 424 |
-
from app.ai_engine import
|
| 425 |
|
| 426 |
days_aggregated_v2 = aggregate_daily_sentiment_v2(session)
|
| 427 |
-
days_aggregated = aggregate_daily_sentiment(session)
|
| 428 |
session.commit()
|
| 429 |
|
| 430 |
-
result["days_aggregated"] = days_aggregated
|
| 431 |
result["days_aggregated_v2"] = days_aggregated_v2
|
| 432 |
|
| 433 |
except Exception as e:
|
|
|
|
| 421 |
# -------------------------------------------------------------------------
|
| 422 |
logger.info(f"[run_id={run_id}] Stage 3: Sentiment aggregation")
|
| 423 |
try:
|
| 424 |
+
from app.ai_engine import aggregate_daily_sentiment_v2
|
| 425 |
|
| 426 |
days_aggregated_v2 = aggregate_daily_sentiment_v2(session)
|
|
|
|
| 427 |
session.commit()
|
| 428 |
|
|
|
|
| 429 |
result["days_aggregated_v2"] = days_aggregated_v2
|
| 430 |
|
| 431 |
except Exception as e:
|