"""期权定价单测.""" from lib.options.options_pricing_lib import ( calc_order_size, premium_per_sheet, sheets_from_eth_amount, total_premium, ) from lib.exchange.okx_options_lib import format_option_px, inst_family_from_inst_id, round_option_px def test_inst_family_from_inst_id(): assert inst_family_from_inst_id("ETH-USD_UM-260707-1790-C") == "ETH-USD_UM" assert inst_family_from_inst_id("BTC-USD-260925-60000-C") == "BTC-USD" def test_round_option_px(): assert round_option_px(14.9184, "0.2", "sell") == 14.8 assert round_option_px(14.81, "0.2", "buy") == 15.0 assert format_option_px(14.8, "0.2") == "14.8" # BTC 期权 tickSz=5: 整数末尾 0 必须保留 (1370 不能显成 137) assert format_option_px(1370, "5") == "1370" assert format_option_px(1160, 5) == "1160" assert format_option_px(1000, "5") == "1000" # 无 tick 时不得透出浮点毛刺 assert format_option_px(482.4881990066513, None) == "482.4882" def test_premium_per_sheet(): assert abs(premium_per_sheet(15.6, 0.01) - 0.156) < 1e-9 def test_total_premium_half_eth(): assert abs(total_premium(15.6, 0.5) - 7.8) < 1e-9 def test_sheets_from_eth(): assert sheets_from_eth_amount(0.5, 0.01) == 50 def test_calc_order_size_budget(): r = calc_order_size( quote_per_unit=15.6, ct_mult=0.01, min_sz=1, budget_usdc=10, budget_buffer=0.95, budget_cap=10, ) assert r["ok"] is True assert r["sheets"] >= 1 assert r["total_premium"] <= 10 def test_calc_order_size_sheets(): r = calc_order_size( quote_per_unit=15.6, ct_mult=0.01, min_sz=1, sheets=3, budget_cap=10, ) assert r["ok"] is True assert r["sheets"] == 3 assert abs(r["total_premium"] - 0.468) < 1e-9 def test_option_moneyness(): from lib.options.options_pricing_lib import option_moneyness, option_moneyness_label assert option_moneyness(opt_type="C", strike=1700, index_px=1800) == "itm" assert option_moneyness(opt_type="C", strike=1900, index_px=1800) == "otm" assert option_moneyness_label("itm") == "实值" assert option_moneyness_label("otm") == "虚值" def test_equivalent_contract_leverage(): from lib.options.options_pricing_lib import equivalent_contract_leverage # index 1768, 0.2 ETH, premium 2.44 -> ~144.9x lev = equivalent_contract_leverage(index_px=1768, eth_amount=0.2, total_premium=2.44) assert lev == 144.9 def test_straddle_pricing(): from lib.options.options_pricing_lib import ( format_straddle_band, straddle_ask_per_unit, straddle_breakeven_band, straddle_premium_total, ) assert straddle_ask_per_unit(0.148, 16.2) == 16.348 assert straddle_premium_total(0.148, 16.2, 1.0) == 16.35 lo, hi = straddle_breakeven_band(1800, 16.348) assert lo == 1783.65 assert hi == 1816.35 assert format_straddle_band(1800, 16.348) == "1784 ~ 1816" assert straddle_ask_per_unit(0.148, None) is None def test_estimate_expiry_value_and_profit_at_index(): from lib.options.options_pricing_lib import ( estimate_expiry_profit_at_index, estimate_expiry_value_at_index, ) value = estimate_expiry_value_at_index( opt_type="C", strike=1800, target_idx=2000, eth_amount=1.0 ) assert value == 200.0 profit = estimate_expiry_profit_at_index( opt_type="C", strike=1800, target_idx=2000, entry_px=0.148, eth_amount=1.0, total_premium=14.8, ) assert profit == 185.2 # Call 1780, ask 12.2, 0.01 ETH, target 1793 -> value 0.13, profit 0.01 v = estimate_expiry_value_at_index( opt_type="C", strike=1780, target_idx=1793, eth_amount=0.01 ) assert v == 0.13 p = estimate_expiry_profit_at_index( opt_type="C", strike=1780, target_idx=1793, entry_px=12.2, eth_amount=0.01, total_premium=0.122, ) assert p == 0.01 # OTM call loses premium p2 = estimate_expiry_profit_at_index( opt_type="C", strike=1780, target_idx=1770, entry_px=12.2, eth_amount=0.01, total_premium=0.122, ) assert p2 == -0.12 def test_resolve_chain_quote_otm_no_quote(): from lib.exchange.okx_options_lib import _resolve_chain_quote q = _resolve_chain_quote( ticker={}, meta={"tickSz": "0.2"}, opt_type="C", strike=1800, index_px=1776, ) assert q["ask"] is None assert q["bid"] is None assert q["ask_estimated"] is False def test_resolve_chain_quote_estimated_ask(): from lib.exchange.okx_options_lib import _resolve_chain_quote q = _resolve_chain_quote( ticker={"bidPx": "0.2", "bidSz": "3500"}, meta={"tickSz": "0.2"}, opt_type="C", strike=1650, index_px=1776, ) assert q["ask_estimated"] is True assert q["ask"] is not None assert q["ask"] >= 120 def test_format_quote_liquidity(): from lib.options.options_pricing_lib import format_quote_liquidity assert format_quote_liquidity(17.2, 150) == "17.2/150" assert format_quote_liquidity(817.6, 11) == "817.6/11" assert format_quote_liquidity(15.6, None) == "15.6" assert format_quote_liquidity(None, 10) is None def test_estimate_close_by_bids_full_depth(): from lib.options.options_pricing_lib import estimate_close_by_bids # 多档估算需显式 max_levels;默认只估买一 out = estimate_close_by_bids( [{"px": 12.3, "sz": 2}, {"px": 12.1, "sz": 3}], 4, ct_mult=0.01, premium_paid=0.4, max_levels=5, ) assert out["covered_sheets"] == 4 assert out["uncovered_sheets"] == 0 assert out["total_received"] == 0.488 assert out["avg_px"] == 12.2 assert out["estimated_pnl"] == 0.088 assert out["estimated_pnl_ratio_pct"] == 22.0 assert [x["sheets"] for x in out["levels"]] == [2, 2] bid1 = estimate_close_by_bids( [{"px": 12.3, "sz": 2}, {"px": 12.1, "sz": 3}], 4, ct_mult=0.01, premium_paid=0.4, ) assert bid1["covered_sheets"] == 2 assert bid1["uncovered_sheets"] == 2 assert [x["sheets"] for x in bid1["levels"]] == [2] def test_estimate_close_by_bids_partial_depth(): from lib.options.options_pricing_lib import estimate_close_by_bids out = estimate_close_by_bids([{"px": 10, "sz": 1}], 3, ct_mult=0.01, premium_paid=0.6) assert out["covered_sheets"] == 1 assert out["uncovered_sheets"] == 2 assert out["total_received"] == 0.1 # 净盈亏 = 回收 − 全部权利金(不按覆盖比例摊薄) assert out["estimated_pnl"] == -0.5 assert out["estimated_pnl_ratio_pct"] == round(-0.5 / 0.6 * 100, 2) def test_estimate_close_by_bids_empty(): from lib.options.options_pricing_lib import estimate_close_by_bids out = estimate_close_by_bids([], 2) assert out["covered_sheets"] == 0 assert out["uncovered_sheets"] == 2 assert out["avg_px"] is None def test_stub_bid_blocks_auto_close_estimate(): from lib.options.options_pricing_lib import estimate_close_by_bids, is_stub_bid_px stub, reason = is_stub_bid_px(0.2, mark_px=42.0) assert stub is True assert "残档" in reason or "无效" in reason or "远低于" in reason out = estimate_close_by_bids( [{"px": 0.2, "sz": 3500}], 66, ct_mult=0.01, premium_paid=9.37, mark_px=42.0, ) assert out["auto_close_blocked"] is True assert out["bid_invalid"] is True assert out["estimated_pnl"] is None assert out["levels"] == [] ok, _ = is_stub_bid_px(30.0, mark_px=42.0) assert ok is False good = estimate_close_by_bids( [{"px": 30.0, "sz": 100}], 10, ct_mult=0.01, premium_paid=1.0, mark_px=42.0, ) assert good["auto_close_blocked"] is False assert good["covered_sheets"] == 10 def test_intrinsic_px_coin_vs_usdc_units(): from lib.options.options_pricing_lib import intrinsic_px_per_unit, is_stub_bid_px # USDC / 默认:美元点差 assert intrinsic_px_per_unit("C", 2250, 2274) == 24.0 assert intrinsic_px_per_unit("C", 2250, 2274, margin_mode="usdc") == 24.0 # 币本位:与盘口同单位的币报价 (S−K)/S coin_iv = intrinsic_px_per_unit("C", 2250, 2274, quote_in_coin=True) assert coin_iv is not None assert abs(coin_iv - 24.0 / 2274.0) < 1e-12 assert abs( intrinsic_px_per_unit("C", 2250, 2274, inst_id="ETH-USD-260822-2250-C") - 24.0 / 2274.0 ) < 1e-12 # USD_UM 仍为点差 assert intrinsic_px_per_unit("C", 2250, 2274, inst_id="ETH-USD_UM-260822-2250-C") == 24.0 # 复现线上误杀:把点差当内在价值会把正常买一判残档 wrong_stub, _ = is_stub_bid_px(0.023, mark_px=0.0241, intrinsic_px=23.58) assert wrong_stub is True # 币报价内在价值后,买一贴近标记价应有效 ok_stub, _ = is_stub_bid_px(0.023, mark_px=0.0241, intrinsic_px=coin_iv) assert ok_stub is False put_iv = intrinsic_px_per_unit("P", 2300, 2274, quote_in_coin=True) assert put_iv is not None assert abs(put_iv - 26.0 / 2274.0) < 1e-12 def test_expiry_breakeven_from_ask(): from lib.options.options_pricing_lib import expiry_breakeven_from_ask assert expiry_breakeven_from_ask(opt_type="C", strike=1760, ask_px=15.6) == 1775.6 assert expiry_breakeven_from_ask(opt_type="P", strike=1760, ask_px=15.6) == 1744.4 assert expiry_breakeven_from_ask(opt_type="C", strike=1760, ask_px=None, mark_px=14.2) == 1774.2 def test_calc_order_size_too_small(): r = calc_order_size( quote_per_unit=2000.0, ct_mult=0.01, min_sz=1, budget_usdc=10, budget_buffer=0.95, budget_cap=10, ) assert r["ok"] is False def test_expiry_breakeven_from_api(): from lib.options.options_pricing_lib import expiry_breakeven_px assert expiry_breakeven_px( opt_type="C", strike=3500, avg_px=15.6, be_px_api=3516.2 ) == 3516.2 def test_expiry_breakeven_call_put(): from lib.options.options_pricing_lib import expiry_breakeven_px assert expiry_breakeven_px(opt_type="C", strike=3500, avg_px=15.6) == 3515.6 assert expiry_breakeven_px(opt_type="P", strike=3500, avg_px=15.6) == 3484.4 def test_close_breakeven_at_mark_equals_avg(): from lib.options.options_pricing_lib import close_breakeven_idx assert close_breakeven_idx( opt_type="C", idx_px=3480, mark_px=15.6, avg_px=15.6 ) == 3480.0 assert close_breakeven_idx( opt_type="P", idx_px=3480, mark_px=15.6, avg_px=15.6 ) == 3480.0 def test_close_breakeven_with_delta(): from lib.options.options_pricing_lib import close_breakeven_idx # mark below avg, delta 0.5 ETH on 0.5 ETH position -> slope 1 be = close_breakeven_idx( opt_type="C", idx_px=3480, mark_px=14.6, avg_px=15.6, delta_pa=0.5, pos=50, ct_mult=0.01, ) assert be == 3481.0 def test_format_options_breakeven_line(): from lib.options.options_pricing_lib import format_options_breakeven_line s = format_options_breakeven_line( expiry_be_px=3515.6, close_be_px=3498.0, idx_px=3480.0 ) assert "到期平衡3516" in s assert "平掉回本3498" in s assert "指数3480" in s def test_format_position_row_premium_and_inst_parse(): from lib.exchange.okx_options_lib import format_position_row row = format_position_row( { "instId": "ETH-USD_UM-260709-1700-P", "pos": "20", "avgPx": "6.2", "markPx": "6.3241", "idxPx": "1746", "upl": "0.0248", "uplRatio": "0.02", } ) assert row["opt_type"] == "P" assert row["strike"] == 1700.0 assert row["premium_paid"] == 1.24 assert row["exp_time_ms"] is not None assert row["exp_time_ms"] > 0 def test_expiry_ms_from_inst_id(): from lib.exchange.okx_options_lib import expiry_ms_from_inst_id, normalize_option_exp_ms ms = expiry_ms_from_inst_id("ETH-USD_UM-260709-1700-P") assert ms is not None from datetime import datetime, timezone dt = datetime.fromtimestamp(ms / 1000, tz=timezone.utc) assert dt.year == 2026 and dt.month == 7 and dt.day == 9 and dt.hour == 8 assert normalize_option_exp_ms(None, "ETH-USD_UM-260709-1700-P") == ms def test_format_position_row_breakeven(): from lib.exchange.okx_options_lib import format_position_row row = format_position_row( { "instId": "ETH-USD_UM-260703-1800-C", "pos": "50", "avgPx": "15.6", "markPx": "16.2", "idxPx": "3480", "bePx": "3515.6", "optType": "C", "stk": "3500", "deltaPA": "0.45", "upl": "0.3", "uplRatio": "0.02", } ) assert row["expiry_be_px"] == 3515.6 assert row["idx_px"] == 3480.0 assert row["close_be_px"] is not None assert row["dist_expiry_be"] == 35.6