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

1""" 

2交易费用计算器 

3""" 

4 

5from decimal import Decimal 

6from typing import Optional 

7 

8from core.models.broker import FeeConfig, HKFeeConfig, TradingFee, USFeeConfig 

9 

10 

11class FeeCalculator: 

12 """交易费用计算器""" 

13 

14 def __init__(self, fee_config: Optional[FeeConfig] = None): 

15 """ 

16 初始化费用计算器 

17 

18 Args: 

19 fee_config: 费用配置(包含美股和港股配置),如果为None则使用默认配置 

20 """ 

21 if fee_config is None: 

22 fee_config = FeeConfig() 

23 

24 self.us_fees = fee_config.us_fees 

25 self.hk_fees = fee_config.hk_fees 

26 

27 def calculate_fee( 

28 self, quantity: Decimal, price: Decimal, side: str, market: str 

29 ) -> TradingFee: 

30 """ 

31 计算交易费用 

32 

33 Args: 

34 quantity: 成交数量 

35 price: 成交价格 

36 side: 买卖方向 ('buy' | 'sell') 

37 market: 市场类型 ('US' | 'HK') 

38 

39 Returns: 

40 TradingFee: 费用明细对象 

41 

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}") 

51 

52 def calculate_us_fees( 

53 self, quantity: Decimal, price: Decimal, side: str 

54 ) -> TradingFee: 

55 """ 

56 计算美股费用 

57 

58 Args: 

59 quantity: 成交数量 

60 price: 成交价格 

61 side: 买卖方向 ('buy' | 'sell') 

62 

63 Returns: 

64 TradingFee: 费用明细 

65 """ 

66 trade_amount = quantity * price 

67 fee = TradingFee(currency="USD") 

68 

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) 

72 

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") 

81 

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) 

86 

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) 

90 

91 # 计算总费用 

92 fee.total_fee = ( 

93 fee.platform_fee + fee.activity_fee + fee.clearing_fee + fee.audit_fee 

94 ) 

95 

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")) 

102 

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 } 

140 

141 return fee 

142 

143 def calculate_hk_fees( 

144 self, quantity: Decimal, price: Decimal, side: str 

145 ) -> TradingFee: 

146 """ 

147 计算港股费用(计算逻辑与美股相同) 

148 

149 Args: 

150 quantity: 成交数量 

151 price: 成交价格 

152 side: 买卖方向 ('buy' | 'sell') 

153 

154 Returns: 

155 TradingFee: 费用明细 

156 """ 

157 trade_amount = quantity * price 

158 fee = TradingFee(currency="HKD") 

159 

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) 

163 

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") 

172 

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) 

177 

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) 

181 

182 # 计算总费用 

183 fee.total_fee = ( 

184 fee.platform_fee + fee.activity_fee + fee.clearing_fee + fee.audit_fee 

185 ) 

186 

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")) 

193 

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 } 

231 

232 return fee