The Position Monitoring script shows how to fetch and display all positions for a wallet, calculate P&L, and summarize portfolio performance.
View source on GitHub →
What It Does
This script:
Fetches all positions for a wallet address
Calculates current value based on latest market prices
Computes P&L (realized + unrealized)
Displays portfolio summary with total invested, current value, and percentage return
Shows user activity (total trades, volume, markets traded)
Quick Start
Setup Environment
TURBINE_API_KEY_ID = your_key_id
TURBINE_API_PRIVATE_KEY = your_private_key
TURBINE_WALLET_ADDRESS = 0x... # Address to monitor
You don’t need the wallet’s private key to monitor positions — only API credentials and the public address.
Run the Script
python examples/position_monitoring.py
Example Output
Monitoring positions for: 0x742d35Cc6634C0532925a3b844Bc9e7595f0bEb0
============================================================
Will BTC be above $97,250 at 3:15 PM?
--------------------------------------------------
YES shares: 5.00
NO shares: 0.00
Invested: $2.50
Current: $3.20
P&L: +$0.70 (+28.0%)
Last price: 64.0%
Will BTC be above $96,800 at 3:00 PM?
--------------------------------------------------
YES shares: 0.00
NO shares: 3.00
Invested: $1.20
Current: $0.90
P&L: -$0.30 (-25.0%)
Last price: 70.0%
RESOLVED: YES won!
============================================================
PORTFOLIO SUMMARY
============================================================
Total invested: $3.70
Current value: $4.10
Total P&L: +$0.40 (+10.8%)
Positions: 2
Total trades: 8
Total volume: $15.40
Markets traded: 4
Code Walkthrough
1. Create Read-Only Client
Position monitoring doesn’t require the wallet’s private key:
from turbine_client import TurbineClient
client = TurbineClient(
host = "https://api.turbinefi.com" ,
chain_id = 137 , # Polygon mainnet
api_key_id = api_key_id,
api_private_key = api_private_key,
# No private_key needed for read-only
)
2. Fetch All Positions
positions = client.get_user_positions(wallet_address)
for pos in positions:
print ( f "Market: { pos.market_id } " )
print ( f "YES shares: { pos.yes_shares / 1_000_000 :.2f} " )
print ( f "NO shares: { pos.no_shares / 1_000_000 :.2f} " )
print ( f "Invested: $ { pos.invested / 1_000_000 :.2f} " )
Key fields:
market_id: Which market the position is in
yes_shares: Number of YES shares (in 6 decimals)
no_shares: Number of NO shares (in 6 decimals)
invested: Total USDC spent on this position (in 6 decimals)
3. Calculate Current Value
To compute P&L, you need the current market price:
# Get latest market stats
stats = client.get_stats(pos.market_id)
yes_price = stats.last_price # Last trade price
no_price = 1_000_000 - yes_price # NO = 100% - YES
# Value of each outcome
yes_value = (pos.yes_shares * yes_price) // 1_000_000
no_value = (pos.no_shares * no_price) // 1_000_000
current_value = yes_value + no_value
# P&L
pnl = current_value - pos.invested
pnl_pct = (pnl / pos.invested * 100 ) if pos.invested > 0 else 0
Why last_price and not mid_price?
last_price is the most recent trade price — it represents the current market clearing price. The mid-price (average of best bid and ask) is an estimate, but last_price is what the market actually traded at.
4. Check for Resolved Markets
market = client.get_market(pos.market_id)
if market.resolved:
outcome = "YES" if market.winning_outcome == 0 else "NO"
print ( f "RESOLVED: { outcome } won!" )
Resolved positions should be claimed to get the USDC back.
5. Get User Activity
activity = client.get_user_activity(wallet_address)
print ( f "Total trades: { activity.total_trades } " )
print ( f "Total volume: $ { activity.total_volume / 1_000_000 :.2f} " )
print ( f "Markets traded: { activity.markets_traded } " )
Understanding P&L
Unrealized P&L
P&L on open positions (markets that haven’t resolved yet).
Example:
Bought 10 YES shares at 40% ($4.00 spent)
Current YES price: 60%
Current value: 10 shares × 60% = $6.00
Unrealized P&L: +$2.00 (+50%)
You haven’t won yet — the market could still reverse. But if you sold now at 60%, you’d lock in +$2.00.
Realized P&L
P&L on closed positions (markets that resolved).
Example:
Bought 10 YES shares at 40% ($4.00 spent)
Market resolved: YES won
Payout: 10 shares × 1.00 = 1.00 = 1.00 = 10.00
Realized P&L: +$6.00 (+150%)
This is locked in — once the market resolves and you claim, you get the full payout.
Portfolio P&L
Sum of all positions:
total_invested = sum (pos.invested for pos in positions)
total_current_value = sum (calculate_value(pos) for pos in positions)
total_pnl = total_current_value - total_invested
total_pnl_pct = (total_pnl / total_invested * 100 ) if total_invested > 0 else 0
Important: This includes both realized and unrealized P&L. To see only realized, filter for market.resolved.
Advanced: Position Breakdown
Per-Market P&L
Group positions by market:
from collections import defaultdict
by_market = defaultdict( list )
for pos in positions:
by_market[pos.market_id].append(pos)
for market_id, positions in by_market.items():
market = client.get_market(market_id)
print ( f " \n { market.question } " )
for pos in positions:
# Calculate P&L for this position
...
Per-Outcome Analysis
How much is in YES vs NO?
total_yes_value = 0
total_no_value = 0
for pos in positions:
stats = client.get_stats(pos.market_id)
yes_price = stats.last_price
no_price = 1_000_000 - yes_price
total_yes_value += (pos.yes_shares * yes_price) // 1_000_000
total_no_value += (pos.no_shares * no_price) // 1_000_000
print ( f "YES exposure: $ { total_yes_value / 1_000_000 :.2f} " )
print ( f "NO exposure: $ { total_no_value / 1_000_000 :.2f} " )
Useful for understanding directional bias.
Time-Weighted Returns
To calculate returns accounting for when capital was deployed:
# Get all trades for the user
trades = []
for market_id in traded_markets:
market_trades = client.get_trades(market_id, limit = 100 )
my_trades = [t for t in market_trades if t.buyer.lower() == wallet_address.lower()]
trades.extend(my_trades)
# Sort by timestamp
trades.sort( key = lambda t : t.timestamp)
# Calculate TWR (time-weighted return)
# Time-weighted returns can be calculated by tracking position values over time
This is more accurate for bots that scale capital over time.
Integrating with Your Bot
Add position monitoring to your trading bot:
class MyBot :
async def monitor_positions ( self ):
"""Background task that logs position summary every 5 minutes."""
while self .running:
try :
positions = self .client.get_user_positions( self .client.address)
total_invested = sum (p.invested for p in positions)
total_value = sum ( self .calculate_value(p) for p in positions)
pnl = total_value - total_invested
print ( f " \n === PORTFOLIO ===" )
print ( f "Invested: $ { total_invested / 1_000_000 :.2f} " )
print ( f "Value: $ { total_value / 1_000_000 :.2f} " )
print ( f "P&L: $ { pnl / 1_000_000 :.2f} ( { pnl / total_invested * 100 :+.1f} %)" )
print ( f "Positions: { len (positions) } " )
except Exception as e:
print ( f "Position monitoring error: { e } " )
await asyncio.sleep( 300 ) # Every 5 minutes
Start it as a background task:
async def run ( self ):
monitor_task = asyncio.create_task( self .monitor_positions())
trade_task = asyncio.create_task( self .trading_loop())
await asyncio.gather(monitor_task, trade_task)
Helper Functions
def format_usdc ( amount : int ) -> str :
"""Format USDC (6 decimals) as USD string."""
return f "$ { amount / 1_000_000 :.2f} "
def format_price ( price : int ) -> str :
"""Format price (6 decimals) as percentage."""
return f " { price / 10000 :.1f} %"
Calculate Position Value
def calculate_position_value ( pos , stats ) -> int :
"""Calculate current value of a position in USDC (6 decimals)."""
yes_price = stats.last_price
no_price = 1_000_000 - yes_price
yes_value = (pos.yes_shares * yes_price) // 1_000_000
no_value = (pos.no_shares * no_price) // 1_000_000
return yes_value + no_value
Common Use Cases
Check if bot is profitable
Run the script and check total P&L: python examples/position_monitoring.py
Look at the Total P&L line. Positive = winning, negative = losing.
Find markets to claim from
Filter for resolved positions: for pos in positions:
market = client.get_market(pos.market_id)
if market.resolved:
print ( f "Claimable: { market.contract_address } " )
Then use claim_winnings.py to claim: python examples/claim_winnings.py 0x... (contract address )
Track bot performance over time
Run the script for each wallet: TURBINE_WALLET_ADDRESS = 0x... python examples/position_monitoring.py > wallet1.txt
TURBINE_WALLET_ADDRESS = 0x... python examples/position_monitoring.py > wallet2.txt
Then compare P&L across wallets.
Limitations
Last price may be stale. If a market hasn’t traded recently, last_price may not reflect current fair value. In low-liquidity markets, use the mid-price (average of best bid and ask) instead.
orderbook = client.get_orderbook(pos.market_id, outcome = Outcome. YES )
if orderbook.bids and orderbook.asks:
mid_price = (orderbook.bids[ 0 ].price + orderbook.asks[ 0 ].price) // 2
yes_price = mid_price # Use mid instead of last
P&L doesn’t account for pending orders. If you have open orders on the book, their value isn’t included. Only filled positions are counted.
API Reference
get_user_positions(address: str) -> list[Position]
Fetch all positions for a wallet.
Returns:
class Position :
market_id: str
yes_shares: int # 6 decimals
no_shares: int # 6 decimals
invested: int # USDC spent (6 decimals)
get_user_activity(address: str) -> UserActivity
Fetch aggregate activity stats.
Returns:
class UserActivity :
total_trades: int
total_volume: int # USDC volume (6 decimals)
markets_traded: int
get_stats(market_id: str) -> MarketStats
Fetch market statistics including last trade price.
Returns:
class MarketStats :
last_price: int # Last trade price (6 decimals)
volume_24h: int # 24h volume (6 decimals)
trade_count: int # Total trades
Next Steps
Claim Winnings Claim resolved positions to get USDC back. View source
Price Action Bot See how the reference bot tracks positions in real-time.
API Reference Explore all position-related SDK methods.
Market Maker Bot Study how the MM uses inventory tracking for risk management.