P&L Fundamentals for Multi-Service AI Agents
A Purple Flea agent typically interacts with multiple services simultaneously: placing casino bets, running trading positions, receiving escrow payments, and managing wallets across 8 chains. Each of these generates distinct P&L events that must be tracked with different accounting treatment.
Realized vs. Unrealized P&L
Realized P&L
Exists only when a position is closed: a bet is settled, a trade is exited, an escrow is released. Money is definitively in or out of the portfolio.
Unrealized P&L
Open perp positions, pending casino bets, held tokens. The P&L is calculated at current market price but can reverse before closing.
Total P&L
Portfolio net change from inception or from a defined reporting period. Includes all fees, funding costs, and network gas expenses.
P&L Event Categories Across Purple Flea Services
| Service | Event Type | P&L Treatment | Fees to Track |
|---|---|---|---|
| Casino API | Bet settlement | Realized (win/loss) | House edge (baked in, track expected value) |
| Trading API (Spot) | Buy/Sell execution | Realized on close, Unrealized while held | Trading fee (0.1-0.3%), gas costs |
| Trading API (Perp) | Position open/close | Realized on close, Unrealized while open | Funding rate, trading fee, liquidation risk |
| Wallet API | Asset appreciation | Unrealized (no taxable event until sale) | Bridge fees, gas on transfers |
| Escrow | Payment receipt/release | Realized (income or cost) | 1% escrow fee, 15% referral income |
| Faucet | USDC claim | Income (realized at receipt) | None (free) |
Unlike trading, casino P&L is stochastic. An agent playing blackjack with correct basic strategy has a -0.5% house edge. A "realized P&L" of +$500 after 100 hands is partially luck. Track both actual P&L and expected P&L (hands * avg_bet * house_edge) to separate skill from variance.
Cost Basis Methods: FIFO, LIFO, and HIFO
When you sell an asset, the "cost basis" of that asset determines your realized P&L. If you bought ETH at different prices on different dates, which purchase are you selling? The answer depends on your chosen accounting method — and it significantly affects both reported P&L and tax liability.
First In, First Out. The oldest purchased units are treated as sold first.
Last In, First Out. Most recently purchased units are sold first.
Highest In, First Out. Highest cost basis units are sold first, minimizing gains.
Concrete Example: Same Trades, Different Methods
Purchase history for ETH (all USDC):
┌──────────────┬──────────┬────────────┬───────────────┐
│ Date │ Quantity │ Price/ETH │ Total Cost │
├──────────────┼──────────┼────────────┼───────────────┤
│ Jan 10, 2026 │ 2.0 ETH │ $3,000 │ $6,000 (Lot A)│
│ Feb 15, 2026 │ 1.5 ETH │ $3,500 │ $5,250 (Lot B)│
│ Mar 01, 2026 │ 1.0 ETH │ $3,200 │ $3,200 (Lot C)│
└──────────────┴──────────┴────────────┴───────────────┘
Sell event: 1.5 ETH at $3,800 on Mar 4, 2026
Sale proceeds: 1.5 × $3,800 = $5,700
┌───────────────────────────────────────────────────────────────┐
│ Method │ Lots Used │ Cost Basis │ Gain/Loss │
├───────────────────────────────────────────────────────────────┤
│ FIFO │ 1.5 ETH from Lot A │ $4,500 │ +$1,200 ✅ │
│ │ (oldest, $3,000/each) │ │ (taxable) │
├───────────────────────────────────────────────────────────────┤
│ LIFO │ 1.0 ETH Lot C + │ $4,950 │ +$750 │
│ │ 0.5 ETH Lot B │ │ (less tax) │
├───────────────────────────────────────────────────────────────┤
│ HIFO │ 1.5 ETH from Lot B │ $5,250 │ +$450 🏆 │
│ │ (highest basis, $3,500) │ │ (min tax) │
└───────────────────────────────────────────────────────────────┘
HIFO saves $750 in taxable gains vs FIFO on a single trade.
At 30% capital gains rate: $225 in tax savings per trade.
Choose a cost basis method and stick with it for the entire tax year. In the US, crypto is treated as property; switching mid-year requires amendments. Consult a tax professional for your jurisdiction before choosing a method.
Fee Accounting: The Hidden P&L Destroyer
Fees are the invisible tax on every agent operation. Across 8 chains and 6 Purple Flea services, fees compound quickly. An agent that does not track fees precisely will dramatically overstate its profitability.
Complete Fee Taxonomy
| Fee Type | Source | Typical Rate | Accounting Treatment |
|---|---|---|---|
| Trading Fee | Purple Flea Trading API | 0.1-0.3% per trade | Deduct from proceeds on sell / add to cost on buy |
| Funding Rate | Perpetual positions | ±0.01%/8h | Realized P&L (positive = income, negative = cost) |
| Escrow Fee | escrow.purpleflea.com | 1% of escrow value | Transaction cost (reduces net received) |
| Referral Income | Escrow referrals | 15% of escrow fees | Realized income |
| Gas (ETH) | Ethereum/MATIC/BNB/AVAX | $0.10-$20 | Cost (adds to basis of asset acquired) |
| Bridge Fee | Stargate/Wormhole/etc | 0.05-0.10% | Cost of capital movement; reduces net transferred |
| Casino House Edge | Purple Flea Casino API | 0.5-5% (game-dependent) | Expected value loss; track separately from variance |
In the US and most jurisdictions, gas fees paid to acquire a crypto asset add to the asset's cost basis. The gas paid when buying ETH increases your basis, reducing future capital gains. Gas paid when selling reduces your proceeds. Track this correctly or you will over-pay taxes.
Multi-Chain P&L Aggregation
Aggregating P&L across 8 chains requires solving the same asset on different chains: ETH on Ethereum and WETH on Polygon are economically equivalent but technically different tokens. A rigorous P&L system must normalize these into a single economic position.
Asset Normalization Rules
- Native ETH and WETH should be treated as the same asset in P&L. Price both using ETH/USD market price.
- USDC on any chain is effectively the same asset at $1.00. Track chain location separately but aggregate in a single USDC position.
- Bridged assets (e.g., Wormhole-wrapped SOL on ETH): track the bridge event as a disposal on SOL chain and acquisition on ETH chain for tax purposes, but treat as the same economic position for portfolio P&L.
- Casino chips / in-game tokens: value at zero cost basis if acquired through play; value at market if purchased.
Agent Portfolio Snapshot (normalized to USD):
─────────────────────────────────────────────────────────
ASSET CLASS CHAIN(S) VALUE P&L TODAY
─────────────────────────────────────────────────────────
ETH (all forms) ETH, MATIC, AVAX $28,400 +$420
SOL SOL $12,200 -$180
BTC BTC $8,100 +$95
USDC (stables) ETH, SOL, BNB $9,500 $0
BNB BNB $2,300 +$45
Casino Winnings (settled, USD eq) $680 +$680
Open Perp PnL ETH/USD Long +$320 +$320
─────────────────────────────────────────────────────────
TOTAL $61,500 +$1,380
─────────────────────────────────────────────────────────
Fees today: Gas: -$12.40 Trading: -$8.20 Total: -$20.60
Net P&L (after fees): +$1,359.40
Python P&L Tracker: Full Implementation
The following tracker pulls from all Purple Flea APIs, normalizes positions across chains, applies configurable cost basis methods, and generates daily P&L reports with full fee breakdown.
""" Multi-chain, multi-service P&L tracker for Purple Flea AI agents. Supports FIFO, LIFO, HIFO cost basis methods. Aggregates: Casino, Trading (Spot + Perp), Wallet, Escrow, Faucet. """ import asyncio import csv import json import logging from collections import defaultdict, deque from dataclasses import dataclass, field, asdict from decimal import Decimal from enum import Enum from typing import Dict, List, Optional, Tuple from datetime import datetime, timezone import aiohttp log = logging.getLogger('pnl_tracker') PURPLEFLEA_BASE = "https://purpleflea.com" API_KEY = "your_api_key_here" # ─── Enums & Constants ─────────────────────────────────────────────────────── class CostBasisMethod(Enum): FIFO = "fifo" LIFO = "lifo" HIFO = "hifo" class EventType(Enum): BUY = "buy" SELL = "sell" CASINO_WIN = "casino_win" CASINO_LOSS = "casino_loss" PERP_OPEN = "perp_open" PERP_CLOSE = "perp_close" FUNDING = "funding" BRIDGE_OUT = "bridge_out" BRIDGE_IN = "bridge_in" FEE = "fee" ESCROW_RECEIVE = "escrow_receive" ESCROW_SEND = "escrow_send" FAUCET_CLAIM = "faucet_claim" REFERRAL_EARN = "referral_earn" # ─── Data Structures ───────────────────────────────────────────────────────── @dataclass class TaxLot: """A single acquired unit for cost basis tracking.""" quantity: Decimal cost_per_unit: Decimal timestamp: float source: str lot_id: str @dataclass class PnLEvent: event_type: EventType asset: str chain: str quantity: Decimal price_usd: Decimal fee_usd: Decimal timestamp: float tx_hash: Optional[str] = None metadata: Dict = field(default_factory=dict) @property def gross_usd(self) -> Decimal: return self.quantity * self.price_usd @property def net_usd(self) -> Decimal: return self.gross_usd - self.fee_usd @dataclass class RealizedGain: asset: str quantity_sold: Decimal sale_price: Decimal cost_basis: Decimal gain_usd: Decimal timestamp: float method: str lot_id: str @dataclass class PnLReport: period_start: datetime period_end: datetime realized_pnl: Decimal unrealized_pnl: Decimal total_fees: Decimal casino_pnl: Decimal trading_pnl: Decimal funding_pnl: Decimal escrow_pnl: Decimal faucet_income: Decimal net_pnl: Decimal gain_events: List[RealizedGain] fee_breakdown: Dict[str, Decimal] # ─── Cost Basis Engine ─────────────────────────────────────────────────────── class CostBasisEngine: def __init__(self, method: CostBasisMethod = CostBasisMethod.HIFO): self.method = method # asset → list of TaxLot (ordered by acquisition time) self.lots: Dict[str, List[TaxLot]] = defaultdict(list) self.gains: List[RealizedGain] = [] def acquire(self, asset: str, quantity: Decimal, cost_per_unit: Decimal, ts: float, source: str): """Record an asset acquisition (buy, faucet claim, etc).""" lot_id = f"{asset}_{int(ts*1000)}" lot = TaxLot( quantity=quantity, cost_per_unit=cost_per_unit, timestamp=ts, source=source, lot_id=lot_id, ) self.lots[asset].append(lot) self.lots[asset].sort(key=lambda x: x.timestamp) # keep chronological log.debug(f"Acquired {quantity} {asset} at ${cost_per_unit:.4f} (lot {lot_id})") def dispose( self, asset: str, quantity: Decimal, sale_price: Decimal, ts: float ) -> Tuple[Decimal, List[RealizedGain]]: """ Record disposal (sell, swap out). Returns (realized_gain, lots_used). Applies configured cost basis method to select lots. """ available = sum(lot.quantity for lot in self.lots[asset]) if quantity > available: log.warning(f"Disposing {quantity} {asset} but only {available} in lots") quantity = available # Sort lots by method ordered = sorted( self.lots[asset], key=lambda l: ( l.timestamp if self.method == CostBasisMethod.FIFO else -l.timestamp if self.method == CostBasisMethod.LIFO else -l.cost_per_unit # HIFO: highest cost first ) ) remaining = quantity total_cost_basis = Decimal('0') new_gains = [] for lot in ordered: if remaining <= 0: break use_qty = min(remaining, lot.quantity) cost_basis_portion = use_qty * lot.cost_per_unit gain = use_qty * sale_price - cost_basis_portion gain_event = RealizedGain( asset=asset, quantity_sold=use_qty, sale_price=sale_price, cost_basis=lot.cost_per_unit, gain_usd=gain, timestamp=ts, method=self.method.value, lot_id=lot.lot_id, ) new_gains.append(gain_event) self.gains.append(gain_event) total_cost_basis += cost_basis_portion lot.quantity -= use_qty remaining -= use_qty # Remove exhausted lots self.lots[asset] = [l for l in self.lots[asset] if l.quantity > 0] total_gain = quantity * sale_price - total_cost_basis return total_gain, new_gains def total_cost_basis(self, asset: str) -> Decimal: return sum(l.quantity * l.cost_per_unit for l in self.lots[asset]) def total_quantity(self, asset: str) -> Decimal: return sum(l.quantity for l in self.lots[asset]) # ─── Purple Flea API Data Fetcher ───────────────────────────────────────────── class PurpleFleasDataFetcher: def __init__(self, api_key: str): self.headers = {"Authorization": f"Bearer {api_key}"} self._session: Optional[aiohttp.ClientSession] = None async def __aenter__(self): self._session = aiohttp.ClientSession(headers=self.headers) return self async def __aexit__(self, *args): await self._session.close() async def get_casino_history(self, since: float) -> List[PnLEvent]: async with self._session.get( f"{PURPLEFLEA_BASE}/casino-api/history", params={"since": int(since)} ) as r: data = await r.json() events = [] for bet in data["bets"]: etype = EventType.CASINO_WIN if bet["outcome"] == "win" else EventType.CASINO_LOSS pnl = Decimal(str(bet["net_pnl"])) events.append(PnLEvent( event_type=etype, asset="USDC", chain=bet.get("chain", "ETH"), quantity=abs(pnl), price_usd=Decimal("1.00"), fee_usd=Decimal(str(bet.get("fee", 0))), timestamp=bet["settled_at"], tx_hash=bet.get("tx_hash"), metadata={"game": bet["game"], "bet_amount": bet["amount"]}, )) return events async def get_trading_history(self, since: float) -> List[PnLEvent]: async with self._session.get( f"{PURPLEFLEA_BASE}/trading-api/history", params={"since": int(since)} ) as r: data = await r.json() events = [] for trade in data["trades"]: etype = EventType.BUY if trade["side"] == "buy" else EventType.SELL events.append(PnLEvent( event_type=etype, asset=trade["base_asset"], chain=trade.get("chain", "ETH"), quantity=Decimal(str(trade["quantity"])), price_usd=Decimal(str(trade["price"])), fee_usd=Decimal(str(trade["fee_usd"])), timestamp=trade["executed_at"], tx_hash=trade.get("tx_hash"), metadata={"pair": trade["pair"], "type": trade["type"]}, )) return events async def get_wallet_history(self, since: float) -> List[PnLEvent]: async with self._session.get( f"{PURPLEFLEA_BASE}/wallet-api/history", params={"since": int(since)} ) as r: data = await r.json() events = [] for tx in data["transactions"]: if tx["type"] == "bridge_out": etype = EventType.BRIDGE_OUT elif tx["type"] == "bridge_in": etype = EventType.BRIDGE_IN else: continue # plain transfers are not P&L events events.append(PnLEvent( event_type=etype, asset=tx["asset"], chain=tx["chain"], quantity=Decimal(str(tx["amount"])), price_usd=Decimal(str(tx.get("price_usd_at_time", 0))), fee_usd=Decimal(str(tx.get("fee_usd", 0))), timestamp=tx["timestamp"], tx_hash=tx.get("tx_hash"), )) return events async def get_escrow_history(self, since: float) -> List[PnLEvent]: async with self._session.get( f"https://escrow.purpleflea.com/api/history", headers=self.headers, params={"since": int(since)} ) as r: data = await r.json() events = [] for e in data["escrows"]: etype = EventType.ESCROW_RECEIVE if e["role"] == "recipient" else EventType.ESCROW_SEND # Escrow fee: 1% platform fee + 15% of that goes to referrer escrow_fee = Decimal(str(e["amount"])) * Decimal("0.01") events.append(PnLEvent( event_type=etype, asset=e["asset"], chain=e["chain"], quantity=Decimal(str(e["amount"])), price_usd=Decimal(str(e.get("price_usd_at_time", 1))), fee_usd=escrow_fee, timestamp=e["released_at"], tx_hash=e.get("tx_hash"), metadata={"counterparty": e.get("counterparty")}, )) # Track referral earnings separately if any if e.get("referral_earned"): events.append(PnLEvent( event_type=EventType.REFERRAL_EARN, asset="USDC", chain=e["chain"], quantity=Decimal(str(e["referral_earned"])), price_usd=Decimal("1.00"), fee_usd=Decimal("0"), timestamp=e["released_at"], )) return events async def get_current_prices(self, assets: List[str]) -> Dict[str, Decimal]: async with self._session.get( f"{PURPLEFLEA_BASE}/wallet-api/prices", params={"assets": ",".join(assets)} ) as r: data = await r.json() return {a: Decimal(str(p)) for a, p in data["prices"].items()} # ─── Main P&L Tracker ──────────────────────────────────────────────────────── class AgentPnLTracker: def __init__(self, api_key: str, method: CostBasisMethod = CostBasisMethod.HIFO): self.engine = CostBasisEngine(method) self.fetcher = PurpleFleasDataFetcher(api_key) self.all_events: List[PnLEvent] = [] async def ingest_all_events(self, since: float): """Pull all P&L events from all Purple Flea services.""" async with self.fetcher: casino_events, trading_events, wallet_events, escrow_events = await asyncio.gather( self.fetcher.get_casino_history(since), self.fetcher.get_trading_history(since), self.fetcher.get_wallet_history(since), self.fetcher.get_escrow_history(since), ) all_events = casino_events + trading_events + wallet_events + escrow_events # Process in chronological order for correct cost basis assignment all_events.sort(key=lambda e: e.timestamp) self.all_events = all_events log.info(f"Ingested {len(all_events)} events from all services") def process_events(self) -> PnLReport: """Process all events and compute P&L report.""" casino_pnl = Decimal("0") trading_pnl = Decimal("0") funding_pnl = Decimal("0") escrow_pnl = Decimal("0") faucet_inc = Decimal("0") total_fees = Decimal("0") fee_breakdown: Dict[str, Decimal] = defaultdict(Decimal) gain_events: List[RealizedGain] = [] for event in self.all_events: total_fees += event.fee_usd fee_breakdown[event.event_type.value] += event.fee_usd if event.event_type == EventType.BUY: self.engine.acquire( event.asset, event.quantity, event.price_usd + (event.fee_usd / event.quantity), # fee adds to basis event.timestamp, "trade" ) elif event.event_type == EventType.SELL: gain, lots = self.engine.dispose( event.asset, event.quantity, event.price_usd - (event.fee_usd / event.quantity), event.timestamp ) trading_pnl += gain gain_events.extend(lots) elif event.event_type == EventType.CASINO_WIN: casino_pnl += event.net_usd elif event.event_type == EventType.CASINO_LOSS: casino_pnl -= event.net_usd elif event.event_type == EventType.FUNDING: funding_pnl += event.net_usd elif event.event_type in (EventType.ESCROW_RECEIVE, EventType.REFERRAL_EARN): escrow_pnl += event.net_usd elif event.event_type == EventType.ESCROW_SEND: escrow_pnl -= event.fee_usd # only the fee is a cost here elif event.event_type == EventType.FAUCET_CLAIM: faucet_inc += event.net_usd self.engine.acquire("USDC", event.quantity, Decimal("0"), event.timestamp, "faucet") realized = trading_pnl + casino_pnl + funding_pnl + escrow_pnl + faucet_inc # Unrealized: would require current prices — computed separately period_events = self.all_events start = datetime.fromtimestamp(period_events[0].timestamp, tz=timezone.utc) if period_events else datetime.now(tz=timezone.utc) end = datetime.fromtimestamp(period_events[-1].timestamp, tz=timezone.utc) if period_events else datetime.now(tz=timezone.utc) return PnLReport( period_start=start, period_end=end, realized_pnl=realized, unrealized_pnl=Decimal("0"), # filled below total_fees=total_fees, casino_pnl=casino_pnl, trading_pnl=trading_pnl, funding_pnl=funding_pnl, escrow_pnl=escrow_pnl, faucet_income=faucet_inc, net_pnl=realized - total_fees, gain_events=gain_events, fee_breakdown=dict(fee_breakdown), ) def export_csv(self, report: PnLReport, path: str): """Export gain events to CSV for tax filing.""" with open(path, "w", newline="") as f: writer = csv.DictWriter(f, fieldnames=[ "date", "asset", "quantity", "sale_price", "cost_basis", "gain_usd", "method", "lot_id" ]) writer.writeheader() for gain in sorted(report.gain_events, key=lambda g: g.timestamp): writer.writerow({ "date": datetime.fromtimestamp(gain.timestamp).strftime("%Y-%m-%d"), "asset": gain.asset, "quantity": str(gain.quantity_sold), "sale_price": str(gain.sale_price), "cost_basis": str(gain.cost_basis), "gain_usd": str(gain.gain_usd), "method": gain.method, "lot_id": gain.lot_id, }) log.info(f"Exported {len(report.gain_events)} gain events to {path}") # ─── Main: Daily Report ────────────────────────────────────────────────────── async def generate_daily_report(api_key: str, method: CostBasisMethod = CostBasisMethod.HIFO): import time since_24h = time.time() - 86400 tracker = AgentPnLTracker(api_key, method) await tracker.ingest_all_events(since_24h) report = tracker.process_events() print(f"\n=== Purple Flea Agent P&L Report ===") print(f"Period: {report.period_start:%Y-%m-%d %H:%M} → {report.period_end:%Y-%m-%d %H:%M}") print(f"Cost Basis Method: {method.value.upper()}") print(f"\nP&L Breakdown:") print(f" Casino: {'+' if report.casino_pnl >= 0 else ''}${report.casino_pnl:.2f}") print(f" Trading: {'+' if report.trading_pnl >= 0 else ''}${report.trading_pnl:.2f}") print(f" Funding: {'+' if report.funding_pnl >= 0 else ''}${report.funding_pnl:.2f}") print(f" Escrow: +${report.escrow_pnl:.2f}") print(f" Faucet: +${report.faucet_income:.2f}") print(f" Fees: -${report.total_fees:.2f}") print(f" ─────────────────────") print(f" Net P&L: {'+' if report.net_pnl >= 0 else ''}${report.net_pnl:.2f}") tracker.export_csv(report, f"pnl_report_{method.value}_{int(time.time())}.csv") return report if __name__ == "__main__": asyncio.run(generate_daily_report(API_KEY, CostBasisMethod.HIFO))
Tax Reporting for AI Agent Activities
Tax treatment of AI agent activities is still evolving in most jurisdictions, but the following principles apply broadly. Consult a qualified tax professional for jurisdiction-specific guidance.
| Activity | US Tax Treatment | Reporting Form | Notes |
|---|---|---|---|
| Token trading gains | Capital gains (short/long term) | Form 8949 | HIFO minimizes gains; 1-year holding for long-term rate |
| Casino winnings | Ordinary income | Schedule C or 1040 | Net gambling income; losses offset winnings up to win amount |
| Faucet income | Ordinary income at fair market value | Schedule 1 | Cost basis = FMV at receipt; future gain is additional cap gain |
| Referral income (Escrow) | Ordinary income | Schedule C | Self-employment income if running an agent business |
| Gas fees | Add to cost basis (on buy) / reduce proceeds (on sell) | Form 8949 | Do not deduct gas separately — adjust basis instead |
| Bridge transfers | Disposal + acquisition event | Form 8949 | Each bridge may be a taxable event; same asset, different chain |
Year-End P&L Optimization Strategies
Tax Loss Harvesting
Sell positions with unrealized losses before year-end to offset realized gains. With 8 chains and 6 services, there are usually harvesting opportunities somewhere.
Wash Sale Awareness
US wash sale rules don't formally apply to crypto (yet), but many jurisdictions may adopt them. Avoid buying back the same asset within 30 days of a harvested loss.
Long-Term Holding
Assets held over 1 year qualify for long-term capital gains rates (0-20% vs 10-37% ordinary). Agents can segregate long-term holds from active trading assets.
Entity Structure
High-volume AI agents may benefit from operating through an LLC or corporation. Trading losses can offset other income; wash sale rules may differ at entity level.
The P&L tracker above generates Form 8949-compatible CSV output. Don't wait until tax season to reconstruct your agent's transaction history. Every Purple Flea API provides complete transaction history via the /history endpoint. Configure your tracker to run daily and archive all events.
Handling Missing Price Data
A common P&L tracking problem is missing historical prices for obscure assets or times when no price feed was available. Best practices:
- Always record prices at the time of execution, not retrospectively. Purple Flea APIs include
price_usd_at_timein all transaction responses. - For assets with no centralized price feed, use the last available on-chain DEX price (e.g., from Uniswap V3 pool) at the block timestamp.
- For casino chips and non-transferable in-game tokens, use $0 cost basis and treat all proceeds as gains upon redemption.
- If price data is genuinely unavailable (e.g., very new token), document the gap and apply a good-faith estimate with supporting evidence in your records.
Start Tracking Your Agent's P&L Today
Every Purple Flea API returns full transaction history with USD prices at execution time. Build your P&L tracker on top of our data and never lose a satoshi to poor accounting.