How to Calculate Profit and Loss for Long/Short Positions in AI-Trader
AI-Trader calculates P&L by aggregating the market value of open positions—using current_price × quantity for longs and (2 × entry_price − current_price) × |quantity| for shorts—then adding cash balance and subtracting the total invested capital (initial $100,000 plus any deposits) in the periodic background task record_profit_history.
AI-Trader is an open-source algorithmic trading framework that tracks agent performance through deterministic profit and loss calculations. To calculate profit and loss for long/short positions in AI-Trader, the system combines SQL aggregation logic with cash balance tracking, evaluating both realized and unrealized gains every 15 minutes or at the interval defined by PROFIT_HISTORY_RECORD_INTERVAL.
The Core P&L Formula
The profit calculation follows a straightforward equity-based formula implemented in service/server/tasks.py (lines 70-74):
total_value = cash + position_value
profit = total_value - (INITIAL_CAPITAL + deposited)
Key components:
- Cash: The agent's current liquid cash balance
- Position value: Aggregated market value of all open long and short positions
- INITIAL_CAPITAL: Fixed at
$100,000(defined inroutes_trading.py) - Deposited: Additional capital injected beyond the initial amount
How Position Values Are Calculated
The system uses a SQL CASE statement (lines 35-51 in tasks.py) to handle the asymmetric payoff structure of long versus short positions:
CASE
WHEN p.current_price IS NULL THEN p.entry_price * ABS(p.quantity)
WHEN p.side = 'long' THEN p.current_price * ABS(p.quantity)
ELSE (2 * p.entry_price - p.current_price) * ABS(p.quantity) -- short side
END
Long Position Valuation
For long positions, the valuation is intuitive:
- Formula:
current_price × |quantity| - Logic: The position value increases linearly as the market price rises above the entry price
Short Position Valuation
For short positions, AI-Trader implements the classic short-sell payoff:
- Formula:
(2 × entry_price − current_price) × |quantity| - Logic: This reflects the inverse relationship where profit accrues when the price falls below entry. If you short at
$100and the price drops to$90, the position value becomes(200 − 90) × 1 = $110, capturing the$10profit while maintaining the initial$100value.
The Background Calculation Process
The record_profit_history task in service/server/tasks.py orchestrates the P&L computation:
- Aggregate position values: Joins the
agentsandpositionstables, applying the SQLCASElogic to sum current market values - Calculate total equity: Adds cash balance to the aggregated position value
- Determine profit: Subtracts the cost basis (
INITIAL_CAPITAL + deposited) from total equity - Clamp extremes: Applies
clamp_profit_for_display(fromroutes_shared.py) to bound anomalous values before storage - Persist results: Inserts the calculated profit into the
profit_historytable with a timestamp
This process captures unrealized P&L from open positions combined with realized gains from closed trades and cash holdings.
Position Management and Side Tracking
Before profit calculation can occur, positions must be created with correct directional metadata. The _update_position_from_signal function in service/server/services.py handles four actions: buy, sell, short, and cover.
Critical implementation details:
- Long positions: Stored with
side='long'and positivequantityvalues - Short positions: Stored with
side='short'and negativequantityvalues
The sign convention ensures the SQL aggregation logic correctly distinguishes between long and short payoff structures when calculating total position value.
Retrieving Profit History via API
Once calculated, profit data is exposed through the REST API endpoint /api/profit/history defined in service/server/routes_trading.py. The endpoint returns both absolute profit values and percentages.
Percentage calculation (lines 34-38 in routes_trading.py):
profit_percent = (profit / (INITIAL_CAPITAL + deposited)) * 100
The raw profit value is first processed through clamp_profit_for_display in routes_shared.py to prevent extreme outliers from distorting the display values, ensuring the API returns sensible, bounded percentages.
Practical Implementation Examples
Opening a Long Position
from service.server.services import _update_position_from_signal
# Buy 0.5 BTC at $25,000
_update_position_from_signal(
agent_id=42,
symbol="BTC",
market="crypto",
action="buy",
quantity=0.5,
price=25000.0,
executed_at="2026-05-09T12:00:00Z"
)
Opening a Short Position
# Short 2 ETH at $1,800
_update_position_from_signal(
agent_id=42,
symbol="ETH",
market="crypto",
action="short",
quantity=2.0,
price=1800.0,
executed_at="2026-05-09T12:01:00Z"
)
Querying Profit via REST API
curl "https://<host>/api/profit/history?limit=5&days=30"
Sample response:
{
"agents": [
{
"agent_id": 42,
"name": "Trader42",
"profit": 12034.5,
"profit_percent": 12.03,
"recorded_at": "2026-05-09T12:30:00Z"
}
]
}
Manual Profit Calculation
To replicate the SQL logic in Python:
def calculate_profit(cash, deposited, positions, initial_capital=100000.0):
"""
positions: list of dicts with keys:
side: 'long' or 'short'
quantity: float (absolute value)
entry_price: float
current_price: float or None
"""
position_value = 0.0
for p in positions:
if p["current_price"] is None:
value = p["entry_price"] * abs(p["quantity"])
elif p["side"] == "long":
value = p["current_price"] * abs(p["quantity"])
else: # short
value = (2 * p["entry_price"] - p["current_price"]) * abs(p["quantity"])
position_value += value
total = cash + position_value
profit = total - (initial_capital + deposited)
return profit
Summary
- Long positions are valued at
current_price × quantity, while short positions use(2 × entry_price − current_price) × quantityto capture inverse payoff - Profit equals
(cash + position_value) − (INITIAL_CAPITAL + deposited), calculated inservice/server/tasks.py - The
record_profit_historybackground task runs every ~15 minutes (configurable viaPROFIT_HISTORY_RECORD_INTERVAL) - Position signs are critical: positive quantities indicate longs, negative quantities indicate shorts (managed in
services.py) - Historical data is stored in the
profit_historytable and served via/api/profit/historywith percentage formatting and clamping
Frequently Asked Questions
How does AI-Trader calculate P&L for short selling differently from long positions?
AI-Trader uses an asymmetric valuation formula where short positions are calculated as (2 × entry_price − current_price) × |quantity|. This mirrors the short-sell payoff structure: when the current price drops below the entry price, the position value increases, reflecting profit. In contrast, long positions simply multiply current price by quantity. The side column in the positions table and the sign of the quantity field determine which branch of the SQL CASE statement applies.
What is the default initial capital for AI-Trader agents?
According to the source code in service/server/routes_trading.py, the INITIAL_CAPITAL is fixed at $100,000 per agent. This base amount is combined with any additional deposited funds to establish the cost basis against which profit is calculated. The denominator for percentage returns is always this total invested capital (INITIAL_CAPITAL + deposited).
How frequently does AI-Trader update profit calculations?
The system updates profit history every 15 minutes by default, though this interval is configurable through the PROFIT_HISTORY_RECORD_INTERVAL environment variable or setting. The record_profit_history task in service/server/tasks.py performs this calculation asynchronously, aggregating current positions, updating market prices, and writing results to the profit_history table.
Where does AI-Trader store historical profit and loss data?
Calculated profit values are persisted in the profit_history table (defined in the database schema), which stores the agent ID, profit amount, timestamp, and associated metadata. The REST endpoint /api/profit/history queries this table to provide time-series data for performance tracking, applying clamp_profit_for_display from routes_shared.py to ensure values remain within displayable bounds.
Have a question about this repo?
These articles cover the highlights, but your codebase questions are specific. Give your agent direct access to the source. Share this with your agent to get started:
curl -s "https://instagit.com/install.md" Maintain an open-source project? Get it listed too →