9f3395de2f
Show premium wipeout plus flat round-trip perp fees as case C for both size and points modes. Co-authored-by: Cursor <cursoragent@cursor.com>
148 lines
5.5 KiB
Python
148 lines
5.5 KiB
Python
"""hub_perp_options_calc_lib 永期对冲测算."""
|
|
|
|
import unittest
|
|
|
|
from lib.hub.hub_perp_options_calc_lib import (
|
|
calc_perp_options,
|
|
calc_perp_options_hedge,
|
|
calc_perp_options_points,
|
|
)
|
|
|
|
|
|
class HubPerpOptionsCalcTests(unittest.TestCase):
|
|
def test_example_1800_50_15_100(self):
|
|
data, err = calc_perp_options_hedge(
|
|
base="ETH",
|
|
spot=1800,
|
|
capital_usdt=3000,
|
|
target_profit_u=15,
|
|
move_mode="points",
|
|
move_value=50,
|
|
perp_leverage=10,
|
|
option_leverage=100,
|
|
ct_mult=0.01,
|
|
)
|
|
self.assertIsNone(err)
|
|
assert data is not None
|
|
self.assertEqual(data["prem_per_coin"], 18.0)
|
|
self.assertEqual(data["perp_gross_u"], 50.0)
|
|
# fee = (1800+1850)*0.0005 = 1.825
|
|
self.assertAlmostEqual(data["perp_fee_u"], 1.825, places=6)
|
|
self.assertAlmostEqual(data["premium_budget_u"], 33.175, places=6)
|
|
self.assertAlmostEqual(data["opt_coins"], 33.175 / 18.0, places=6)
|
|
self.assertAlmostEqual(data["opt_sheets"], data["opt_coins"] / 0.01, places=4)
|
|
self.assertAlmostEqual(data["case_a"]["net_u"], 15.0, places=6)
|
|
# B: opt_net = coins*50 - coins*18 = coins*32; portfolio = opt_net - 50
|
|
coins = data["opt_coins"]
|
|
self.assertAlmostEqual(data["case_b"]["opt_net_u"], coins * 32.0, places=6)
|
|
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], coins * 32.0 - 50.0, places=6)
|
|
self.assertAlmostEqual(data["perp_margin_u"], 180.0, places=6)
|
|
self.assertTrue(data["capital_ok"])
|
|
sw = data["case_sideways"]
|
|
self.assertAlmostEqual(sw["premium_u"], data["premium_total_u"], places=6)
|
|
self.assertAlmostEqual(sw["fee_u"], 1.8, places=6)
|
|
self.assertAlmostEqual(sw["max_loss_u"], data["premium_total_u"] + 1.8, places=6)
|
|
self.assertAlmostEqual(sw["net_u"], -sw["max_loss_u"], places=6)
|
|
|
|
def test_pct_mode(self):
|
|
data, err = calc_perp_options_hedge(
|
|
base="BTC",
|
|
spot=100000,
|
|
capital_usdt=5000,
|
|
target_profit_u=100,
|
|
move_mode="pct",
|
|
move_value=1,
|
|
perp_leverage=5,
|
|
option_leverage=50,
|
|
ct_mult=0.01,
|
|
)
|
|
self.assertIsNone(err)
|
|
assert data is not None
|
|
self.assertAlmostEqual(data["move_points"], 1000.0, places=6)
|
|
self.assertAlmostEqual(data["perp_gross_u"], 1000.0, places=6)
|
|
self.assertAlmostEqual(data["prem_per_coin"], 2000.0, places=6)
|
|
|
|
def test_budget_too_small(self):
|
|
data, err = calc_perp_options_hedge(
|
|
base="ETH",
|
|
spot=1800,
|
|
capital_usdt=3000,
|
|
target_profit_u=60,
|
|
move_mode="points",
|
|
move_value=50,
|
|
perp_leverage=10,
|
|
option_leverage=100,
|
|
)
|
|
self.assertIsNone(data)
|
|
self.assertIn("无法开期权", err or "")
|
|
|
|
def test_bad_base(self):
|
|
data, err = calc_perp_options_hedge(
|
|
base="SOL",
|
|
spot=100,
|
|
capital_usdt=1000,
|
|
target_profit_u=10,
|
|
move_mode="points",
|
|
move_value=5,
|
|
perp_leverage=5,
|
|
option_leverage=20,
|
|
)
|
|
self.assertIsNone(data)
|
|
self.assertIsNotNone(err)
|
|
|
|
def test_points_ratio_1_to_2(self):
|
|
# spot=1800, optLev=100 → prem/coin=18; ratio 1:2 → opt=2, premium=36
|
|
# A: move = (15+36+2*1800*0.0005)/(1-0.0005) = (51+1.8)/0.9995
|
|
data, err = calc_perp_options_points(
|
|
base="ETH",
|
|
spot=1800,
|
|
capital_usdt=3000,
|
|
target_profit_u=15,
|
|
perp_leverage=10,
|
|
option_leverage=100,
|
|
ratio_perp=1,
|
|
ratio_opt=2,
|
|
ct_mult=0.01,
|
|
)
|
|
self.assertIsNone(err)
|
|
assert data is not None
|
|
self.assertEqual(data["calc_mode"], "points")
|
|
self.assertEqual(data["opt_coins"], 2.0)
|
|
self.assertEqual(data["premium_total_u"], 36.0)
|
|
expect_a = (15 + 36 + 2 * 1800 * 0.0005) / 0.9995
|
|
self.assertAlmostEqual(data["case_a"]["move_points"], expect_a, places=6)
|
|
self.assertAlmostEqual(data["case_a"]["net_u"], 15.0, places=5)
|
|
# 期权净利=目标: (15+36)/2 = 25.5
|
|
self.assertAlmostEqual(data["case_b"]["move_points_opt_net"], 25.5, places=6)
|
|
self.assertAlmostEqual(data["case_b"]["opt_net_u"], 15.0, places=6)
|
|
# 组合净利=目标: (15+36)/(2-1) = 51
|
|
self.assertAlmostEqual(data["case_b"]["move_points_portfolio"], 51.0, places=6)
|
|
self.assertAlmostEqual(data["case_b"]["portfolio_net_u"], 15.0, places=6)
|
|
# 横盘:权利金36 + 同价开平费 2*1800*0.0005=1.8 → 37.8
|
|
sw = data["case_sideways"]
|
|
self.assertAlmostEqual(sw["premium_u"], 36.0, places=6)
|
|
self.assertAlmostEqual(sw["fee_u"], 1.8, places=6)
|
|
self.assertAlmostEqual(sw["max_loss_u"], 37.8, places=6)
|
|
self.assertAlmostEqual(sw["net_u"], -37.8, places=6)
|
|
|
|
def test_points_ratio_1_to_1_no_portfolio(self):
|
|
data, err = calc_perp_options(
|
|
calc_mode="points",
|
|
base="ETH",
|
|
spot=1800,
|
|
capital_usdt=3000,
|
|
target_profit_u=15,
|
|
perp_leverage=10,
|
|
option_leverage=100,
|
|
ratio_perp=1,
|
|
ratio_opt=1,
|
|
)
|
|
self.assertIsNone(err)
|
|
assert data is not None
|
|
self.assertIsNone(data["case_b"]["move_points_portfolio"])
|
|
self.assertIsNotNone(data["case_b"]["portfolio_error"])
|
|
|
|
|
|
if __name__ == "__main__":
|
|
unittest.main()
|