Metrics
All checks were successful
Build and Deploy MoneyMaker / build-and-deploy (push) Successful in 12m22s

This commit is contained in:
Henrik Jess Nielsen
2026-05-28 13:11:29 +02:00
parent 7feff0589f
commit 2294f5bc07
2 changed files with 377 additions and 9 deletions

View File

@@ -29,6 +29,7 @@ import math
import sqlite3
import logging
import warnings
from datetime import datetime
from pathlib import Path
# Silence transformer noise before importing
@@ -78,6 +79,37 @@ MIN_COVERAGE_SPREAD = 0.0 # disabled: signal_score naturally zeros out single-
FINBERT_MIN_CONF = 0.70 # drop neutral articles below this FinBERT confidence
ALERT_THRESHOLD = 0.35 # signal_score > this → alert
# ---------------------------------------------------------------------------
# Claude metrics
# ---------------------------------------------------------------------------
METRICS_FILE = Path(__file__).parent / "metrics.json"
# Pricing: Claude Haiku 4.5 — https://www.anthropic.com/pricing
_PRICE_INPUT_PER_TOKEN = 0.80 / 1_000_000 # $0.80 per MTok
_PRICE_OUTPUT_PER_TOKEN = 4.00 / 1_000_000 # $4.00 per MTok
def calc_cost(input_tokens: int, output_tokens: int) -> float:
return round(input_tokens * _PRICE_INPUT_PER_TOKEN + output_tokens * _PRICE_OUTPUT_PER_TOKEN, 6)
def update_metrics(input_tokens: int, output_tokens: int) -> None:
"""Accumulate Claude token usage into metrics.json."""
cost = calc_cost(input_tokens, output_tokens)
data: dict = {}
if METRICS_FILE.exists():
try:
data = json.loads(METRICS_FILE.read_text())
except Exception:
pass
data["total_calls"] = data.get("total_calls", 0) + 1
data["total_input_tokens"] = data.get("total_input_tokens", 0) + input_tokens
data["total_output_tokens"] = data.get("total_output_tokens", 0) + output_tokens
data["total_cost_usd"] = round(data.get("total_cost_usd", 0.0) + cost, 6)
data["last_updated"] = datetime.now().isoformat(timespec="seconds")
METRICS_FILE.write_text(json.dumps(data, indent=2))
# ---------------------------------------------------------------------------
# Model loading
@@ -195,16 +227,16 @@ def coverage_spread_score(row) -> float:
# Claude structured extraction
# ---------------------------------------------------------------------------
def claude_extract(title: str, text: str, tickers: list[str]) -> dict:
def claude_extract(title: str, text: str, tickers: list[str]) -> tuple[dict, int, int]:
"""
Use Claude Haiku to extract structured financial signal.
Returns {"confirmed_tickers", "magnitude", "timeframe", "reasoning"}.
Returns ({"confirmed_tickers", "magnitude", "timeframe", "reasoning"}, input_tokens, output_tokens).
"""
import anthropic
api_key = os.environ.get("ANTHROPIC_API_KEY")
if not api_key:
return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": "(no API key)"}
return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": "(no API key)"}, 0, 0
client = anthropic.Anthropic(api_key=api_key)
ticker_ctx = "\n".join(
@@ -245,10 +277,10 @@ Fields:
raw = msg.content[0].text.strip()
raw = re.sub(r"^```(?:json)?\n?", "", raw)
raw = re.sub(r"\n?```$", "", raw)
return json.loads(raw)
return json.loads(raw), msg.usage.input_tokens, msg.usage.output_tokens
except Exception as e:
print(f" [warn] Claude failed: {e}")
return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": str(e)[:120]}
return {"confirmed_tickers": tickers, "magnitude": 5, "timeframe": "days", "reasoning": str(e)[:120]}, 0, 0
# ---------------------------------------------------------------------------
@@ -476,7 +508,9 @@ def analyze_articles(
claude_data: dict = {}
if use_claude and not dry_run and os.environ.get("ANTHROPIC_API_KEY"):
print(f" [claude] {slug[:50]}")
claude_data = claude_extract(title, full_text, list(matches.keys()))
claude_data, _in_tok, _out_tok = claude_extract(title, full_text, list(matches.keys()))
if _in_tok:
update_metrics(_in_tok, _out_tok)
# ------------------------------------------------------------------
# Phase 6 — yfinance momentum + scoring