Skip to content

Execution

From an edge to a recorded trade, to a settled one: signals, the paper broker, the live broker, settlement and re-settlement.

EdgeSignal dataclass

EdgeSignal(
    market_id: str,
    city_or_segment: str,
    target_date: str,
    horizon: str,
    outcome_label: str,
    direction: str,
    gamma_price: float,
    model_prob: float,
    best_edge: float,
    yes_edge: float,
    no_edge: float,
    question: str = "",
)

A trading signal with computed edge metrics ready for paper-broker ingestion.

market_id instance-attribute

market_id: str

city_or_segment instance-attribute

city_or_segment: str

target_date instance-attribute

target_date: str

horizon instance-attribute

horizon: str

outcome_label instance-attribute

outcome_label: str

direction instance-attribute

direction: str

gamma_price instance-attribute

gamma_price: float

model_prob instance-attribute

model_prob: float

best_edge instance-attribute

best_edge: float

yes_edge instance-attribute

yes_edge: float

no_edge instance-attribute

no_edge: float

question class-attribute instance-attribute

question: str = ''

from_quote classmethod

from_quote(
    *,
    market_id: str,
    city_or_segment: str,
    target_date: str,
    horizon: str,
    outcome_label: str,
    yes_price: float,
    model_prob: float,
    costs: CostModel,
    stake: float,
    directions: tuple[str, ...] = ("yes", "no"),
    question: str = "",
) -> EdgeSignal | None

The signal for one outcome label, edges computed as the backtest computes them.

pmlab.core.edge.quote_edges is the one definition of the after-fee edge of each side; the backtest's live policy calls it too, so a plugin's scan-edge step that builds its signals here trades the strategy the champion was promoted on.

Parameters:

Name Type Description Default
yes_price float

The label's YES price (recorded as gamma_price, whichever side is bought).

required
model_prob float

The probability the label occurs (recorded as model_prob, whichever side is bought; calibration scores it against the label).

required
costs CostModel

The champion's cost model.

required
stake float

The flat stake the broker will trade.

required
directions tuple[str, ...]

The sides the strategy may take.

('yes', 'no')

Returns:

Type Description
EdgeSignal | None

The signal, or None if no allowed side can be bought (a price of 0, or a

EdgeSignal | None

fill pushed to certainty). best_edge can still be negative; deciding

EdgeSignal | None

whether it is worth a trade is the caller's min_edge.

PaperBroker

PaperBroker(
    trades_path: Path,
    allowed_segments: set[str] | None = None,
    flat_stake: float = 1.0,
    taker_bps: float | None = None,
    costs: CostModel | None = None,
)

Records paper trades from EdgeSignals with dedup, staleness, and segment filtering.

Record paper trades from EdgeSignals.

Parameters:

Name Type Description Default
trades_path Path

JSON file the trade log is appended to.

required
allowed_segments set[str] | None

Only record signals in these segments (from the champion's passing gate segments). None means no filtering, and so does "all" on its own: a champion promoted on an aggregate gate makes no per-segment claim (see pmlab.core.segments); beside other names it is a segment called "all". An empty set records nothing.

None
flat_stake float

Notional USDC per trade.

1.0
taker_bps float | None

Legacy fee-only knob. Mutually exclusive with costs.

None
costs CostModel | None

Full CostModel. Use the SAME model the backtest was run with, otherwise live and backtest PnL are not comparable and a drift alarm cannot tell the market from our own arithmetic. Omit both for the historical default (30bps, fill at the quote).

None

Raises:

Type Description
ValueError

If both taker_bps and costs are supplied.

trades_path instance-attribute

trades_path = trades_path

allowed_segments instance-attribute

allowed_segments = allowed_segments

flat_stake instance-attribute

flat_stake = flat_stake

costs instance-attribute

costs = (
    costs
    if costs is not None
    else CostModel(
        taker_bps=30.0 if taker_bps is None else taker_bps
    )
)

taker_bps instance-attribute

taker_bps = self.costs.taker_bps

record

record(
    signals: list[EdgeSignal],
    now_utc: datetime | None = None,
    city_timezones: dict[str, str] | None = None,
) -> list[dict[str, Any]]

Process signals and append non-stale, non-duplicate trades.

Parameters:

Name Type Description Default
signals list[EdgeSignal]

List of EdgeSignal objects to process.

required
now_utc datetime | None

Current time in UTC (injected for testing; defaults to now).

None
city_timezones dict[str, str] | None

Mapping of city_or_segment -> tz string (defaults to UTC).

None

Returns:

Type Description
list[dict[str, Any]]

List of newly added trade dicts.

load_trades

load_trades() -> list[dict[str, Any]]

Load existing trades from trades_path, or return empty list.

SettlementEngine

SettlementEngine(plugin: MarketPlugin, trades_path: Path)

Settles open paper trades using plugin truth resolution.

plugin instance-attribute

plugin = plugin

trades_path instance-attribute

trades_path = trades_path

settle_all

settle_all(
    specs: list[MarketSpec],
    today_str: str | None = None,
    now_utc: datetime | None = None,
) -> dict[str, Any]

Settle all unsettled trades that have a final truth available.

Each trade it settles is stamped with settled_at (UTC): when its PnL became known, which is what lets the watchdog replay an earlier --as-of without seeing truth that had not arrived yet.

Parameters:

Name Type Description Default
specs list[MarketSpec]

The markets to resolve against.

required
today_str str | None

YYYY-MM-DD of "today"; trades dated after it stay open.

None
now_utc datetime | None

The settlement time to stamp (injected for testing; defaults to now). Must be timezone-aware.

None

Raises:

Type Description
ValueError

If now_utc is naive.

resettle_trades

resettle_trades(
    trades: Sequence[Mapping[str, Any]],
    *,
    now_utc: datetime,
) -> ResettleResult

Recompute the PnL of settled trades booked before the current settlement version.

Parameters:

Name Type Description Default
trades Sequence[Mapping[str, Any]]

The log's trades. Not modified.

required
now_utc datetime

When the re-settlement happens, stamped as resettled_at on each trade it recomputes. Must be timezone-aware.

required

Returns:

Type Description
ResettleResult

The log with those trades recomputed and stamped with settlement_version, and the

ResettleResult

counts and per-trade changes that say what happened.

ResettleResult dataclass

ResettleResult(
    trades: list[dict[str, Any]],
    changes: list[Change] = list(),
    n_resettled: int = 0,
    n_changed: int = 0,
    n_open: int = 0,
    n_current: int = 0,
    n_unrecomputable: int = 0,
    unrecomputable: list[str] = list(),
    total_before: float = 0.0,
    total_after: float = 0.0,
)

The re-settled log, and an account of what was and was not done to it.

trades instance-attribute

trades: list[dict[str, Any]]

changes class-attribute instance-attribute

changes: list[Change] = field(default_factory=list)

n_resettled class-attribute instance-attribute

n_resettled: int = 0

n_changed class-attribute instance-attribute

n_changed: int = 0

n_open class-attribute instance-attribute

n_open: int = 0

n_current class-attribute instance-attribute

n_current: int = 0

n_unrecomputable class-attribute instance-attribute

n_unrecomputable: int = 0

unrecomputable class-attribute instance-attribute

unrecomputable: list[str] = field(default_factory=list)

total_before class-attribute instance-attribute

total_before: float = 0.0

total_after class-attribute instance-attribute

total_after: float = 0.0

LiveBroker

LiveBroker(
    api_key: str,
    api_secret: str,
    api_passphrase: str,
    private_key: str = "",
    chain_id: int = 137,
    signature_type: int = 0,
    funder_address: str | None = None,
    base_url: str = CLOB_API_BASE,
    dry_run: bool = False,
)

Real-money order execution via the Polymarket CLOB API.

Requires
  • private_key: L1 ECDSA private key (0x-prefixed hex string)
  • api_key / api_secret / api_passphrase: L2 API credentials

In dry_run mode, no requests are made to the API.

api_key instance-attribute

api_key = api_key

api_secret instance-attribute

api_secret = api_secret

api_passphrase instance-attribute

api_passphrase = api_passphrase

private_key instance-attribute

private_key = private_key

chain_id instance-attribute

chain_id = chain_id

signature_type instance-attribute

signature_type = signature_type

funder_address instance-attribute

funder_address = funder_address

base_url instance-attribute

base_url = base_url

dry_run instance-attribute

dry_run = dry_run

place_order

place_order(
    token_id: str,
    side: str,
    price: float,
    size: float,
    order_type: str = "GTC",
) -> OrderReceipt

cancel_order

cancel_order(order_id: str) -> dict[str, Any]

cancel_all_orders

cancel_all_orders() -> dict[str, Any]

get_open_orders

get_open_orders() -> list[dict[str, Any]]

get_balance

get_balance() -> float

preflight

preflight() -> dict[str, Any]

Run a health check against the CLOB API. Returns status dict.

LiveBrokerError

Bases: Exception

A live order could not be placed or confirmed (bad credentials, rejected order, API error).

OrderReceipt dataclass

OrderReceipt(
    order_id: str,
    token_id: str,
    side: str,
    price: float,
    size: float,
    status: str,
    placed_at: str = (
        lambda: datetime.now(UTC).isoformat()
    )(),
)

order_id instance-attribute

order_id: str

token_id instance-attribute

token_id: str

side instance-attribute

side: str

price instance-attribute

price: float

size instance-attribute

size: float

status instance-attribute

status: str

placed_at class-attribute instance-attribute

placed_at: str = field(
    default_factory=lambda: datetime.now(UTC).isoformat()
)