DEV Community

ABROWNFOX001
ABROWNFOX001

Posted on

The Signal Contract for a 5-Minute TWAP Market

Polymarket: https://polymarket.com/@abrownfox001?tab=activity

GitHub: https://github.com/abrownfox0/abrownfox001-twap60-prediction-trigger-system

Telegram: https://t.me/abrownfox001

Most 5-minute bots fail here. They have a “direction model,” then treat the output like a trade. On a TWAP-settled binary, that is a category error.

What P(up) is allowed to mean

In this engine, P(up) is only one thing:

The probability that the official end-of-slot TWAP finishes above the frozen slot-open reference.

Not:

  • “BTC looks bullish”
  • “Binance just printed a higher mid”
  • “the CLOB Up token is 54¢ so Up is winning”
  • “momentum is positive on a 15-second EMA”

Those can be inputs. They are not the target.

The target is the settlement indicator:

[
Y = \mathbf{1}{\text{TWAP}_{\text{end}} > \text{open_ref}}
]

[
P(\text{up}) = P(Y=1 \mid \text{state}_t)
]

If the model is not estimating that indicator, it does not belong in src/engine.ts.

The state the signal may see

src/signal.ts gets a narrow state object.

Allowed:

  • frozen open_ref
  • latest official TWAP + age
  • seconds elapsed in the 300s slot
  • optional CEX lead-lag residual
  • previous accepted P(up) (for decay, not for momentum theater)

Not allowed:

  • last CLOB trade as a proxy for settlement
  • depth on one side as “proof” of direction
  • unresolved PnL on the current ticket
  • another wallet’s latest fill

The model should not be allowed to peek at whether we are already long. That creates hold-bias. Conviction decay has to come from the world state, not from the fact that the bot already bought.

Edge is not “P(up) > 0.50”

A coin-flip book sits near 50¢ because short-horizon BTC direction is noisy.

The live rule is closer to:

edge = P(up) - book_up
enter only if |edge| >= threshold
and twap is fresh
and slot time is inside the tradable window
and clip size still respects the cap
Enter fullscreen mode Exit fullscreen mode

P(up) = 0.53 against a 53¢ ask is not a trade.

P(up) = 0.53 against a 47¢ ask might be.

The signal answers fair probability.

The engine answers whether the book is paying you to express it.

Those are different functions. Mixing them is how people start chasing 58–62¢ “favorites” and destroy the 50¢ economics from Part 1.

Time-in-slot changes the meaning of the same number

A 56% read at t=20s and a 56% read at t=280s are not the same object.

Early in the slot:

  • TWAP has moved little
  • one impulse can still flip the 30s window
  • 50¢ liquidity is usually available
  • the right action is often wait or take a small starter

Late in the slot:

  • the official window is already committing
  • book prices usually separate from 50¢
  • scratch cost rises if you are wrong
  • a weak 56% is often “do nothing”

So the signal contract includes a clock.

if t < t_min: allow only starter size
if t_min <= t <= t_max: full clip allowed
if t > t_max: no new entries; only hold / scratch / redeem
Enter fullscreen mode Exit fullscreen mode

The model can still publish P(up) after t_max.

The engine is not required to obey it.

Lead-lag is a feature, not a second oracle

src/cexFeed.ts exists because BTC often moves on CEX first.

That does not give CEX the right to decide the binary.

The only legal use is residual information:

[
r_t = \text{CEX_mid}_t - \text{TWAP}_t
]

If CEX is already through a move that TWAP has not absorbed, r_t may justify a small update to P(up).

If CEX and TWAP disagree violently and the TWAP feed is stale, the correct output is not a heroic directional call. It is untrusted.

untrusted is a first-class signal state.

Decay is part of the signal, not a separate trading idea

Public activity on this account is not “buy and pray.”

Buys cluster earlier. Sells cluster later. Many same-market round trips sit near a zero spread.

That is the signal being allowed to change its mind.

Decay rule I want in the contract:

  • If P(up) crosses back through 0.50 and stays there, flatten
  • If |P(up) - 0.50| compresses under the hold threshold, flatten
  • If TWAP age blows the freshness gate, stop adding; flatten only if the position was already low-conviction
  • Do not scratch just because the book moved against you

Scratch is for lost belief, not for mark-to-market pain.

If you flatten every time the token goes from 51¢ to 48¢, you are not running a directional engine. You are running a jittery mean-reversion exit on noise.

Calibration beats raw accuracy

A model that says 70% and wins 55% is worse than a model that says 58% and wins 58%.

On a 50¢ book, miscalibration is fatal because size and hold/scratch decisions depend on the number, not just the sign.

Minimum calibration contract:

  • track P(up) buckets vs realized TWAP outcomes
  • refuse extreme outputs unless the slot is nearly over
  • cap published P(up) away from 0.01 / 0.99 early in the slot
  • log resolution_src so you do not score the model against the wrong close

If you train on snapshot close and trade TWAP, you will think you have edge that the venue will not pay.

What the signal is forbidden to do

This is the actual contract.

src/signal.ts may not:

  1. Place orders
  2. Choose clip size
  3. Decide scratch vs hold by itself
  4. Read the bot’s current inventory
  5. Use CLOB mid as settlement truth
  6. Output a trade when the TWAP state is stale
  7. Invent an open_ref because slot join was late
  8. Keep a high-confidence value through a reconnect gap

It outputs one of:

type SignalOut = {
  pUp: number;
  side: "up" | "down" | "none";
  trusted: boolean;
  reason: string;
};
Enter fullscreen mode Exit fullscreen mode

side = none and trusted = false are successful outputs.

Silence is not a crash. It is the system working.

Why the weights stay private

The public repo documents the loop, not the exact mapping from TWAP gap → P(up).

That is intentional.

If the edge is a calibrated short-horizon read, publishing the weights donates the only scarce part. The feed, clock, and scratch policy are not scarce. They are just easy to implement badly.

What is public, and checkable, is the behavior:

  • BTC 5m as core
  • entries clustered near 50¢
  • later sells than buys
  • lots of flat scratches
  • no 99¢ farm as the main book

That is enough to audit the contract without cloning the model.

Handoff to Part 4

Once P(up) is a calibrated probability with a trust flag, execution gets simple and strict:

  • no trusted signal → no order
  • trusted signal but no 45–55¢ liquidity → no order
  • trusted signal and fair ask → small taker clip
  • later decay → scratch
  • no decay → hold to redeem

Part 4 is that execution layer: CLOB v2 fills, rate-limit budget, clip sizing, and intra-slot timing.

Not financial advice. Paper or micro-size first.

If you have more questions, please feel free to contact me at any time: https://t.me/abrownfox001

My Polymarket Activity: https://polymarket.com/@abrownfox001?tab=activity

#Polymarket #TradingBot #TWAP #SignalProcessing #BTC #AlgoTrading #PredictionMarkets #QuantTrading
Enter fullscreen mode Exit fullscreen mode

Top comments (0)