mirror of
https://github.com/theodore-song/polymarket-analyst.git
synced 2026-08-23 20:48:08 +00:00
Gate maker exposure behind shadow evidence
This commit is contained in:
@@ -15,7 +15,7 @@ https://polymarket-site-eta.vercel.app/personal.html
|
|||||||
|
|
||||||
The site fetches live Polymarket markets, generates agent suggestions, lets you
|
The site fetches live Polymarket markets, generates agent suggestions, lets you
|
||||||
run frequent paper cycles, and syncs the shared arena state through Neon or
|
run frequent paper cycles, and syncs the shared arena state through Neon or
|
||||||
Vercel Blob. Build 64 also installs an offline app shell and caches timestamped
|
Vercel Blob. Build 65 also installs an offline app shell and caches timestamped
|
||||||
market snapshots. During an outage, cycles continue locally; cached entries are
|
market snapshots. During an outage, cycles continue locally; cached entries are
|
||||||
allowed for 90 minutes, older snapshots become mark-only, and all cached data
|
allowed for 90 minutes, older snapshots become mark-only, and all cached data
|
||||||
expires after 24 hours.
|
expires after 24 hours.
|
||||||
@@ -26,7 +26,7 @@ team names, Over/Under, or another pair are rejected instead of being silently
|
|||||||
reinterpreted as Yes/No. The same semantic check applies to complete event
|
reinterpreted as Yes/No. The same semantic check applies to complete event
|
||||||
bundles and the offline evaluators.
|
bundles and the offline evaluators.
|
||||||
|
|
||||||
Build 64 ranks the competition by each agent's return since Strategy 53 began.
|
Build 65 ranks the competition by each agent's return since Strategy 54 began.
|
||||||
Historical replay equity remains visible for context, but it no longer makes an
|
Historical replay equity remains visible for context, but it no longer makes an
|
||||||
agent look like the current leader when the live adaptive strategy is losing.
|
agent look like the current leader when the live adaptive strategy is losing.
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ The learner shrinks small samples toward neutral, caps sizing changes to
|
|||||||
15% of candidates for deterministic exploration so a stale regime cannot become
|
15% of candidates for deterministic exploration so a stale regime cannot become
|
||||||
permanent.
|
permanent.
|
||||||
|
|
||||||
Strategy 53 treats each binary stake as capable of falling to zero even when the
|
Strategy 54 treats each binary stake as capable of falling to zero even when the
|
||||||
18% stop cannot fill. New core positions are capped at 2.5%-4% of equity and
|
18% stop cannot fill. New core positions are capped at 2.5%-4% of equity and
|
||||||
aggressive positions at 3%-5%, with lower limits for near-term, extreme-price,
|
aggressive positions at 3%-5%, with lower limits for near-term, extreme-price,
|
||||||
reversal, and fast-moving setups. Oversized positions inherited from older
|
reversal, and fast-moving setups. Oversized positions inherited from older
|
||||||
@@ -74,24 +74,35 @@ Run `npm run evaluate:adaptive` for a stricter chronological search across 1,920
|
|||||||
predefined price-action rules. It uses a 60/20/20 train, validation, and holdout
|
predefined price-action rules. It uses a 60/20/20 train, validation, and holdout
|
||||||
split and never selects a rule from the holdout period. The August 19 run loaded
|
split and never selects a rule from the holdout period. The August 19 run loaded
|
||||||
all 300 requested histories and found no directional rule that passed both train
|
all 300 requested histories and found no directional rule that passed both train
|
||||||
and validation at either 24 or 72 hours. Strategy 53 therefore keeps directional
|
and validation at either 24 or 72 hours. Strategy 54 therefore keeps directional
|
||||||
signals in the walk-forward observation ledger until current, independent-event
|
signals in the walk-forward observation ledger until current, independent-event
|
||||||
evidence proves an edge.
|
evidence proves an edge.
|
||||||
|
|
||||||
Run `npm run evaluate:liquidity` to inspect live reward-scoring markets for
|
Run `npm run evaluate:liquidity` to inspect live reward-scoring markets for
|
||||||
paired resting YES and NO bids whose combined cost is below the $1 settlement
|
paired resting YES and NO bids whose combined cost is below the $1 settlement
|
||||||
payout. Strategy 53 lets Value Hunter stage a maximum of six paper quote pairs,
|
payout. Run `npm run evaluate:maker` for the chronological fill-path audit; set
|
||||||
reserving at most 10% of equity and 2% per quote. A later live cycle must show
|
`MAKER_MARKETS`, `MAKER_HISTORY_DAYS`, `MAKER_CONCURRENCY`, or
|
||||||
that each bid was touched in Polymarket's public CLOB price history or crossed by
|
`MAKER_EXIT_COST_CENTS` to change it and `MAKER_SUMMARY=1` for compact output.
|
||||||
the current executable book before a paper fill is recorded. Repriced legs
|
|
||||||
ignore all history from before the new
|
The August 19 audit loaded both token histories for all 300 requested markets,
|
||||||
quote timestamp. The first unmatched leg is unwound after 24 hours; once both
|
formed 5,477 non-overlapping observations, and tested 3,024 rules over 3, 6, 12,
|
||||||
legs fill, the complete hedge is held intact through settlement. Locked pairs,
|
and 24-hour horizons. Zero rules passed training, validation, or untouched
|
||||||
adverse one-leg exits, and unfilled attempts feed a persistent category and
|
holdout. At three hours, the broad 0.5-cent quote-gap rule still lost 0.63% per
|
||||||
spread-cohort learner. At least five outcomes are required before profitable
|
event in holdout; only 0.61% of observations completed both legs while 23.33%
|
||||||
cohorts can scale up to 1.5x or losing cohorts rotate out, with a deterministic
|
produced adverse one-leg inventory. Wider quotes traded less but remained
|
||||||
20% exploration lane preserving adaptation. The engine does not credit
|
negative. Strategy 54 therefore does not risk paper capital on an unproven
|
||||||
hypothetical fills or unverified liquidity rewards.
|
maker rule.
|
||||||
|
|
||||||
|
Value Hunter now tracks up to six zero-capital shadow quote pairs. A later live
|
||||||
|
cycle must still verify each touch from public CLOB price history or a current
|
||||||
|
book cross. A three-hour executable exit grades one-sided touches after a
|
||||||
|
conservative half-cent cost, while paired touches and unfilled attempts are also
|
||||||
|
retained. Results are clustered by Polymarket event. Capital exposure can only
|
||||||
|
resume after at least 20 current-strategy event clusters in both the matching
|
||||||
|
category and spread cohort have positive 90% confidence bounds and at least
|
||||||
|
three completed pairs, with any mature losing cohort acting as a veto. There is
|
||||||
|
no capital-backed exploration lane. Existing paper inventory from prior builds
|
||||||
|
is still reconciled honestly. The engine does not credit hypothetical rewards.
|
||||||
See Polymarket's official [fees](https://docs.polymarket.com/trading/fees),
|
See Polymarket's official [fees](https://docs.polymarket.com/trading/fees),
|
||||||
[maker rebates](https://docs.polymarket.com/market-makers/maker-rebates), and
|
[maker rebates](https://docs.polymarket.com/market-makers/maker-rebates), and
|
||||||
[liquidity rewards](https://docs.polymarket.com/market-makers/liquidity-rewards)
|
[liquidity rewards](https://docs.polymarket.com/market-makers/liquidity-rewards)
|
||||||
@@ -103,7 +114,7 @@ negative in all three chronological segments. Reversals averaged -3.69%, with a
|
|||||||
market-clustered 90% interval entirely below zero. Crypto and Sports were also
|
market-clustered 90% interval entirely below zero. Crypto and Sports were also
|
||||||
negative but covered only three and five markets. The 24-hour cohort improved to
|
negative but covered only three and five markets. The 24-hour cohort improved to
|
||||||
-0.82% row mean and +1.31% market mean, with no rule robustly negative across all
|
-0.82% row mean and +1.31% market mean, with no rule robustly negative across all
|
||||||
segments. Strategy 53 therefore keeps reversal entries observation-only until
|
segments. Strategy 54 therefore keeps reversal entries observation-only until
|
||||||
their recent signal and quality cohorts independently earn promotion, retains
|
their recent signal and quality cohorts independently earn promotion, retains
|
||||||
their signals for paper grading, and evaluates adaptation at 24 and 72 hours.
|
their signals for paper grading, and evaluates adaptation at 24 and 72 hours.
|
||||||
|
|
||||||
@@ -112,7 +123,7 @@ markets with no failures and produced 3,597 net-of-cost 24-hour outcomes across
|
|||||||
142 markets. No tested follow or fade rule was robustly positive. Crypto trends
|
142 markets. No tested follow or fade rule was robustly positive. Crypto trends
|
||||||
averaged -3.83% per observation and -3.99% per market; Sports trends averaged
|
averaged -3.83% per observation and -3.99% per market; Sports trends averaged
|
||||||
-5.44% and -6.61%. Both stayed negative in every chronological segment and their
|
-5.44% and -6.61%. Both stayed negative in every chronological segment and their
|
||||||
market-clustered 90% intervals were entirely below zero. Strategy 53 therefore
|
market-clustered 90% intervals were entirely below zero. Strategy 54 therefore
|
||||||
keeps Crypto and Sports trends observation-only while continuing to grade them.
|
keeps Crypto and Sports trends observation-only while continuing to grade them.
|
||||||
|
|
||||||
The August 18 event-clustered rerun loaded 499 of 500 active markets and produced
|
The August 18 event-clustered rerun loaded 499 of 500 active markets and produced
|
||||||
@@ -121,7 +132,7 @@ The broad mean was -1.14%, the event mean was -1.11%, and the event-clustered
|
|||||||
90% interval stayed below zero. No tested category, side, price band, signal
|
90% interval stayed below zero. No tested category, side, price band, signal
|
||||||
strength, or combined feature cohort was robustly positive. Broad trends,
|
strength, or combined feature cohort was robustly positive. Broad trends,
|
||||||
YES trends, favorite trends, strong trends, and hour-confirmed trends were all
|
YES trends, favorite trends, strong trends, and hour-confirmed trends were all
|
||||||
robustly negative. Strategy 53 therefore makes every directional trend or
|
robustly negative. Strategy 54 therefore makes every directional trend or
|
||||||
reversal observation-only until its own signal, side, and category cohorts each
|
reversal observation-only until its own signal, side, and category cohorts each
|
||||||
earn positive promotion from recent independent events. This is a strategy reset,
|
earn positive promotion from recent independent events. This is a strategy reset,
|
||||||
so current adaptive returns begin from the portfolio equity at migration.
|
so current adaptive returns begin from the portfolio equity at migration.
|
||||||
@@ -130,7 +141,7 @@ The final August 19 300-market rerun loaded all 300 eligible Yes/No price
|
|||||||
histories without a failure and produced 2,598 twelve-hour observations across
|
histories without a failure and produced 2,598 twelve-hour observations across
|
||||||
64 independent events. The broad row mean was -0.65% and the event-cluster mean
|
64 independent events. The broad row mean was -0.65% and the event-cluster mean
|
||||||
was -1.71%. The 72-hour event-cluster mean was -3.59% with its 90% interval below
|
was -1.71%. The 72-hour event-cluster mean was -3.59% with its 90% interval below
|
||||||
zero, and no tested directional rule was robustly positive. Strategy 53
|
zero, and no tested directional rule was robustly positive. Strategy 54
|
||||||
therefore requires positive evidence at both 24 and 72 hours rather than
|
therefore requires positive evidence at both 24 and 72 hours rather than
|
||||||
allowing one favorable short-horizon mark to authorize cash exposure.
|
allowing one favorable short-horizon mark to authorize cash exposure.
|
||||||
|
|
||||||
@@ -140,24 +151,24 @@ segment and averaged -4.13%. Sports trends were negative in train and test and
|
|||||||
averaged -3.53% at 72 hours. Politics trends were the sole cohort with positive
|
averaged -3.53% at 72 hours. Politics trends were the sole cohort with positive
|
||||||
row-level returns in all three 72-hour segments, but its market-cluster interval
|
row-level returns in all three 72-hour segments, but its market-cluster interval
|
||||||
still crossed zero; that supports a longer hold test, not a larger entry bet.
|
still crossed zero; that supports a longer hold test, not a larger entry bet.
|
||||||
Strategy 53 gives previously opened Politics trend positions that 72-hour observation window before
|
Strategy 54 gives previously opened Politics trend positions that 72-hour observation window before
|
||||||
ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget
|
ordinary signal exits. Stops, profit locks, settlement handling, and risk-budget
|
||||||
reductions remain immediate.
|
reductions remain immediate.
|
||||||
|
|
||||||
Strategy 53 also subtracts a half-cent round-trip cost when grading each live
|
Strategy 54 also subtracts a half-cent round-trip cost when grading each live
|
||||||
walk-forward signal. Confidence uses the largest independent matching bucket,
|
walk-forward signal. Confidence uses the largest independent matching bucket,
|
||||||
not the sum of five overlapping feature buckets, and evidence from older engine
|
not the sum of five overlapping feature buckets, and evidence from older engine
|
||||||
versions is down-weighted. This prevents a handful of duplicated observations
|
versions is down-weighted. This prevents a handful of duplicated observations
|
||||||
from authorizing larger positions or hiding a modest negative regime.
|
from authorizing larger positions or hiding a modest negative regime.
|
||||||
|
|
||||||
Strategy 53 adds uncertainty-aware, multi-horizon promotion and demotion. A
|
Strategy 54 adds uncertainty-aware, multi-horizon promotion and demotion. A
|
||||||
matching setup must accumulate at least eight effective independent-event
|
matching setup must accumulate at least eight effective independent-event
|
||||||
observations, including at least five from the current strategy, and agree across
|
observations, including at least five from the current strategy, and agree across
|
||||||
at least two feature views at both the 24-hour and 72-hour checkpoints before it
|
at least two feature views at both the 24-hour and 72-hour checkpoints before it
|
||||||
can risk cash. Mixed or one-horizon evidence stays observation-only instead of
|
can risk cash. Mixed or one-horizon evidence stays observation-only instead of
|
||||||
being mistaken for an edge.
|
being mistaken for an edge.
|
||||||
|
|
||||||
Build 64 enforces the documented offline boundary end to end. Cached snapshots
|
Build 65 enforces the documented offline boundary end to end. Cached snapshots
|
||||||
under 90 minutes old may continue paper execution. Older snapshots remain usable
|
under 90 minutes old may continue paper execution. Older snapshots remain usable
|
||||||
for valuation and chart snapshots for up to 24 hours, but cannot trigger entries,
|
for valuation and chart snapshots for up to 24 hours, but cannot trigger entries,
|
||||||
stop-losses, gain-stops, risk rebalances, settlements, or policy exits. Network
|
stop-losses, gain-stops, risk rebalances, settlements, or policy exits. Network
|
||||||
@@ -170,26 +181,26 @@ adaptive baselines, pending signal grades, and trade evidence remain in one stra
|
|||||||
lineage until the actual entry, sizing, or exit logic changes. Legacy build 40 and 41
|
lineage until the actual entry, sizing, or exit logic changes. Legacy build 40 and 41
|
||||||
records are migrated into the same strategy lineage without losing evidence.
|
records are migrated into the same strategy lineage without losing evidence.
|
||||||
|
|
||||||
Build 64 independently refreshes markets for due pending signals that have
|
Build 65 independently refreshes markets for due pending signals that have
|
||||||
left the current top-500 activity scan. Unavailable markets remain queued for a
|
left the current top-500 activity scan. Unavailable markets remain queued for a
|
||||||
bounded retry window. This prevents activity-rank survivorship from deciding
|
bounded retry window. This prevents activity-rank survivorship from deciding
|
||||||
which wins and losses reach the adaptive calibration ledger.
|
which wins and losses reach the adaptive calibration ledger.
|
||||||
|
|
||||||
Build 64 also allocates the 300 pending observation slots by evidence coverage.
|
Build 65 also allocates the 300 pending observation slots by evidence coverage.
|
||||||
Under-sampled signal/side/category cohorts are observed first, followed by
|
Under-sampled signal/side/category cohorts are observed first, followed by
|
||||||
under-sampled independent events and market sides, with conviction used only as
|
under-sampled independent events and market sides, with conviction used only as
|
||||||
a later tie-breaker. This prevents the same popular contracts from monopolizing
|
a later tie-breaker. This prevents the same popular contracts from monopolizing
|
||||||
the ledger and gives the learner a realistic path to promote or reject more
|
the ledger and gives the learner a realistic path to promote or reject more
|
||||||
diverse cohorts.
|
diverse cohorts.
|
||||||
|
|
||||||
Build 64 retains safe shared-state provider diagnostics from both reads and
|
Build 65 retains safe shared-state provider diagnostics from both reads and
|
||||||
writes. A device now says `local only` when Neon is paused or a Blob credential
|
writes. A device now says `local only` when Neon is paused or a Blob credential
|
||||||
is rejected, instead of presenting a local browser save as a successful
|
is rejected, instead of presenting a local browser save as a successful
|
||||||
cross-device sync. Completed cycle statuses retain that `local only` warning
|
cross-device sync. Completed cycle statuses retain that `local only` warning
|
||||||
until a cloud provider succeeds. Database URLs still fail over across configured Neon
|
until a cloud provider succeeds. Database URLs still fail over across configured Neon
|
||||||
aliases without exposing credentials in the API response.
|
aliases without exposing credentials in the API response.
|
||||||
|
|
||||||
Strategy 53 coordinates high-risk exploration globally. Near-term, extreme-price,
|
Strategy 54 coordinates high-risk exploration globally. Near-term, extreme-price,
|
||||||
and other gap-prone positions may be held materially by only one agent, while
|
and other gap-prone positions may be held materially by only one agent, while
|
||||||
ordinary independently confirmed markets retain the two-agent cap. The robustly
|
ordinary independently confirmed markets retain the two-agent cap. The robustly
|
||||||
negative Sports- and Crypto-trend cohorts cannot enter through exploration.
|
negative Sports- and Crypto-trend cohorts cannot enter through exploration.
|
||||||
@@ -215,7 +226,7 @@ removed the apparent edge. The August 18 rerun found 35 eligible events and one
|
|||||||
three-leg NO bundle with a 0.25%
|
three-leg NO bundle with a 0.25%
|
||||||
modeled margin after estimated costs. The final August 19 scan found 49 eligible
|
modeled margin after estimated costs. The final August 19 scan found 49 eligible
|
||||||
events and no currently actionable bundle; its closest complete bundle remained
|
events and no currently actionable bundle; its closest complete bundle remained
|
||||||
0.25% negative after modeled costs. Strategy 53 can paper-trade either a
|
0.25% negative after modeled costs. Strategy 54 can paper-trade either a
|
||||||
complete YES or complete NO bundle only from live executable prices, opens every
|
complete YES or complete NO bundle only from live executable prices, opens every
|
||||||
leg together, and holds
|
leg together, and holds
|
||||||
the hedge intact until settlement. It also requires at least a 0.15% modeled net
|
the hedge intact until settlement. It also requires at least a 0.15% modeled net
|
||||||
@@ -238,19 +249,19 @@ static settlement-direction boost from this audit.
|
|||||||
|
|
||||||
The earlier 200-resolved-market audit found short-dated NO entries strongly
|
The earlier 200-resolved-market audit found short-dated NO entries strongly
|
||||||
negative, but the 500-market rerun did not reproduce that loss in its newer test
|
negative, but the 500-market rerun did not reproduce that loss in its newer test
|
||||||
segment. Strategy 53 therefore treats the result as a provisional prior instead
|
segment. Strategy 54 therefore treats the result as a provisional prior instead
|
||||||
of a permanent ban: NO entries with 21 days or less remain observation-only until
|
of a permanent ban: NO entries with 21 days or less remain observation-only until
|
||||||
the recent walk-forward calibration promotes their matching side and duration
|
the recent walk-forward calibration promotes their matching side and duration
|
||||||
cohorts. Exact numeric-range contracts are excluded from new entries because a
|
cohorts. Exact numeric-range contracts are excluded from new entries because a
|
||||||
settlement jump can pass directly through an 18% stop; the live audit found that
|
settlement jump can pass directly through an 18% stop; the live audit found that
|
||||||
this failure mode caused the largest latest-day loss.
|
this failure mode caused the largest latest-day loss.
|
||||||
|
|
||||||
Strategy 53 also excludes path-dependent barriers such as "reach $66,000," "hit
|
Strategy 54 also excludes path-dependent barriers such as "reach $66,000," "hit
|
||||||
$90," and "dip to $62,000." These contracts can resolve abruptly as soon as the
|
$90," and "dip to $62,000." These contracts can resolve abruptly as soon as the
|
||||||
barrier is touched, so a later hourly stop cannot reliably cap the loss. Fixed-date
|
barrier is touched, so a later hourly stop cannot reliably cap the loss. Fixed-date
|
||||||
level questions such as "above $66,000 on August 23" remain eligible.
|
level questions such as "above $66,000 on August 23" remain eligible.
|
||||||
|
|
||||||
Strategy 53 clusters live walk-forward observations by Polymarket event and checkpoint before
|
Strategy 54 clusters live walk-forward observations by Polymarket event and checkpoint before
|
||||||
calculating confidence. Multiple six-hour snapshots and correlated outcome
|
calculating confidence. Multiple six-hour snapshots and correlated outcome
|
||||||
markets from the same event are averaged into one effective outcome, so one
|
markets from the same event are averaged into one effective outcome, so one
|
||||||
election or tournament cannot promote or demote an entire feature cohort.
|
election or tournament cannot promote or demote an entire feature cohort.
|
||||||
|
|||||||
+145
-61
@@ -341,7 +341,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
<nav class="topnav">
|
<nav class="topnav">
|
||||||
<div class="brand">
|
<div class="brand">
|
||||||
<div class="logo">🏆</div>
|
<div class="logo">🏆</div>
|
||||||
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · 5 core + 5 aggressive</div><div class="build-badge">Adaptive strategy 53 · build 64</div></div>
|
<div><div class="brand-name">Polymarket Arena</div><div class="brand-sub">10 agents · 5 core + 5 aggressive</div><div class="build-badge">Adaptive strategy 54 · build 65</div></div>
|
||||||
</div>
|
</div>
|
||||||
<div class="tabs" id="tabs">
|
<div class="tabs" id="tabs">
|
||||||
<button class="tab" data-tab="overview">Overview</button>
|
<button class="tab" data-tab="overview">Overview</button>
|
||||||
@@ -363,7 +363,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
<div class="personal-banner" id="personalBanner">
|
<div class="personal-banner" id="personalBanner">
|
||||||
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
|
<b>Personal research mode.</b> This copy is for your own analysis, paper tracking, and manual trade research only. It does not pool money, onboard investors, custody funds, bypass eligibility rules, or place orders without your manual approval.
|
||||||
</div>
|
</div>
|
||||||
<div class="live-build-banner"><b>Build 64 active:</b> Value Hunter verifies resting-bid touches from Polymarket's live CLOB price history, then learns from every locked pair, adverse one-leg exit, and unfilled quote. Profitable cohorts can receive modestly larger quotes; persistently losing cohorts are rotated out while a bounded exploration slot keeps the learner adaptable. Offline snapshots can value positions but cannot invent fills. This remains paper trading; profits are not guaranteed.</div>
|
<div class="live-build-banner"><b>Build 65 active:</b> a 300-market chronological audit rejected all 3,024 tested paired-maker rules. New maker candidates now run as zero-capital shadow observations; real paper exposure stays disabled until 20 current-strategy event clusters in both the matching category and spread cohort show a positive confidence bound. Existing inventory is still reconciled honestly. Offline snapshots can value positions but cannot invent fills. This remains paper trading; profits are not guaranteed.</div>
|
||||||
|
|
||||||
<!-- ============ OVERVIEW ============ -->
|
<!-- ============ OVERVIEW ============ -->
|
||||||
<section class="tabpanel" data-tab="overview">
|
<section class="tabpanel" data-tab="overview">
|
||||||
@@ -745,7 +745,7 @@ footer{margin-top:34px;padding-top:22px;border-top:1px solid var(--border);color
|
|||||||
</section>
|
</section>
|
||||||
|
|
||||||
<footer>
|
<footer>
|
||||||
Build 64 · Adaptive strategy 53 · Paper trading only · Live prices from Polymarket's public Gamma and CLOB APIs · Not financial advice ·
|
Build 65 · Adaptive strategy 54 · Paper trading only · Live prices from Polymarket's public Gamma and CLOB APIs · Not financial advice ·
|
||||||
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
|
<a class="market-link" href="https://github.com/theodore-song/polymarket-analyst" target="_blank" rel="noopener">Source on GitHub</a>
|
||||||
</footer>
|
</footer>
|
||||||
</div>
|
</div>
|
||||||
@@ -774,9 +774,9 @@ const POLITICS_TREND_MIN_HOLD_HOURS = 72;
|
|||||||
const EXIT_CONFIRM_HOURS = 6;
|
const EXIT_CONFIRM_HOURS = 6;
|
||||||
const AGENTS_KEY = "pma_agents_v2";
|
const AGENTS_KEY = "pma_agents_v2";
|
||||||
const SUG_KEY = "pma_suggestions_v5";
|
const SUG_KEY = "pma_suggestions_v5";
|
||||||
const BUILD_VERSION = 64;
|
const BUILD_VERSION = 65;
|
||||||
const SUGGESTION_ENGINE_VERSION = 53;
|
const SUGGESTION_ENGINE_VERSION = 54;
|
||||||
const PREVIOUS_STRATEGY_VERSION = 52;
|
const PREVIOUS_STRATEGY_VERSION = 53;
|
||||||
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
|
const LEGACY_BUILD_STRATEGY_LINEAGE = Object.freeze({40:40,41:40});
|
||||||
function normalizedStrategyVersion(value){
|
function normalizedStrategyVersion(value){
|
||||||
const version=Number(value||0);
|
const version=Number(value||0);
|
||||||
@@ -1158,9 +1158,11 @@ const MAKER_MAX_QUOTES=6;
|
|||||||
const MAKER_MAX_QUOTE_CAPITAL_PCT=0.02;
|
const MAKER_MAX_QUOTE_CAPITAL_PCT=0.02;
|
||||||
const MAKER_TOTAL_CAPITAL_PCT=0.10;
|
const MAKER_TOTAL_CAPITAL_PCT=0.10;
|
||||||
const MAKER_QUOTE_EXPIRY_HOURS=24;
|
const MAKER_QUOTE_EXPIRY_HOURS=24;
|
||||||
|
const MAKER_SHADOW_HORIZON_HOURS=3;
|
||||||
const MAKER_MIN_LOCK_MARGIN=0.005;
|
const MAKER_MIN_LOCK_MARGIN=0.005;
|
||||||
const MAKER_OUTCOME_LIMIT=120;
|
const MAKER_OUTCOME_LIMIT=240;
|
||||||
const MAKER_MIN_COHORT_ATTEMPTS=5;
|
const MAKER_MIN_COHORT_ATTEMPTS=20;
|
||||||
|
const MAKER_MIN_PROMOTION_LOCKS=3;
|
||||||
const MAKER_TOUCH_FIDELITY_MINUTES=1;
|
const MAKER_TOUCH_FIDELITY_MINUTES=1;
|
||||||
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
|
const clamp=(x,a,b)=>Math.max(a,Math.min(b,x));
|
||||||
function liquiditySignal(m){const vol=Math.min(1,Math.log10(m.volume+1)/6.0);const liq=Math.min(1,Math.log10(m.liquidity+1)/5.3);return 0.6*vol+0.4*liq;}
|
function liquiditySignal(m){const vol=Math.min(1,Math.log10(m.volume+1)/6.0);const liq=Math.min(1,Math.log10(m.liquidity+1)/5.3);return 0.6*vol+0.4*liq;}
|
||||||
@@ -2348,15 +2350,23 @@ function summarizeMakerOutcomes(rows){
|
|||||||
(rows||[]).forEach(row=>{
|
(rows||[]).forEach(row=>{
|
||||||
const version=normalizedStrategyVersion(row.strategy_version),weight=version===SUGGESTION_ENGINE_VERSION?1:(version===PREVIOUS_STRATEGY_VERSION?0.5:0.2);
|
const version=normalizedStrategyVersion(row.strategy_version),weight=version===SUGGESTION_ENGINE_VERSION?1:(version===PREVIOUS_STRATEGY_VERSION?0.5:0.2);
|
||||||
const reserved=Math.max(0.01,Number(row.reserved_capital||row.deployed_capital||0.01));
|
const reserved=Math.max(0.01,Number(row.reserved_capital||row.deployed_capital||0.01));
|
||||||
weighted.push({value:Number(row.pnl||0)/reserved,weight,status:row.status});
|
weighted.push({value:Number(row.pnl||0)/reserved,weight,status:row.status,shadow:Boolean(row.shadow_only),
|
||||||
|
current:version===SUGGESTION_ENGINE_VERSION,eventKey:String(row.event_key||row.market_id||row.quote_id)});
|
||||||
});
|
});
|
||||||
const totalWeight=weighted.reduce((sum,row)=>sum+row.weight,0),attempts=weighted.length;
|
const clusters=new Map();
|
||||||
const mean=totalWeight?weighted.reduce((sum,row)=>sum+row.value*row.weight,0)/totalWeight:0;
|
weighted.forEach(row=>{const cluster=clusters.get(row.eventKey)||{weighted:0,weight:0,evidenceWeight:0,current:false};
|
||||||
const variance=totalWeight?weighted.reduce((sum,row)=>sum+row.weight*(row.value-mean)**2,0)/totalWeight:0;
|
cluster.weighted+=row.value*row.weight;cluster.weight+=row.weight;cluster.evidenceWeight=Math.max(cluster.evidenceWeight,row.weight);
|
||||||
const margin=attempts>1?1.282*Math.sqrt(variance/Math.max(1,totalWeight)):1;
|
cluster.current=cluster.current||row.current;clusters.set(row.eventKey,cluster);});
|
||||||
return {attempts,effective_attempts:+totalWeight.toFixed(2),locked:weighted.filter(row=>row.status==="locked").length,
|
const evidence=[...clusters.values()].map(cluster=>({value:cluster.weighted/Math.max(0.01,cluster.weight),weight:cluster.evidenceWeight,current:cluster.current}));
|
||||||
|
const totalWeight=evidence.reduce((sum,row)=>sum+row.weight,0),attempts=weighted.length;
|
||||||
|
const mean=totalWeight?evidence.reduce((sum,row)=>sum+row.value*row.weight,0)/totalWeight:0;
|
||||||
|
const variance=totalWeight?evidence.reduce((sum,row)=>sum+row.weight*(row.value-mean)**2,0)/totalWeight:0;
|
||||||
|
const margin=evidence.length>1?1.645*Math.sqrt(variance/Math.max(1,totalWeight)):1;
|
||||||
|
return {attempts,events:evidence.length,current_events:evidence.filter(row=>row.current).length,effective_attempts:+totalWeight.toFixed(2),
|
||||||
|
locked:weighted.filter(row=>row.status==="locked").length,current_locked:weighted.filter(row=>row.current&&row.status==="locked").length,
|
||||||
adverse:weighted.filter(row=>row.status==="single-exit").length,unfilled:weighted.filter(row=>row.status==="unfilled").length,
|
adverse:weighted.filter(row=>row.status==="single-exit").length,unfilled:weighted.filter(row=>row.status==="unfilled").length,
|
||||||
pnl:+(rows||[]).reduce((sum,row)=>sum+Number(row.pnl||0),0).toFixed(2),mean:+mean.toFixed(5),
|
pnl:+(rows||[]).filter(row=>!row.shadow_only).reduce((sum,row)=>sum+Number(row.pnl||0),0).toFixed(2),
|
||||||
|
shadow_pnl:+(rows||[]).filter(row=>row.shadow_only).reduce((sum,row)=>sum+Number(row.pnl||0),0).toFixed(2),mean:+mean.toFixed(5),
|
||||||
lower:+(mean-margin).toFixed(5),upper:+(mean+margin).toFixed(5),confidence:+(totalWeight/(totalWeight+8)).toFixed(4)};
|
lower:+(mean-margin).toFixed(5),upper:+(mean+margin).toFixed(5),confidence:+(totalWeight/(totalWeight+8)).toFixed(4)};
|
||||||
}
|
}
|
||||||
function buildMakerProfile(p){
|
function buildMakerProfile(p){
|
||||||
@@ -2367,27 +2377,28 @@ function buildMakerProfile(p){
|
|||||||
buckets:Object.fromEntries(Object.entries(buckets).map(([key,rows])=>[key,summarizeMakerOutcomes(rows)]))};
|
buckets:Object.fromEntries(Object.entries(buckets).map(([key,rows])=>[key,summarizeMakerOutcomes(rows)]))};
|
||||||
}
|
}
|
||||||
function makerCandidateLearning(candidate,profile){
|
function makerCandidateLearning(candidate,profile){
|
||||||
const matching=[profile.global,profile.buckets[`category:${candidate.category}`],profile.buckets[`spread:${makerSpreadBand(candidate.spread)}`]].filter(Boolean);
|
const category=profile.buckets[`category:${candidate.category}`],spread=profile.buckets[`spread:${makerSpreadBand(candidate.spread)}`];
|
||||||
const mature=matching.filter(stats=>stats.attempts>=MAKER_MIN_COHORT_ATTEMPTS),losing=mature.find(stats=>stats.upper<0),
|
const matching=[profile.global,category,spread].filter(Boolean),mature=matching.filter(stats=>stats.current_events>=MAKER_MIN_COHORT_ATTEMPTS);
|
||||||
profitable=mature.filter(stats=>stats.lower>0&&stats.locked>=2).sort((a,b)=>b.lower-a.lower)[0];
|
const losing=mature.some(stats=>stats.upper<0),promoted=!losing&&category&&spread&&[category,spread].every(stats=>
|
||||||
const exploration=Boolean(losing)&&stableHash(`maker-explore:${candidate.market_id}`)%100<20;
|
stats.current_events>=MAKER_MIN_COHORT_ATTEMPTS&&stats.current_locked>=MAKER_MIN_PROMOTION_LOCKS&&stats.lower>0);
|
||||||
if(losing&&!exploration)return null;
|
const positive=[category,spread].filter(Boolean).sort((a,b)=>b.lower-a.lower)[0];
|
||||||
const multiplier=profitable?clamp(1+profitable.lower*8*profitable.confidence,1,1.5):1;
|
const multiplier=promoted?clamp(1+Math.max(0,positive.lower)*6*positive.confidence,1,1.25):1;
|
||||||
const units=+(candidate.units*multiplier).toFixed(2),requiredCapital=+(units*candidate.paired_cost).toFixed(2),lockedProfit=+(units*(1-candidate.paired_cost)).toFixed(2);
|
const units=+(candidate.units*multiplier).toFixed(2),requiredCapital=+(units*candidate.paired_cost).toFixed(2),lockedProfit=+(units*(1-candidate.paired_cost)).toFixed(2);
|
||||||
const learned=mature.length?mature.reduce((sum,stats)=>sum+stats.mean,0)/mature.length:0;
|
const learned=mature.length?mature.reduce((sum,stats)=>sum+stats.mean,0)/mature.length:0;
|
||||||
return Object.assign({},candidate,{units,required_capital:requiredCapital,locked_profit:lockedProfit,
|
return Object.assign({},candidate,{units,required_capital:requiredCapital,locked_profit:lockedProfit,
|
||||||
maker_learning_multiplier:+multiplier.toFixed(3),maker_learning_state:profitable?"promoted":losing?"exploration":"observing",
|
maker_learning_multiplier:+multiplier.toFixed(3),maker_learning_state:promoted?"promoted":losing?"shadow-rejected":"shadow-observing",
|
||||||
maker_expected_return:+learned.toFixed(5),score:+(candidate.score+learned*60+(profitable?profitable.lower*100:0)).toFixed(4)});
|
maker_capital_enabled:promoted,maker_expected_return:+learned.toFixed(5),score:+(candidate.score+learned*60+(promoted?positive.lower*100:0)).toFixed(4)});
|
||||||
}
|
}
|
||||||
function recordMakerOutcome(p,quote,status,pnl,deployedCapital){
|
function recordMakerOutcome(p,quote,status,pnl,deployedCapital){
|
||||||
p.maker_outcomes=Array.isArray(p.maker_outcomes)?p.maker_outcomes:[];
|
p.maker_outcomes=Array.isArray(p.maker_outcomes)?p.maker_outcomes:[];
|
||||||
if(p.maker_outcomes.some(row=>row.quote_id===quote.id))return false;
|
if(p.maker_outcomes.some(row=>row.quote_id===quote.id))return false;
|
||||||
const reserved=Math.max(0.01,Number(quote.required_capital||deployedCapital||0.01));
|
const reserved=Math.max(0.01,Number(quote.required_capital||deployedCapital||0.01));
|
||||||
p.maker_outcomes.push({quote_id:quote.id,market_id:quote.market_id,condition_id:quote.condition_id||"",question:quote.question,
|
p.maker_outcomes.push({quote_id:quote.id,market_id:quote.market_id,condition_id:quote.condition_id||"",question:quote.question,
|
||||||
category:quote.category||"Other",spread:Number(quote.spread||0),spread_band:makerSpreadBand(quote.spread),status,pnl:+Number(pnl||0).toFixed(2),
|
event_key:String(quote.event_key||quote.url||quote.event||quote.market_id),category:quote.category||"Other",spread:Number(quote.spread||0),spread_band:makerSpreadBand(quote.spread),
|
||||||
|
status,pnl:+Number(pnl||0).toFixed(2),shadow_only:Boolean(quote.shadow_only),
|
||||||
deployed_capital:+Number(deployedCapital||0).toFixed(2),reserved_capital:+reserved.toFixed(2),return_on_reserved:+(Number(pnl||0)/reserved).toFixed(5),
|
deployed_capital:+Number(deployedCapital||0).toFixed(2),reserved_capital:+reserved.toFixed(2),return_on_reserved:+(Number(pnl||0)/reserved).toFixed(5),
|
||||||
created_at:quote.created_at,completed_at:cycleIso(),hours_to_outcome:+hoursSince(quote.created_at).toFixed(2),
|
created_at:quote.created_at,completed_at:cycleIso(),hours_to_outcome:+hoursSince(quote.created_at).toFixed(2),
|
||||||
strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION});
|
strategy_version:Number(quote.strategy_version||SUGGESTION_ENGINE_VERSION),build_version:Number(quote.build_version||BUILD_VERSION)});
|
||||||
p.maker_outcomes=p.maker_outcomes.slice(-MAKER_OUTCOME_LIMIT);
|
p.maker_outcomes=p.maker_outcomes.slice(-MAKER_OUTCOME_LIMIT);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@@ -2401,7 +2412,7 @@ function makerPairCandidate(m){
|
|||||||
&&pairedCost<=1-MAKER_MIN_LOCK_MARGIN&&dayMove<=Math.max(0.02,spread)&&Number(m.liquidity||0)>=5000
|
&&pairedCost<=1-MAKER_MIN_LOCK_MARGIN&&dayMove<=Math.max(0.02,spread)&&Number(m.liquidity||0)>=5000
|
||||||
&&Number(m.volume_24hr||0)>=2000&&requiredCapital>0))return null;
|
&&Number(m.volume_24hr||0)>=2000&&requiredCapital>0))return null;
|
||||||
const competition=clamp(Number(m.competitive||0),0,1),activity=Math.log10(Math.max(1,Number(m.volume_24hr||0)))+Math.log10(Math.max(1,Number(m.liquidity||0)));
|
const competition=clamp(Number(m.competitive||0),0,1),activity=Math.log10(Math.max(1,Number(m.volume_24hr||0)))+Math.log10(Math.max(1,Number(m.liquidity||0)));
|
||||||
return {market_id:String(m.id),condition_id:m.condition_id||"",question:m.question,event:m.event,url:m.url||"",category:m.category||"Other",
|
return {market_id:String(m.id),condition_id:m.condition_id||"",event_key:String(m.url||m.event||m.id),question:m.question,event:m.event,url:m.url||"",category:m.category||"Other",
|
||||||
clob_token_ids:(m.clob_token_ids||[]).slice(0,2),
|
clob_token_ids:(m.clob_token_ids||[]).slice(0,2),
|
||||||
yes_quote:+yesQuote.toFixed(4),no_quote:+noQuote.toFixed(4),paired_cost:+pairedCost.toFixed(4),units:+minSize.toFixed(2),
|
yes_quote:+yesQuote.toFixed(4),no_quote:+noQuote.toFixed(4),paired_cost:+pairedCost.toFixed(4),units:+minSize.toFixed(2),
|
||||||
required_capital:+requiredCapital.toFixed(2),locked_profit:+(minSize*(1-pairedCost)).toFixed(2),reward_daily_rate:+reward.toFixed(3),
|
required_capital:+requiredCapital.toFixed(2),locked_profit:+(minSize*(1-pairedCost)).toFixed(2),reward_daily_rate:+reward.toFixed(3),
|
||||||
@@ -2411,21 +2422,28 @@ function makerPairCandidate(m){
|
|||||||
function stageMakerQuotes(p,markets){
|
function stageMakerQuotes(p,markets){
|
||||||
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
|
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
|
||||||
const eq=equity(p),existing=new Set(p.maker_quotes.map(quote=>String(quote.market_id))),held=new Set((p.positions||[]).map(pos=>String(pos.market_id)));
|
const eq=equity(p),existing=new Set(p.maker_quotes.map(quote=>String(quote.market_id))),held=new Set((p.positions||[]).map(pos=>String(pos.market_id)));
|
||||||
let reserved=p.maker_quotes.reduce((sum,quote)=>sum+Number(quote.required_capital||0),0),staged=0;
|
let reserved=p.maker_quotes.filter(quote=>!quote.shadow_only).reduce((sum,quote)=>sum+Number(quote.required_capital||0),0),staged=0,shadowStaged=0,capitalStaged=0;
|
||||||
const makerProfile=buildMakerProfile(p);
|
const makerProfile=buildMakerProfile(p);
|
||||||
const candidates=(markets||[]).map(makerPairCandidate).filter(Boolean).map(candidate=>makerCandidateLearning(candidate,makerProfile)).filter(Boolean).sort((a,b)=>b.score-a.score);
|
const candidates=(markets||[]).map(makerPairCandidate).filter(Boolean).map(candidate=>makerCandidateLearning(candidate,makerProfile)).filter(Boolean).sort((a,b)=>b.score-a.score);
|
||||||
for(const candidate of candidates){
|
for(const candidate of candidates){
|
||||||
if(p.maker_quotes.length>=MAKER_MAX_QUOTES||reserved>=eq*MAKER_TOTAL_CAPITAL_PCT)break;
|
if(p.maker_quotes.length>=MAKER_MAX_QUOTES)break;
|
||||||
if(existing.has(candidate.market_id)||held.has(candidate.market_id)||candidate.required_capital>eq*MAKER_MAX_QUOTE_CAPITAL_PCT
|
const capitalEnabled=Boolean(candidate.maker_capital_enabled);
|
||||||
||reserved+candidate.required_capital>Math.min(p.cash,eq*MAKER_TOTAL_CAPITAL_PCT))continue;
|
if(existing.has(candidate.market_id)||held.has(candidate.market_id))continue;
|
||||||
|
if(capitalEnabled&&(reserved>=eq*MAKER_TOTAL_CAPITAL_PCT||candidate.required_capital>eq*MAKER_MAX_QUOTE_CAPITAL_PCT
|
||||||
|
||reserved+candidate.required_capital>Math.min(p.cash,eq*MAKER_TOTAL_CAPITAL_PCT)))continue;
|
||||||
const createdAt=cycleIso(),quote=Object.assign({},candidate,{id:`maker:${candidate.market_id}:${Date.now()}:${p.maker_quotes.length}`,
|
const createdAt=cycleIso(),quote=Object.assign({},candidate,{id:`maker:${candidate.market_id}:${Date.now()}:${p.maker_quotes.length}`,
|
||||||
created_at:createdAt,yes_quote_updated_at:createdAt,no_quote_updated_at:createdAt,strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION,
|
created_at:createdAt,yes_quote_updated_at:createdAt,no_quote_updated_at:createdAt,strategy_version:SUGGESTION_ENGINE_VERSION,build_version:BUILD_VERSION,
|
||||||
|
shadow_only:!capitalEnabled,shadow_horizon_hours:MAKER_SHADOW_HORIZON_HOURS,
|
||||||
yes_filled_at:null,no_filled_at:null,yes_fill_price:null,no_fill_price:null});
|
yes_filled_at:null,no_filled_at:null,yes_fill_price:null,no_fill_price:null});
|
||||||
p.maker_quotes.push(quote);existing.add(candidate.market_id);reserved+=candidate.required_capital;staged++;
|
p.maker_quotes.push(quote);existing.add(candidate.market_id);staged++;
|
||||||
p.history.push({date:logDay(),action:"QUOTE",question:candidate.question,side:"PAIR",
|
if(capitalEnabled){reserved+=candidate.required_capital;capitalStaged++;}else shadowStaged++;
|
||||||
detail:`Staged ${candidate.units} resting YES @ ${pct(candidate.yes_quote)} and NO @ ${pct(candidate.no_quote)} · paired cost ${pct(candidate.paired_cost)} · ${fmtUSD(candidate.locked_profit)} locked settlement margin only if both legs fill · maker learning ${candidate.maker_learning_state} at ${candidate.maker_learning_multiplier.toFixed(2)}x · no hypothetical reward credited`});
|
p.history.push({date:logDay(),action:capitalEnabled?"QUOTE":"SHADOW_QUOTE",question:candidate.question,side:"PAIR",
|
||||||
|
detail:capitalEnabled
|
||||||
|
?`Staged ${candidate.units} capital-backed resting YES @ ${pct(candidate.yes_quote)} and NO @ ${pct(candidate.no_quote)} · paired cost ${pct(candidate.paired_cost)} · ${fmtUSD(candidate.locked_profit)} locked settlement margin only if both legs fill · no hypothetical reward credited`
|
||||||
|
:`Watching resting YES @ ${pct(candidate.yes_quote)} and NO @ ${pct(candidate.no_quote)} for ${MAKER_SHADOW_HORIZON_HOURS}h with zero capital · outcome will train the event-clustered maker gate`});
|
||||||
}
|
}
|
||||||
return {staged,active:p.maker_quotes.length,candidates:candidates.length,reserved:+reserved.toFixed(2),profile:makerProfile};
|
return {staged,shadowStaged,capitalStaged,active:p.maker_quotes.length,shadowActive:p.maker_quotes.filter(quote=>quote.shadow_only).length,
|
||||||
|
capitalActive:p.maker_quotes.filter(quote=>!quote.shadow_only).length,candidates:candidates.length,reserved:+reserved.toFixed(2),profile:makerProfile};
|
||||||
}
|
}
|
||||||
function makerQuotePosition(p,quote,side){return (p.positions||[]).find(pos=>pos.maker_quote_id===quote.id&&pos.side===side);}
|
function makerQuotePosition(p,quote,side){return (p.positions||[]).find(pos=>pos.maker_quote_id===quote.id&&pos.side===side);}
|
||||||
async function fetchMakerTouchHistory(quotes,marketMap){
|
async function fetchMakerTouchHistory(quotes,marketMap){
|
||||||
@@ -2487,6 +2505,35 @@ function completeMakerPair(p,quote){
|
|||||||
detail:`Both resting legs filled for ${pct(1-margin)} combined · locked settlement value ${fmtUSD(Number(quote.units)*margin)} before any separately verified reward`});
|
detail:`Both resting legs filled for ${pct(1-margin)} combined · locked settlement value ${fmtUSD(Number(quote.units)*margin)} before any separately verified reward`});
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
function markShadowMakerLeg(quote,side,evidence="live-book-cross"){
|
||||||
|
const key=side.toLowerCase();
|
||||||
|
if(quote[`${key}_filled_at`])return false;
|
||||||
|
quote[`${key}_filled_at`]=cycleIso();quote[`${key}_fill_price`]=+Number(side==="YES"?quote.yes_quote:quote.no_quote).toFixed(4);
|
||||||
|
quote[`${key}_fill_evidence`]=evidence;return true;
|
||||||
|
}
|
||||||
|
function completeShadowMakerPair(p,quote){
|
||||||
|
const margin=1-Number(quote.yes_fill_price)-Number(quote.no_fill_price),deployed=Number(quote.units)*(1-margin),pnl=Number(quote.units)*margin;
|
||||||
|
recordMakerOutcome(p,quote,"locked",pnl,deployed);
|
||||||
|
p.history.push({date:logDay(),action:"SHADOW_LOCK",question:quote.question,side:"PAIR",
|
||||||
|
detail:`Shadow YES and NO bids both touched for ${pct(1-margin)} combined · simulated ${fmtUSD(pnl)} settlement margin · portfolio cash unchanged`});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
function finishShadowMakerQuote(p,quote,fresh){
|
||||||
|
const side=quote.yes_filled_at&&!quote.no_filled_at?"YES":quote.no_filled_at&&!quote.yes_filled_at?"NO":null;
|
||||||
|
if(!side){
|
||||||
|
recordMakerOutcome(p,quote,"unfilled",0,0);
|
||||||
|
p.history.push({date:logDay(),action:"SHADOW_EXPIRE",question:quote.question,side:"PAIR",
|
||||||
|
detail:`Shadow bids completed their ${quote.shadow_horizon_hours||MAKER_SHADOW_HORIZON_HOURS}h window without a verified touch; portfolio cash unchanged`});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
const entry=Number(side==="YES"?quote.yes_fill_price:quote.no_fill_price),executable=side==="YES"?Number(fresh.best_bid):1-Number(fresh.best_ask);
|
||||||
|
if(!(executable>0&&executable<1))return false;
|
||||||
|
const deployed=Number(quote.units)*entry,pnl=Number(quote.units)*(executable-entry-SIGNAL_ROUND_TRIP_COST);
|
||||||
|
recordMakerOutcome(p,quote,"single-exit",pnl,deployed);
|
||||||
|
p.history.push({date:logDay(),action:"SHADOW_EXIT",question:quote.question,side,
|
||||||
|
detail:`Only the shadow ${side} bid touched; ${quote.shadow_horizon_hours||MAKER_SHADOW_HORIZON_HOURS}h executable exit grades ${fmtUSD(pnl)} after modeled cost · portfolio cash unchanged`});
|
||||||
|
return true;
|
||||||
|
}
|
||||||
function unwindMakerInventory(p,quote,fresh){
|
function unwindMakerInventory(p,quote,fresh){
|
||||||
const side=quote.yes_filled_at&&!quote.no_filled_at?"YES":quote.no_filled_at&&!quote.yes_filled_at?"NO":null;
|
const side=quote.yes_filled_at&&!quote.no_filled_at?"YES":quote.no_filled_at&&!quote.yes_filled_at?"NO":null;
|
||||||
if(!side){
|
if(!side){
|
||||||
@@ -2506,19 +2553,37 @@ function unwindMakerInventory(p,quote,fresh){
|
|||||||
function manageMakerQuotes(p,marketMap,{executeTrades=false,touchHistory={}}={}){
|
function manageMakerQuotes(p,marketMap,{executeTrades=false,touchHistory={}}={}){
|
||||||
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
|
p.maker_quotes=Array.isArray(p.maker_quotes)?p.maker_quotes:[];
|
||||||
if(!executeTrades)return {active:p.maker_quotes.length,fills:0,locked:0,expired:0};
|
if(!executeTrades)return {active:p.maker_quotes.length,fills:0,locked:0,expired:0};
|
||||||
const keep=[];let fills=0,locked=0,expired=0,historyTouches=0;
|
const keep=[];let fills=0,locked=0,expired=0,historyTouches=0,shadowCompleted=0;
|
||||||
for(const quote of p.maker_quotes){
|
for(const quote of p.maker_quotes){
|
||||||
const fresh=marketMap[String(quote.market_id)];
|
const fresh=marketMap[String(quote.market_id)];
|
||||||
if(!fresh){keep.push(quote);continue;}
|
if(!fresh){keep.push(quote);continue;}
|
||||||
|
const legacyCapital=!quote.shadow_only&&normalizedStrategyVersion(quote.strategy_version)<SUGGESTION_ENGINE_VERSION;
|
||||||
|
if(legacyCapital&&!quote.yes_filled_at&&!quote.no_filled_at){
|
||||||
|
p.history.push({date:logDay(),action:"MAKER_RETIRE",question:quote.question,side:"PAIR",
|
||||||
|
detail:`Canceled untouched Strategy ${normalizedStrategyVersion(quote.strategy_version)} maker bids after the chronological audit rejected the rule; no fill or profit recorded`});
|
||||||
|
expired++;continue;
|
||||||
|
}
|
||||||
|
if(legacyCapital&&Boolean(quote.yes_filled_at)!==Boolean(quote.no_filled_at)){
|
||||||
|
if(unwindMakerInventory(p,quote,fresh)){expired++;continue;}
|
||||||
|
keep.push(quote);continue;
|
||||||
|
}
|
||||||
|
if(legacyCapital&"e.yes_filled_at&"e.no_filled_at){if(completeMakerPair(p,quote)){locked++;continue;}}
|
||||||
const age=hoursSince(quote.created_at),oldEnough=age>=RUN_INTERVAL_MS/3600000*0.8;
|
const age=hoursSince(quote.created_at),oldEnough=age>=RUN_INTERVAL_MS/3600000*0.8;
|
||||||
const yesTouch=oldEnough&&!quote.yes_filled_at?makerTouchEvidence(quote,"YES",touchHistory):null;
|
const yesTouch=oldEnough&&!quote.yes_filled_at?makerTouchEvidence(quote,"YES",touchHistory):null;
|
||||||
if(oldEnough&&!quote.yes_filled_at&&(yesTouch||(Number(fresh.best_ask)>0&&Number(fresh.best_ask)<=Number(quote.yes_quote)+0.00001))){
|
if(oldEnough&&!quote.yes_filled_at&&(yesTouch||(Number(fresh.best_ask)>0&&Number(fresh.best_ask)<=Number(quote.yes_quote)+0.00001))){
|
||||||
if(fillMakerLeg(p,quote,"YES",fresh,yesTouch?yesTouch.source:"live-book-cross")){fills++;if(yesTouch)historyTouches++;}
|
const filled=quote.shadow_only?markShadowMakerLeg(quote,"YES",yesTouch?yesTouch.source:"live-book-cross"):fillMakerLeg(p,quote,"YES",fresh,yesTouch?yesTouch.source:"live-book-cross");
|
||||||
|
if(filled){fills++;if(yesTouch)historyTouches++;}
|
||||||
}
|
}
|
||||||
const noAsk=1-Number(fresh.best_bid);
|
const noAsk=1-Number(fresh.best_bid);
|
||||||
const noTouch=oldEnough&&!quote.no_filled_at?makerTouchEvidence(quote,"NO",touchHistory):null;
|
const noTouch=oldEnough&&!quote.no_filled_at?makerTouchEvidence(quote,"NO",touchHistory):null;
|
||||||
if(oldEnough&&!quote.no_filled_at&&(noTouch||(Number(fresh.best_bid)>0&&noAsk<=Number(quote.no_quote)+0.00001))){
|
if(oldEnough&&!quote.no_filled_at&&(noTouch||(Number(fresh.best_bid)>0&&noAsk<=Number(quote.no_quote)+0.00001))){
|
||||||
if(fillMakerLeg(p,quote,"NO",fresh,noTouch?noTouch.source:"live-book-cross")){fills++;if(noTouch)historyTouches++;}
|
const filled=quote.shadow_only?markShadowMakerLeg(quote,"NO",noTouch?noTouch.source:"live-book-cross"):fillMakerLeg(p,quote,"NO",fresh,noTouch?noTouch.source:"live-book-cross");
|
||||||
|
if(filled){fills++;if(noTouch)historyTouches++;}
|
||||||
|
}
|
||||||
|
if(quote.shadow_only){
|
||||||
|
if(quote.yes_filled_at&"e.no_filled_at){completeShadowMakerPair(p,quote);locked++;shadowCompleted++;continue;}
|
||||||
|
if(age>=Number(quote.shadow_horizon_hours||MAKER_SHADOW_HORIZON_HOURS)&&finishShadowMakerQuote(p,quote,fresh)){expired++;shadowCompleted++;continue;}
|
||||||
|
keep.push(quote);continue;
|
||||||
}
|
}
|
||||||
if(quote.yes_filled_at&"e.no_filled_at){if(completeMakerPair(p,quote)){locked++;continue;}}
|
if(quote.yes_filled_at&"e.no_filled_at){if(completeMakerPair(p,quote)){locked++;continue;}}
|
||||||
if(age>=MAKER_QUOTE_EXPIRY_HOURS){if(unwindMakerInventory(p,quote,fresh)){expired++;continue;}}
|
if(age>=MAKER_QUOTE_EXPIRY_HOURS){if(unwindMakerInventory(p,quote,fresh)){expired++;continue;}}
|
||||||
@@ -2534,7 +2599,8 @@ function manageMakerQuotes(p,marketMap,{executeTrades=false,touchHistory={}}={})
|
|||||||
keep.push(quote);
|
keep.push(quote);
|
||||||
}
|
}
|
||||||
p.maker_quotes=keep;
|
p.maker_quotes=keep;
|
||||||
return {active:keep.length,fills,locked,expired,historyTouches,profile:buildMakerProfile(p)};
|
return {active:keep.length,shadowActive:keep.filter(quote=>quote.shadow_only).length,capitalActive:keep.filter(quote=>!quote.shadow_only).length,
|
||||||
|
fills,locked,expired,historyTouches,shadowCompleted,profile:buildMakerProfile(p)};
|
||||||
}
|
}
|
||||||
function peerMarketStats(st,selfId){
|
function peerMarketStats(st,selfId){
|
||||||
const stats={};
|
const stats={};
|
||||||
@@ -2999,10 +3065,12 @@ async function runDailyCycle(){
|
|||||||
if(cfg.id==="value"&&runMode==="live"&&entriesAllowed){
|
if(cfg.id==="value"&&runMode==="live"&&entriesAllowed){
|
||||||
const staged=stageMakerQuotes(p,analysisMarkets);
|
const staged=stageMakerQuotes(p,analysisMarkets);
|
||||||
p.lastDecision=Object.assign({},p.lastDecision,{makerQuotes:p.maker_quotes.length,makerCandidates:staged.candidates,makerStaged:staged.staged,
|
p.lastDecision=Object.assign({},p.lastDecision,{makerQuotes:p.maker_quotes.length,makerCandidates:staged.candidates,makerStaged:staged.staged,
|
||||||
|
makerShadowStaged:staged.shadowStaged,makerCapitalStaged:staged.capitalStaged,makerShadowActive:staged.shadowActive,makerCapitalActive:staged.capitalActive,
|
||||||
makerFills:makerActivity.fills,makerLocked:makerActivity.locked,makerExpired:makerActivity.expired,makerHistoryTouches:makerActivity.historyTouches,
|
makerFills:makerActivity.fills,makerLocked:makerActivity.locked,makerExpired:makerActivity.expired,makerHistoryTouches:makerActivity.historyTouches,
|
||||||
|
makerShadowCompleted:makerActivity.shadowCompleted,
|
||||||
makerReserved:staged.reserved,makerProfile:makerActivity.profile});
|
makerReserved:staged.reserved,makerProfile:makerActivity.profile});
|
||||||
if(staged.staged||makerActivity.fills||makerActivity.locked||makerActivity.expired){
|
if(staged.staged||makerActivity.fills||makerActivity.locked||makerActivity.expired){
|
||||||
p.lastDecision.allocationStatus=`${p.lastDecision.allocationStatus||""} Paired maker book: ${p.maker_quotes.length} active quote${p.maker_quotes.length===1?"":"s"}, ${makerActivity.fills} fill${makerActivity.fills===1?"":"s"}, ${makerActivity.locked} newly locked pair${makerActivity.locked===1?"":"s"}; no hypothetical rewards credited.`.trim();
|
p.lastDecision.allocationStatus=`${p.lastDecision.allocationStatus||""} Maker learner: ${staged.shadowActive} zero-capital shadow observation${staged.shadowActive===1?"":"s"}, ${staged.capitalActive} evidence-promoted capital quote${staged.capitalActive===1?"":"s"}, ${makerActivity.shadowCompleted||0} shadow outcome${makerActivity.shadowCompleted===1?"":"s"} graded this cycle; no hypothetical rewards credited.`.trim();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
recordSnapshot(p);
|
recordSnapshot(p);
|
||||||
@@ -3194,7 +3262,7 @@ function decisionSummary(p){
|
|||||||
const learning=d.learning?` Learning: ${d.learning.samples} completed trades retained with older strategies down-weighted, ${(d.learning.global_score*100).toFixed(2)}% shrunk expectancy; ${d.learning.current_samples||0} completed under adaptive strategy ${SUGGESTION_ENGINE_VERSION}${d.learning.best?`; strongest ${d.learning.best.feature.replace(":"," ")}`:""}${d.learning.worst?`; weakest ${d.learning.worst.feature.replace(":"," ")}`:""}.`:"";
|
const learning=d.learning?` Learning: ${d.learning.samples} completed trades retained with older strategies down-weighted, ${(d.learning.global_score*100).toFixed(2)}% shrunk expectancy; ${d.learning.current_samples||0} completed under adaptive strategy ${SUGGESTION_ENGINE_VERSION}${d.learning.best?`; strongest ${d.learning.best.feature.replace(":"," ")}`:""}${d.learning.worst?`; weakest ${d.learning.worst.feature.replace(":"," ")}`:""}.`:"";
|
||||||
const calibration=d.marketLearning?` Walk-forward calibration: ${d.marketLearning.samples||0} net-of-cost checkpoint observations across ${d.marketLearning.events||d.marketLearning.markets||0} event clusters / ${d.marketLearning.markets||0} markets, graded separately near ${SIGNAL_EVAL_HORIZONS.join("h and ")}h (${d.marketLearning.current_samples||0} observations / ${d.marketLearning.current_events||0} events under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future checkpoint${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired checkpoints`:""}; ${d.marketLearning.promoted_buckets||0} horizon-specific feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Promotion requires positive current-strategy evidence at both horizons across independent events. Missed windows expire rather than borrowing a later price. New observations prioritize under-sampled signal/side/category cohorts and independent events before repeats. Correlated outcome markets in one event count as one effective outcome. Historical prior: every directional trend and reversal remains observation-only until its exact recent cohorts independently promote; settlement-jump barriers stay excluded.`:"";
|
const calibration=d.marketLearning?` Walk-forward calibration: ${d.marketLearning.samples||0} net-of-cost checkpoint observations across ${d.marketLearning.events||d.marketLearning.markets||0} event clusters / ${d.marketLearning.markets||0} markets, graded separately near ${SIGNAL_EVAL_HORIZONS.join("h and ")}h (${d.marketLearning.current_samples||0} observations / ${d.marketLearning.current_events||0} events under adaptive strategy ${SUGGESTION_ENGINE_VERSION}), ${d.marketLearning.pending||0} awaiting a future checkpoint${d.marketLearning.expired_ungraded?`, ${d.marketLearning.expired_ungraded} expired checkpoints`:""}; ${d.marketLearning.promoted_buckets||0} horizon-specific feature cohorts promoted and ${d.marketLearning.demoted_buckets||0} demoted. Promotion requires positive current-strategy evidence at both horizons across independent events. Missed windows expire rather than borrowing a later price. New observations prioritize under-sampled signal/side/category cohorts and independent events before repeats. Correlated outcome markets in one event count as one effective outcome. Historical prior: every directional trend and reversal remains observation-only until its exact recent cohorts independently promote; settlement-jump barriers stay excluded.`:"";
|
||||||
const makerStats=d.makerProfile&&d.makerProfile.global;
|
const makerStats=d.makerProfile&&d.makerProfile.global;
|
||||||
const maker=d.makerQuotes!=null?` Paired maker book: ${d.makerQuotes} active, ${d.makerFills||0} fills (${d.makerHistoryTouches||0} verified from CLOB history) and ${d.makerLocked||0} completed locks this cycle, ${fmtUSD(d.makerReserved||0)} quote capital reserved.${makerStats?` Learning ledger: ${makerStats.attempts} attempts, ${makerStats.locked} locked, ${makerStats.adverse} adverse exits, ${makerStats.unfilled} unfilled, ${fmtUSD(makerStats.pnl)} net.`:""} Rewards remain excluded until externally verified.`:"";
|
const maker=d.makerQuotes!=null?` Maker learner: ${d.makerShadowActive||0} zero-capital shadow observations and ${d.makerCapitalActive||0} evidence-promoted capital quotes active; ${d.makerFills||0} verified touches and ${d.makerShadowCompleted||0} shadow outcomes completed this cycle, ${fmtUSD(d.makerReserved||0)} capital reserved.${makerStats?` Event-clustered ledger: ${makerStats.attempts} attempts / ${makerStats.events} events, ${makerStats.locked} paired touches, ${makerStats.adverse} adverse single touches, ${makerStats.unfilled} unfilled, ${fmtUSD(makerStats.shadow_pnl)} simulated shadow net and ${fmtUSD(makerStats.pnl)} actual paper net.`:""} Capital promotion requires ${MAKER_MIN_COHORT_ATTEMPTS} current-strategy events with positive confidence bounds in both category and spread cohorts. Rewards remain excluded until externally verified.`:"";
|
||||||
return `${d.mode} mode: ${d.reason}${emotion} Limits now: ${d.maxNew} new trade${d.maxNew===1?"":"s"}, max ${(d.maxFrac*100).toFixed(1)}% per position${d.minConv?`, conviction ${d.minConv}+`:""}.${learning}${calibration}${maker}${exposure}${allocation}${candidates}${blockers}`;
|
return `${d.mode} mode: ${d.reason}${emotion} Limits now: ${d.maxNew} new trade${d.maxNew===1?"":"s"}, max ${(d.maxFrac*100).toFixed(1)}% per position${d.minConv?`, conviction ${d.minConv}+`:""}.${learning}${calibration}${maker}${exposure}${allocation}${candidates}${blockers}`;
|
||||||
}
|
}
|
||||||
function renderAgentBrief(cfg,p,st){
|
function renderAgentBrief(cfg,p,st){
|
||||||
@@ -3204,13 +3272,13 @@ function renderAgentBrief(cfg,p,st){
|
|||||||
const cadence=`${cfg.maxNew||0} new trades max per cycle`;
|
const cadence=`${cfg.maxNew||0} new trades max per cycle`;
|
||||||
const exitRule=`Exit after a confirmed reversal, a 12h unconfirmed fade after 48h, a trailing-profit retrace, or ${EXIT_STALE_DAYS}d stale while losing; Politics trends get 72h before ordinary signal exits`;
|
const exitRule=`Exit after a confirmed reversal, a 12h unconfirmed fade after 48h, a trailing-profit retrace, or ${EXIT_STALE_DAYS}d stale while losing; Politics trends get 72h before ordinary signal exits`;
|
||||||
const thesis=agentPlainBlurb(cfg,st);
|
const thesis=agentPlainBlurb(cfg,st);
|
||||||
const quoteSummary=(p.maker_quotes||[]).slice(0,3).map(quote=>`${quote.question.slice(0,34)}${quote.question.length>34?"...":""}: YES ${pct(quote.yes_quote)} + NO ${pct(quote.no_quote)}${quote.yes_filled_at||quote.no_filled_at?" (one leg filled)":""}`).join("; ");
|
const quoteSummary=(p.maker_quotes||[]).slice(0,3).map(quote=>`${quote.shadow_only?"Shadow":"Capital"} · ${quote.question.slice(0,34)}${quote.question.length>34?"...":""}: YES ${pct(quote.yes_quote)} + NO ${pct(quote.no_quote)}${quote.yes_filled_at||quote.no_filled_at?" (one leg touched)":""}`).join("; ");
|
||||||
root.innerHTML=`
|
root.innerHTML=`
|
||||||
<div class="brief-main" style="border-top:3px solid ${cfg.color}">
|
<div class="brief-main" style="border-top:3px solid ${cfg.color}">
|
||||||
<div class="brief-title"><span>${cfg.emoji}</span><span>${cfg.name}</span></div>
|
<div class="brief-title"><span>${cfg.emoji}</span><span>${cfg.name}</span></div>
|
||||||
<p>${esc(thesis)}</p>
|
<p>${esc(thesis)}</p>
|
||||||
<p style="margin-top:9px"><b>Adaptation:</b> ${esc(decisionSummary(p))}</p>
|
<p style="margin-top:9px"><b>Adaptation:</b> ${esc(decisionSummary(p))}</p>
|
||||||
${quoteSummary?`<p style="margin-top:9px"><b>Resting maker quotes:</b> ${esc(quoteSummary)}</p>`:""}
|
${quoteSummary?`<p style="margin-top:9px"><b>Maker observations:</b> ${esc(quoteSummary)}</p>`:""}
|
||||||
</div>
|
</div>
|
||||||
<div class="brief-mini"><div class="k">Style</div><div class="v">${esc(risk)}</div></div>
|
<div class="brief-mini"><div class="k">Style</div><div class="v">${esc(risk)}</div></div>
|
||||||
<div class="brief-mini"><div class="k">Trade Rule</div><div class="v">${esc(cadence)}</div></div>
|
<div class="brief-mini"><div class="k">Trade Rule</div><div class="v">${esc(cadence)}</div></div>
|
||||||
@@ -3451,8 +3519,8 @@ function renderPortfolioTab(){
|
|||||||
{ic:"🕒",label:"24h change",value:format24h(change24h),cls:signClass(change24h.pct)},
|
{ic:"🕒",label:"24h change",value:format24h(change24h),cls:signClass(change24h.pct)},
|
||||||
{ic:"🧪",label:"Adaptive",value:fmtPct(engineBase>0?(eq/engineBase-1)*100:0),cls:signClass(enginePnl)},
|
{ic:"🧪",label:"Adaptive",value:fmtPct(engineBase>0?(eq/engineBase-1)*100:0),cls:signClass(enginePnl)},
|
||||||
{ic:"📂",label:"Open",value:p.positions.length},
|
{ic:"📂",label:"Open",value:p.positions.length},
|
||||||
{ic:"⇄",label:"Resting quotes",value:(p.maker_quotes||[]).length},
|
{ic:"⇄",label:"Maker shadow",value:(p.maker_quotes||[]).filter(quote=>quote.shadow_only).length},
|
||||||
{ic:"Σ",label:"Maker net",value:fmtUSD(makerProfile.global.pnl),cls:signClass(makerProfile.global.pnl)},
|
{ic:"Σ",label:"Maker paper net",value:fmtUSD(makerProfile.global.pnl),cls:signClass(makerProfile.global.pnl)},
|
||||||
];
|
];
|
||||||
$("statsPf").innerHTML=stats.map(s=>`<div class="stat"><div class="ic">${s.ic}</div><div class="label">${s.label}</div><div class="value ${s.cls||""}">${s.value}</div></div>`).join("");
|
$("statsPf").innerHTML=stats.map(s=>`<div class="stat"><div class="ic">${s.ic}</div><div class="label">${s.label}</div><div class="value ${s.cls||""}">${s.value}</div></div>`).join("");
|
||||||
const sort=$("positionSort");
|
const sort=$("positionSort");
|
||||||
@@ -4813,10 +4881,10 @@ window.PMA_ENGINE_DIAGNOSTICS=Object.freeze({
|
|||||||
onePendingObservationPerMarketSide:true,oldestPendingEvidenceFirst:true,coverageAwareObservationSampling:true,uncertaintyGatedCalibration:true,
|
onePendingObservationPerMarketSide:true,oldestPendingEvidenceFirst:true,coverageAwareObservationSampling:true,uncertaintyGatedCalibration:true,
|
||||||
directionalSignalsRequirePromotion:true,liveOnlyCompleteBundles:true,negativeRiskBundleSides:["YES","NO"],negativeRiskEventScanLimit:NEG_RISK_EVENT_SCAN_LIMIT,
|
directionalSignalsRequirePromotion:true,liveOnlyCompleteBundles:true,negativeRiskBundleSides:["YES","NO"],negativeRiskEventScanLimit:NEG_RISK_EVENT_SCAN_LIMIT,
|
||||||
negativeRiskMinimumNetReturnPct:NEG_RISK_MIN_NET_RETURN*100,
|
negativeRiskMinimumNetReturnPct:NEG_RISK_MIN_NET_RETURN*100,
|
||||||
pairedMakerQuotes:true,makerQuoteExpiryHours:MAKER_QUOTE_EXPIRY_HOURS,makerMinimumLockMarginPct:MAKER_MIN_LOCK_MARGIN*100,
|
pairedMakerQuotes:"shadow-until-promoted",makerShadowHorizonHours:MAKER_SHADOW_HORIZON_HOURS,makerLegacyQuoteExpiryHours:MAKER_QUOTE_EXPIRY_HOURS,makerMinimumLockMarginPct:MAKER_MIN_LOCK_MARGIN*100,
|
||||||
makerTouchSource:"Polymarket CLOB batch price history",makerOutcomeLimit:MAKER_OUTCOME_LIMIT,makerMinimumCohortAttempts:MAKER_MIN_COHORT_ATTEMPTS,
|
makerTouchSource:"Polymarket CLOB batch price history",makerOutcomeLimit:MAKER_OUTCOME_LIMIT,makerMinimumCohortAttempts:MAKER_MIN_COHORT_ATTEMPTS,
|
||||||
makerMaximumLearningMultiplier:1.5,makerLosingCohortExplorationPct:20,hypotheticalRewardsCredited:false,
|
makerMinimumPromotionLocks:MAKER_MIN_PROMOTION_LOCKS,makerMaximumLearningMultiplier:1.25,makerCapitalExplorationPct:0,hypotheticalRewardsCredited:false,
|
||||||
historicalPrior:"All directional trends and reversals require positive independent cohort promotion; Sports and Crypto trends remain excluded; exact ranges and path-dependent barriers are excluded; live-priced complete negative-risk bundles and crossed paired maker quotes may trade"}),
|
historicalPrior:"All directional trends and reversals require positive independent cohort promotion; Sports and Crypto trends remain excluded; exact ranges and path-dependent barriers are excluded; live-priced complete negative-risk bundles may trade; paired maker quotes remain zero-capital shadow observations until their current event-clustered cohorts promote"}),
|
||||||
});
|
});
|
||||||
function runEngineSelfTest(){
|
function runEngineSelfTest(){
|
||||||
const market=(overrides={})=>Object.assign({
|
const market=(overrides={})=>Object.assign({
|
||||||
@@ -4986,37 +5054,44 @@ function runEngineSelfTest(){
|
|||||||
const makerMarket=market({id:"maker-test",question:"Will the paired quote test pass?",yes_price:0.42,no_price:0.58,best_bid:0.40,best_ask:0.44,spread:0.04,
|
const makerMarket=market({id:"maker-test",question:"Will the paired quote test pass?",yes_price:0.42,no_price:0.58,best_bid:0.40,best_ask:0.44,spread:0.04,
|
||||||
tick_size:0.01,order_min_size:5,rewards_daily_rate:10,rewards_min_size:50,rewards_max_spread:4.5,competitive:0.8,price_change_1d:0.01});
|
tick_size:0.01,order_min_size:5,rewards_daily_rate:10,rewards_min_size:50,rewards_max_spread:4.5,competitive:0.8,price_change_1d:0.01});
|
||||||
const makerBook=defaultPortfolio(),makerStage=stageMakerQuotes(makerBook,[makerMarket]),makerQuote=makerBook.maker_quotes[0];
|
const makerBook=defaultPortfolio(),makerStage=stageMakerQuotes(makerBook,[makerMarket]),makerQuote=makerBook.maker_quotes[0];
|
||||||
|
makerQuote.shadow_only=false;
|
||||||
makerQuote.created_at=hoursAgo(1);
|
makerQuote.created_at=hoursAgo(1);
|
||||||
const makerFirst=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
|
const makerFirst=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
|
||||||
const makerSingleFill=makerBook.positions.length===1&&makerBook.positions[0].side==="YES"&&makerBook.maker_quotes.length===1;
|
const makerSingleFill=makerBook.positions.length===1&&makerBook.positions[0].side==="YES"&&makerBook.maker_quotes.length===1;
|
||||||
|
const legacyUntouchedBook=defaultPortfolio();stageMakerQuotes(legacyUntouchedBook,[makerMarket]);legacyUntouchedBook.maker_quotes[0].shadow_only=false;legacyUntouchedBook.maker_quotes[0].strategy_version=PREVIOUS_STRATEGY_VERSION;
|
||||||
|
const legacyUntouched=manageMakerQuotes(legacyUntouchedBook,{"maker-test":makerMarket},{executeTrades:true});
|
||||||
|
const legacyOneLegBook=defaultPortfolio();stageMakerQuotes(legacyOneLegBook,[makerMarket]);legacyOneLegBook.maker_quotes[0].shadow_only=false;legacyOneLegBook.maker_quotes[0].created_at=hoursAgo(1);
|
||||||
|
manageMakerQuotes(legacyOneLegBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
|
||||||
|
legacyOneLegBook.maker_quotes[0].strategy_version=PREVIOUS_STRATEGY_VERSION;legacyOneLegBook.positions[0].strategy_version=PREVIOUS_STRATEGY_VERSION;
|
||||||
|
const legacyOneLeg=manageMakerQuotes(legacyOneLegBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.36,no_price:0.64,best_bid:0.35,best_ask:0.37}))},{executeTrades:true});
|
||||||
const makerSecond=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.60,no_price:0.40,best_bid:0.57,best_ask:0.61}))},{executeTrades:true});
|
const makerSecond=manageMakerQuotes(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.60,no_price:0.40,best_bid:0.57,best_ask:0.61}))},{executeTrades:true});
|
||||||
const makerExpectedProfit=+(makerQuote.units*(1-Number(makerQuote.yes_fill_price)-Number(makerQuote.no_fill_price))).toFixed(2);
|
const makerExpectedProfit=+(makerQuote.units*(1-Number(makerQuote.yes_fill_price)-Number(makerQuote.no_fill_price))).toFixed(2);
|
||||||
const makerOverlapState=defaultState();makerOverlapState.agents.value=makerBook;reduceStrategyOverlap(makerOverlapState);
|
const makerOverlapState=defaultState();makerOverlapState.agents.value=makerBook;reduceStrategyOverlap(makerOverlapState);
|
||||||
const makerPairSurvivesOverlap=makerBook.positions.length===2&&makerBook.positions.every(pos=>pos.requires_complete_bundle);
|
const makerPairSurvivesOverlap=makerBook.positions.length===2&&makerBook.positions.every(pos=>pos.requires_complete_bundle);
|
||||||
markToMarket(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:1,no_price:0,best_bid:1,best_ask:1,closed:true,accepting_orders:false}))},AGENTS[0],{policyExits:true,executeTrades:true});
|
markToMarket(makerBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:1,no_price:0,best_bid:1,best_ask:1,closed:true,accepting_orders:false}))},AGENTS[0],{policyExits:true,executeTrades:true});
|
||||||
const makerExpiryBook=defaultPortfolio();stageMakerQuotes(makerExpiryBook,[makerMarket]);makerExpiryBook.maker_quotes[0].created_at=hoursAgo(1);
|
const makerExpiryBook=defaultPortfolio();stageMakerQuotes(makerExpiryBook,[makerMarket]);makerExpiryBook.maker_quotes[0].shadow_only=false;makerExpiryBook.maker_quotes[0].created_at=hoursAgo(1);
|
||||||
manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
|
manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:true});
|
||||||
makerExpiryBook.maker_quotes[0].created_at=hoursAgo(25);
|
makerExpiryBook.maker_quotes[0].created_at=hoursAgo(25);
|
||||||
const makerExpired=manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.36,no_price:0.64,best_bid:0.35,best_ask:0.37}))},{executeTrades:true});
|
const makerExpired=manageMakerQuotes(makerExpiryBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.36,no_price:0.64,best_bid:0.35,best_ask:0.37}))},{executeTrades:true});
|
||||||
const makerOfflineBook=defaultPortfolio();stageMakerQuotes(makerOfflineBook,[makerMarket]);makerOfflineBook.maker_quotes[0].created_at=hoursAgo(1);
|
const makerOfflineBook=defaultPortfolio();stageMakerQuotes(makerOfflineBook,[makerMarket]);makerOfflineBook.maker_quotes[0].shadow_only=false;makerOfflineBook.maker_quotes[0].created_at=hoursAgo(1);
|
||||||
const makerOffline=manageMakerQuotes(makerOfflineBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:false});
|
const makerOffline=manageMakerQuotes(makerOfflineBook,{"maker-test":market(Object.assign({},makerMarket,{yes_price:0.39,no_price:0.61,best_bid:0.38,best_ask:0.40}))},{executeTrades:false});
|
||||||
const touchBook=defaultPortfolio();stageMakerQuotes(touchBook,[makerMarket]);const touchQuote=touchBook.maker_quotes[0];touchQuote.created_at=hoursAgo(1);touchQuote.yes_quote_updated_at=touchQuote.created_at;touchQuote.no_quote_updated_at=touchQuote.created_at;
|
const touchBook=defaultPortfolio();stageMakerQuotes(touchBook,[makerMarket]);const touchQuote=touchBook.maker_quotes[0];touchQuote.shadow_only=false;touchQuote.created_at=hoursAgo(1);touchQuote.yes_quote_updated_at=touchQuote.created_at;touchQuote.no_quote_updated_at=touchQuote.created_at;
|
||||||
const touchTimestamp=Math.floor((Date.now()-30*60000)/1000),touchMarket=market(Object.assign({},makerMarket,{best_bid:0.40,best_ask:0.44,yes_price:0.42,no_price:0.58}));
|
const touchTimestamp=Math.floor((Date.now()-30*60000)/1000),touchMarket=market(Object.assign({},makerMarket,{best_bid:0.40,best_ask:0.44,yes_price:0.42,no_price:0.58}));
|
||||||
const touchFill=manageMakerQuotes(touchBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{yes:[{t:touchTimestamp,p:0.395}],no:[{t:touchTimestamp,p:0.58}]}});
|
const touchFill=manageMakerQuotes(touchBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{yes:[{t:touchTimestamp,p:0.395}],no:[{t:touchTimestamp,p:0.58}]}});
|
||||||
const offlineTouchBook=defaultPortfolio();stageMakerQuotes(offlineTouchBook,[makerMarket]);offlineTouchBook.maker_quotes[0].created_at=hoursAgo(1);
|
const offlineTouchBook=defaultPortfolio();stageMakerQuotes(offlineTouchBook,[makerMarket]);offlineTouchBook.maker_quotes[0].shadow_only=false;offlineTouchBook.maker_quotes[0].created_at=hoursAgo(1);
|
||||||
const offlineTouch=manageMakerQuotes(offlineTouchBook,{"maker-test":touchMarket},{executeTrades:false,touchHistory:{yes:[{t:touchTimestamp,p:0.395}]}});
|
const offlineTouch=manageMakerQuotes(offlineTouchBook,{"maker-test":touchMarket},{executeTrades:false,touchHistory:{yes:[{t:touchTimestamp,p:0.395}]}});
|
||||||
const repriceBook=defaultPortfolio();stageMakerQuotes(repriceBook,[makerMarket]);const repriceQuote=repriceBook.maker_quotes[0];repriceQuote.created_at=hoursAgo(2);
|
const repriceBook=defaultPortfolio();stageMakerQuotes(repriceBook,[makerMarket]);const repriceQuote=repriceBook.maker_quotes[0];repriceQuote.shadow_only=false;repriceQuote.created_at=hoursAgo(2);
|
||||||
manageMakerQuotes(repriceBook,{"maker-test":market(Object.assign({},makerMarket,{best_bid:0.38,best_ask:0.40,yes_price:0.39,no_price:0.61}))},{executeTrades:true});
|
manageMakerQuotes(repriceBook,{"maker-test":market(Object.assign({},makerMarket,{best_bid:0.38,best_ask:0.40,yes_price:0.39,no_price:0.61}))},{executeTrades:true});
|
||||||
const preRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
|
const preRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
|
||||||
const preRepriceIgnored=preRepriceTouch.fills===0&&repriceBook.positions.length===1&&repriceBook.maker_quotes.length===1;
|
const preRepriceIgnored=preRepriceTouch.fills===0&&repriceBook.positions.length===1&&repriceBook.maker_quotes.length===1;
|
||||||
repriceQuote.no_quote_updated_at=hoursAgo(1);
|
repriceQuote.no_quote_updated_at=hoursAgo(1);
|
||||||
const postRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
|
const postRepriceTouch=manageMakerQuotes(repriceBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{no:[{t:touchTimestamp,p:0.58}]}});
|
||||||
const unfilledBook=defaultPortfolio();stageMakerQuotes(unfilledBook,[makerMarket]);unfilledBook.maker_quotes[0].created_at=hoursAgo(25);
|
const unfilledBook=defaultPortfolio();stageMakerQuotes(unfilledBook,[makerMarket]);unfilledBook.maker_quotes[0].shadow_only=false;unfilledBook.maker_quotes[0].created_at=hoursAgo(25);
|
||||||
const unfilledExpiry=manageMakerQuotes(unfilledBook,{"maker-test":touchMarket},{executeTrades:true});
|
const unfilledExpiry=manageMakerQuotes(unfilledBook,{"maker-test":touchMarket},{executeTrades:true});
|
||||||
const profitableMakerBook=defaultPortfolio(),losingMakerBook=defaultPortfolio();
|
const profitableMakerBook=defaultPortfolio(),losingMakerBook=defaultPortfolio();
|
||||||
for(let i=0;i<6;i++){
|
for(let i=0;i<24;i++){
|
||||||
profitableMakerBook.maker_outcomes.push({quote_id:`profit-${i}`,market_id:`profit-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"locked",pnl:4,reserved_capital:100,strategy_version:SUGGESTION_ENGINE_VERSION});
|
profitableMakerBook.maker_outcomes.push({quote_id:`profit-${i}`,market_id:`profit-${i}`,event_key:`profit-event-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"locked",pnl:4,reserved_capital:100,shadow_only:true,strategy_version:SUGGESTION_ENGINE_VERSION});
|
||||||
losingMakerBook.maker_outcomes.push({quote_id:`loss-${i}`,market_id:`loss-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"single-exit",pnl:-4,reserved_capital:100,strategy_version:SUGGESTION_ENGINE_VERSION});
|
losingMakerBook.maker_outcomes.push({quote_id:`loss-${i}`,market_id:`loss-${i}`,event_key:`loss-event-${i}`,category:"Politics",spread:0.04,spread_band:"medium",status:"single-exit",pnl:-4,reserved_capital:100,shadow_only:true,strategy_version:SUGGESTION_ENGINE_VERSION});
|
||||||
}
|
}
|
||||||
const profitableCandidate=makerCandidateLearning(makerPairCandidate(makerMarket),buildMakerProfile(profitableMakerBook));
|
const profitableCandidate=makerCandidateLearning(makerPairCandidate(makerMarket),buildMakerProfile(profitableMakerBook));
|
||||||
let blockedMarket=null,explorationMarket=null;
|
let blockedMarket=null,explorationMarket=null;
|
||||||
@@ -5025,6 +5100,10 @@ function runEngineSelfTest(){
|
|||||||
if(lane>=20&&!blockedMarket)blockedMarket=candidate;if(lane<20&&!explorationMarket)explorationMarket=candidate;
|
if(lane>=20&&!blockedMarket)blockedMarket=candidate;if(lane<20&&!explorationMarket)explorationMarket=candidate;
|
||||||
}
|
}
|
||||||
const losingProfile=buildMakerProfile(losingMakerBook),blockedMakerCandidate=makerCandidateLearning(blockedMarket,losingProfile),explorationMakerCandidate=makerCandidateLearning(explorationMarket,losingProfile);
|
const losingProfile=buildMakerProfile(losingMakerBook),blockedMakerCandidate=makerCandidateLearning(blockedMarket,losingProfile),explorationMakerCandidate=makerCandidateLearning(explorationMarket,losingProfile);
|
||||||
|
const shadowBook=defaultPortfolio(),shadowStage=stageMakerQuotes(shadowBook,[makerMarket]),shadowQuote=shadowBook.maker_quotes[0];
|
||||||
|
shadowQuote.created_at=hoursAgo(4);shadowQuote.yes_quote_updated_at=shadowQuote.created_at;shadowQuote.no_quote_updated_at=shadowQuote.created_at;
|
||||||
|
const shadowTouchTimestamp=Math.floor((Date.now()-30*60000)/1000),shadowCashBefore=shadowBook.cash;
|
||||||
|
const shadowFinished=manageMakerQuotes(shadowBook,{"maker-test":touchMarket},{executeTrades:true,touchHistory:{yes:[{t:shadowTouchTimestamp,p:0.395}]}});
|
||||||
const makerDedupeBook=defaultPortfolio(),dedupeQuote=Object.assign({},makerQuote,{id:"dedupe-maker"});
|
const makerDedupeBook=defaultPortfolio(),dedupeQuote=Object.assign({},makerQuote,{id:"dedupe-maker"});
|
||||||
recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);
|
recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);recordMakerOutcome(makerDedupeBook,dedupeQuote,"unfilled",0,0);
|
||||||
const makerCompactionBook=defaultPortfolio();makerCompactionBook.maker_quotes=[Object.assign({},makerQuote)];makerCompactionBook.maker_outcomes=[...profitableMakerBook.maker_outcomes];
|
const makerCompactionBook=defaultPortfolio();makerCompactionBook.maker_quotes=[Object.assign({},makerQuote)];makerCompactionBook.maker_outcomes=[...profitableMakerBook.maker_outcomes];
|
||||||
@@ -5211,7 +5290,10 @@ function runEngineSelfTest(){
|
|||||||
adaptiveReturnSetsLeader:adaptiveRankFixture[0].c.id==="adaptive-leader",
|
adaptiveReturnSetsLeader:adaptiveRankFixture[0].c.id==="adaptive-leader",
|
||||||
},
|
},
|
||||||
makerLiquidity:{
|
makerLiquidity:{
|
||||||
stagesEligiblePairedQuote:makerStage.staged===1&&makerStage.active===1&&makerQuote.paired_cost===0.96,
|
stagesEligiblePairedQuoteAsShadow:makerStage.staged===1&&makerStage.shadowStaged===1&&makerStage.capitalStaged===0&&makerStage.active===1&&makerQuote.paired_cost===0.96,
|
||||||
|
unprovenQuoteDoesNotReserveCapital:makerStage.reserved===0,
|
||||||
|
untouchedLegacyQuotesAreCanceled:legacyUntouched.expired===1&&legacyUntouchedBook.maker_quotes.length===0&&legacyUntouchedBook.positions.length===0&&legacyUntouchedBook.cash===STARTING_BALANCE,
|
||||||
|
filledLegacyInventoryIsUnwoundHonestly:legacyOneLeg.expired===1&&legacyOneLegBook.maker_quotes.length===0&&legacyOneLegBook.positions.length===0&&legacyOneLegBook.cash<STARTING_BALANCE,
|
||||||
firstCrossCreatesOnlyOneInventoryLeg:makerFirst.fills===1&&makerSingleFill,
|
firstCrossCreatesOnlyOneInventoryLeg:makerFirst.fills===1&&makerSingleFill,
|
||||||
secondCrossLocksComplementaryPair:makerSecond.fills===1&&makerSecond.locked===1&&makerBook.maker_quotes.length===0,
|
secondCrossLocksComplementaryPair:makerSecond.fills===1&&makerSecond.locked===1&&makerBook.maker_quotes.length===0,
|
||||||
overlapCleanupPreservesLockedPair:makerPairSurvivesOverlap,
|
overlapCleanupPreservesLockedPair:makerPairSurvivesOverlap,
|
||||||
@@ -5225,11 +5307,13 @@ function runEngineSelfTest(){
|
|||||||
offlineHistoryCannotInventFill:offlineTouch.fills===0&&offlineTouchBook.positions.length===0&&offlineTouchBook.maker_quotes.length===1,
|
offlineHistoryCannotInventFill:offlineTouch.fills===0&&offlineTouchBook.positions.length===0&&offlineTouchBook.maker_quotes.length===1,
|
||||||
repricedQuoteRejectsEarlierTouch:preRepriceIgnored,
|
repricedQuoteRejectsEarlierTouch:preRepriceIgnored,
|
||||||
repricedQuoteAcceptsLaterTouch:postRepriceTouch.fills===1&&postRepriceTouch.locked===1&&repriceBook.maker_quotes.length===0,
|
repricedQuoteAcceptsLaterTouch:postRepriceTouch.fills===1&&postRepriceTouch.locked===1&&repriceBook.maker_quotes.length===0,
|
||||||
profitableCohortScalesWithinCap:profitableCandidate&&profitableCandidate.maker_learning_state==="promoted"&&profitableCandidate.units>makerQuote.units&&profitableCandidate.units<=makerQuote.units*1.5,
|
profitableCohortScalesWithinCap:profitableCandidate&&profitableCandidate.maker_learning_state==="promoted"&&profitableCandidate.maker_capital_enabled&&profitableCandidate.units>makerQuote.units&&profitableCandidate.units<=makerQuote.units*1.25,
|
||||||
losingCohortRotatesOut:blockedMakerCandidate===null,
|
losingCohortRemainsShadowOnly:blockedMakerCandidate&&blockedMakerCandidate.maker_learning_state==="shadow-rejected"&&!blockedMakerCandidate.maker_capital_enabled,
|
||||||
losingCohortKeepsBoundedExploration:explorationMakerCandidate&&explorationMakerCandidate.maker_learning_state==="exploration"&&explorationMakerCandidate.maker_learning_multiplier===1,
|
noRealMoneyExplorationLane:explorationMakerCandidate&&explorationMakerCandidate.maker_learning_state==="shadow-rejected"&&!explorationMakerCandidate.maker_capital_enabled,
|
||||||
|
shadowSingleTouchChangesNoCash:shadowFinished.shadowCompleted===1&&shadowBook.cash===shadowCashBefore&&shadowBook.positions.length===0&&shadowBook.maker_quotes.length===0,
|
||||||
|
shadowSingleTouchRecordsAdverseOutcome:shadowBook.maker_outcomes.length===1&&shadowBook.maker_outcomes[0].shadow_only&&shadowBook.maker_outcomes[0].status==="single-exit"&&shadowBook.maker_outcomes[0].pnl<0,
|
||||||
outcomesAreDeduplicated:makerDedupeBook.maker_outcomes.length===1,
|
outcomesAreDeduplicated:makerDedupeBook.maker_outcomes.length===1,
|
||||||
makerLearningSurvivesCompaction:compactedMakerBook.maker_quotes.length===1&&compactedMakerBook.maker_outcomes.length===6,
|
makerLearningSurvivesCompaction:compactedMakerBook.maker_quotes.length===1&&compactedMakerBook.maker_outcomes.length===24,
|
||||||
noHypotheticalRewardCredited:makerStage.staged===1&&Math.abs(makerBook.cash-(10000+makerExpectedProfit))<=0.02,
|
noHypotheticalRewardCredited:makerStage.staged===1&&Math.abs(makerBook.cash-(10000+makerExpectedProfit))<=0.02,
|
||||||
rejectsUnrewardedMarket:makerPairCandidate(Object.assign({},makerMarket,{rewards_daily_rate:0}))===null,
|
rejectsUnrewardedMarket:makerPairCandidate(Object.assign({},makerMarket,{rewards_daily_rate:0}))===null,
|
||||||
rejectsVolatileMarket:makerPairCandidate(Object.assign({},makerMarket,{price_change_1d:0.12}))===null,
|
rejectsVolatileMarket:makerPairCandidate(Object.assign({},makerMarket,{price_change_1d:0.12}))===null,
|
||||||
|
|||||||
@@ -5,6 +5,7 @@
|
|||||||
"evaluate:dominance": "node scripts/evaluate-dominance.mjs",
|
"evaluate:dominance": "node scripts/evaluate-dominance.mjs",
|
||||||
"evaluate:adaptive": "node scripts/evaluate-adaptive.mjs",
|
"evaluate:adaptive": "node scripts/evaluate-adaptive.mjs",
|
||||||
"evaluate:liquidity": "node scripts/evaluate-liquidity.mjs",
|
"evaluate:liquidity": "node scripts/evaluate-liquidity.mjs",
|
||||||
|
"evaluate:maker": "node scripts/evaluate-maker.mjs",
|
||||||
"evaluate:signals": "node scripts/evaluate-signals.mjs",
|
"evaluate:signals": "node scripts/evaluate-signals.mjs",
|
||||||
"evaluate:settlements": "node scripts/evaluate-settlements.mjs",
|
"evaluate:settlements": "node scripts/evaluate-settlements.mjs",
|
||||||
"test:server-state": "node scripts/test-server-state.mjs"
|
"test:server-state": "node scripts/test-server-state.mjs"
|
||||||
|
|||||||
@@ -0,0 +1,233 @@
|
|||||||
|
const GAMMA = "https://gamma-api.polymarket.com";
|
||||||
|
const CLOB = "https://clob.polymarket.com";
|
||||||
|
const MARKET_LIMIT = Math.max(100, Math.min(500, Number(process.env.MAKER_MARKETS || 300)));
|
||||||
|
const HISTORY_DAYS = Math.max(14, Math.min(30, Number(process.env.MAKER_HISTORY_DAYS || 30)));
|
||||||
|
const CONCURRENCY = Math.max(1, Math.min(8, Number(process.env.MAKER_CONCURRENCY || 4)));
|
||||||
|
const EXIT_COST = Math.max(0, Math.min(0.05, Number(process.env.MAKER_EXIT_COST_CENTS || 0.5) / 100));
|
||||||
|
const SUMMARY_ONLY = process.env.MAKER_SUMMARY === "1";
|
||||||
|
const HOUR = 3600;
|
||||||
|
|
||||||
|
function parseJson(value) {
|
||||||
|
if (Array.isArray(value)) return value;
|
||||||
|
try { return JSON.parse(value || "[]"); } catch { return []; }
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchJson(url, options = {}, attempts = 3) {
|
||||||
|
let lastError;
|
||||||
|
for (let attempt = 0; attempt < attempts; attempt++) {
|
||||||
|
try {
|
||||||
|
const response = await fetch(url, { ...options, signal: AbortSignal.timeout(30000),
|
||||||
|
headers: { accept: "application/json", ...(options.headers || {}) } });
|
||||||
|
if (response.ok) return response.json();
|
||||||
|
lastError = new Error(`${response.status} ${response.statusText}`);
|
||||||
|
if (response.status !== 429 && response.status < 500) break;
|
||||||
|
} catch (error) { lastError = error; }
|
||||||
|
await new Promise((resolve) => setTimeout(resolve, 600 * (attempt + 1)));
|
||||||
|
}
|
||||||
|
throw lastError || new Error("request failed");
|
||||||
|
}
|
||||||
|
|
||||||
|
async function mapLimit(items, limit, task) {
|
||||||
|
const output = new Array(items.length);
|
||||||
|
let cursor = 0;
|
||||||
|
async function worker() {
|
||||||
|
while (cursor < items.length) {
|
||||||
|
const index = cursor++;
|
||||||
|
try { output[index] = await task(items[index], index); }
|
||||||
|
catch (error) { output[index] = { error: error.message }; }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
await Promise.all(Array.from({ length: Math.min(limit, items.length) }, worker));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
function categoryOf(raw) {
|
||||||
|
const text = `${raw.question || ""} ${(raw.tags || []).map((tag) => tag.slug || tag.label || "").join(" ")}`.toLowerCase();
|
||||||
|
if (/\b(election|president|politic|senate|congress|parliament|minister|governor|government|nominee|primary)\b/.test(text)) return "Politics";
|
||||||
|
if (/\b(bitcoin|crypto|ethereum|btc|eth|solana|xrp|token|stablecoin)\b/.test(text)) return "Crypto";
|
||||||
|
if (/\b(nba|nfl|nhl|mlb|soccer|football|baseball|basketball|tennis|ufc|boxing|championship|match|game|tournament|league)\b/.test(text)) return "Sports";
|
||||||
|
if (/\b(fed|inflation|gdp|recession|stock|company|economy|tariff|interest rate|unemployment|earnings)\b/.test(text)) return "Economy";
|
||||||
|
return "Other";
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchMarkets(limit) {
|
||||||
|
const markets = [], seen = new Set();
|
||||||
|
for (let offset = 0; markets.length < limit && offset < limit * 5; offset += 100) {
|
||||||
|
const params = new URLSearchParams({ active: "true", closed: "false", archived: "false", include_tag: "true",
|
||||||
|
limit: "100", offset: String(offset), order: "volume24hr", ascending: "false" });
|
||||||
|
const page = await fetchJson(`${GAMMA}/markets?${params}`);
|
||||||
|
if (!Array.isArray(page) || !page.length) break;
|
||||||
|
for (const raw of page) {
|
||||||
|
const labels = parseJson(raw.outcomes).map((value) => String(value).trim().toLowerCase());
|
||||||
|
const tokens = parseJson(raw.clobTokenIds).map(String), id = String(raw.id || "");
|
||||||
|
const reward = (raw.clobRewards || []).reduce((sum, row) => sum + Number(row.rewardsDailyRate || 0), 0);
|
||||||
|
if (!id || seen.has(id) || labels[0] !== "yes" || labels[1] !== "no" || tokens.length !== 2 || reward < 1) continue;
|
||||||
|
seen.add(id);
|
||||||
|
markets.push({ id, tokens, category: categoryOf(raw), question: raw.question || "",
|
||||||
|
eventKey: String(raw.events?.[0]?.id || raw.events?.[0]?.slug || raw.eventId || id) });
|
||||||
|
if (markets.length >= limit) break;
|
||||||
|
}
|
||||||
|
if (page.length < 100) break;
|
||||||
|
}
|
||||||
|
return markets;
|
||||||
|
}
|
||||||
|
|
||||||
|
function chunks(items, size) {
|
||||||
|
const output = [];
|
||||||
|
for (let index = 0; index < items.length; index += size) output.push(items.slice(index, index + size));
|
||||||
|
return output;
|
||||||
|
}
|
||||||
|
|
||||||
|
async function fetchHistories(markets) {
|
||||||
|
const tokenRows = markets.flatMap((market) => market.tokens.map((token) => ({ token, marketId: market.id })));
|
||||||
|
const tokenChunks = chunks(tokenRows, 20);
|
||||||
|
const responses = await mapLimit(tokenChunks, CONCURRENCY, async (chunk) => fetchJson(`${CLOB}/batch-prices-history`, {
|
||||||
|
method: "POST", headers: { "content-type": "application/json" },
|
||||||
|
body: JSON.stringify({ markets: chunk.map((row) => row.token), interval: "1m", fidelity: 60 })
|
||||||
|
}));
|
||||||
|
const history = {};
|
||||||
|
responses.forEach((response) => {
|
||||||
|
if (response?.history) Object.entries(response.history).forEach(([token, points]) => {
|
||||||
|
history[token] = (points || []).map((point) => ({ t: Number(point.t), p: Number(point.p) }))
|
||||||
|
.filter((point) => Number.isFinite(point.t) && Number.isFinite(point.p)).sort((a, b) => a.t - b.t);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
return { history, failures: responses.filter((response) => response?.error).length,
|
||||||
|
failureMessages: [...new Set(responses.filter((response) => response?.error).map((response) => response.error))].slice(0, 5) };
|
||||||
|
}
|
||||||
|
|
||||||
|
function atOrBefore(points, target) {
|
||||||
|
let result = null;
|
||||||
|
for (const point of points) {
|
||||||
|
if (point.t > target) break;
|
||||||
|
result = point;
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
function observations(market, yesPoints, noPoints) {
|
||||||
|
const rows = [], seen = new Set();
|
||||||
|
for (const yes of yesPoints) {
|
||||||
|
const bucket = Math.floor(yes.t / (24 * HOUR));
|
||||||
|
if (seen.has(bucket)) continue;
|
||||||
|
const no = atOrBefore(noPoints, yes.t + HOUR);
|
||||||
|
if (!no || Math.abs(no.t - yes.t) > 2 * HOUR || yes.p < 0.08 || yes.p > 0.92) continue;
|
||||||
|
const prior = yesPoints.filter((point) => point.t >= yes.t - 24 * HOUR && point.t <= yes.t);
|
||||||
|
const futureYes = yesPoints.filter((point) => point.t > yes.t && point.t <= yes.t + 24 * HOUR);
|
||||||
|
const futureNo = noPoints.filter((point) => point.t > yes.t && point.t <= yes.t + 24 * HOUR);
|
||||||
|
if (prior.length < 12 || futureYes.length < 12 || futureNo.length < 12) continue;
|
||||||
|
const endYes = atOrBefore(yesPoints, yes.t + 24 * HOUR), endNo = atOrBefore(noPoints, yes.t + 24 * HOUR);
|
||||||
|
if (!endYes || !endNo || yes.t + 24 * HOUR - Math.min(endYes.t, endNo.t) > 2 * HOUR) continue;
|
||||||
|
seen.add(bucket);
|
||||||
|
const prices = prior.map((point) => point.p);
|
||||||
|
rows.push({ marketId: market.id, eventKey: market.eventKey, category: market.category, observedAt: yes.t,
|
||||||
|
yes: yes.p, no: no.p, priorRange: Math.max(...prices) - Math.min(...prices), futureYes, futureNo,
|
||||||
|
endYes: endYes.p, endNo: endNo.p });
|
||||||
|
}
|
||||||
|
return rows;
|
||||||
|
}
|
||||||
|
|
||||||
|
function simulate(row, rule) {
|
||||||
|
if (row.priorRange > rule.maxPriorRange || (rule.category !== "All" && row.category !== rule.category)) return null;
|
||||||
|
if (rule.band === "mid" && (row.yes < 0.2 || row.yes > 0.8)) return null;
|
||||||
|
if (rule.band === "tails" && row.yes >= 0.2 && row.yes <= 0.8) return null;
|
||||||
|
const yesQuote = row.yes - rule.gap, noQuote = row.no - rule.gap, reserved = yesQuote + noQuote;
|
||||||
|
if (yesQuote < 0.02 || noQuote < 0.02 || reserved >= 0.995) return null;
|
||||||
|
const horizonEnd = row.observedAt + rule.horizon * HOUR;
|
||||||
|
const futureYes = row.futureYes.filter((point) => point.t <= horizonEnd), futureNo = row.futureNo.filter((point) => point.t <= horizonEnd);
|
||||||
|
const endYes = atOrBefore(futureYes, horizonEnd), endNo = atOrBefore(futureNo, horizonEnd);
|
||||||
|
if (!endYes || !endNo || horizonEnd - Math.min(endYes.t, endNo.t) > 2 * HOUR) return null;
|
||||||
|
const yesFill = futureYes.some((point) => point.p <= yesQuote), noFill = futureNo.some((point) => point.p <= noQuote);
|
||||||
|
let pnl = 0, status = "unfilled";
|
||||||
|
if (yesFill && noFill) { pnl = 1 - reserved; status = "locked"; }
|
||||||
|
else if (yesFill) { pnl = endYes.p - yesQuote - EXIT_COST; status = "single-exit"; }
|
||||||
|
else if (noFill) { pnl = endNo.p - noQuote - EXIT_COST; status = "single-exit"; }
|
||||||
|
return { marketId: row.marketId, eventKey: row.eventKey, observedAt: row.observedAt, pnl,
|
||||||
|
netReturn: pnl / reserved, status };
|
||||||
|
}
|
||||||
|
|
||||||
|
function summary(rows) {
|
||||||
|
if (!rows.length) return { attempts: 0, markets: 0, events: 0, pnl: 0, mean: 0, eventMean: 0, lower: 0, upper: 0, lockedRate: 0, adverseRate: 0 };
|
||||||
|
const events = new Map();
|
||||||
|
rows.forEach((row) => {
|
||||||
|
const values = events.get(row.eventKey) || [];
|
||||||
|
values.push(row.netReturn); events.set(row.eventKey, values);
|
||||||
|
});
|
||||||
|
const eventReturns = [...events.values()].map((values) => values.reduce((sum, value) => sum + value, 0) / values.length);
|
||||||
|
const eventMean = eventReturns.reduce((sum, value) => sum + value, 0) / eventReturns.length;
|
||||||
|
const variance = eventReturns.length > 1 ? eventReturns.reduce((sum, value) => sum + (value - eventMean) ** 2, 0) / (eventReturns.length - 1) : 0;
|
||||||
|
const margin = 1.645 * Math.sqrt(variance / Math.max(1, eventReturns.length));
|
||||||
|
return { attempts: rows.length, markets: new Set(rows.map((row) => row.marketId)).size, events: eventReturns.length,
|
||||||
|
pnl: rows.reduce((sum, row) => sum + row.pnl, 0), mean: rows.reduce((sum, row) => sum + row.netReturn, 0) / rows.length,
|
||||||
|
eventMean, lower: eventMean - margin, upper: eventMean + margin,
|
||||||
|
lockedRate: rows.filter((row) => row.status === "locked").length / rows.length,
|
||||||
|
adverseRate: rows.filter((row) => row.status === "single-exit").length / rows.length };
|
||||||
|
}
|
||||||
|
|
||||||
|
const rules = [];
|
||||||
|
for (const horizon of [3, 6, 12, 24]) {
|
||||||
|
for (const gap of [0.005, 0.01, 0.015, 0.02, 0.025, 0.03, 0.04]) {
|
||||||
|
for (const maxPriorRange of [0.02, 0.04, 0.06, 0.08, 0.12, 0.2]) {
|
||||||
|
for (const band of ["all", "mid", "tails"]) {
|
||||||
|
for (const category of ["All", "Politics", "Sports", "Crypto", "Economy", "Other"])
|
||||||
|
rules.push({ id: `h${horizon}_gap${gap}_range${maxPriorRange}_${band}_${category}`, horizon, gap, maxPriorRange, band, category });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
const markets = await fetchMarkets(MARKET_LIMIT), fetched = await fetchHistories(markets);
|
||||||
|
const rows = markets.flatMap((market) => observations(market, fetched.history[market.tokens[0]] || [], fetched.history[market.tokens[1]] || []));
|
||||||
|
const timestamps = rows.map((row) => row.observedAt).sort((a, b) => a - b), trainCut = timestamps[Math.floor(timestamps.length * 0.6)] || 0,
|
||||||
|
validationCut = timestamps[Math.floor(timestamps.length * 0.8)] || 0;
|
||||||
|
const partitions = {
|
||||||
|
train: rows.filter((row) => row.observedAt < trainCut && row.observedAt + 24 * HOUR < trainCut),
|
||||||
|
validation: rows.filter((row) => row.observedAt >= trainCut && row.observedAt + 24 * HOUR < validationCut),
|
||||||
|
holdout: rows.filter((row) => row.observedAt >= validationCut)
|
||||||
|
};
|
||||||
|
const evaluated = rules.map((rule) => {
|
||||||
|
const train = summary(partitions.train.map((row) => simulate(row, rule)).filter(Boolean));
|
||||||
|
const validation = summary(partitions.validation.map((row) => simulate(row, rule)).filter(Boolean));
|
||||||
|
const holdout = summary(partitions.holdout.map((row) => simulate(row, rule)).filter(Boolean));
|
||||||
|
const trainPassed = train.attempts >= 80 && train.events >= 15 && train.lower > 0;
|
||||||
|
const validationPassed = trainPassed && validation.attempts >= 35 && validation.events >= 8 && validation.lower > 0;
|
||||||
|
return { rule, train, validation, holdout, trainPassed, validationPassed,
|
||||||
|
passesHoldout: validationPassed && holdout.attempts >= 35 && holdout.events >= 8 && holdout.lower > 0 };
|
||||||
|
});
|
||||||
|
const candidates = evaluated.filter((candidate) => candidate.validationPassed);
|
||||||
|
|
||||||
|
const compact = (stats) => Object.fromEntries(Object.entries(stats).map(([key, value]) => [key, Number.isFinite(value) ? +value.toFixed(5) : value]));
|
||||||
|
candidates.sort((a, b) => Number(b.passesHoldout) - Number(a.passesHoldout) || b.holdout.lower - a.holdout.lower);
|
||||||
|
const nearMisses = evaluated.filter((candidate) => candidate.train.attempts >= 80 && candidate.validation.attempts >= 35 && candidate.holdout.attempts >= 35)
|
||||||
|
.sort((a, b) => Math.min(b.train.lower, b.validation.lower) - Math.min(a.train.lower, a.validation.lower)).slice(0, 15);
|
||||||
|
const broadByGap = evaluated.filter((candidate) => candidate.rule.maxPriorRange === 0.2 && candidate.rule.band === "all" && candidate.rule.category === "All");
|
||||||
|
const report = { generatedAt: new Date().toISOString(), requestedMarkets: MARKET_LIMIT, rewardMarkets: markets.length,
|
||||||
|
marketsWithBothHistories: markets.filter((market) => fetched.history[market.tokens[0]]?.length && fetched.history[market.tokens[1]]?.length).length,
|
||||||
|
batchFailures: fetched.failures, batchFailureMessages: fetched.failureMessages, observations: rows.length, testedRules: rules.length,
|
||||||
|
methodology: { historyDays: HISTORY_DAYS, quoteHorizonHours: [3, 6, 12, 24], observationSpacingHours: 24, exitCostCents: EXIT_COST * 100,
|
||||||
|
split: "60% train / 20% validation / 20% untouched holdout", clusterUnit: "Polymarket event",
|
||||||
|
fillProxy: "public CLOB token price touched the resting bid after placement", noFillPnl: 0 },
|
||||||
|
partitionRows: Object.fromEntries(Object.entries(partitions).map(([key, value]) => [key, value.length])),
|
||||||
|
trainPassed: evaluated.filter((candidate) => candidate.trainPassed).length, validationSelected: candidates.length,
|
||||||
|
holdoutPassed: candidates.filter((candidate) => candidate.passesHoldout).length,
|
||||||
|
broadByGap: broadByGap.map((candidate) => ({ horizon: candidate.rule.horizon, gap: candidate.rule.gap, train: compact(candidate.train), validation: compact(candidate.validation), holdout: compact(candidate.holdout) })),
|
||||||
|
nearMisses: nearMisses.map((candidate) => ({ rule: candidate.rule, train: compact(candidate.train), validation: compact(candidate.validation), holdout: compact(candidate.holdout) })),
|
||||||
|
candidates: candidates.slice(0, 30).map((candidate) => ({ rule: candidate.rule, passesHoldout: candidate.passesHoldout,
|
||||||
|
train: compact(candidate.train), validation: compact(candidate.validation), holdout: compact(candidate.holdout) })) };
|
||||||
|
if (SUMMARY_ONLY) {
|
||||||
|
const summarizeCandidate = (candidate) => ({ rule: candidate.rule, passesHoldout: candidate.passesHoldout,
|
||||||
|
train: { attempts: candidate.train.attempts, events: candidate.train.events, lower: +candidate.train.lower.toFixed(5), eventMean: +candidate.train.eventMean.toFixed(5) },
|
||||||
|
validation: { attempts: candidate.validation.attempts, events: candidate.validation.events, lower: +candidate.validation.lower.toFixed(5), eventMean: +candidate.validation.eventMean.toFixed(5) },
|
||||||
|
holdout: { attempts: candidate.holdout.attempts, events: candidate.holdout.events, lower: +candidate.holdout.lower.toFixed(5), eventMean: +candidate.holdout.eventMean.toFixed(5), lockedRate: +candidate.holdout.lockedRate.toFixed(4), adverseRate: +candidate.holdout.adverseRate.toFixed(4) } });
|
||||||
|
console.log(JSON.stringify({ generatedAt: report.generatedAt, requestedMarkets: report.requestedMarkets,
|
||||||
|
rewardMarkets: report.rewardMarkets, marketsWithBothHistories: report.marketsWithBothHistories,
|
||||||
|
batchFailures: report.batchFailures, observations: report.observations, testedRules: report.testedRules,
|
||||||
|
partitionRows: report.partitionRows, trainPassed: report.trainPassed, validationSelected: report.validationSelected,
|
||||||
|
holdoutPassed: report.holdoutPassed,
|
||||||
|
broadByGap: broadByGap.map((candidate) => ({ horizon: candidate.rule.horizon, gap: candidate.rule.gap,
|
||||||
|
trainLower: +candidate.train.lower.toFixed(5), validationLower: +candidate.validation.lower.toFixed(5),
|
||||||
|
holdoutMean: +candidate.holdout.eventMean.toFixed(5), holdoutLower: +candidate.holdout.lower.toFixed(5),
|
||||||
|
holdoutLockedRate: +candidate.holdout.lockedRate.toFixed(4), holdoutAdverseRate: +candidate.holdout.adverseRate.toFixed(4) })),
|
||||||
|
nearMisses: nearMisses.slice(0, 5).map(summarizeCandidate),
|
||||||
|
candidates: candidates.slice(0, 10).map(summarizeCandidate) }, null, 2));
|
||||||
|
} else console.log(JSON.stringify(report, null, 2));
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
const CACHE_NAME = "polymarket-arena-build-64";
|
const CACHE_NAME = "polymarket-arena-build-65";
|
||||||
const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"];
|
const APP_SHELL = ["/", "/index.html", "/personal.html", "/cycle-worker.js"];
|
||||||
|
|
||||||
self.addEventListener("install", event => {
|
self.addEventListener("install", event => {
|
||||||
|
|||||||
Reference in New Issue
Block a user