Compare commits
2 Commits
2294f5bc07
...
026b470b31
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
026b470b31 | ||
|
|
6a407cf216 |
63
analyze.py
63
analyze.py
@@ -83,11 +83,11 @@ ALERT_THRESHOLD = 0.35 # signal_score > this → alert
|
|||||||
# Claude metrics
|
# Claude metrics
|
||||||
# ---------------------------------------------------------------------------
|
# ---------------------------------------------------------------------------
|
||||||
|
|
||||||
METRICS_FILE = Path(__file__).parent / "metrics.json"
|
METRICS_FILE = Path(os.getenv("DATA_DIR", str(Path(__file__).parent / "data"))) / "metrics.json"
|
||||||
|
|
||||||
# Pricing: Claude Haiku 4.5 — https://www.anthropic.com/pricing
|
# Pricing: Claude 3 Haiku — https://www.anthropic.com/pricing
|
||||||
_PRICE_INPUT_PER_TOKEN = 0.80 / 1_000_000 # $0.80 per MTok
|
_PRICE_INPUT_PER_TOKEN = 0.25 / 1_000_000 # $0.25 per MTok
|
||||||
_PRICE_OUTPUT_PER_TOKEN = 4.00 / 1_000_000 # $4.00 per MTok
|
_PRICE_OUTPUT_PER_TOKEN = 1.25 / 1_000_000 # $1.25 per MTok
|
||||||
|
|
||||||
|
|
||||||
def calc_cost(input_tokens: int, output_tokens: int) -> float:
|
def calc_cost(input_tokens: int, output_tokens: int) -> float:
|
||||||
@@ -270,7 +270,7 @@ Fields:
|
|||||||
|
|
||||||
try:
|
try:
|
||||||
msg = client.messages.create(
|
msg = client.messages.create(
|
||||||
model="claude-haiku-4-5",
|
model="claude-3-haiku-20240307",
|
||||||
max_tokens=256,
|
max_tokens=256,
|
||||||
messages=[{"role": "user", "content": prompt}],
|
messages=[{"role": "user", "content": prompt}],
|
||||||
)
|
)
|
||||||
@@ -372,6 +372,7 @@ def analyze_articles(
|
|||||||
dry_run: bool = False,
|
dry_run: bool = False,
|
||||||
use_claude: bool = True,
|
use_claude: bool = True,
|
||||||
auto_fetch: bool = True,
|
auto_fetch: bool = True,
|
||||||
|
max_claude_calls: int = 50,
|
||||||
) -> None:
|
) -> None:
|
||||||
db = get_db()
|
db = get_db()
|
||||||
migrate_db(db)
|
migrate_db(db)
|
||||||
@@ -486,6 +487,16 @@ def analyze_articles(
|
|||||||
now = int(time.time())
|
now = int(time.time())
|
||||||
signals_written = 0
|
signals_written = 0
|
||||||
alerts_triggered = 0
|
alerts_triggered = 0
|
||||||
|
claude_calls_this_run = 0
|
||||||
|
|
||||||
|
# Daily spend guard — read current metrics before starting
|
||||||
|
_existing = {}
|
||||||
|
if METRICS_FILE.exists():
|
||||||
|
try:
|
||||||
|
_existing = json.loads(METRICS_FILE.read_text())
|
||||||
|
except Exception:
|
||||||
|
pass
|
||||||
|
DAILY_COST_CAP = float(os.getenv("CLAUDE_DAILY_CAP_USD", "2.0"))
|
||||||
|
|
||||||
for row, matches, cov, full_text in final:
|
for row, matches, cov, full_text in final:
|
||||||
slug = row["slug"]
|
slug = row["slug"]
|
||||||
@@ -507,10 +518,19 @@ def analyze_articles(
|
|||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
claude_data: dict = {}
|
claude_data: dict = {}
|
||||||
if use_claude and not dry_run and os.environ.get("ANTHROPIC_API_KEY"):
|
if use_claude and not dry_run and os.environ.get("ANTHROPIC_API_KEY"):
|
||||||
print(f" [claude] {slug[:50]}")
|
if claude_calls_this_run >= max_claude_calls:
|
||||||
claude_data, _in_tok, _out_tok = claude_extract(title, full_text, list(matches.keys()))
|
print(f" [claude] ⚠️ per-run cap ({max_claude_calls}) reached — skipping remaining articles")
|
||||||
if _in_tok:
|
use_claude = False
|
||||||
update_metrics(_in_tok, _out_tok)
|
elif _existing.get("total_cost_usd", 0.0) >= DAILY_COST_CAP:
|
||||||
|
print(f" [claude] ⚠️ daily spend cap ${DAILY_COST_CAP} reached — skipping Claude for remaining articles")
|
||||||
|
use_claude = False
|
||||||
|
else:
|
||||||
|
print(f" [claude] {slug[:50]}")
|
||||||
|
claude_data, _in_tok, _out_tok = claude_extract(title, full_text, list(matches.keys()))
|
||||||
|
if _in_tok:
|
||||||
|
update_metrics(_in_tok, _out_tok)
|
||||||
|
_existing["total_cost_usd"] = _existing.get("total_cost_usd", 0.0) + calc_cost(_in_tok, _out_tok)
|
||||||
|
claude_calls_this_run += 1
|
||||||
|
|
||||||
# ------------------------------------------------------------------
|
# ------------------------------------------------------------------
|
||||||
# Phase 6 — yfinance momentum + scoring
|
# Phase 6 — yfinance momentum + scoring
|
||||||
@@ -587,9 +607,28 @@ def analyze_articles(
|
|||||||
|
|
||||||
def main() -> None:
|
def main() -> None:
|
||||||
import sys
|
import sys
|
||||||
force = "--force" in sys.argv
|
force = "--force" in sys.argv
|
||||||
dry_run = "--dry" in sys.argv
|
dry_run = "--dry-run" in sys.argv or "--dry" in sys.argv
|
||||||
analyze_articles(force=force, dry_run=dry_run)
|
no_claude = "--no-claude" in sys.argv
|
||||||
|
|
||||||
|
# --max-claude=N override per-run cap
|
||||||
|
max_calls = 50
|
||||||
|
for arg in sys.argv:
|
||||||
|
if arg.startswith("--max-claude="):
|
||||||
|
try:
|
||||||
|
max_calls = int(arg.split("=", 1)[1])
|
||||||
|
except ValueError:
|
||||||
|
pass
|
||||||
|
|
||||||
|
if force:
|
||||||
|
print(f"[analyze] ⚠️ --force mode: re-analyzing ALL articles (claude cap={max_calls})")
|
||||||
|
|
||||||
|
analyze_articles(
|
||||||
|
force=force,
|
||||||
|
dry_run=dry_run,
|
||||||
|
use_claude=not no_claude,
|
||||||
|
max_claude_calls=max_calls,
|
||||||
|
)
|
||||||
|
|
||||||
|
|
||||||
if __name__ == "__main__":
|
if __name__ == "__main__":
|
||||||
|
|||||||
15
dashboard.py
15
dashboard.py
@@ -28,12 +28,7 @@ from report import _c25_day_return, _unrealised_pnl, _realised_pnl, _total_fees,
|
|||||||
|
|
||||||
CAPITAL = 10_000
|
CAPITAL = 10_000
|
||||||
LOG_DIR = Path(os.getenv("LOG_DIR", str(Path(__file__).parent / "logs")))
|
LOG_DIR = Path(os.getenv("LOG_DIR", str(Path(__file__).parent / "logs")))
|
||||||
METRICS_FILE = Path(__file__).parent / "metrics.json"
|
METRICS_FILE = Path(os.getenv("DATA_DIR", str(Path(__file__).parent / "data"))) / "metrics.json"
|
||||||
REFRESH = 60 # seconds
|
|
||||||
|
|
||||||
CAPITAL = 10_000
|
|
||||||
LOG_DIR = Path(os.getenv("LOG_DIR", str(Path(__file__).parent / "logs")))
|
|
||||||
METRICS_FILE = Path(__file__).parent / "metrics.json"
|
|
||||||
REFRESH = 60 # seconds
|
REFRESH = 60 # seconds
|
||||||
|
|
||||||
app = Flask(__name__)
|
app = Flask(__name__)
|
||||||
@@ -246,9 +241,9 @@ METRICS_TEMPLATE = """<!DOCTYPE html>
|
|||||||
</tr>
|
</tr>
|
||||||
</thead>
|
</thead>
|
||||||
<tbody>
|
<tbody>
|
||||||
<tr><td>Model</td><td class="mono">claude-haiku-4-5</td><td>Anthropic</td></tr>
|
<tr><td>Model</td><td class="mono">claude-3-haiku-20240307</td><td>Anthropic</td></tr>
|
||||||
<tr><td>Input price</td><td class="mono">$0.80 / MTok</td><td>${{ "%.6f"|format(0.80/1_000_000) }} per token</td></tr>
|
<tr><td>Input price</td><td class="mono">$0.25 / MTok</td><td>${{ "%.6f"|format(0.25/1_000_000) }} per token</td></tr>
|
||||||
<tr><td>Output price</td><td class="mono">$4.00 / MTok</td><td>${{ "%.6f"|format(4.00/1_000_000) }} per token</td></tr>
|
<tr><td>Output price</td><td class="mono">$1.25 / MTok</td><td>${{ "%.6f"|format(1.25/1_000_000) }} per token</td></tr>
|
||||||
<tr><td>Total calls</td><td class="mono">{{ total_calls }}</td><td>articles sent to Claude</td></tr>
|
<tr><td>Total calls</td><td class="mono">{{ total_calls }}</td><td>articles sent to Claude</td></tr>
|
||||||
<tr><td>Total input tokens</td><td class="mono">{{ "{:,}".format(total_input_tokens) }}</td><td>cost ${{ "%.5f"|format(cost_input) }}</td></tr>
|
<tr><td>Total input tokens</td><td class="mono">{{ "{:,}".format(total_input_tokens) }}</td><td>cost ${{ "%.5f"|format(cost_input) }}</td></tr>
|
||||||
<tr><td>Total output tokens</td><td class="mono">{{ "{:,}".format(total_output_tokens) }}</td><td>cost ${{ "%.5f"|format(cost_output) }}</td></tr>
|
<tr><td>Total output tokens</td><td class="mono">{{ "{:,}".format(total_output_tokens) }}</td><td>cost ${{ "%.5f"|format(cost_output) }}</td></tr>
|
||||||
@@ -298,7 +293,7 @@ window.addEventListener('resize', () => { barChart.resize(); donut.resize(); });
|
|||||||
</script>
|
</script>
|
||||||
{% endif %}
|
{% endif %}
|
||||||
|
|
||||||
<div class="footer">MoneyMaker · claude-haiku-4-5 · metrics updated on each analyze run</div>
|
<div class="footer">MoneyMaker · claude-3-haiku-20240307 · metrics updated on each analyze run</div>
|
||||||
</body>
|
</body>
|
||||||
</html>
|
</html>
|
||||||
"""
|
"""
|
||||||
|
|||||||
Reference in New Issue
Block a user