1- """Paper trading — health & liquidation inspection.
1+ """Paper trading — cross-market health & liquidation inspection.
22
3- Demonstrates how account health and liquidation prices change
4- depending on leverage. Runs two scenarios:
5- 1. Conservative — large collateral, small position, no liquidation risk
6- 2. Aggressive — small collateral, large position, tight liquidation price
3+ Demonstrates how account health and liquidation prices change for the
4+ same two-market portfolio under different collateral levels. Runs two
5+ cross-margin scenarios:
6+ 1. Conservative — large collateral, modest ETH + BTC exposure
7+ 2. Aggressive — smaller collateral, larger ETH + BTC exposure
78"""
89
910import asyncio
1011import lighter
1112
13+ ETH_MARKET_ID = 0
14+ BTC_MARKET_ID = 1
15+ MARKETS = [
16+ (ETH_MARKET_ID , "ETH-PERP" ),
17+ (BTC_MARKET_ID , "BTC-PERP" ),
18+ ]
1219
13- def print_health (paper : lighter .PaperClient , markets : list ):
20+
21+ def round_size (size : float , decimals : int ) -> float :
22+ return round (size , decimals )
23+
24+
25+ def size_for_notional (
26+ paper : lighter .PaperClient ,
27+ market_id : int ,
28+ notional_usdc : float ,
29+ ) -> float :
30+ config = paper .market_configs [market_id ]
31+ raw_size = notional_usdc / config .last_trade_price
32+ return max (round_size (raw_size , config .size_decimals ), config .min_base_amount )
33+
34+
35+ async def track_markets (paper : lighter .PaperClient , markets : list [tuple [int , str ]]) -> None :
36+ for market_id , _ in markets :
37+ await paper .track_market_snapshot (market_id )
38+
39+
40+ def print_health (paper : lighter .PaperClient , markets : list [tuple [int , str ]]) -> None :
1441 health = paper .get_health ()
1542 print (f" Health status: { health .status .name } " )
1643 print (f" Total account value: { health .total_account_value :.2f} USDC" )
@@ -26,7 +53,7 @@ def print_health(paper: lighter.PaperClient, markets: list):
2653 liq_str = f"${ liq_price :.2f} " if liq_price > 0 else "n/a (fully collateralized)"
2754 side = "LONG" if position .size > 0 else "SHORT"
2855 print (
29- f" { label } { side } { abs (position .size )} "
56+ f" { label } { side } { abs (position .size ):g } "
3057 f" entry=${ position .avg_entry_price :.2f} "
3158 f" mark=${ position .mark_price :.2f} "
3259 f" unrealized_pnl={ position .unrealized_pnl :.2f} "
@@ -45,59 +72,93 @@ async def main():
4572 )
4673
4774 # ── Scenario 1: Conservative (low leverage) ──────────────────────
48- # $10,000 collateral backing a 0.5 ETH long → ~0.12x leverage .
49- # The position is so over-collateralized that no liquidation price
50- # exists (ETH would have to go below $0) .
75+ # $10,000 collateral backing a modest ETH long + BTC short .
76+ # The portfolio stays lightly levered with wide or nonexistent
77+ # liquidation thresholds .
5178 print ("=" * 60 )
52- print ("SCENARIO 1: Conservative — $10,000 collateral, 0.5 ETH long " )
79+ print ("SCENARIO 1: Conservative — $10,000 collateral, ETH + BTC portfolio " )
5380 print ("=" * 60 )
5481
5582 conservative = lighter .PaperClient (api_client , initial_collateral_usdc = 10_000 )
56- await conservative .track_market_snapshot (market_id = 0 )
83+ await track_markets (conservative , MARKETS )
84+
85+ conservative_eth_size = size_for_notional (conservative , ETH_MARKET_ID , 1_500 )
86+ conservative_btc_size = size_for_notional (conservative , BTC_MARKET_ID , 1_000 )
5787
5888 await conservative .create_paper_order (
5989 lighter .PaperOrderRequest (
60- market_id = 0 ,
90+ market_id = ETH_MARKET_ID ,
6191 side = lighter .PaperOrderSide .BUY ,
62- base_amount = 0.5 ,
92+ base_amount = conservative_eth_size ,
6393 )
6494 )
65- print_health (conservative , [(0 , "ETH-PERP" )])
95+ await conservative .create_paper_order (
96+ lighter .PaperOrderRequest (
97+ market_id = BTC_MARKET_ID ,
98+ side = lighter .PaperOrderSide .SELL ,
99+ base_amount = conservative_btc_size ,
100+ )
101+ )
102+ print (
103+ f"Opened { conservative_eth_size :g} ETH long and "
104+ f"{ conservative_btc_size :g} BTC short."
105+ )
106+ print_health (conservative , MARKETS )
66107
67108 # ── Scenario 2: Aggressive (high leverage) ───────────────────────
68- # $600 collateral backing a 4 ETH long → ~15x leverage.
69- # Liquidation price will be tight — a ~5% drop would trigger it.
109+ # $1,500 collateral backing the same market mix at much larger size.
110+ # Cross-margin still shares collateral across both markets, but
111+ # liquidation prices should move much closer to the current marks.
70112 print ()
71113 print ("=" * 60 )
72- print ("SCENARIO 2: Aggressive — $600 collateral, 4 ETH long " )
114+ print ("SCENARIO 2: Aggressive — $1,500 collateral, same markets at larger size " )
73115 print ("=" * 60 )
74116
75- aggressive = lighter .PaperClient (api_client , initial_collateral_usdc = 600 )
76- await aggressive .track_market_snapshot (market_id = 0 )
117+ aggressive = lighter .PaperClient (api_client , initial_collateral_usdc = 1_500 )
118+ await track_markets (aggressive , MARKETS )
119+
120+ aggressive_eth_size = size_for_notional (aggressive , ETH_MARKET_ID , 6_000 )
121+ aggressive_btc_size = size_for_notional (aggressive , BTC_MARKET_ID , 3_000 )
77122
78123 await aggressive .create_paper_order (
79124 lighter .PaperOrderRequest (
80- market_id = 0 ,
125+ market_id = ETH_MARKET_ID ,
81126 side = lighter .PaperOrderSide .BUY ,
82- base_amount = 4.0 ,
127+ base_amount = aggressive_eth_size ,
128+ )
129+ )
130+ await aggressive .create_paper_order (
131+ lighter .PaperOrderRequest (
132+ market_id = BTC_MARKET_ID ,
133+ side = lighter .PaperOrderSide .SELL ,
134+ base_amount = aggressive_btc_size ,
83135 )
84136 )
85- print_health (aggressive , [(0 , "ETH-PERP" )])
137+ print (
138+ f"Opened { aggressive_eth_size :g} ETH long and "
139+ f"{ aggressive_btc_size :g} BTC short."
140+ )
141+ print_health (aggressive , MARKETS )
86142
87143 # Show the contrast
88- cons_liq = conservative .get_liquidation_price (0 )
89- aggr_liq = aggressive .get_liquidation_price (0 )
90- aggr_pos = aggressive .get_position (0 )
91144 print ()
92145 print ("-" * 60 )
93146 print ("COMPARISON" )
94- cons_liq_str = "n/a (can't be liquidated)" if cons_liq == 0 else f"${ cons_liq :.2f} "
95- print (f" Conservative liq price: { cons_liq_str } " )
96- if aggr_pos is None :
97- reason = "already liquidated" if aggressive .get_health ().has_been_liquidated else "no open position"
98- print (f" Aggressive position: { reason } " )
99- else :
100- print (f" Aggressive liq price: ${ aggr_liq :.2f} (${ aggr_pos .mark_price - aggr_liq :.2f} below mark)" )
147+ for market_id , label in MARKETS :
148+ cons_liq = conservative .get_liquidation_price (market_id )
149+ aggr_liq = aggressive .get_liquidation_price (market_id )
150+ aggr_pos = aggressive .get_position (market_id )
151+ cons_liq_str = "n/a (can't be liquidated)" if cons_liq == 0 else f"${ cons_liq :.2f} "
152+ print (f" { label } conservative liq: { cons_liq_str } " )
153+ if aggr_pos is None :
154+ reason = "already liquidated" if aggressive .get_health ().has_been_liquidated else "no open position"
155+ print (f" { label } aggressive: { reason } " )
156+ else :
157+ distance = abs (aggr_pos .mark_price - aggr_liq )
158+ print (
159+ f" { label } aggressive liq: ${ aggr_liq :.2f} "
160+ f"(${ distance :.2f} from mark)"
161+ )
101162 print ("-" * 60 )
102163
103164 await api_client .close ()
0 commit comments