Files
crypto_monitor_user/tests/test_hub_calculator_lib.py
T
dekun 53863559f4 Initialize crypto_monitor_user (user edition) from monitor codebase.
Retarget git remote, install path, and deploy docs from crypto_monitor to crypto_monitor_user.

Co-authored-by: Cursor <cursoragent@cursor.com>
2026-07-17 16:18:13 +08:00

164 lines
5.3 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""hub_calculator_lib 测算逻辑."""
import unittest
from unittest.mock import patch
from lib.hub.hub_calculator_lib import (
calc_initial_roll_qty,
calc_roll_calculator,
calc_trend_calculator,
solve_add_amount_for_total_risk,
)
MOCK_MARKET = {
"exchange_id": "0",
"exchange_key": "binance",
"exchange_name": "币安 · crypto_monitor_binance",
"exchange_label": "币安 · crypto_monitor_binance",
"base": "ETH",
"exchange_symbol": "ETH/USDT:USDT",
"display_symbol": "ETH/USDT",
"contract_size": 1.0,
"price_tick": 0.01,
"price_decimals": 2,
"amount_decimals": 3,
"min_amount": 0.001,
}
def _mock_resolve(_exchange="binance", _base="ETH"):
return MOCK_MARKET, lambda amount: round(float(amount), 3), None
class HubCalculatorLibTests(unittest.TestCase):
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_trend_calculator_long_basic(self, _mock):
data, err = calc_trend_calculator(
direction="long",
capital_usdt=1000,
risk_percent=5,
leverage=5,
entry_price=100,
stop_loss=95,
add_upper=110,
take_profit=120,
dca_legs=3,
exchange_id="0",
base="ETH",
)
self.assertIsNone(err)
self.assertIsNotNone(data)
assert data is not None
self.assertEqual(data["risk_budget_u"], 50.0)
self.assertGreaterEqual(len(data["rows"]), 2)
self.assertEqual(data["rows"][0]["label"], "首仓")
self.assertEqual(data["market"]["display_symbol"], "ETH/USDT")
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_trend_calculator_short_rejects_bad_bounds(self, _mock):
data, err = calc_trend_calculator(
direction="short",
capital_usdt=1000,
risk_percent=5,
leverage=5,
entry_price=100,
stop_loss=90,
add_upper=110,
take_profit=80,
dca_legs=3,
)
self.assertIsNone(data)
self.assertIsNotNone(err)
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_roll_calculator_first_leg_auto(self, _mock):
data, err = calc_roll_calculator(
direction="long",
capital_usdt=1000,
risk_percent=5,
entry_price=100,
stop_loss=95,
take_profit=120,
add_legs=[],
legs_done=0,
)
self.assertIsNone(err)
self.assertIsNotNone(data)
assert data is not None
self.assertEqual(data["first_contracts"], 10.0)
self.assertEqual(len(data["rows"]), 1)
self.assertEqual(data["rows"][0]["loss_at_sl_u"], 50.0)
# 毛利 200 双边费 (1000+1200)*0.0005=1.1 → 198.9
self.assertEqual(data["rows"][0]["profit_at_tp_u"], 198.9)
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_roll_calculator_chain_two_legs(self, _mock):
data, err = calc_roll_calculator(
direction="long",
capital_usdt=1000,
risk_percent=5,
entry_price=100,
stop_loss=95,
take_profit=120,
add_legs=[
{"add_price": 105, "new_stop_loss": 98},
{"add_price": 108, "new_stop_loss": 101},
],
legs_done=0,
)
self.assertIsNone(err)
self.assertIsNotNone(data)
assert data is not None
self.assertEqual(len(data["rows"]), 3)
self.assertEqual(data["rows"][1]["label"], "滚仓1")
self.assertGreater(float(data["final_contracts"]), float(data["first_contracts"]))
@patch("lib.hub.hub_calculator_lib._resolve_market", return_value=_mock_resolve())
def test_roll_calculator_rejects_too_many_legs(self, _mock):
data, err = calc_roll_calculator(
direction="long",
capital_usdt=1000,
risk_percent=5,
entry_price=100,
stop_loss=95,
take_profit=120,
add_legs=[
{"add_price": 105, "new_stop_loss": 98},
{"add_price": 108, "new_stop_loss": 101},
{"add_price": 110, "new_stop_loss": 103},
{"add_price": 112, "new_stop_loss": 105},
],
legs_done=0,
)
self.assertIsNone(data)
self.assertIsNotNone(err)
def test_initial_roll_qty(self):
qty, err = calc_initial_roll_qty("long", 100, 95, 50, 1.0)
self.assertIsNone(err)
self.assertEqual(qty, 10.0)
def test_initial_roll_qty_with_contract_size(self):
qty, err = calc_initial_roll_qty("long", 100, 95, 50, 0.1)
self.assertIsNone(err)
self.assertEqual(qty, 100.0)
def test_solve_add_with_contract_size(self):
q2, err = solve_add_amount_for_total_risk(
"long",
qty_existing=10.0,
entry_existing=100.0,
add_price=105.0,
new_stop=98.0,
risk_budget_usdt=50.0,
contract_size=1.0,
)
self.assertIsNone(err)
self.assertIsNotNone(q2)
assert q2 is not None
self.assertGreater(q2, 0)
if __name__ == "__main__":
unittest.main()