Skip to content

feat(polymarket): edge-band + spread-band bet-selection gates (default no-op)#999

Merged
jmoreira-valory merged 6 commits into
mainfrom
feat/polymarket-bet-selection-gates
Jun 30, 2026
Merged

feat(polymarket): edge-band + spread-band bet-selection gates (default no-op)#999
jmoreira-valory merged 6 commits into
mainfrom
feat/polymarket-bet-selection-gates

Conversation

@jmoreira-valory

@jmoreira-valory jmoreira-valory commented Jun 26, 2026

Copy link
Copy Markdown
Contributor

Summary

Adds two optional, default-no-op, Polymarket-only bet-selection gates to the trader, so an operator can restrict betting to the market conditions an out-of-sample ROI analysis found least-bad. Both gates are inert until explicitly configured via env, so behaviour on main is unchanged. The Omen trader (trader_pearl) is behaviourally untouched.

Gates shipped

  1. Edge band (customs/kelly_criterion/kelly_criterion.py) — the CLOB pre-filter is widened from edge < min_edge to a band edge outside [min_edge, max_edge]. max_edge rides inside the existing strategies_kwargs dict and defaults to 1.0 (edge = p − best_ask is always ≤ 1.0, so 1.0 = no-op). The FPMM (Omen) edge gate is untouched — the band is CLOB-only.
  2. Spread band (decision_maker_abci/behaviours/decision_receive.py) — skips a bet when the chosen side's live-CLOB bid-ask spread (best_ask − best_bid) is outside [polymarket_spread_min, polymarket_spread_max]. The gate keeps the CLOB bids (previously discarded), fires after strategy_vote is known (so it reads the correct side's book), and guards a missing/empty book (passes through silently). Defaults 0.0 / 1.0 widen the band to [0, 1] = no-op.

New params (polymarket_spread_min, polymarket_spread_max) are plumbed end-to-end (decision_maker_abci/skill.yaml, trader_abci/skill.yaml, agent aea-config.yaml, polymarket_trader/service.yaml, models.py). max_edge needs no plumbing — it lives in the STRATEGIES_KWARGS JSON blob.

Activation (operator config, Polymarket service only)

# spread band, e.g. 0.02–0.05
polymarket_spread_min: ${POLYMARKET_SPREAD_MIN:float:0.02}   # default 0.0
polymarket_spread_max: ${POLYMARKET_SPREAD_MAX:float:0.05}   # default 1.0
# edge band, e.g. 0.05–0.10 — inside STRATEGIES_KWARGS JSON:
#   set "min_edge": 0.05 and add "max_edge": 0.10

Defaults stay at the no-op values; operators flip the knobs via env when intentionally experimenting.

Scope & invariants

  • Polymarket only. Activation config is in polymarket_trader/service.yaml. trader_pearl/service.yaml is content byte-identical (the only diff is the cascaded agent: CID from autonomy packages lock, since both services share the valory/trader agent).
  • No FSM change. Each gate reuses an existing skip path — edge/spread route through _is_profitableEvent.UNPROFITABLEBlacklistingRound. autonomy analyse fsm-specs reports no diff. No new rounds/events/states.
  • Minimum diff. ~50 LOC of real code across 2 behaviour/strategy files + param plumbing; the rest is tests and lock-cascade fingerprints.

A note on the dropped horizon gate

This work originally explored a third gate — a 5–8-day time-to-resolution horizon floor. It was removed before this PR after a close-vs-resolution reconciliation: the +20pp ROI the analysis found lives in oracle-resolution time (res_ts − bet_ts), which is not observable at bet time, whereas a live gate can only filter on market close (openingTimestamp). The close-time 5–8d band had ~10 historical bets with 0.000 Jaccard overlap against the resolution-time winners, so a close-time horizon gate would not reproduce the ROI. The horizon machinery is fully reverted here (sampling stage unchanged from main); the investigation is retained outside the PR for a possible future revisit.

Testing

  • 3824 / 3824 dev-package unit tests pass (includes new edge-band tests in test_kelly_criterion.py and new spread-gate tests in test_decision_receive.py, covering on/off and the empty-book guard).
  • No-op proof: with defaults, both gates are inert (max_edge=1.0 never trips the new or branch; any spread in [0,1] passes).
  • Lint pipeline green: isort, black, flake8, mypy, pylint, darglint.
  • autonomy analyse fsm-specs no diff; autonomy analyse docstrings no update; autonomy packages lock clean; check-hash OK.

Forward-test caveat (pre-activation, not a merge blocker)

The spread lift in the backing analysis was measured on the Gamma snapshot market_spread; this gate uses the live-CLOB chosen-side best_ask − best_bid. The two measures may differ, so the expected lift is not guaranteed to transfer — confirm on a live forward-test before any operator activation of the spread band. Both gates default to no-op, so this is a pre-activation check.

Live mainnet smoke test

Beyond the unit tests, both gates were validated end-to-end against live Polygon mainnet — a real superforcaster-polymarket-v1 mech prediction + the live CLOB order book — by running the agent via make run-agent with bet/collateral txs blocked by guards and a halt that prints the gate verdict (no bet tx is ever sent). This directly exercises the forward-test caveat above: it confirms the spread gate reads and gates on the live-CLOB chosen-side best_ask − best_bid. Full methodology + reproduction (guards patch, env, EOA-gas gotcha, knob matrix): .claude/testing/polymarket-bet-selection-gates-smoke-test.md.

Method: one live market sampled (2363872), three runs changing one knob at a time (the other left at its no-op default), reading the BET DECISION verdict each run.

Run min_edge spread band mech p_yes measured edge measured spread Edge gate Spread gate Verdict
A — baseline 0.01 [0, 1] 0.98 +0.66 0.17 PASS PASS WOULD PLACE BET (YES)
B — edge-tight 0.99 [0, 1] 0.97 +0.54 REJECT n/a WOULD NOT PLACE A BET
C — spread-tight 0.01 [0, 0.0001] 0.97 pass 0.17 PASS REJECT WOULD NOT PLACE A BET

Each gate independently flips bet → no-bet when its own knob is tightened (other knob off), confirming both bands genuinely gate against the live book and that the defaults are inert. Guards held every run: zero bet/collateral txs, only the authorized mech requests settled. The gate code needed no change.

🤖 Generated with Claude Code

jmoreira-valory and others added 4 commits June 25, 2026 15:43
Add operator-tunable horizon / edge-band / spread-band gates to the
Polymarket trader. Each gate defaults to a no-op so behaviour is
identical to `main` until activated in env config. The Omen trader
(`trader_pearl`) is byte-identical from a config perspective and the
gates' semantics are guarded by the CLOB code path.

- horizon (sampling.py): optional `min_bets_closing_days` floor
  consolidated into the existing lower bound via `max(safe_offset, ...)`,
  liveness-aware via `kpi_is_met` so a too-tight window cannot starve
  the staking activity target. The blacklist side-effect stays tied to
  the true safety floor only. Mirrored read-only in
  `_classify_processable_bet` with a new `out_of_open_min` reason.
- edge (kelly_criterion.py): widen the CLOB pre-filter from
  `edge < min_edge` to `outside [min_edge, max_edge]`. `max_edge`
  defaults to 1.0 (no-op) and rides inside `strategies_kwargs`. The
  FPMM edge gate is untouched.
- spread (decision_receive.py): preserve the live CLOB bids alongside
  the asks, then gate after `strategy_vote` is known so the chosen
  side's book drives the check. A missing/empty book passes through
  silently. Defaults `[0.0, 1.0]` = no-op.

Activation is operator-side: set the env vars in
`polymarket_trader/service.yaml` (or `${SAMPLE_BETS_CLOSING_DAYS}` /
`${MIN_BETS_CLOSING_DAYS}` / `${POLYMARKET_SPREAD_MIN}` /
`${POLYMARKET_SPREAD_MAX}`) and inject `max_edge` into
`STRATEGIES_KWARGS`. `trader_pearl/service.yaml` is intentionally
left at defaults; only its agent CID line updates from
`autonomy packages lock`.

3832/3832 dev-package tests pass (incl. 5 new horizon-floor tests,
3 new classifier-mirror tests, 5 new spread-gate tests, 4 new
edge-band tests). isort, black, flake8, mypy, pylint (no new
warnings vs main), and darglint clean. `autonomy analyse fsm-specs`
reports no diff (no FSM changes). `autonomy packages lock` and
`autonomy analyse docstrings` run.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Address reviewer's critical finding on iteration 1: the kpi_is_met guard
on the horizon floor was a safe-but-inert crutch — the agent races to N=8
mech predictions per epoch with kpi_is_met=False, so apply_horizon was
False on every KPI-counting prediction; the +20pp horizon lift the gate
exists to capture was therefore forfeited in practice.

① Supply-aware horizon (sampling.py)
  - processable_bet / _classify_processable_bet take apply_horizon kwarg
    (default True) instead of consulting self.kpi_is_met internally
  - _sample orchestrates a last-resort relaxation: after strict +
    multi-bets passes, if pool is still empty AND min_bets_closing_days
    is configured, retry once with apply_horizon=False
  - new horizon_relaxed flag added to the [POLYSTRAT] filter log line
  - effect: filter binds on real KPI-counting predictions; liveness is
    still safe (relaxes only on actual scarcity)

② Misconfig diagnostic (models.py)
  - logger.warning at __init__ when
    min_bets_closing_days > sample_bets_closing_days (loud diagnostic,
    not fatal, per plan §9.4)

Tests
  - rework 5 TestHorizonFloorGate cases around apply_horizon (not
    kpi_is_met); add test_kpi_is_met_is_not_the_relax_trigger regression
  - rework TestClassifyProcessableBet horizon mirrors around
    apply_horizon kwarg; add test_classify_kpi_is_met_does_not_relax_
    classifier regression
  - 3 new TestSample tests for _sample two-pass: strict pass binds,
    relax fallback fires on empty pool (asserts horizon_relaxed=True in
    log), relaxation branch skipped when floor=0
  - 2 new TestDecisionMakerParams caplog tests for the misconfig warning
    (fires when min>sample; silent when min<=sample)

3839/3839 dev-package tests pass. Lint pipeline green: isort, black,
flake8, mypy, darglint, autonomy analyse fsm-specs (no diff), autonomy
analyse docstrings (no update), autonomy packages lock, check-hash.
trader_pearl/service.yaml content remains semantically untouched
(1-line agent: CID cascade only).

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
Iteration 3 of T0002. The close-vs-resolution reconciliation (maxedge
report Section 4) showed the 5-8d horizon ROI lives in oracle-resolution
time (res_ts - bet_ts), which is hindsight not observable at bet time.
The live gate can only filter on market close (openingTimestamp): the
close-time [5,8]d band has 10 bets and Jaccard 0.000 with the winning
res-time band. A gate is valid only if its variable is known at
prediction time, so the horizon knob is dropped. The supply question was
moot: MARKETS_TIME_WINDOW_DAYS=4 makes the 5-8d close band empty by
construction anyway.

Reverts ALL horizon machinery from c361c16 + 80d3384:
- sampling.py: back to main (removes the consolidated lower bound, the
  apply_horizon kwarg, the _sample empty-pool fallback, the classifier
  out_of_open_min mirror)
- models.py: removes min_bets_closing_days _ensure + the min>sample
  misconfig warning + the module logger (keeps polymarket_spread_min/max)
- skill.yaml x2 / aea-config.yaml / polymarket_trader/service.yaml:
  removes the min_bets_closing_days param (keeps polymarket_spread_*)
- tests: removes all horizon-only tests (TestHorizonFloorGate, the
  _sample two-pass cases, the classifier-mirror horizon cases, the
  caplog misconfig tests, the min_bets fixture entry)

MARKETS_TIME_WINDOW_DAYS left untouched (no fetch-window change once the
horizon knob is gone).

Final PR ships two Polymarket-only, default-no-op gates:
- edge band (kelly_criterion.py, CLOB gate only; FPMM untouched)
- spread band (decision_receive.py, live-CLOB chosen-side best_ask-best_bid)

3824/3824 dev-package tests pass. Lint green: isort, black, flake8,
mypy, pylint (no new warnings vs main), darglint. autonomy analyse
fsm-specs no diff; analyse docstrings no update; autonomy packages lock
clean; check-hash OK. trader_pearl/service.yaml content byte-identical
(1-line agent CID cascade only). The dropped horizon work is preserved
in .claude/plans/horizon-knob-dropped.md with a revive path.

Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
…election-gates

# Conflicts:
#	packages/packages.json
#	packages/valory/agents/trader/aea-config.yaml
#	packages/valory/services/polymarket_trader/service.yaml
#	packages/valory/services/trader_pearl/service.yaml

@OjusWiZard OjusWiZard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Automated multi-perspective review (code / tests / comments / silent-failure / type-design / simplify). No critical bugs — defaults are inert for well-formed books, vote==0 → YES side selection is correct, and plumbing is consistent. Two themes worth fixing before merge: (1) an inverted band on either gate silently turns the agent into a no-trade zombie with no startup error, and (2) two test gaps let a side-selection regression ship green. Inline comments below.

Comment thread packages/valory/skills/decision_maker_abci/models.py
Comment thread packages/valory/customs/kelly_criterion/kelly_criterion.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/tests/test_models.py

@DIvyaNautiyal07 DIvyaNautiyal07 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Two findings below sit on lines outside any diff hunk, so GitHub can't anchor them inline. Captured here so they don't get lost.


P2 — New knobs are undocumented for operators (README.md line ~253, STRATEGIES_KWARGS table)

The STRATEGIES_KWARGS key table omits max_edge. The new POLYMARKET_SPREAD_MIN / POLYMARKET_SPREAD_MAX env vars aren't documented anywhere operator-facing either. Since both gates are default-no-op and only useful when operators flip them, the value of the PR depends on operators knowing the knobs exist. Add max_edge to the STRATEGIES_KWARGS table and add the spread env vars to the env-var section.


P2 — max_edge missing from the diagnostic dump (kelly_criterion.py line ~293)

The startup info.append line f"max_bet: {max_bet}, n_bets: {n_bets}, min_edge: {min_edge}" was not updated to include max_edge. An operator configuring a narrow band (e.g. max_edge=0.15) won't see it confirmed in the run info until the rejection message fires. One-token fix:

- info.append(f"max_bet: {max_bet}, n_bets: {n_bets}, min_edge: {min_edge}")
+ info.append(f"max_bet: {max_bet}, n_bets: {n_bets}, "
+     f"min_edge: {min_edge}, max_edge: {max_edge}")

Comment thread packages/valory/skills/decision_maker_abci/models.py
Comment thread packages/valory/customs/kelly_criterion/kelly_criterion.py
Comment thread packages/valory/customs/kelly_criterion/kelly_criterion.py Outdated
Comment thread packages/valory/skills/decision_maker_abci/behaviours/decision_receive.py Outdated
Comment thread packages/valory/customs/kelly_criterion/kelly_criterion.py
@dagacha

dagacha commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Reviewed the branch locally (ran the suite — 202 passing, 100% coverage on the 3 changed modules; packages lock and analyse fsm-specs both clean). Nice, tightly-scoped change. Three things worth addressing before activation:

1. (medium) Crossed/locked book trips the spread gate even at defaults — breaks the no-op invariant.
In decision_receive.py the gate is if not lo <= spread <= hi: with spread = best_ask − best_bid and defaults lo=0.0, hi=1.0. On a crossed/locked book (best_bid > best_ask, a real transient CLOB condition) spread < 0, so 0.0 <= spread is false and the bet is skipped even with the default polymarket_spread_min=0.0. That contradicts the stated "any spread in [0,1] passes" guarantee.
Impact is limited (the skip routes to BlacklistingRound, which only soft-bumps queue_status — recoverable, re-queued next cycle), so it's a transient missed-bet, not fund risk. Suggest skipping the gate when spread < 0 (a crossed book is a buy opportunity, not a wide one), e.g.:

if spread >= 0 and not lo <= spread <= hi:
    ...

plus a dedicated test alongside the empty-book one.

2. (low) No ordering validation on the bands. models.py ensures both spread params are floats but doesn't check spread_min <= spread_max (unlike the adjacent sample_bets_closing_days, which raises). A misconfigured MIN=0.5 / MAX=0.1 silently rejects every bet; same for min_edge > max_edge in kelly. A guard + log would prevent a silent "agent stopped betting" incident. Not a blocker since defaults are safe.

3. (nit) Broken doc link. The description links to .claude/testing/polymarket-bet-selection-gates-smoke-test.md, but that file isn't included in the PR (and .claude/ isn't tracked), so the methodology/repro link is dead for reviewers. Either add the file or drop the link.

jmoreira-valory and others added 2 commits June 29, 2026 17:02
Address reviewer findings on the edge-band + spread-band gates:

- Reject inverted bands up front: spread band raises ValueError at param
  init; edge band returns a no-trade (error-level) instead of silently
  skipping every bet.
- Edge-rejection message is now directional (< min_edge / > max_edge),
  matching the FPMM path; add max_edge to the startup diagnostic dump.
- Spread gate: guard order-book price parsing (a null/unparseable price
  no longer stalls the round) and emit a warning when the gate is
  bypassed under a non-default band; document that crossed books are
  intentionally rejected.
- Tests: assert side-selection follows strategy_vote (distinct YES/NO
  books), cover the below-band case, parse-bypass, empty-book paths, and
  that inverted bands raise; assert the new params in test_full_init.
- README: document max_edge and POLYMARKET_SPREAD_MIN/MAX.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
- test_models.py: apply tomte isort (case_sensitive=True, order_by_type=False)
  so the model-import block is correctly ordered.
- _setup_clob_with_book: collapse the helper docstring to a single line so
  darglint (short strictness) does not require per-param documentation.
- re-lock affected dev packages.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jmoreira-valory

Copy link
Copy Markdown
Contributor Author

Thanks for the thorough review — all findings are addressed in e0100470e (lint follow-up in b44cf78f3), and I've replied inline on each thread. Summary:

Inverted-band guards (both gates) — the spread band raises ValueError at param init; the edge band returns an error-level _no_trade. No more silent no-trade with a healthy-looking agent.

Diagnostics — edge-rejection message is directional again (< min_edge / > max_edge, matching the FPMM path); max_edge added to the startup dump; the comment references DEFAULT_MIN_EDGE/DEFAULT_MAX_EDGE.

Spread gatetry/except around price parsing (a null price no longer stalls the round); a warning is logged when the gate is bypassed under a non-default band; is_yes + tuple-unpack; crossed-book handling clarified (intentionally rejected as bad data) in both the code comment and the README.

Tests — side-selection now asserts the verdict follows strategy_vote via distinct YES/NO books keyed on the token id; below-band, parse-bypass, empty-book and inverted-band-raise cases added; the new params are asserted in test_full_init. 100% statement + branch coverage retained.

Docsmax_edge and POLYMARKET_SPREAD_MIN/POLYMARKET_SPREAD_MAX documented in the README.

Deferred: hoisting max_edge to a first-class model param (parallel to the spread band) is left to the ChatUI-homogenization follow-up (#1000), which reworks both knobs together; the runtime-safety concern is covered here by the inverted-band guard.

@dagacha

dagacha commented Jun 29, 2026

Copy link
Copy Markdown
Contributor

Re-reviewed after e0100470 + b44cf78f — thanks for the thorough turnaround. Verified locally on the updated branch: 209 tests passing (+7), 100% coverage on all three changed modules, packages lock clean, analyse fsm-specs no diff, and both new commits stay scoped to the gate files + tests + docs + hash cascade.

All three of my findings are resolved:

  • Create functionality to fetch and update bets #1 (crossed book) — resolved the other way (and better): crossed books (spread < 0) are now intentionally rejected as bad data, with the code comment + README both stating that intent, so the behaviour is no longer a surprise. Re-ran the predicate matrix to confirm the default band passes well-formed books and only rejects crossed/out-of-band ones. 👍
  • Add functionality to decide for the bet's choice #2 (band ordering) — fully fixed on both gates: models.py raises ValueError at init for an inverted spread band, kelly returns an error-level _no_trade for min_edge > max_edge, both with dedicated tests.
  • Add functionality for temporarily blacklisting a bet #3 (broken doc link) — still open: the description links to .claude/testing/polymarket-bet-selection-gates-smoke-test.md, which isn't in the tree (.claude/ is untracked), so it 404s for reviewers. Cosmetic, non-blocking — just drop the link or add the file.

LGTM once the dead link is dropped/added. Nice work hardening the gates.

@DIvyaNautiyal07 DIvyaNautiyal07 left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Re-reviewed the hardening commits (e010047, b44cf78). All P1s from the previous round are resolved with new regression tests; P2s and one nit are addressed too. Approving — one trivial follow-up nit below, non-blocking.

Resolved

  • Inverted spread bandmodels.py:442-447 raises ValueError at param init; test_inverted_spread_band_raises covers it.
  • Inverted edge bandkelly_criterion.py:296-302 short-circuits via _no_trade(); test_inverted_edge_band_rejected covers it.
  • Untested YES/NO side-selection_setup_clob_with_book now keys _fetch_orderbook on token id (books = {"token_yes_123": ob, "token_no_123": ob_no}, line 1163) and test_spread_gate_side_selection_follows_vote flips the vote across distinct YES/NO books and asserts the verdict flips with it. Mutation gap closed.
  • Untested spread lower-boundtest_spread_gate_below_band_skips covers spread=0.01, spread_min=0.02.
  • Operator-facing docsREADME.md adds max_edge to the STRATEGIES_KWARGS table and a dedicated POLYMARKET_SPREAD_MIN/MAX section with the "agent refuses to start on inverted band" guarantee.
  • max_edge in diagnostic dump — included in the startup info.append at kelly_criterion.py:292-295.
  • Rejection-message regressionkelly_criterion.py:368-379 now branches by which bound tripped; the < min_edge phrasing matches the FPMM path.
  • Crossed-book "no-op" mismatch — behavior unchanged, but now explicit: README documents it, code comment calls out that crossed books are intentionally rejected as bad data, and test_spread_gate_crossed_book_rejected_under_default pins it.
  • Silent bypass on fetch failuredecision_receive.py:561-566 logs WARNING on bypass only when a non-default band is configured; default-config users stay quiet, operators who tightened the band hear about it. Good compromise.
  • max_edge plumbing asymmetry — addressed via runtime check in run() (option b from the original review).
  • Two-ternary collapsedecision_receive.py:535-540 is a single tuple unpack.

Acceptable to defer

  • _setup_clob_with_book still duplicates _setup_behaviour. Fine to leave for a separate cleanup PR.

New nit (non-blocking)

decision_receive.py:548 catches (ValueError, TypeError) but not KeyError. The new test_spread_gate_unparseable_price_bypasses covers the null-price case (string "None" -> float("None") raises ValueError), but if a book level dict ever lacks the "price" key, a["price"] raises KeyError and the gate crashes the round instead of bypassing. Trivial one-token addition:

except (ValueError, TypeError, KeyError) as exc:

Approving.

@OjusWiZard OjusWiZard left a comment

Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Approving after a multi-perspective re-review of the hardened code (HEAD b44cf78). Two prior hardening rounds closed all consensus issues: both gates now validate min <= max, the order-book float() parse is wrapped in try/except, side-selection is covered by a discriminative test, and crossed-book/inverted-band cases are tested. Code-review, silent-failure, type-design, and simplify passes found nothing at the reporting threshold. Remaining items are non-blocking polish (warning-branch test assertions, one stale models.py comment, log-context tweaks). LGTM.

jmoreira-valory added a commit that referenced this pull request Jun 30, 2026
Correct the activation values to match the analysis: the min_edge FLOOR
is a dead knob (raising it does nothing for ROI); the lever is the CAP.

  min_edge            : 0.05 -> 0.01  (revert — keep the floor low, as in prod)
  max_edge            : 0.10           (cap — the actual ROI lever, kept)
  POLYMARKET_SPREAD_MIN : 0.02 -> 0.0  (revert — no spread floor)
  POLYMARKET_SPREAD_MAX : 0.05         (cap — reject wide/illiquid spreads, kept)

Net vs #999: add max_edge=0.1 and cap spread at 0.05. No floors raised.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@jmoreira-valory jmoreira-valory merged commit cf91156 into main Jun 30, 2026
28 checks passed
@jmoreira-valory jmoreira-valory deleted the feat/polymarket-bet-selection-gates branch June 30, 2026 10:39
jmoreira-valory added a commit that referenced this pull request Jun 30, 2026
…gy CID

dagacha's P1: file_hash_to_strategies still mapped kelly_criterion to the
pre-999 CID (bafybeididlci...), whose strategy ignores max_edge — so the
max_edge=0.15 cap added by this PR was inert at runtime (the deployed
agent downloads the strategy by hash, not the repo file). Repoint to the
#999 CID (bafybeidme2u...) and re-lock.

- polymarket_trader/service.yaml: activates the max_edge cap (functional)
- trader_pearl/service.yaml: consistency only — Omen is FPMM, max_edge is
  CLOB-only and unset there, so behaviour is unchanged; this just stops
  Omen deploying the stale pre-999 strategy
- aea-config.yaml: agent default (the merged #999 left this stale too)

autonomy packages lock --check passes.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
jmoreira-valory added a commit that referenced this pull request Jul 1, 2026
…ion-values

Activate edge + spread gate values (forward-test) — companion to #999
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants