Wire any data source into any trading strategy for prediction markets.
Polymarket + Kalshi scanning, DB-managed Python runtimes, and paper/live execution orchestration.
Homerun is a full-stack platform for building and running prediction-market trading systems where:
- Data sources are first-class, DB-managed Python classes (
python,rss,rest_api) with validation, run history, and normalized records. - Strategies are first-class, DB-managed Python classes with full lifecycle hooks (
detect,evaluate,should_exit). - Strategies can consume any source directly through
StrategySDK(get_data_records,run_data_source,list_data_sources, etc.). - Trader intelligence is available through a formal
TradersSDK(get_firehose_signals,get_strategy_filtered_signals,get_confluence_signals, etc.). - You can validate and backtest strategy code before enabling it.
- Opportunities flow into a risk-gated orchestrator for paper or live execution.
If you can fetch the signal, you can trade the signal.
Most trading bots force a rigid pipeline or a toy DSL. Homerun does not.
- Full Python strategies, not expression-language rules.
- Full Python data sources, not fixed connectors only.
- Runtime editing via API/UI with source validation and hot reload.
- Unified opportunity model across scanner/news/weather/events/trader flows.
- Built for prediction markets first (binary contracts, event settlement, cross-venue dislocations).
- Start local dev:
make setup && make dev - Start terminal UI:
make run - Strategy API docs:
GET /api/strategy-manager/docs - Data source API docs:
GET /api/data-sources/docs - FastAPI swagger:
http://localhost:8000/docs
Two ways to connect source data into strategy logic:
- Event-driven: subscribe strategy to
EventType.DATA_SOURCE_UPDATE. - Direct read: call
StrategySDK.get_data_records(...)inside your strategy.
In practice, teams combine both: event for wake-up signal, SDK reads for filtered payload access.
from services.data_source_sdk import BaseDataSource
class MacroFeedSource(BaseDataSource):
name = "Macro Feed"
description = "Example REST ingest for macro events"
default_config = {
"endpoint": "https://example.com/macro",
"limit": 200,
}
async def fetch_async(self):
endpoint = str(self.config.get("endpoint") or "").strip()
if not endpoint:
return []
payload = await self._http_get_json(endpoint, default=[])
rows = payload if isinstance(payload, list) else payload.get("items", [])
out = []
for row in rows[: self._as_int(self.config.get("limit"), 200, 1, 5000)]:
observed = self._parse_datetime(row.get("timestamp"))
out.append(
{
"external_id": str(row.get("id") or ""),
"title": str(row.get("title") or "").strip(),
"summary": str(row.get("summary") or "").strip(),
"category": "macro",
"source": "macro_feed",
"url": row.get("url"),
"observed_at": observed.isoformat() if observed else None,
"payload": row,
"tags": ["macro", "custom"],
}
)
return outThen use:
POST /api/data-sources/validatePOST /api/data-sourcesPOST /api/data-sources/{id}/run
Or create it from the UI: Data -> Sources.
from models import Opportunity
from services.data_events import EventType
from services.strategies.base import BaseStrategy
from services.strategy_sdk import StrategySDK
class MacroShockStrategy(BaseStrategy):
strategy_type = "macro_shock"
name = "Macro Shock"
description = "Uses custom macro feed records to find directional mispricing"
source_key = "scanner"
subscriptions = [EventType.MARKET_DATA_REFRESH]
default_config = {
"source_slug": "macro_feed_source",
"min_confidence": 0.55,
}
async def detect_async(self, events, markets, prices) -> list[Opportunity]:
source_slug = str(self.config.get("source_slug") or "macro_feed_source")
records = await StrategySDK.get_data_records(source_slug=source_slug, limit=100)
if not records:
return []
opportunities: list[Opportunity] = []
confidence = max(0.0, min(1.0, float(self.config.get("min_confidence", 0.55))))
for market in markets:
if market.closed or not market.active:
continue
if not market.clob_token_ids:
continue
yes_token = market.clob_token_ids[0]
yes_price = market.yes_price
if yes_token in prices:
yes_price = float(prices[yes_token].get("mid", yes_price) or yes_price)
if yes_price >= 0.60:
continue
opp = self.create_opportunity(
title=f"Macro Shock: {market.question[:80]}",
description="Directional YES based on external macro feed signal",
total_cost=yes_price,
expected_payout=1.0,
is_guaranteed=False,
markets=[market],
positions=[
{
"action": "BUY",
"outcome": "YES",
"price": yes_price,
"token_id": yes_token,
}
],
confidence=confidence,
)
if opp:
opp.strategy_context = {
"source_slug": source_slug,
"records_used": len(records),
}
opportunities.append(opp)
return opportunitiesThen use:
POST /api/strategy-manager/validatePOST /api/validation/code-backtestPOST /api/strategy-managerPOST /api/strategy-manager/{id}/reload
Or create/edit it from the UI: Strategies.
External APIs / RSS / Python Sources
|
v
Data Source Runtime
(fetch -> transform -> upsert)
|
v
data_source_records table <-----+
| |
| StrategySDK
| (read/run/list sources)
v |
Event Dispatcher + Workers |
(market/news/weather/events/traders) |
| |
+----------> Strategy Runtime (DB-loaded Python)
detect / evaluate / should_exit
|
v
Opportunity + Signal
|
v
Trader Orchestrator
(risk gates + order manager)
|
v
Paper / Live execution paths
- Python 3
- Node.js
- Docker or
redis-server(setup script can bootstrap Redis runtime prerequisites)
git clone <your-repo-url>
cd homerun
make setup
make devApp endpoints:
- Frontend:
http://localhost:3000 - Backend API:
http://localhost:8000 - FastAPI docs:
http://localhost:8000/docs - WebSocket:
ws://localhost:8000/ws
make runmake run launches the Textual/Rich TUI (tui.py) and ensures Redis is up.
| Endpoint | Purpose |
|---|---|
GET /api/strategy-manager |
List strategies with capabilities/runtime metadata |
GET /api/strategy-manager/template |
Strategy starter template |
GET /api/strategy-manager/docs |
Strategy developer reference |
POST /api/strategy-manager/validate |
Validate strategy source code |
POST /api/strategy-manager/{id}/reload |
Recompile/reload strategy runtime |
GET /api/data-sources |
List data sources |
GET /api/data-sources/template |
Data-source templates/presets |
GET /api/data-sources/docs |
Data-source developer reference |
POST /api/data-sources/validate |
Validate source code |
POST /api/data-sources/{id}/run |
Execute ingestion now |
GET /api/data-sources/{id}/runs |
Source run history |
GET /api/data-sources/{id}/records |
Normalized records |
GET /api/opportunities |
Unified opportunities feed |
POST /api/validation/code-backtest* |
Detect/evaluate/exit code backtests |
homerun/
├── backend/
│ ├── api/ # FastAPI routes
│ ├── services/ # Strategy runtime, data-source runtime, orchestrator
│ ├── workers/ # Background workers
│ ├── models/ # ORM + Pydantic models
│ └── main.py # App entrypoint
├── frontend/
│ └── src/
│ ├── components/ # UI panels (Strategies, Data Sources, Trading, Validation)
│ ├── services/ # API client layer
│ └── hooks/store/lib
├── scripts/ # setup/run helpers
└── tui.py # terminal UI
make setup # install backend/frontend deps and bootstrap runtime prerequisites
make dev # backend + frontend dev servers
make run # launch TUI runtime
make stop # stop services on dev ports
make build # frontend build
make clean # remove generated artifacts- This software can drive real-money trading decisions.
- Strategy code should be validated and backtested before enabling execution.
- Start in paper mode and graduate carefully.
- Nothing in this repository is financial advice.
- Read
./docs/CONTRIBUTING.md - Security policy:
./docs/SECURITY.md
MIT. See ./LICENSE.


