Coverage for core/trading/utils/fee_calculator.py: 86.44%
59 statements
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-13 18:58 +0000
« prev ^ index » next coverage.py v7.10.7, created at 2025-10-13 18:58 +0000
1"""
2交易费用计算器
3"""
5from decimal import Decimal
6from typing import Optional
8from core.models.broker import FeeConfig, HKFeeConfig, TradingFee, USFeeConfig
11class FeeCalculator:
12 """交易费用计算器"""
14 def __init__(self, fee_config: Optional[FeeConfig] = None):
15 """
16 初始化费用计算器
18 Args:
19 fee_config: 费用配置(包含美股和港股配置),如果为None则使用默认配置
20 """
21 if fee_config is None:
22 fee_config = FeeConfig()
24 self.us_fees = fee_config.us_fees
25 self.hk_fees = fee_config.hk_fees
27 def calculate_fee(
28 self, quantity: Decimal, price: Decimal, side: str, market: str
29 ) -> TradingFee:
30 """
31 计算交易费用
33 Args:
34 quantity: 成交数量
35 price: 成交价格
36 side: 买卖方向 ('buy' | 'sell')
37 market: 市场类型 ('US' | 'HK')
39 Returns:
40 TradingFee: 费用明细对象
42 Raises:
43 ValueError: 不支持的市场类型
44 """
45 if market.upper() == "US":
46 return self.calculate_us_fees(quantity, price, side)
47 elif market.upper() == "HK":
48 return self.calculate_hk_fees(quantity, price, side)
49 else:
50 raise ValueError(f"不支持的市场类型: {market}")
52 def calculate_us_fees(
53 self, quantity: Decimal, price: Decimal, side: str
54 ) -> TradingFee:
55 """
56 计算美股费用
58 Args:
59 quantity: 成交数量
60 price: 成交价格
61 side: 买卖方向 ('buy' | 'sell')
63 Returns:
64 TradingFee: 费用明细
65 """
66 trade_amount = quantity * price
67 fee = TradingFee(currency="USD")
69 # 1. 平台费(买入和卖出都收取)
70 platform_fee_by_share = quantity * self.us_fees.platform_fee_per_share
71 fee.platform_fee = max(platform_fee_by_share, self.us_fees.platform_fee_minimum)
73 # 2. 交易活动费(仅卖出收取)
74 if side.lower() == "sell":
75 activity_fee_by_share = quantity * self.us_fees.activity_fee_per_share
76 fee.activity_fee = min(
77 activity_fee_by_share, self.us_fees.activity_fee_maximum
78 )
79 else:
80 fee.activity_fee = Decimal("0")
82 # 3. 交收费(买入和卖出都收取)
83 clearing_fee_by_share = quantity * self.us_fees.clearing_fee_per_share
84 clearing_fee_by_amount = trade_amount * self.us_fees.clearing_fee_max_pct
85 fee.clearing_fee = min(clearing_fee_by_share, clearing_fee_by_amount)
87 # 4. 综合审计费(买入和卖出都收取)
88 audit_fee_by_share = quantity * self.us_fees.audit_fee_per_share
89 fee.audit_fee = max(audit_fee_by_share, self.us_fees.audit_fee_minimum)
91 # 计算总费用
92 fee.total_fee = (
93 fee.platform_fee + fee.activity_fee + fee.clearing_fee + fee.audit_fee
94 )
96 # 将所有费用四舍五入到2位小数
97 fee.platform_fee = fee.platform_fee.quantize(Decimal("0.01"))
98 fee.activity_fee = fee.activity_fee.quantize(Decimal("0.01"))
99 fee.clearing_fee = fee.clearing_fee.quantize(Decimal("0.01"))
100 fee.audit_fee = fee.audit_fee.quantize(Decimal("0.01"))
101 fee.total_fee = fee.total_fee.quantize(Decimal("0.01"))
103 # 记录计算详情
104 fee.calculation_details = {
105 "quantity": str(quantity),
106 "price": str(price),
107 "trade_amount": str(trade_amount),
108 "side": side,
109 "market": "US",
110 "fee_breakdown": {
111 "platform_fee": {
112 "by_share": str(platform_fee_by_share),
113 "minimum": str(self.us_fees.platform_fee_minimum),
114 "applied": str(fee.platform_fee),
115 },
116 "activity_fee": {
117 "by_share": (
118 str(quantity * self.us_fees.activity_fee_per_share)
119 if side.lower() == "sell"
120 else "0"
121 ),
122 "maximum": str(self.us_fees.activity_fee_maximum),
123 "applied": str(fee.activity_fee),
124 "note": (
125 "only_on_sell" if side.lower() == "sell" else "not_applicable"
126 ),
127 },
128 "clearing_fee": {
129 "by_share": str(clearing_fee_by_share),
130 "by_amount": str(clearing_fee_by_amount),
131 "applied": str(fee.clearing_fee),
132 },
133 "audit_fee": {
134 "by_share": str(audit_fee_by_share),
135 "minimum": str(self.us_fees.audit_fee_minimum),
136 "applied": str(fee.audit_fee),
137 },
138 },
139 }
141 return fee
143 def calculate_hk_fees(
144 self, quantity: Decimal, price: Decimal, side: str
145 ) -> TradingFee:
146 """
147 计算港股费用(计算逻辑与美股相同)
149 Args:
150 quantity: 成交数量
151 price: 成交价格
152 side: 买卖方向 ('buy' | 'sell')
154 Returns:
155 TradingFee: 费用明细
156 """
157 trade_amount = quantity * price
158 fee = TradingFee(currency="HKD")
160 # 1. 平台费(买入和卖出都收取)
161 platform_fee_by_share = quantity * self.hk_fees.platform_fee_per_share
162 fee.platform_fee = max(platform_fee_by_share, self.hk_fees.platform_fee_minimum)
164 # 2. 交易活动费(仅卖出收取)
165 if side.lower() == "sell":
166 activity_fee_by_share = quantity * self.hk_fees.activity_fee_per_share
167 fee.activity_fee = min(
168 activity_fee_by_share, self.hk_fees.activity_fee_maximum
169 )
170 else:
171 fee.activity_fee = Decimal("0")
173 # 3. 交收费(买入和卖出都收取)
174 clearing_fee_by_share = quantity * self.hk_fees.clearing_fee_per_share
175 clearing_fee_by_amount = trade_amount * self.hk_fees.clearing_fee_max_pct
176 fee.clearing_fee = min(clearing_fee_by_share, clearing_fee_by_amount)
178 # 4. 综合审计费(买入和卖出都收取)
179 audit_fee_by_share = quantity * self.hk_fees.audit_fee_per_share
180 fee.audit_fee = max(audit_fee_by_share, self.hk_fees.audit_fee_minimum)
182 # 计算总费用
183 fee.total_fee = (
184 fee.platform_fee + fee.activity_fee + fee.clearing_fee + fee.audit_fee
185 )
187 # 将所有费用四舍五入到2位小数
188 fee.platform_fee = fee.platform_fee.quantize(Decimal("0.01"))
189 fee.activity_fee = fee.activity_fee.quantize(Decimal("0.01"))
190 fee.clearing_fee = fee.clearing_fee.quantize(Decimal("0.01"))
191 fee.audit_fee = fee.audit_fee.quantize(Decimal("0.01"))
192 fee.total_fee = fee.total_fee.quantize(Decimal("0.01"))
194 # 记录计算详情
195 fee.calculation_details = {
196 "quantity": str(quantity),
197 "price": str(price),
198 "trade_amount": str(trade_amount),
199 "side": side,
200 "market": "HK",
201 "fee_breakdown": {
202 "platform_fee": {
203 "by_share": str(platform_fee_by_share),
204 "minimum": str(self.hk_fees.platform_fee_minimum),
205 "applied": str(fee.platform_fee),
206 },
207 "activity_fee": {
208 "by_share": (
209 str(quantity * self.hk_fees.activity_fee_per_share)
210 if side.lower() == "sell"
211 else "0"
212 ),
213 "maximum": str(self.hk_fees.activity_fee_maximum),
214 "applied": str(fee.activity_fee),
215 "note": (
216 "only_on_sell" if side.lower() == "sell" else "not_applicable"
217 ),
218 },
219 "clearing_fee": {
220 "by_share": str(clearing_fee_by_share),
221 "by_amount": str(clearing_fee_by_amount),
222 "applied": str(fee.clearing_fee),
223 },
224 "audit_fee": {
225 "by_share": str(audit_fee_by_share),
226 "minimum": str(self.hk_fees.audit_fee_minimum),
227 "applied": str(fee.audit_fee),
228 },
229 },
230 }
232 return fee