Skip to main content

Overview

The engine.py module provides a paper trading engine that simulates order fills using real market data. It manages bot accounts, positions, and settlement without making actual trades.

PaperPosition Class

Represents a single open or settled paper position.

Fields

id
string
Unique 12-character position ID (auto-generated)
bot_id
string
ID of the bot that owns this position
market_ticker
string
Market ticker symbol
side
string
“yes” or “no”
contracts
int
Number of contracts purchased
entry_price
float
Price paid per contract in dollars
cost
float
Total cost: contracts × entry_price
entry_time
datetime
When position was opened
close_time
datetime | None
Market close time (for settlement scheduling)
settled
bool
default:"False"
Whether position has been settled
result
string
Market outcome: “yes”, “no”, or “timeout”
payout
float
Payout from winning contracts (0 if lost)
profit
float
Net profit: payout - cost
settle_time
datetime | None
When position was settled
from genetic.engine import PaperPosition
from datetime import datetime, timezone

position = PaperPosition(
    bot_id="bot_abc123",
    market_ticker="KXBTC15M-24DEC05-T1030",
    side="yes",
    contracts=10,
    entry_price=0.55,
    cost=5.50,
    close_time=datetime.now(timezone.utc)
)

print(f"Position: {position.contracts} {position.side} @ ${position.entry_price}")
print(f"Cost: ${position.cost:.2f}")

BotAccount Class

Paper trading account for one bot with P&L tracking.

Fields

bot_id
string
required
Bot identifier
initial_bankroll
float
default:"100.0"
Starting capital
cash
float
default:"100.0"
Available cash for new trades
open_positions
dict[str, PaperPosition]
Dict of ticker → position for open positions
closed_positions
list[PaperPosition]
List of settled positions
total_trades
int
default:"0"
Lifetime trade count
trades_today
int
default:"0"
Trades executed today (resets daily)
daily_pnl
float
default:"0.0"
Today’s realized P&L (resets daily)
last_trade_date
string
Last trade date YYYY-MM-DD for daily reset

Properties

equity

Total account value: cash + open position costs.
acct = BotAccount(bot_id="bot_123", initial_bankroll=100.0)
print(f"Equity: ${acct.equity:.2f}")

realized_pnl

Sum of all settled position profits.
print(f"Realized P&L: ${acct.realized_pnl:+.2f}")

roi_pct

Return on investment percentage based on realized P&L.
print(f"ROI: {acct.roi_pct:+.2f}%")

win_rate

Percentage of settled positions that were profitable.
print(f"Win rate: {acct.win_rate:.1%}")

n_settled

Number of settled positions.
print(f"Settled trades: {acct.n_settled}")

n_open

Number of currently open positions.
print(f"Open positions: {acct.n_open}")

Methods

unrealized_pnl(feed)

Estimate P&L of open positions using current bid prices.
feed
MarketDataFeed
required
Market data feed for current prices
pnl
float
Unrealized profit/loss
unrealized = acct.unrealized_pnl(feed)
print(f"Unrealized P&L: ${unrealized:+.2f}")

total_pnl(feed)

Realized + unrealized P&L.
total = acct.total_pnl(feed)
print(f"Total P&L: ${total:+.2f}")

total_roi_pct(feed)

ROI including unrealized positions.
roi = acct.total_roi_pct(feed)
print(f"Total ROI: {roi:+.2f}%")

PaperTradingEngine Class

Simulates fills locally using real market data.

Constructor

feed
MarketDataFeed
required
Market data feed instance
from genetic.engine import PaperTradingEngine
from genetic.feed import MarketDataFeed
from kalshi_client import KalshiClient

client = KalshiClient()
feed = MarketDataFeed(client)
engine = PaperTradingEngine(feed)

Methods

create_account(bot_id, bankroll=100.0)

Create a new paper trading account for a bot.
bot_id
string
required
Unique bot identifier
bankroll
float
default:"100.0"
Initial capital in USD
account
BotAccount
New account instance
acct = engine.create_account("bot_abc123", bankroll=100.0)
print(f"Created account with ${acct.cash:.2f} cash")

try_buy(bot_id, market_ticker, side, usd_amount)

Attempt to buy a position. Returns position if successful, None if rejected.
bot_id
string
required
Bot account ID
market_ticker
string
required
Market to trade
side
string
required
“yes” or “no”
usd_amount
float
required
Target USD amount to invest
position
PaperPosition | None
Created position, or None if rejected
Fill logic:
  1. Fill at displayed ask price (yes_ask or no_ask)
  2. Calculate integer contracts only
  3. Check available cash
  4. No duplicate positions in same market
position = engine.try_buy(
    bot_id="bot_abc123",
    market_ticker="KXBTC15M-24DEC05-T1030",
    side="yes",
    usd_amount=10.0
)

if position:
    print(f"Bought {position.contracts} @ ${position.entry_price:.2f}")
    print(f"Total cost: ${position.cost:.2f}")
else:
    print("Order rejected (no cash, bad price, or duplicate)")

get_open_tickers()

Get all unique tickers with open positions across all accounts.
tickers
set[str]
Set of market tickers
tickers = engine.get_open_tickers()
print(f"Open positions in {len(tickers)} markets")

get_closeable_tickers()

Get tickers whose markets have passed their close time.
tickers
set[str]
Set of closeable market tickers
closeable = engine.get_closeable_tickers()
if closeable:
    print(f"Markets ready to settle: {closeable}")

total_open_positions()

Total open positions across all accounts.
count
int
Number of open positions
open_count = engine.total_open_positions()
print(f"{open_count} total open positions")

total_settled()

Total settled positions across all accounts.
count
int
Number of settled positions
settled_count = engine.total_settled()
print(f"{settled_count} settled positions")

settle_markets()

Check all open positions against cached settlement data. Called each tick by main loop. Uses cached results from feed.
# Each tick
engine.settle_markets()

settle_with_targeted_check()

Check specific closeable tickers via API, then settle. More efficient - only checks markets that are ready to settle.
# Every N ticks
engine.settle_with_targeted_check()

force_close_remaining()

Force-close all open positions as total losses. Called after settlement timeout. Treats unsettled positions as losses for fitness calculation.
# After generation ends + settlement wait period
engine.force_close_remaining()
print(f"Forced closure of {engine.total_open_positions()} positions")

Example: Full Trading Cycle

from genetic.engine import PaperTradingEngine
from genetic.feed import MarketDataFeed
from kalshi_client import KalshiClient
import time

# Setup
client = KalshiClient()
feed = MarketDataFeed(client)
engine = PaperTradingEngine(feed)

# Create bot account
acct = engine.create_account("bot_test", bankroll=100.0)
print(f"Starting bankroll: ${acct.cash:.2f}")

# Update market data
feed.update()

# Place some trades
for ticker in list(feed.get_open_markets().keys())[:3]:
    pos = engine.try_buy("bot_test", ticker, "yes", 10.0)
    if pos:
        print(f"Opened: {ticker} {pos.contracts} @ ${pos.entry_price:.2f}")

print(f"\nCash after trades: ${acct.cash:.2f}")
print(f"Open positions: {acct.n_open}")
print(f"Equity: ${acct.equity:.2f}")

# Wait and settle
time.sleep(60)
engine.settle_with_targeted_check()

print(f"\nSettled positions: {acct.n_settled}")
print(f"Win rate: {acct.win_rate:.1%}")
print(f"Realized P&L: ${acct.realized_pnl:+.2f}")
print(f"ROI: {acct.roi_pct:+.2f}%")

Build docs developers (and LLMs) love