Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
26 changes: 26 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,32 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0

## [Unreleased]

### Added

- **Polymarket section** — prediction markets alongside the Binance side:
- Restored `PolymarketVenue` (CLOB market data + wallet-signed live orders
via `py-clob-client`) with per-venue encrypted credentials
(`private_key`, `api_key`, `api_secret`, `passphrase`, `wallet_address`).
- **Per-strategy venue routing**: strategies (and AI signals) carry a
`venue`, so Binance and Polymarket strategies run side by side; Testnet
mode falls back to Sim for venues without a sandbox. Symbol columns
widened to 80 chars for outcome-token ids (migrations 0022–0023).
- **Market discovery**: scheduled Gamma-API refresh into a local catalogue
with a watchlist; new Polymarket page to browse, search and pin markets.
- **AI bet analysis**: the report-writer LLM estimates each market's true
probability from question-matched news headlines and market context; the
edge (estimate − price) is stored per analysis and shown on the page.
- **Suggestions & alerts**: a scheduled screener analyses the watchlist +
top-volume markets; analyses clearing the configurable edge/confidence
bars become pending AI signals (Telegram + dashboard) that execute
through the existing risk-gated confirmation path.
- **Prediction AI strategy**: a pickable, Polymarket-pinned strategy type
that trades one outcome token on the stored analyses with notify/auto
action modes, allocation, risk and loss-cap enforcement.
- **Resolution settlement**: watched/held markets are re-checked hourly;
on resolution, held tokens settle at 1/0 USDC into the ledger and the
operator is alerted.

### Changed

- Adopted the [Dev-Template](https://github.com/FurkanEdizkan/Dev-Template)
Expand Down
15 changes: 10 additions & 5 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

# Capital

**Self-hosted automated trading platform for Binance.**
**Self-hosted automated trading platform for Binance and Polymarket.**

[![CI](https://github.com/FurkanEdizkan/Capital/actions/workflows/ci.yml/badge.svg)](https://github.com/FurkanEdizkan/Capital/actions/workflows/ci.yml)
[![License: Apache 2.0](https://img.shields.io/badge/license-Apache%202.0-blue.svg)](LICENSE)
Expand Down Expand Up @@ -34,8 +34,12 @@ explicitly opt in to Testnet or live trading behind safeguards.
net of fees, and money math uses `Decimal` throughout.
- **Capital allocation** — assign a budget per strategy; the engine enforces it.
- **Venue-pluggable** — a stable `Venue` interface keeps the engine
venue-agnostic. Binance is the first venue; others are re-added on top of a
solid base.
venue-agnostic. Binance and Polymarket ship today; each strategy carries
its own venue, so strategies on both run side by side.
- **Prediction markets** — browse Polymarket's most-traded markets, pin a
watchlist, and let the AI estimate each bet's true probability from news
and market context. Enough edge becomes a suggestion (Telegram + dashboard)
or an automated Prediction AI trade; resolutions settle into the ledger.
- **AI strategies** — LLM-driven strategies with per-strategy model selection,
daily spend caps, and a per-model performance rollup.
- **Roles & audit** — JWT login with `admin` / `user` roles; config changes are
Expand Down Expand Up @@ -121,6 +125,7 @@ Placing orders on Testnet or Live needs venue credentials, entered (encrypted)
through the Settings page.

- [docs/venues/binance-setup.md](docs/venues/binance-setup.md) — Binance (crypto)
- [docs/venues/polymarket-setup.md](docs/venues/polymarket-setup.md) — Polymarket (prediction markets)
- [docs/venues/api-features.md](docs/venues/api-features.md) — what Binance offers vs. what Capital uses

## Deployment
Expand Down Expand Up @@ -185,7 +190,7 @@ Capital is designed to be navigable and contributable by AI agents:
- [Releases](docs/releases.md)
- [Development setup](docs/development.md) — manual setup, project structure
- [Operations](docs/operations/) — deployment, backup & restore
- [Venues](docs/venues/) — Binance setup + design
- [Venues](docs/venues/) — Binance & Polymarket setup + design
- [Contributing](CONTRIBUTING.md) — dev setup and PR rules
- [Security policy](SECURITY.md)
- [Agent guide](AGENTS.md)
Expand All @@ -202,7 +207,7 @@ Capital is designed to be navigable and contributable by AI agents:
| 5 | Live trading (Testnet → real) | Done |
| 6 | 24/7 hardening, resilience, deployment | Done |
| 7 | AI strategies + agent/MCP integration | Done |
| 8 | Multi-venue expansion (stocks, Polymarket) | Deferred |
| 8 | Multi-venue expansion Polymarket + AI bets | In progress |

## License

Expand Down
6 changes: 4 additions & 2 deletions docs/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ Quick navigation. For the project itself see the [main README](../README.md).

## Venues

Capital currently supports Binance only; the `Venue` abstraction is preserved
so others can be re-added on top of a solid Binance base.
Capital supports Binance (crypto) and Polymarket (prediction markets). Each
strategy carries its own venue, so strategies on different venues run side by
side in the same engine loop.

- [Binance setup](venues/binance-setup.md) — crypto
- [Polymarket setup](venues/polymarket-setup.md) — prediction markets, AI bet analysis
- [Venue abstraction](venues/abstraction.md) — the common interface
- [Venue API features](venues/api-features.md) — what Binance offers vs. what Capital uses
- [Venue research](venues/research.md) — original multi-venue survey (audit trail)
16 changes: 13 additions & 3 deletions docs/venues/abstraction.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,22 @@ The [`Venue`](../../engine/venues/base.py) interface is how Capital stays
venue-pluggable: every trading venue implements one contract, and the rest of
the engine never learns which venue it is talking to.

This document is the abstraction's design (issue #46). Capital currently ships
**Binance only**; the interface is preserved so additional venues can be
re-added later as one new implementation file plus a registry entry. See
This document is the abstraction's design (issue #46). Capital ships
**Binance** (crypto) and **Polymarket** (prediction markets); additional
venues are one new implementation file plus a registry entry. See
[research.md](research.md) for the original multi-venue survey that informed
the abstraction's shape.

## Per-strategy venue routing

Venue routing is **per strategy**, not a global switch: every strategy (and
stored `StrategyInstance`) carries a `venue`, and the engine resolves both the
market-data venue and the order executor per strategy each tick. Binance and
Polymarket strategies therefore run side by side in the same loop. The
`active_venue` setting remains the default for anything that does not name a
venue (e.g. manual orders from the Markets page). In Testnet mode a venue
without a sandbox (Polymarket) falls back to Sim with a warning.

## The interface

`engine/venues/base.py` defines:
Expand Down
7 changes: 4 additions & 3 deletions docs/venues/api-features.md
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@ A per-venue inventory of what each trading API *can* do, and what Capital
> Treat this as a decision-level summary; verify specifics against each
> provider's live docs before building on them.

> Capital currently supports **Binance only**. The `Venue` abstraction is
> preserved so additional venues can be re-added as one new implementation
> plus a registry entry. See [abstraction.md](abstraction.md) and
> Capital supports **Binance** and **Polymarket** (see
> [polymarket-setup.md](polymarket-setup.md)); this page covers the Binance
> offered-vs-used surface. Additional venues are one new implementation plus
> a registry entry — see [abstraction.md](abstraction.md) and
> [research.md](research.md) for the design and the broader multi-venue
> survey.

Expand Down
108 changes: 108 additions & 0 deletions docs/venues/polymarket-setup.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,108 @@
# Connecting to Polymarket

How Capital talks to [Polymarket](https://polymarket.com/) — the prediction-market
venue — and how to obtain the credentials it needs.

> ⚠ **No sandbox.** Polymarket has **no paper environment** — every order is
> real and settles on-chain in USDC. In Capital's mode model Polymarket
> supports only **Sim** (Capital's own simulator on live prices) and **Live**;
> in Testnet mode Polymarket strategies fall back to Sim with a warning.

Polymarket strategies run **side by side** with Binance ones: each strategy
carries its own venue, so market data and orders route per strategy — there is
no global switch to flip. Market data (the market catalogue, prices, history)
is **public and needs no credentials**; the credential set below is required
only for **live** order placement.

## What the Polymarket section does

- **Discovery** — the engine pulls the most-traded markets from the public
Gamma API on a schedule into a local catalogue; browse and search them on
the **Polymarket** page and pin markets to the **watchlist**.
- **AI bet analysis** — on the screening schedule (and on demand) the
configured report-writer LLM estimates each market's true probability from
matched news headlines and market context; the **edge** is the estimate
minus the market price. Analyses that clear the operator's edge and
confidence bars become pending **AI signals** (Telegram + dashboard) you
confirm or dismiss.
- **Automated trading** — the **Prediction AI** strategy type trades one
outcome token on the stored analyses, through the same allocation, risk and
notify/auto machinery as every other strategy.
- **Resolution** — watched/held markets are re-checked hourly; when one
resolves, held tokens settle at 1/0 USDC into the ledger and you are
alerted. Live tokens may still need redemption on polymarket.com.

Cadences and bars are configured on the Settings page (Polymarket card).

## How Polymarket auth differs

Polymarket is not an API-key/secret venue like Binance. It is an on-chain
order book (CLOB) on **Polygon**, and auth has two levels:

- **L1 — wallet signing.** Your Ethereum/Polygon wallet's private key signs an
EIP-712 message. This is used once to create or derive API credentials, and
to sign the orders themselves. Trading stays non-custodial — the private key
never leaves your control.
- **L2 — API credentials.** L1 yields a triple — **apiKey, secret, passphrase**.
Every trading request is then signed with these via HMAC-SHA256. The secret
is used to sign but is never sent over the wire.

The `Venue` abstraction was designed for this — it does not assume a fixed
key/secret pair (see [research.md](research.md)).

## 1. Prepare a wallet

1. You need a **Polygon mainnet** wallet (chain ID `137`) funded with **USDC**
for collateral (and a little MATIC for gas).
2. Log in at <https://polymarket.com/> with that wallet **at least once** — the
funder address must exist in your Polymarket profile before credentials can
be created.

## 2. Create API credentials (L1 → L2)

Use the official [`py-clob-client`](https://github.com/Polymarket/py-clob-client)
SDK, which handles the EIP-712 signing:

```bash
pip install py-clob-client
```

```python
from py_clob_client.client import ClobClient

client = ClobClient(
host="https://clob.polymarket.com",
chain_id=137,
key="YOUR_WALLET_PRIVATE_KEY", # L1 — used only to derive credentials
)
creds = client.create_or_derive_api_key()
# creds → apiKey, secret, passphrase (the L2 credential triple)
```

Alternatively, generate them from your Polymarket account settings, or via the
REST endpoints `POST /auth/api-key` (create) and `GET /auth/derive-api-key`
(retrieve) with the proper L1 headers.

## 3. Store the credentials in Capital

On **Settings → Venue credentials → polymarket**, store five fields,
**encrypted at rest** (Fernet, keyed by `CAPITAL_SECRET_KEY`):

| Field | What it is |
|-------|------------|
| `private_key` | The wallet's private key — signs orders (EIP-712) |
| `api_key`, `api_secret`, `passphrase` | The L2 credential triple |
| `wallet_address` | The Polymarket (proxy) wallet that holds the USDC — also enables position reads for reconciliation |

Live orders are placed only when **all five** fields are stored; otherwise the
venue stays read-only and Sim trading continues to work.

## Security notes

- The **wallet private key** is the most sensitive secret here — it controls
real on-chain funds. Use a dedicated trading wallet, not a primary wallet,
and fund it with only what the strategy needs.
- There is no withdrawal-permission toggle as on Binance — a compromised wallet
key is a full compromise. Keep it encrypted and never commit it.
- Polymarket has **regional restrictions** — confirm availability in your
jurisdiction before funding a wallet.
12 changes: 7 additions & 5 deletions docs/venues/research.md
Original file line number Diff line number Diff line change
@@ -1,10 +1,12 @@
# Venue research — open trading APIs

> **Status: audit trail.** Capital today supports **Binance only**. This file
> documents the original multi-venue survey that informed the `Venue`
> abstraction's design — kept as the rationale for the abstraction's shape,
> not as a current to-do list. When a venue is re-introduced, revisit the
> relevant section below and refresh it against the venue's live docs.
> **Status: audit trail.** Capital today supports **Binance** and
> **Polymarket** (re-introduced — see
> [polymarket-setup.md](polymarket-setup.md)). This file documents the
> original multi-venue survey that informed the `Venue` abstraction's design —
> kept as the rationale for the abstraction's shape, not as a current to-do
> list. When another venue is introduced, revisit the relevant section below
> and refresh it against the venue's live docs.

Phase 8 originally expanded Capital beyond Binance to **stocks, stock-index
futures and prediction markets**. This was the research deliverable: a survey
Expand Down
11 changes: 8 additions & 3 deletions engine/ai/signals.py
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,8 @@ class AISignal(SQLModel, table=True):

id: int | None = Field(default=None, primary_key=True)
strategy: str = Field(index=True, max_length=64)
symbol: str = Field(index=True, max_length=24)
symbol: str = Field(index=True, max_length=80) # Polymarket token ids are long
venue: str = Field(default="binance", max_length=24)
market: str = Field(default="spot", max_length=8)
action: str = Field(max_length=8)
confidence: Decimal = Field(default=Decimal(0), **_AMT)
Expand All @@ -63,11 +64,13 @@ def record_signal(
reasoning: str,
reference_price: Decimal,
quantity: Decimal,
venue: str = "binance",
) -> AISignal:
"""Insert one pending AI signal."""
row = AISignal(
strategy=strategy,
symbol=symbol,
venue=venue,
market=market,
action=action,
confidence=confidence,
Expand Down Expand Up @@ -113,7 +116,7 @@ def execute_signal(
Raises `VenueError` (pricing failed), `SignalBlockedError` (risk refused)
or `ExecutionError` (the executor rejected the order).
"""
price = venues.resolve(session).price(signal.symbol)
price = venues.resolve(session, venue=signal.venue).price(signal.symbol)
position = get_or_create_position(
session, signal.strategy, signal.market, signal.symbol
)
Expand All @@ -131,7 +134,9 @@ def execute_signal(
raise SignalBlockedError(
"order blocked by the risk manager (size cap or kill switch)"
)
executor = (executor_router or ExecutorRouter()).resolve(session)
executor = (executor_router or ExecutorRouter()).resolve(
session, venue=signal.venue
)
executor.execute(session, reviewed, reference_price=price)
signal.status = SignalStatus.executed.value
session.add(signal)
Expand Down
1 change: 1 addition & 0 deletions engine/alembic/env.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
from db import SQLModel
from marketdata import models as _md_models # noqa: F401 — register tables on metadata
from news import models as _news_models # noqa: F401 — register tables on metadata
from polymarket import models as _polymarket_models # noqa: F401 — register tables
from trading import models as _trading_models # noqa: F401 — register tables on metadata

# Model modules are imported above so their tables register on
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
"""per-strategy venue routing — venue columns + wide symbols for token ids

Polymarket outcome-token ids are ~77-character numeric strings; every column
that stores a tradeable symbol widens from 24 to 80. `strategy_instance` and
`ai_signal` gain a `venue` column so strategies (and their signals) on
different venues run side by side.

Revision ID: 0022
Revises: 0021
Create Date: 2026-06-11 00:00:00.000000
"""

from collections.abc import Sequence

import sqlalchemy as sa
import sqlmodel
from alembic import op

# revision identifiers, used by Alembic.
revision: str = '0022'
down_revision: str | None = '0021'
branch_labels: Sequence[str] | None = None
depends_on: Sequence[str] | None = None

_VENUE = sqlmodel.sql.sqltypes.AutoString(length=24)
_WIDE = sqlmodel.sql.sqltypes.AutoString(length=80)
_NARROW = sqlmodel.sql.sqltypes.AutoString(length=24)

#: Every (table, column) that stores a tradeable symbol.
_SYMBOL_COLUMNS = (
('position', 'symbol'),
('trade', 'symbol'),
('candle', 'symbol'),
('feed_latency', 'symbol'),
('strategy_instance', 'symbol'),
('ai_signal', 'symbol'),
)


def upgrade() -> None:
for table, column in _SYMBOL_COLUMNS:
op.alter_column(table, column, type_=_WIDE, existing_nullable=False)
op.add_column(
'strategy_instance',
sa.Column('venue', _VENUE, nullable=False, server_default='binance'),
)
op.add_column(
'ai_signal',
sa.Column('venue', _VENUE, nullable=False, server_default='binance'),
)


def downgrade() -> None:
op.drop_column('ai_signal', 'venue')
op.drop_column('strategy_instance', 'venue')
for table, column in _SYMBOL_COLUMNS:
op.alter_column(table, column, type_=_NARROW, existing_nullable=False)
Loading
Loading