CtrlK
BlogDocsLog inGet started
Tessl Logo

portfolio-sync

Sync portfolio positions from Polymarket, Kalshi, and Manifold

68

Quality

59%

Does it follow best practices?

Impact

Pending

No eval scenarios have been run

SecuritybySnyk

Advisory

Suggest reviewing before use

Optimize this skill with Tessl

npx tessl skill review --optimize ./src/skills/bundled/portfolio-sync/SKILL.md
SKILL.md
Quality
Evals
Security

Portfolio Sync Skill

Real methods to fetch and sync positions from each prediction market platform.

Polymarket Position Sync

Polymarket positions are held as ERC-1155 tokens on Polygon. Query on-chain balances.

import os
import requests

WALLET = os.getenv("POLY_FUNDER_ADDRESS")
CTF_CONTRACT = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045"  # Conditional Token Framework

def get_polymarket_positions(token_ids: list[str]) -> dict:
    """
    Get balances for specific token IDs

    Args:
        token_ids: List of token IDs to check (from market data)

    Returns:
        Dict of token_id -> balance in shares
    """
    positions = {}

    for token_id in token_ids:
        token_int = int(token_id)

        # ERC-1155 balanceOf call
        data = f"0x00fdd58e000000000000000000000000{WALLET[2:].lower()}{token_int:064x}"

        r = requests.post("https://polygon-rpc.com/", json={
            "jsonrpc": "2.0",
            "method": "eth_call",
            "params": [{"to": CTF_CONTRACT, "data": data}, "latest"],
            "id": 1
        })

        result = r.json().get("result", "0x0")
        balance = int(result, 16) / 1e6  # Raw to shares

        if balance > 0:
            positions[token_id] = balance

    return positions

# Example: Check positions for BTC 15-min market
btc_tokens = [
    "21742633143463906290569050155826241533067272736897614950488156847949938836455",  # YES
    "48331043336612883890938759509493159234755048973500640148014422747788308965745"   # NO
]

positions = get_polymarket_positions(btc_tokens)
for token_id, balance in positions.items():
    print(f"Token {token_id[:20]}...: {balance} shares")

Get All Polymarket Positions (via Gamma API)

def get_all_polymarket_positions(wallet: str):
    """Get all positions for a wallet via Gamma API"""
    url = f"https://gamma-api.polymarket.com/positions?user={wallet.lower()}"
    r = requests.get(url)

    if r.status_code != 200:
        return []

    positions = r.json()

    result = []
    for p in positions:
        result.append({
            "market_id": p.get("conditionId"),
            "market_question": p.get("title", "Unknown"),
            "token_id": p.get("tokenId"),
            "outcome": p.get("outcome"),
            "size": float(p.get("size", 0)),
            "avg_price": float(p.get("avgPrice", 0)),
            "current_price": float(p.get("currentPrice", 0)),
            "pnl": float(p.get("pnl", 0)),
            "value": float(p.get("value", 0))
        })

    return result

positions = get_all_polymarket_positions(WALLET)
for p in positions:
    print(f"{p['market_question'][:40]}")
    print(f"  {p['outcome']}: {p['size']} shares @ {p['avg_price']:.2f} -> {p['current_price']:.2f}")
    print(f"  PnL: ${p['pnl']:.2f}")

Get USDC Balance

def get_usdc_balance(wallet: str) -> float:
    """Get USDC balance on Polygon"""
    USDC = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174"  # USDC on Polygon

    # ERC-20 balanceOf
    data = f"0x70a08231000000000000000000000000{wallet[2:].lower()}"

    r = requests.post("https://polygon-rpc.com/", json={
        "jsonrpc": "2.0",
        "method": "eth_call",
        "params": [{"to": USDC, "data": data}, "latest"],
        "id": 1
    })

    result = r.json().get("result", "0x0")
    balance = int(result, 16) / 1e6  # USDC has 6 decimals

    return balance

usdc = get_usdc_balance(WALLET)
print(f"USDC Balance: ${usdc:.2f}")

Kalshi Position Sync

import requests
import time

BASE_URL = "https://trading-api.kalshi.com/trade-api/v2"

class KalshiSync:
    def __init__(self, email: str, password: str):
        self.email = email
        self.password = password
        self.token = None
        self.token_expiry = 0

    def _auth(self):
        if time.time() > self.token_expiry - 60:
            r = requests.post(f"{BASE_URL}/login", json={
                "email": self.email,
                "password": self.password
            })
            r.raise_for_status()
            self.token = r.json()["token"]
            self.token_expiry = time.time() + 29 * 60

    def _headers(self):
        self._auth()
        return {"Authorization": f"Bearer {self.token}"}

    def get_positions(self):
        """Get all Kalshi positions"""
        r = requests.get(f"{BASE_URL}/portfolio/positions", headers=self._headers())
        r.raise_for_status()

        positions = []
        for p in r.json().get("market_positions", []):
            # Get market details
            market = requests.get(
                f"{BASE_URL}/markets/{p['ticker']}",
                headers=self._headers()
            ).json().get("market", {})

            positions.append({
                "market_id": p["ticker"],
                "market_question": market.get("title", p["ticker"]),
                "side": "YES" if p.get("position", 0) > 0 else "NO",
                "size": abs(p.get("position", 0)),
                "avg_price": p.get("average_price", 0) / 100,
                "current_price": market.get("yes_bid", 50) / 100,
                "value": abs(p.get("position", 0)) * market.get("yes_bid", 50) / 100,
                "pnl": p.get("realized_pnl", 0) / 100
            })

        return positions

    def get_balance(self):
        """Get Kalshi balance"""
        r = requests.get(f"{BASE_URL}/portfolio/balance", headers=self._headers())
        r.raise_for_status()
        data = r.json()
        return {
            "available": data.get("balance", 0) / 100,
            "portfolio_value": data.get("portfolio_value", 0) / 100
        }

# Usage
sync = KalshiSync(os.getenv("KALSHI_EMAIL"), os.getenv("KALSHI_PASSWORD"))

positions = sync.get_positions()
for p in positions:
    print(f"{p['market_question'][:40]}")
    print(f"  {p['side']}: {p['size']} @ {p['avg_price']:.2f} -> {p['current_price']:.2f}")

balance = sync.get_balance()
print(f"\nAvailable: ${balance['available']:.2f}")
print(f"Portfolio: ${balance['portfolio_value']:.2f}")

Manifold Position Sync

import requests

API_URL = "https://api.manifold.markets/v0"
API_KEY = os.getenv("MANIFOLD_API_KEY")

def get_manifold_positions():
    """Get all Manifold positions"""
    headers = {"Authorization": f"Key {API_KEY}"}

    # Get user profile
    r = requests.get(f"{API_URL}/me", headers=headers)
    r.raise_for_status()
    user = r.json()
    user_id = user["id"]
    balance = user.get("balance", 0)

    # Get all bets
    r = requests.get(f"{API_URL}/bets", headers=headers, params={"userId": user_id, "limit": 1000})
    bets = r.json()

    # Aggregate positions by market
    markets = {}
    for bet in bets:
        if bet.get("isSold") or bet.get("isCancelled"):
            continue

        mid = bet["contractId"]
        if mid not in markets:
            markets[mid] = {
                "yes_shares": 0,
                "no_shares": 0,
                "invested": 0,
                "question": bet.get("contractQuestion", "Unknown")
            }

        if bet["outcome"] == "YES":
            markets[mid]["yes_shares"] += bet.get("shares", 0)
        else:
            markets[mid]["no_shares"] += bet.get("shares", 0)

        markets[mid]["invested"] += bet["amount"]

    # Get current prices
    positions = []
    for mid, data in markets.items():
        if data["yes_shares"] == 0 and data["no_shares"] == 0:
            continue

        # Fetch current market price
        r = requests.get(f"{API_URL}/market/{mid}")
        if r.status_code == 200:
            market = r.json()
            prob = market.get("probability", 0.5)

            yes_value = data["yes_shares"] * prob
            no_value = data["no_shares"] * (1 - prob)
            total_value = yes_value + no_value
            pnl = total_value - data["invested"]

            positions.append({
                "market_id": mid,
                "market_question": data["question"],
                "yes_shares": data["yes_shares"],
                "no_shares": data["no_shares"],
                "invested": data["invested"],
                "current_value": total_value,
                "probability": prob,
                "pnl": pnl,
                "url": market.get("url", "")
            })

    return positions, balance

positions, balance = get_manifold_positions()
print(f"Mana Balance: {balance}")

for p in positions:
    print(f"\n{p['market_question'][:50]}")
    print(f"  YES: {p['yes_shares']:.1f} shares, NO: {p['no_shares']:.1f} shares")
    print(f"  Value: {p['current_value']:.0f}M, PnL: {p['pnl']:+.0f}M")

Unified Portfolio Sync

#!/usr/bin/env python3
"""
Sync portfolio from all prediction markets
"""

import os
from dataclasses import dataclass
from typing import List

@dataclass
class Position:
    platform: str
    market_id: str
    market_question: str
    side: str
    size: float
    avg_price: float
    current_price: float
    value: float
    pnl: float
    pnl_pct: float

def sync_all_portfolios() -> List[Position]:
    """Sync positions from all platforms"""
    all_positions = []

    # Polymarket
    if os.getenv("POLY_FUNDER_ADDRESS"):
        poly_positions = get_all_polymarket_positions(os.getenv("POLY_FUNDER_ADDRESS"))
        for p in poly_positions:
            avg = p["avg_price"] or 0.01
            pnl_pct = ((p["current_price"] - avg) / avg * 100) if avg > 0 else 0

            all_positions.append(Position(
                platform="polymarket",
                market_id=p["market_id"],
                market_question=p["market_question"],
                side=p["outcome"],
                size=p["size"],
                avg_price=avg,
                current_price=p["current_price"],
                value=p["value"],
                pnl=p["pnl"],
                pnl_pct=pnl_pct
            ))

    # Kalshi
    if os.getenv("KALSHI_EMAIL"):
        kalshi = KalshiSync(os.getenv("KALSHI_EMAIL"), os.getenv("KALSHI_PASSWORD"))
        kalshi_positions = kalshi.get_positions()
        for p in kalshi_positions:
            avg = p["avg_price"] or 0.01
            pnl_pct = ((p["current_price"] - avg) / avg * 100) if avg > 0 else 0

            all_positions.append(Position(
                platform="kalshi",
                market_id=p["market_id"],
                market_question=p["market_question"],
                side=p["side"],
                size=p["size"],
                avg_price=avg,
                current_price=p["current_price"],
                value=p["value"],
                pnl=p["pnl"],
                pnl_pct=pnl_pct
            ))

    # Manifold
    if os.getenv("MANIFOLD_API_KEY"):
        mani_positions, _ = get_manifold_positions()
        for p in mani_positions:
            invested = p["invested"] or 1
            pnl_pct = (p["pnl"] / invested * 100) if invested > 0 else 0

            # Add YES position
            if p["yes_shares"] > 0:
                all_positions.append(Position(
                    platform="manifold",
                    market_id=p["market_id"],
                    market_question=p["market_question"],
                    side="YES",
                    size=p["yes_shares"],
                    avg_price=0,  # Manifold doesn't track this
                    current_price=p["probability"],
                    value=p["yes_shares"] * p["probability"],
                    pnl=p["pnl"] / 2,  # Split PnL
                    pnl_pct=pnl_pct
                ))

            # Add NO position
            if p["no_shares"] > 0:
                all_positions.append(Position(
                    platform="manifold",
                    market_id=p["market_id"],
                    market_question=p["market_question"],
                    side="NO",
                    size=p["no_shares"],
                    avg_price=0,
                    current_price=1 - p["probability"],
                    value=p["no_shares"] * (1 - p["probability"]),
                    pnl=p["pnl"] / 2,
                    pnl_pct=pnl_pct
                ))

    return all_positions

# Run sync
positions = sync_all_portfolios()

# Print summary
total_value = sum(p.value for p in positions)
total_pnl = sum(p.pnl for p in positions)

print(f"\n{'='*60}")
print(f"PORTFOLIO SUMMARY")
print(f"{'='*60}")
print(f"Total Value: ${total_value:.2f}")
print(f"Total PnL: ${total_pnl:+.2f}")
print(f"{'='*60}")

for platform in ["polymarket", "kalshi", "manifold"]:
    plat_positions = [p for p in positions if p.platform == platform]
    if plat_positions:
        plat_value = sum(p.value for p in plat_positions)
        plat_pnl = sum(p.pnl for p in plat_positions)
        print(f"\n{platform.upper()}: ${plat_value:.2f} (PnL: ${plat_pnl:+.2f})")

        for p in plat_positions:
            print(f"  {p.market_question[:35]}")
            print(f"    {p.side}: {p.size:.1f} @ {p.avg_price:.2f} -> {p.current_price:.2f}")
            print(f"    Value: ${p.value:.2f}, PnL: ${p.pnl:+.2f} ({p.pnl_pct:+.1f}%)")

Cron Job for Auto-Sync

#!/usr/bin/env python3
"""
Run every hour to sync positions to database
"""

import sqlite3
from datetime import datetime

def sync_to_db():
    """Sync all positions to SQLite"""
    conn = sqlite3.connect("~/.clodds/clodds.db")
    positions = sync_all_portfolios()

    for p in positions:
        conn.execute("""
            INSERT OR REPLACE INTO positions
            (platform, market_id, market_question, side, size, avg_price, current_price, value, pnl, updated_at)
            VALUES (?, ?, ?, ?, ?, ?, ?, ?, ?, ?)
        """, (
            p.platform, p.market_id, p.market_question, p.side,
            p.size, p.avg_price, p.current_price, p.value, p.pnl,
            datetime.now().isoformat()
        ))

    conn.commit()
    conn.close()
    print(f"Synced {len(positions)} positions at {datetime.now()}")

if __name__ == "__main__":
    sync_to_db()

Add to crontab:

# Sync every hour
0 * * * * cd /path/to/clodds && python3 -c "from skills.portfolio_sync import sync_to_db; sync_to_db()"
Repository
alsk1992/CloddsBot
Last updated
Created

Is this your skill?

If you maintain this skill, you can claim it as your own. Once claimed, you can manage eval scenarios, bundle related skills, attach documentation or rules, and ensure cross-agent compatibility.