Commit Graph

51 Commits

Author SHA1 Message Date
chemavx 39cebd3be3 feat(notify): Telegram alerts on trade open and close
CI/CD / build-and-push (push) Successful in 7s
New module bot/notify/telegram.py — httpx async, fire-and-forget via
asyncio.create_task, swallows all errors so notifications never affect
trade execution.

Three alert types:
  📈/📉 TRADE ABIERTO  — direction, size, edge_net (in execute())
  /  GANADO/PERDIDO — approx PnL (in close_position())
  🔒    LEGACY CLOSE   — recovered capital + reason (in close_legacy_position())

close_position() and close_legacy_position() gain an optional question=""
param so the message shows the market name instead of market_id.
bot/main.py updated to pass question= to close_legacy_position().

Credentials (TELEGRAM_BOT_TOKEN, TELEGRAM_CHAT_ID) read from env vars
injected via bot-secrets k8s secret.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-26 15:02:39 +00:00
chemavx 1f40c59e3c test: verify CI/CD pipeline
CI/CD / build-and-push (push) Successful in 6s
2026-04-25 10:05:47 +00:00
chemavx fe242ca5b3 ci: install pyyaml before YAML validation step
CI/CD / build-and-push (push) Successful in 6s
catthehacker/ubuntu:act-22.04 does not include PyYAML pre-installed.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 10:03:02 +00:00
chemavx dcf0f01508 test: telegram notifications
CI/CD / build-and-push (push) Failing after 5s
2026-04-25 09:58:23 +00:00
chemavx 116104507a ci: add image verification, YAML validation, and Telegram notifications
CI/CD / build-and-push (push) Failing after 8s
- Verify all 3 images exist in Gitea registry via Docker API before updating manifests
- Validate YAML of modified manifests after sed (python3 yaml.safe_load)
- Notify Telegram on success/failure with job status (if: always())

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-25 09:56:25 +00:00
chemavx ffd3ee2fb8 ci: retrigger after registry-cache upstream fix (mirror.gcr.io)
CI/CD / build-and-push (push) Successful in 47s
2026-04-22 20:36:12 +00:00
chemavx 042a460f0c ci: migrate to buildx docker-container driver with standalone buildkitd
CI/CD / build-and-push (push) Failing after 4m15s
2026-04-22 20:10:56 +00:00
chemavx cecbc3e9ee ci: re-enable BuildKit with buildkitd.toml for OCI registry compatibility
CI/CD / build-and-push (push) Failing after 51s
Legacy builder (DOCKER_BUILDKIT=0) cannot handle OCI image indexes from
registry-cache, causing fallback to Docker Hub which is unreachable.
BuildKit sends proper OCI Accept headers and reads buildkitd.toml to use
HTTP for both the registry-cache mirror and internal Gitea registry.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:23:30 +00:00
chemavx 8b76802676 ci: use internal Gitea registry URL to bypass Cloudflare
CI/CD / build-and-push (push) Failing after 5m33s
Docker in DinD cannot reach git.chemavx.xyz (Cloudflare) from within
the cluster — TCP :443 times out. Switch docker login/build/push to
gitea.gitea.svc.cluster.local:3000 (insecure, same backend storage).
k8s manifest updates still reference git.chemavx.xyz for node pulls.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 19:00:43 +00:00
chemavx a4c04dedd7 ci: disable BuildKit to fix registry-mirror HTTPS bug in Docker 24
CI/CD / build-and-push (push) Failing after 5m14s
Docker 24's embedded BuildKit ignores the http:// prefix in registry-mirrors
and always attempts HTTPS, breaking the local pull-through cache.
DOCKER_BUILDKIT=0 uses the legacy builder which respects the daemon mirror
config correctly. Cache still works via --cache-from + buildcache tag.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 17:42:05 +00:00
chemavx f411d84e91 ci: switch to plain docker build avoiding docker-container driver
CI/CD / build-and-push (push) Failing after 33s
The docker-container buildkitd driver creates an isolated process that
cannot use DinD's registry-mirror config, and the cluster's registry-cache
returns 500 on BuildKit's ?ns=docker.io mirror protocol.

Plain docker build routes through the DinD daemon directly, which already
has registry-mirrors configured for docker.io pull-through cache.
Uses BUILDKIT_INLINE_CACHE=1 for layer caching between builds.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 17:38:42 +00:00
chemavx 359ce8928a ci: add docker.io mirror to buildkitd config for registry-cache
CI/CD / build-and-push (push) Failing after 4m22s
BuildKit docker-container driver runs isolated from DinD daemon config,
so it needs its own mirror declaration to route docker.io pulls through
the cluster-local pull-through cache instead of Cloudflare CDN directly.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 17:29:28 +00:00
chemavx bbcc4c183b ci: retry buildkit run after runner memory fix
CI/CD / build-and-push (push) Failing after 4m19s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 17:21:51 +00:00
chemavx e50f783e04 ci: migrate from kaniko to buildkit/buildx with registry cache
CI/CD / build-and-push (push) Failing after 15m1s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 16:57:40 +00:00
chemavx adf2917cda feat(attribution): dominant_feature per trade + /api/metrics/attribution
CI/CD / build-and-push (push) Successful in 1m52s
Adds alpha attribution by dominant signal feature — which feat_*_lo had
the largest absolute log-odds value on each trade.

Changes:
- _dominant_feature() helper in api/main.py: picks the winning feature
  from signal_components (threshold 0.0001, same as "triggered" in
  /api/metrics/features)
- _enrich_trade() refactored to single exit point; adds dominant_feature
  field to every open trade in /api/trades
- compute_attribution_from_db() in db.py: VALUES subquery finds dominant
  feature per trade in SQL, then aggregates trade_count/avg_edge_net/
  unrealized_pnl_est/realized_pnl/resolved_count/win_rate per group
- /api/metrics/attribution endpoint: returns attribution dict + total_attributed_trades

No schema changes, no strategy changes. Pure observability.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 16:35:24 +00:00
chemavx 6d23e8042b chore: test registry cache
CI/CD / build-and-push (push) Successful in 2m11s
2026-04-22 11:28:47 +00:00
chemavx 8a56bf77d1 fix(accounting): use size_usdc (not net_cost) for positions in initialize()
CI/CD / build-and-push (push) Successful in 1m48s
Eliminates the phantom 0.4% exposure overage after pod restarts.

During live trading execute() stores size_usdc in portfolio.positions and
deducts net_cost from cash — so total_value = bankroll − fees and
exposure_pct = sum(size_usdc) / (bankroll − fees).

Old initialize() stored net_cost in positions, making total_value = bankroll
and inflating exposure_pct (observed: 30.085% vs runtime 29.670%).

Fix: new get_open_position_data() returns both {market_id: size_usdc} and
total_net_cost in one query; initialize() uses size_usdc for positions and
total_net_cost for cash — identical model to execute().

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 11:05:47 +00:00
chemavx 8479a63174 feat(phase6): per-feature signal attribution in log-odds space
CI/CD / build-and-push (push) Successful in 1m56s
Adds feat_fg_lo / feat_mom_lo / feat_news_lo / feat_mfld_lo / feat_btc_dom_lo
to every trade, all normalized to log-odds contribution for direct comparability.

- fg / mom / btc_dom: raw probability-delta × 2 → log-odds
- news / mfld: already log-odds (LOGODDS_WEIGHT already applied), no scaling
- btc_dom tracked separately in bayesian.py instead of bundled in total_adj
- reasoning string updated to fg_lo= / mom_lo= notation for self-documentation

Schema: 5 new DOUBLE PRECISION columns + 2 partial indexes
Stack: TradingSignal → Order → Trade → save_trade all carry feat fields
Startup: backfill_feature_columns() recovers fg/mom/news/mfld from old
  reasoning strings (×2 applied to fg/mom); btc_dom_lo stays NULL for legacy
API: /api/metrics/features — triggered/material split per feature with
  two-level thresholds (0.05 for fg/mom/btc_dom, 0.10 for news/mfld)
API: /api/trades/legacy — exposes pre-Phase-1 trades (edge_net IS NULL)
API: _enrich_trade backward-compat: reads DB columns first, falls back to
  reasoning regex with unit conversion for pre-Phase-6 trades

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-22 07:04:53 +00:00
chemavx 9a5be27532 feat(metrics): Fix 3 — DB-computed metrics, stateless tracker, resolution tracking
CI/CD / build-and-push (push) Successful in 1m47s
schema.sql
  trades:        + close_pnl, resolution (market outcome storage)
  metrics_daily: + unrealized_pnl_est, realized_pnl, open/closed/resolved_count

db.py
  close_paper_position(): accepts resolution; computes close_pnl in SQL
    BUY_YES: (resolution − entry_price) × shares
    BUY_NO:  ((1 − resolution) − entry_price) × shares
  save_daily_metrics(): persists new columns
  compute_metrics_from_db(): single DB query for all metrics; no in-memory state

tracker.py — complete rewrite (stateless)
  Removed self._trades, self._daily_returns, compute_metrics(), _compute_sharpe(),
  check_promotion_thresholds(), _empty_metrics()
  update_daily_summary() now reads compute_metrics_from_db() every cycle
  Safe across pod restarts: always reflects full DB history

paper.py
  close_position(): passes resolution to close_paper_position()

api/main.py  /api/summary
  Added unrealized_pnl_est (estimated, open trades) and realized_pnl (exact,
  closed+resolved) as separate fields alongside total_pnl
  win_rate: null if < 5 resolved trades (was proxy on entry_price < 0.5)
  calibration_score: Brier-based, null if < 10 resolved trades
  resolved_count exposed as field
  Each field annotated with: exact/estimated, source, null conditions

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:34:48 +00:00
chemavx 9b62636a3e ci: add --registry-mirror=mirror.gcr.io to all kaniko build steps
CI/CD / build-and-push (push) Successful in 2m12s
Kaniko has its own HTTP client and ignores the dind --registry-mirror flag.
Passing --registry-mirror=mirror.gcr.io directly to kaniko lets it pull
python:3.11-slim (and other Docker Hub base images) via Google's mirror
instead of the Cloudflare CDN endpoint (172.64.66.1) which is unreachable
from the runner's dind network.

Also adds mirror to dind deployment args (--registry-mirror=https://mirror.gcr.io)
so node:16-bullseye (the job runner container) is also fetched via mirror.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 17:24:57 +00:00
chemavx 5a3df975d9 fix(metrics): replace inflated PnL formula; drop fake calibration_score
CI/CD / build-and-push (push) Failing after 1m20s
total_pnl now uses edge_net × net_cost instead of (0.5 - entry_price) × shares.
The old formula overestimated BUY_NO trades at low entry prices by 3–10× because
buying at price 0.158 yields 3164 shares — any exit-at-0.5 assumption produced
$1072 PnL on $500 deployed. edge_net × net_cost is bounded by net_cost per trade
and uses the model's own signal, giving $122 for the same position.

calibration_score is now None (null in API) instead of 1 - 2×|avg_edge|. That
formula was not a real calibration: it requires knowing market resolutions
(YES=1/NO=0) which we do not store yet. Returning null is more honest than
returning 0.0 or a meaningless proxy. Fix 3 will compute it from closed trades.

check_promotion_thresholds updated to handle None calibration (null → not ready).

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 16:47:05 +00:00
chemavx 46f8f4b79a feat(observability): fine-grained metrics for summary, trades, and cycle log
CI/CD / build-and-push (push) Successful in 1m51s
api/summary — new fields:
  open_trades_count, closed_trades_count, cash_available (bankroll−deployed),
  legacy_incomplete_count, reentry_guard_blocks_24h
  parallel fetch via asyncio.gather for sub-ms overhead

api/trades?status=open — trade enrichment:
  days_open (float, rounded to 1 decimal)
  signal_components {fg, mom, news, mfld} parsed from reasoning via regex
  Old trades without feat_str in reasoning return signal_components: null

bayesian.py — reasoning now embeds feat_str:
  "fg=+0.0600 mom=+0.0000 news=+0.0000 mfld=-0.7483 |"
  Manifold counters: _manifold_fetched / _manifold_on_trade per cycle
  get_cycle_stats() exposes manifold_matches_accepted / manifold_matches_rejected

bot/main.py — CYCLE SUMMARY 4 new fields:
  reentry_guard_blocked, legacy_incomplete_seen,
  family_conflicts_prevented, manifold_matches_accepted/rejected
  legacy_incomplete_count queried from DB once per cycle

db.py — get_legacy_incomplete_count(): open trades with NULL edge_net

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:48:31 +00:00
chemavx e2fb697c0c fix: family_key repair, reentry guard, legacy_incomplete, trades status filter
CI/CD / build-and-push (push) Successful in 1m54s
- db: update_family_key() persists corrected family slugs for open trades
- db: get_recently_closed_inverted() returns markets closed for inversion
  within N hours; used as reentry guard in the trading loop
- db: get_recent_trades() accepts status=open|closed|None and adds a
  computed "status" field to every row
- bot/main.py: legacy scan now computes family_key from stored question
  alone (dummy Market) when a position's market is no longer active —
  fixes NULL family_key on legacy trades like Ken Paxton (562186)
- bot/main.py: legacy scan (Step 2.5) persists corrected family_keys in
  DB so family conflict guards work correctly on next restart
- bot/main.py: positions with NULL edge_net and no live market are tagged
  legacy_incomplete instead of OK; counted separately in scan summary
- bot/main.py: reentry_guard blocks re-entering any market closed for
  inversion bug within 24h; logs reentry_guard_triggered per skip
- api/main.py: /api/trades now accepts ?status=open|closed|all (default
  open) and includes status_filter in response

DB fix (applied directly): 629558 family_key politics-2026 →
ohio-gubernatorial-2026; 562186 family_key NULL → texas-republican-2026

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-21 09:37:45 +00:00
chemavx d698544f30 feat(scan): legacy position scan — re-key, Manifold re-validate, auto-close
CI/CD / build-and-push (push) Successful in 2m21s
Adds run_legacy_scan() that executes once at startup before the trading loop:

  1. Re-keys every open DB position using the current market_family_key()
  2. Groups by new family key; KEEP = highest edge_net, CLOSE_RECOMMENDED = sibling
  3. Manifold re-query for positions whose family key changed; if corrected
     probability contradicts the trade direction → CLOSE_RECOMMENDED
  4. Logs full report (KEEP / REVIEW / CLOSE_RECOMMENDED) before any closures
  5. In paper mode: auto-closes all CLOSE_RECOMMENDED positions

For the existing Ohio bug:
  - Democrats win Ohio governor (629557): CLOSE_RECOMMENDED
    family changed ohio-democrat-2026 → ohio-gubernatorial-2026
    Manifold re-query confirms prob=0.05 contradicts BUY_YES (inversion bug)
    $X returned to cash at break-even
  - Republicans win Ohio governor (629558): KEEP
    higher edge_net (0.349 > 0.247)

Infrastructure:
  - schema.sql: closed_at TIMESTAMPTZ, close_reason TEXT on trades
  - db.py: all open-position queries filter WHERE closed_at IS NULL
           + close_paper_position(market_id, reason)
  - paper.py: close_legacy_position(market_id, reason) → float

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:43:45 +00:00
chemavx 9add52ab05 fix(polymarket): _PARTY_RE: add Republicans? plural support for symmetry
CI/CD / build-and-push (push) Successful in 2m24s
Republicans (plural) previously didn't match _PARTY_RE because the pattern
was r"\bRepublican\b" (no optional s).  Added Republicans? for symmetry with
Democrats?.  The general-election family fix already handles this case via
etype_m, but the plural match is needed for the party-only fallback branch.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:35:07 +00:00
chemavx ebdcff5a6e fix(critical): complementary market family grouping + Manifold inversion guard
CI/CD / build-and-push (push) Successful in 2m23s
FASE 1 — market_family_key() general election fix
General elections now group by office, not by party, so complementary
markets ("Republicans win Ohio governor" / "Democrats win Ohio governor")
share the same family key (ohio-gubernatorial-2026).  The second market
is blocked by the occupied_families check rather than traded as independent.

Primaries still keep the party (texas-republican-2026) because each party
runs its own separate primary race.

FASE 2 — Manifold party inversion guard
_detect_party() identifies the winning side in both the Polymarket question
and the matched Manifold title.  If they are confirmed opposites (republican
vs democrat), the probability is inverted (1 - prob) before use.

Full audit log per query:
  poly_question / manifold_title / manifold_url / match_score /
  prob_raw / inverted / prob_final

Root cause of Ohio Manifold:0.95 on both sides: both queries matched the
same Manifold market ("Republicans win Ohio governor" prob=0.95).  For the
"Democrats win" query the inversion now produces prob_final=0.05 instead of
blindly applying 0.95 to the wrong direction.

FASE 4 — startup contradiction scan
get_open_position_details() added to db.py.  main.py checks all open
positions at startup, warns on any family with >1 position, and recommends
keeping the one with the highest edge_net.  No auto-close.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:26:29 +00:00
chemavx 0cdb0758c4 feat(strategy): Manifold cross-market signal + per-feature contribution logging
CI/CD / build-and-push (push) Successful in 2m21s
Signal 5: ManifoldClient queries Manifold Markets API for a matching binary
market by keyword overlap (threshold 0.25) and applies a log-odds adjustment
proportional to the divergence from the Polymarket prior.

  manifold_log_adj = (log_odds(manifold_prob) - log_odds(prior)) × 0.6

A 30pp divergence (Manifold 0.75 vs Poly 0.45) produces edge_gross ≈ 0.19,
clearing the politics far-horizon regime_min=0.12 after costs. Confidence
boosted +0.08 when Manifold match found.

Per-feature observability: every SKIP_EDGE_NET and TRADE log line now includes
  fg=±X.XXX  mom=±X.XXX  mfld=±X.XXXX  news=±X.XXXX
so the contribution of each signal to edge is auditable per market.

Files: bot/data/manifold.py (new), bot/strategy/bayesian.py, bot/main.py

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-17 10:07:47 +00:00
chemavx 411d346261 feat(bot): add [CYCLE SUMMARY] diagnostic block at end of each cycle
CI/CD / build-and-push (push) Successful in 2m16s
BayesianStrategy now tracks per-cycle counters (reset each cycle):
  - skip_prior_extreme, skip_family
  - skip_edge_net_nonpositive (edge_net ≤ 0)
  - skip_edge_net_below_regime (0 < edge_net < regime_min)
  - evaluated_edges list for max/pct computations

main.py logs one structured [CYCLE SUMMARY] block per cycle with:
  markets_total, markets_uncertainty_zone, max_edge_gross, max_edge_net,
  pct_edge_gross_gt_002, pct_edge_gross_gt_004, all blocked_by_* counters,
  trades_executed, gnews_queries_used/cap

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:55:22 +00:00
chemavx 63d9f637ff feat(bot): 5-phase strategy upgrade — edge neto, families, GNews priority, regimes
CI/CD / build-and-push (push) Successful in 2m30s
Phase 1 — Edge neto real (paper.py, bayesian.py, risk/manager.py, db.py):
- Trade records now store edge_gross, edge_net, prior_prob, final_prob,
  mid_price, spread_estimate, commission, family_key
- edge_net = edge_gross - SPREAD_ESTIMATE(0.02) - COMMISSION_RATE(0.02)
  NOTE: both constants are heuristics, not exact Polymarket exchange costs
- Execution gate changed from edge_gross > MIN_EDGE to edge_net > regime_min_edge

Phase 2 — Market families (polymarket.py):
- market_family_key(market) groups related markets:
    texas-republican-2026, fed-april-2026, openai-2026, etc.
- At most 1 trade per family per cycle; occupied_families propagated via main.py
- Family key logged on every TRADE and SKIP line

Phase 3 — GNews priority (news.py, bayesian.py, main.py):
- NewsClient.get_freshness() returns 1.0/0.75/0.40/0.10 by cache age
- gnews_priority(market, news) = uncertainty × volume_score × freshness
- Politics markets sorted by priority DESC before eval so best markets get
  the 5-query/cycle GNews budget first

Phase 4 — Regime min-edge by category/horizon (bayesian.py):
- politics >60d → 0.12, 30-60d → 0.10, <30d → 0.08
- tech / crypto/finance → 0.10
- All thresholds applied to edge_net (not edge_gross)

Phase 5 — Observability (bayesian.py, main.py):
- Structured skip labels: SKIP_UNSUPPORTED, SKIP_NO_SIGNALS,
  SKIP_PRIOR_EXTREME, SKIP_FAMILY, SKIP_GNEWS_PRIORITY, SKIP_EDGE_NET
- TRADE lines now include family_key, edge_gross, edge_net, regime_min, days
- schema.sql: 8 new cols on trades, 7 new cols on signals (via ALTER TABLE IF NOT EXISTS)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 15:34:46 +00:00
chemavx a0cbdc0256 fix(polymarket): correct sports/tech/finance categorization + widen 90d window
CI/CD / build-and-push (push) Successful in 2m31s
- Add European football leagues (La Liga, Premier League, Bundesliga, etc.)
  to _SPORTS_EXCLUSIONS so those markets are filtered before category detection
- Reorder _detect_category: check tech before crypto/finance so company-specific
  markets (OpenAI IPO, NVIDIA, Apple) resolve to "tech" instead of "crypto/finance"
- Widen resolution horizon default from 60 to 90 days to surface more
  markets in the 0.08–0.92 uncertainty zone

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-16 14:30:34 +00:00
chemavx 9bdafaa51e feat: add dashboard source code with Vite + React + Recharts and CI/CD build
CI/CD / build-and-push (push) Successful in 2m24s
- Reconstruct dashboard from compiled container: App.jsx, main.jsx, index.css
- nginx.conf with SPA routing and /api proxy to api:8000
- Multi-stage Dockerfile: node:20-alpine build + nginx:alpine serve
- Add third kaniko build step in ci.yml for chemavx/polymarket-bot-dashboard
- Update k8s manifest sed to patch deployment-dashboard.yaml image on each push

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 17:18:32 +00:00
chemavx 324edbe4c8 feat(polymarket): exclude sports markets before category detection
CI/CD / build-and-push (push) Successful in 1m33s
Add _SPORTS_EXCLUSIONS list checked first in _detect_category so NBA/NFL/
MLB/NHL/tennis/golf/UFC/boxing/wrestling/tournament markets never bleed into
politics or events categories. Also removes 'super bowl' from _EVENTS_KEYWORDS
since it's now covered by the sports exclusion.

Keywords excluded: nba, nfl, mlb, nhl, basketball, football, baseball, hockey,
soccer, mvp, rookie of the, championship, super bowl, world series, playoffs,
playoff, tournament, tennis, golf, ufc, boxing, wrestler, wrestling,
slam dunk, home run, touchdown.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 13:03:49 +00:00
chemavx 7b9c5751ea feat(polymarket): widen market filter for more uncertainty-zone markets
CI/CD / build-and-push (push) Successful in 1m31s
- min_volume: 1000 → 500 USDC (surface lower-liquidity but real markets)
- max_days_to_resolution: 30 → 60 days (more markets with unresolved uncertainty)
- Tech keywords: +tesla, +elon, +nuclear, +quantum, +chip
- Macro keywords: +recession, +gdp, +unemployment, +trade war, +trade deal
  (inflation/tariff already present)

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:53:53 +00:00
chemavx 5a9c6add41 feat(strategy): skip markets with extreme priors (< 0.08 or > 0.92)
CI/CD / build-and-push (push) Successful in 1m33s
Markets where Polymarket consensus is near-certain leave no room for our
signals to generate MIN_EDGE=0.10 — evaluating them wastes GNews quota and
produces noise. Filter them out early with a clear log reason.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:48:23 +00:00
chemavx 82d6d357eb feat(news): replace keyword sentiment with VADER
CI/CD / build-and-push (push) Successful in 1m27s
vaderSentiment==3.3.2 added to requirements.txt.

_score_headlines now:
- scores each article (title + description) with VADER compound ∈ [-1, +1]
- filters out articles with |compound| ≤ 0.05 (no clear signal)
- weights remaining articles by recency (GNews newest-first, rank 0 → highest weight)
- returns weighted mean clamped to [-1, +1]

Removes the custom keyword sets (_POSITIVE/_NEGATIVE) and the set-based
bag-of-words algorithm that capped scores at ~±0.5 in practice.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:42:19 +00:00
chemavx 33ad86f352 feat(news): 6h cache, politics-only, max 5/cycle, 2s sleep between calls
CI/CD / build-and-push (push) Successful in 1m32s
- CACHE_TTL: 4h → 6h (≤36 req/day with ≤9 politics markets)
- GNews only called for is_politics markets (BTC/F&G cover crypto/macro)
- MAX_NEWS_QUERIES_PER_CYCLE=5: BayesianStrategy.reset_cycle() called each
  iteration; counter increments only on actual API call (cache hits free)
- 2s asyncio.sleep in news.py finally block after each real HTTP request
- main.py sorts markets: politics first by end_date ascending, so soonest-
  resolving markets consume the 5-query budget before others

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 12:33:26 +00:00
chemavx d642dbd9cf fix(news): remove paid-tier 'from' param, add User-Agent, log status+body on error
CI/CD / build-and-push (push) Successful in 1m34s
- Drop the 'from' date filter — it's a paid GNews feature, causes 403 on free tier
- Add User-Agent header to httpx client; urllib default passes, httpx default blocked
- Log actual HTTP status code for every request (INFO) and response body on non-200
- Cache neutral result on 400/401/403/429 to avoid hammering the quota
- Remove unused _iso_days_ago() helper and 'days' param from get_sentiment()

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:36:46 +00:00
chemavx 4dadd3c2c4 feat: add GNews sentiment signal for politics/tech/events markets
CI/CD / build-and-push (push) Successful in 1m28s
bot/data/news.py (new):
- NewsClient with in-memory cache (TTL=4h) to stay within 100 req/day limit
- _build_query(): strips dates, punctuation and stopwords from market question
- _score_headlines(): keyword-based pos/neg vote per article, averaged ∈ [-1, +1]
- Degrades to 0.0 on missing key, 403 quota, or network error

bot/strategy/bayesian.py:
- BayesianStrategy(news=NewsClient) — optional, backwards compatible
- Signal 4: GNews sentiment applied as direct log-odds shift (weight=1.5)
  so a ±1.0 sentiment score moves a 50% prior to 82%/18%
- +0.10 confidence boost when news signal is present
- NEWS_LOGODDS_WEIGHT constant documented at module level

bot/main.py:
- Instantiate NewsClient, pass to BayesianStrategy, close in finally block

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:24:11 +00:00
chemavx 98e7f5fe73 feat(logging): log prior/estimate/edge/reason for every evaluated market
CI/CD / build-and-push (push) Successful in 1m35s
Every market now emits an INFO line:
  TRADE/SKIP <question> | cat=... | prior=... | est=... | edge=... | conf=... | dir=... | signals=... [| reason=...]
Unsupported-category and no-external-signals early exits also log at INFO
so the full evaluation funnel is visible without changing log level.

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:15:49 +00:00
chemavx b8d2b733fd feat: expand market coverage to politics, tech, and events categories
CI/CD / build-and-push (push) Successful in 1m31s
- polymarket.py: add keyword lists for politics (election, trump, ukraine…),
  tech (AI, OpenAI, Apple, nvidia…), and events (super bowl, oscar, spacex…);
  introduce _detect_category() so all four categories flow through a single
  code path; filter already-expired markets (end_dt < now) in addition to
  the existing future-cutoff filter; log per-category counts at startup
- bayesian.py: extend is_any_supported to include is_politics / is_tech /
  is_events; use BTC as a risk-sentiment proxy for non-crypto categories
  (halved weight to reflect weaker correlation); cap confidence_cap at 0.65
  for macro/politics/tech/events; MIN_EDGE stays at 0.10

Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-04-14 08:07:17 +00:00
chemavx f8c4f8b78a ci: test pipeline trigger — minor comment update
CI/CD / build-and-push (push) Successful in 1m41s
2026-04-13 20:40:13 +00:00
chemavx 1dd01e03b1 feat: replace Docker CLI builds with Kaniko (no daemon needed in job)
CI/CD / build-and-push (push) Successful in 2m35s
2026-04-13 16:35:36 +00:00
chemavx bc27f89aa3 ci: retry with container.network=host so job containers reach DinD via localhost
CI/CD / build-and-push (push) Failing after 58s
2026-04-13 16:28:14 +00:00
chemavx f808a3cecf fix: drop container block, set DOCKER_HOST/TLS_VERIFY as job env, install via get.docker.com
CI/CD / build-and-push (push) Failing after 54s
2026-04-13 16:22:30 +00:00
chemavx f37055821e fix: clear Docker TLS config before login + DOCKER_CERT_PATH empty in runner
CI/CD / build-and-push (push) Failing after 15s
2026-04-13 16:20:50 +00:00
chemavx cfeb404907 fix: use node:20-alpine + apk install docker-cli for Node.js + Docker
CI/CD / build-and-push (push) Failing after 10s
2026-04-13 16:18:59 +00:00
chemavx 3241b8a638 fix: use docker:24-cli container image with DOCKER_HOST pre-configured
CI/CD / build-and-push (push) Failing after 11s
2026-04-13 16:17:29 +00:00
chemavx 44a238cfd5 fix: install docker CLI in job container + pass DOCKER_HOST via runner envs
CI/CD / build-and-push (push) Failing after 51s
2026-04-13 16:15:32 +00:00
chemavx 2fb6fbfa1d ci: retry pipeline with DEFAULT_ACTIONS_URL=github and insecure runner
CI/CD / build-and-push (push) Failing after 36s
2026-04-13 16:10:25 +00:00
chemavx b99215f1a4 fix: use internal cluster URL and disable TLS verify for git ops
CI/CD / build-and-push (push) Failing after 1s
2026-04-13 16:08:23 +00:00