Working with Positions¶
Positions are the core domain objects in qlcore, representing your holdings in an instrument.
Position Types¶
SpotPosition¶
For spot markets and equities where you own the underlying asset.
import qlcore as qc
from decimal import Decimal
position = qc.SpotPosition(
instrument_id="BTC-USD",
side=qc.PositionSide.LONG,
size=Decimal("1.0"),
avg_entry_price=Decimal("45000"),
realized_pnl=Decimal("0"),
unrealized_pnl=Decimal("0"),
total_fees=Decimal("45"),
)
PerpetualPosition¶
For perpetual futures with funding payments.
position = qc.PerpetualPosition(
instrument_id="BTC-USDT-PERP",
side=qc.PositionSide.LONG,
size=Decimal("1.0"),
avg_entry_price=Decimal("45000"),
accumulated_funding=Decimal("-12.50"), # Funding paid out
realized_pnl=Decimal("0"),
unrealized_pnl=Decimal("0"),
total_fees=Decimal("45"),
)
# Apply funding
new_position = position.apply_funding(Decimal("-5.00"))
print(new_position.accumulated_funding) # -17.50
# Total realized includes funding
print(new_position.total_realized_pnl) # realized_pnl + accumulated_funding
FuturesPosition¶
For expiring futures contracts.
import time
expiry = int(time.time() * 1000) + (30 * 24 * 60 * 60 * 1000) # 30 days
position = qc.FuturesPosition(
instrument_id="BTC-USD-MAR25",
side=qc.PositionSide.SHORT,
size=Decimal("0.5"),
avg_entry_price=Decimal("46000"),
expiry_ms=expiry,
realized_pnl=Decimal("0"),
unrealized_pnl=Decimal("0"),
total_fees=Decimal("23"),
)
# Check expiry status
print(position.is_expired()) # False
print(position.time_to_expiry()) # ~30 days in ms
# Settle at expiry
settled = position.settle(settlement_price=Decimal("47000"))
Creating Positions¶
Direct Construction¶
position = qc.SpotPosition(
instrument_id="BTC-USD",
side=qc.PositionSide.LONG,
size=Decimal("1.0"),
avg_entry_price=Decimal("45000"),
)
PositionBuilder (Fluent API)¶
position = (
qc.PositionBuilder("BTC-PERP")
.long()
.with_size("1.5")
.with_entry_price("45000")
.with_realized_pnl("100")
.with_fees("15")
.as_perpetual()
.build()
)
Builder methods:
| Method | Description |
|---|---|
.long() |
Set side to LONG |
.short() |
Set side to SHORT |
.with_size(size) |
Set position size |
.with_entry_price(price) |
Set average entry price |
.with_realized_pnl(pnl) |
Set realized PnL |
.with_unrealized_pnl(pnl) |
Set unrealized PnL |
.with_fees(fees) |
Set accumulated fees |
.as_perpetual() |
Build as PerpetualPosition |
.as_futures(expiry_ms) |
Build as FuturesPosition |
.build() |
Build the position (SpotPosition by default) |
From Dictionary¶
data = {
"instrument_id": "BTC-USD",
"side": "LONG",
"size": "1.0",
"avg_entry_price": "45000",
}
# SpotPosition.from_dict()
position = qc.SpotPosition.from_dict(data)
Updating Positions¶
Positions are immutable. Use evolve() to create modified copies:
# Update single field
updated = position.evolve(size=Decimal("2.0"))
# Update multiple fields
updated = position.evolve(
size=Decimal("2.0"),
avg_entry_price=Decimal("46000"),
realized_pnl=Decimal("500"),
)
Position Metrics¶
Mark to Market¶
Update unrealized PnL based on current price:
current_price = Decimal("46000")
mtm_position = qc.mark_to_market(position, mark_price=current_price)
print(mtm_position.unrealized_pnl) # Updated to current price
Unrealized PnL¶
Calculate PnL without modifying position:
Leverage¶
For positions with margin:
leverage = qc.position_leverage(position, margin=Decimal("4500"))
print(leverage) # 10x if notional is 45000
Position Summary¶
Get a REPL-friendly string representation:
Output:
Serialization¶
To Dictionary¶
To JSON¶
# Serialize
json_str = qc.to_json(position)
# Deserialize (type-aware)
position = qc.from_json(json_str, qc.PerpetualPosition)
# Or use generic deserialize
position = qc.deserialize_position(json_str) # Infers type
Cost Basis Tracking¶
Track individual lots for FIFO/LIFO tax calculations:
from qlcore.positions import Lot
lot1 = Lot(
quantity=Decimal("0.5"),
price=Decimal("44000"),
timestamp_ms=1701849600000,
)
lot2 = Lot(
quantity=Decimal("0.5"),
price=Decimal("46000"),
timestamp_ms=1701936000000,
)
# SpotPosition can track lots
position = qc.SpotPosition(
instrument_id="BTC-USD",
side=qc.PositionSide.LONG,
size=Decimal("1.0"),
avg_entry_price=Decimal("45000"),
lots=[lot1, lot2],
)