Commit Graph
23 Commits
Author SHA1 Message Date
chemavxandClaude Fable 5 0ac48ba7f8 feat(replay): R1 replay core — clock injection + replay of archived cycles
Re-executes BayesianStrategy.evaluate() over the R0 archive and stores
results in replay_runs/replay_decisions, tagged with git sha + a hash of
the strategy constants (same hash vs archive = determinism check,
different hash = counterfactual run).

- bayesian.py: optional as_of param on evaluate()/_days_to_resolution()
  (clock injection; default None = wall clock, prod behavior unchanged —
  the only touch to frozen code, purely additive)
- bot/replay.py: replay engine + CLI (python -m bot.replay --from --to);
  ReplayNews feeds archived sentiment back (GNews never called, per-cycle
  budget bypassed — archived sentiment already encodes it); manifold/db
  not wired (observational-only in prod); recorded-vs-replayed compare
  at 1e-9 tolerance
- schema.sql: replay_runs + replay_decisions (+ indexes), idempotent
- db.py: 6 replay accessors/writers
- tests: 19 new round-trip fidelity tests (104 total green)

Validated against a real prod cycle (2026-07-02T14:03:15Z, 46 markets,
4 skip paths incl. the Georgia confidence record): 46/46 matched,
max float delta 0.0.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 14:05:25 +00:00
chemavxandClaude Fable 5 919fe1617a feat(replay): R0 snapshot recorder — archive per-cycle decisions into signals
The signals and markets tables existed since Phase 2/5 but never had a
writer; the replay engine (phase plan line 2.1) needs a per-(market, cycle)
archive of what the strategy saw and decided. This wires them up:

- signals: one row per evaluated market per cycle, now carrying INPUTS
  (news_sentiment, feat_*_lo, volume_24h, days_to_resolution) plus the
  existing outputs (probs, edges, gates, skip_reason). skip_reason is
  granular: unsupported/no_signals/prior_extreme/family/edge_net/
  confidence/reentry_guard. news_budget_skipped distinguishes "GNews not
  asked" (5-query budget) from "no news".
- ext_snapshots: one row per cycle with the ExternalSignals snapshot;
  signals rows join on cycle_ts.
- markets: metadata upserted each cycle (replay rebuilds Market from it).
- Retention: prune > SIGNALS_RETENTION_DAYS (default 90) once a day.
- SIGNAL_RECORDER_ENABLED (default true) gates all DB writes; every write
  is try/except — the recorder can never break trading.

Strategy changes are purely additive (record accumulation at each exit
path of evaluate()); no weights, thresholds, gates or sizing touched,
per the freeze in the current phase plan.

Tests: 10 new deterministic tests (85 total passing). Schema migration
dry-run validated against prod postgres inside a rolled-back transaction.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-02 08:52:07 +00:00
chemavxandClaude Fable 5 7f84bc3ec7 feat(strategy): GNews guardrail — clamp news-only shifts to prior±0.25
Post-mortem NVIDIA 631181: one uncorroborated high-weight signal (legacy
Manifold 0.13 at weight 0.6) flipped a 0.845 market to 0.431 and lost.
With Manifold observational-only and macro signals gated behind
is_non_price, GNews (weight 1.5) is the only live signal able to move
politics markets 20-30 pp against the order-book consensus.  This adds a
catastrophic fuse, not a fine calibration:

- apply_news_guardrail(): when |news_lo| >= NEWS_MATERIAL_LOGODDS_THRESHOLD
  (0.10) and every other signal (fg, mom, btc_dom, mfld) is below it,
  clamp the posterior to prior ± MAX_NEWS_ONLY_PROB_SHIFT (0.25).  Any
  corroborating material signal disables the clamp.  Config via env
  (NEWS_GUARDRAIL_ENABLED=true by default).
- edge_gross/edge_net computed from the clamped posterior; raw_final_prob
  preserved in reasoning (persisted via trades.reasoning — no schema
  migration) and in the NEWS_MATERIAL log line.
- guardrail_changed_trade_decision: raw edge crossed the regime gate but
  the clamped edge no longer does (fuse prevented a trade).  Note: with
  the default 0.25 band the clamped edge_net is 0.21, above every regime
  minimum, so the flag only fires with a tighter configured band.
- Observability gated on materiality: NEWS_MATERIAL per-market line and a
  compact NEWS SUMMARY cycle line, only when with_news > 0 — no flood
  from the ~145 news-less markets per cycle.
- 9 deterministic tests (extreme clamp, in-band passthrough, corroboration,
  inclusive threshold, disabled, changed_decision).

No changes to NEWS_LOGODDS_WEIGHT, Manifold flags, edge thresholds,
sizing, payout, resolution, or historical trades.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-07-01 20:26:02 +00:00
chemavxandClaude Opus 4.8 54fc8fa11a fix(news): strip GNews operator dashes and stop phantom query-budget counting
Two minor faults found during the GNews capture/prioritisation diagnostic:

1. Hyphens/dashes reached the GNews query verbatim. '-' is GNews's exclusion
   operator, so a token like "El-Sayed" returned HTTP 400 and wasted a query.
   _PUNCT_RE now strips '-', en dash and em dash to spaces.

2. The per-cycle GNews budget counter incremented in evaluate() before
   get_sentiment() checked the API key, so with no key configured the
   [CYCLE SUMMARY] reported a phantom "gnews_queries_used: 5/5" with zero real
   requests. Added NewsClient.enabled and gated the GNews block on it; with no
   key the counter stays 0/5 and no spurious SKIP_GNEWS_PRIORITY is logged.
   No behaviour change when a key is present.

Prioritisation itself was confirmed correct and is left untouched: politics
markets are sorted by gnews_priority DESC and prior-extreme markets return
before the budget is consumed, so no query is ever spent on a market that
cannot trade.

Tests: tests/test_news_query.py (4 new); full suite 66 passed.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
2026-06-26 08:07:05 +00:00
chemavxandClaude Fable 5 4867141c4b fix(strategy): gate BTC-dominance signal behind is_non_price like momentum and fear-greed
CI/CD / build-and-push (push) Successful in 7s
Phase 3 excluded momentum and Fear & Greed from politics/tech/events
markets; Phase 4 fixed ticker detection.  But the BTC-dominance signal was
still applied to non-price markets that legitimately mention a ticker
('Will the ETH ETF be approved?'), despite having no demonstrated causality
for non-price outcomes.  Reuse the existing is_non_price gate so the
contribution stays 0.0 -> feat_btc_dom_lo = 0.0 for those markets.

Price-market behavior unchanged: ETH/altcoin/general-crypto markets keep
the +/-0.03 dominance adjustment.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:42:59 +00:00
chemavxandClaude Fable 5 f5ac302a86 fix(strategy): use word-boundary token matching for short crypto tickers to prevent false positives
CI/CD / build-and-push (push) Successful in 8s
Substring matching over question_lower flagged non-crypto markets as crypto:
'dissolved' matched 'sol', 'Canada' matched 'ada', 'Seth' matched 'eth'.
Those false flags armed the BTC-dominance signal (btc_dom_lo=+0.06 observed
on politics markets in production).

Short tickers (btc, eth, sol, xrp, doge, ltc, bnb, ada, avax) now go through
has_token(), which requires non-alphanumeric boundaries so 'ETH', '$ETH' and
'ETH/USD' still match. Long unambiguous names (bitcoin, ethereum, solana,
cardano, ...) remain substring checks.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 16:34:09 +00:00
chemavxandClaude Fable 5 4002f03d0c fix(strategy): exclude momentum and fear-greed signals from non-price markets (politics/tech/events)
CI/CD / build-and-push (push) Successful in 7s
For politics/tech/events markets there is no above/below price notion, so
is_price_above defaulted to False (or flipped on accidental wording like
"reach") and sign-inverted the macro adjustments: BTC +5% or high Fear&Greed
subtracted probability from YES on "Will X win the election?" markets.

Skip both signals entirely for non-price markets: contributions stay 0.0,
feat_mom_lo / feat_fg_lo persist as 0.0. Price markets (BTC/ETH/crypto)
keep the exact current behavior, including the below-market sign flip.
Removes the now-dead BTC(sentiment) momentum branch and its 0.5 attenuator.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
2026-06-11 15:40:28 +00:00
chemavxandClaude Opus 4.8 3a353c7e5b feat(manifold): decouple Manifold from edge model (observational-only)
CI/CD / build-and-push (push) Successful in 8s
Per-category coverage audit showed coverage_rate=0.0 across every category in the
bot's current universe, so any edge Manifold produced was false edge. Retire it as
an ACTIVE trading signal while keeping the full audit/coverage/cooldown trail for a
future reactivation decision.

Two module-level flags in bayesian.py (read from env):
- MANIFOLD_SIGNAL_ENABLED (default False): when False, Manifold never touches the
  edge model — manifold_log_adj stays 0.0 (no posterior shift), no confidence bump,
  feat_mfld_lo=0.0 (so it can never be the dominant feature), no trade contribution,
  and mfld_audit_id is not propagated so the audit's used_in_trade stays FALSE.
- MANIFOLD_AUDIT_ENABLED (default True): matcher still runs; audit/coverage rows and
  cooldowns are still written. The matcher is only called when a flag is on.

When signal is disabled, logs and reasoning carry "Manifold: observational_only".
Endpoints /api/metrics/manifold-matches and /api/metrics/manifold-coverage,
cooldowns, audit tables and existing trades are unchanged. No code or tables removed.

Other signals, thresholds, exposure, risk manager and the executor are untouched.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 15:58:02 +00:00
chemavxandClaude Opus 4.8 98abd96fd2 feat(manifold): add persistent cooldowns to reduce redundant evaluations
CI/CD / build-and-push (push) Successful in 8s
The trading loop re-evaluated the same ~22 politics/tech markets every ~60s,
flooding manifold_match_audit with ~76k rows (~3,500 attempts/market) of which
none carried new information, making the metrics uninterpretable.

Add per-market persistent cooldowns:
- New table manifold_eval_cooldown (poly_market_id PK, last_evaluated_at,
  last_status, retry_after, cooldown_reason) created via run_migrations.
- bayesian.evaluate() consults the cooldown BEFORE calling the matcher and skips
  the call entirely while now() < retry_after — no matcher call, no audit row,
  no signal (equivalent to no_results). After a real evaluation it upserts the
  cooldown with a verdict-dependent backoff: no_results/low_score/outcome_mismatch/
  ambiguous_inversion -> 24h, conditional_market -> 7d, accepted -> 1h.
- manifold.py stays a pure client/matcher with no DB access.
- New db methods get_manifold_cooldown / upsert_manifold_cooldown.
- /api/metrics/manifold-matches summary gains unique_markets
  {evaluated, accepted, coverage_rate} for per-market (not per-attempt) coverage.

Matching logic, MANIFOLD_MATCHER_VERSION, MANIFOLD_LOGODDS_WEIGHT, edge/exposure
thresholds and existing audit rows are unchanged.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-08 12:19:31 +00:00
chemavxandClaude Opus 4.8 664ecab174 feat(manifold): add matcher versioning to separate legacy accepted matches from v3_outcome_guard metrics
CI/CD / build-and-push (push) Successful in 9s
Add MANIFOLD_MATCHER_VERSION="v3_outcome_guard" tag persisted to
manifold_match_audit.matcher_version so metrics can isolate current-matcher
stats from pre-versioning records, whose accepted matches the outcome
guard would now reject.

- schema: add matcher_version column + index; idempotent startup backfill
  tagging NULL rows as legacy_pre_outcome_guard (no outcome types) or
  v2_outcome_guard_no_version (has outcome type, version not persisted)
- save_manifold_audit: write matcher_version on every new record
- get_manifold_matches: split summary into current_version / all_time /
  legacy; recent_matches now carry matcher_version

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-06-02 08:59:19 +00:00
chemavxandClaude Opus 4.8 34fd1f8719 feat(manifold): add outcome compatibility guard and conditional market rejection
CI/CD / build-and-push (push) Successful in 7s
Reject false-positive matches where Jaccard overlap is high but the outcome is
not equivalent (e.g. Poly nomination vs Manifold "If X is nominee, will he win").

- _is_conditional(): detect conditional Manifold markets (If/Conditional on/
  Assuming/Given that prefixes + mid-sentence " if ...," clauses) -> reject with
  reason "conditional_market".
- _classify_outcome(): classify into nomination|primary_win|general_win|
  conditional|other; reject when poly/mfld types differ or either is conditional
  -> reason "outcome_mismatch: poly=... manifold=...".
- Persist poly_outcome_type/mfld_outcome_type on ManifoldMatchResult, in
  manifold_match_audit (CREATE + idempotent ALTER), save_manifold_audit() and
  the bayesian call site.
- Tests covering classification, conditional detection and the Graham Platner
  regression (now rejected); valid nomination<->nomination still accepted.

Untouched: _MATCH_THRESHOLD (0.40), MANIFOLD_LOGODDS_WEIGHT, edge thresholds,
exposure, trading logic.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-05-31 15:28:26 +00:00
chemavxandClaude Sonnet 4.6 9abaae44fd feat(manifold): audit matching quality with ManifoldMatchResult and manifold_match_audit table
CI/CD / build-and-push (push) Successful in 14s
Co-Authored-By: Claude Sonnet 4.6 <noreply@anthropic.com>
2026-05-27 15:58:07 +00:00
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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
chemavxandClaude Sonnet 4.6 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 4fda34df3b feat: initial commit — polymarket-bot source + CI/CD pipeline
CI/CD / build-and-push (push) Failing after 30s
2026-04-13 16:05:45 +00:00