tea-bond 0.3.11__cp38-abi3-manylinux_2_17_x86_64.manylinux2014_x86_64.whl
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Potentially problematic release.
This version of tea-bond might be problematic. Click here for more details.
- pybond/__init__.py +18 -0
- pybond/bond.py +190 -0
- pybond/download.py +147 -0
- pybond/ffi/__init__.py +4 -0
- pybond/ffi/bond.py +68 -0
- pybond/ffi/datetime.py +58 -0
- pybond/ffi/duration.py +19 -0
- pybond/ffi/evaluators.py +186 -0
- pybond/ffi/lib.py +8 -0
- pybond/nb/__init__.py +31 -0
- pybond/nb/ir_utils.py +47 -0
- pybond/nb/nb_bond.py +213 -0
- pybond/nb/nb_date.py +209 -0
- pybond/nb/nb_datetime.py +415 -0
- pybond/nb/nb_duration.py +70 -0
- pybond/nb/nb_evaluators.py +557 -0
- pybond/nb/nb_time.py +279 -0
- pybond/pd.py +458 -0
- pybond/pl.py +480 -0
- pybond/pnl.py +110 -0
- pybond/polars_utils.py +96 -0
- pybond/pybond.abi3.so +0 -0
- pybond/pybond.pyi +391 -0
- pybond.libs/libcom_err-2abe824b.so.2 +0 -0
- pybond.libs/libcrypto-d3570994.so.10 +0 -0
- pybond.libs/libgssapi_krb5-497db0c6.so.2 +0 -0
- pybond.libs/libk5crypto-b1f99d5c.so.3 +0 -0
- pybond.libs/libkeyutils-dfe70bd6.so.1 +0 -0
- pybond.libs/libkrb5-fcafa220.so.3 +0 -0
- pybond.libs/libkrb5support-d0bcff84.so.0 +0 -0
- pybond.libs/libpcre-9513aab5.so.1 +0 -0
- pybond.libs/libselinux-0922c95c.so.1 +0 -0
- pybond.libs/libssl-cd1d6220.so.10 +0 -0
- tea_bond-0.3.11.dist-info/METADATA +7 -0
- tea_bond-0.3.11.dist-info/RECORD +36 -0
- tea_bond-0.3.11.dist-info/WHEEL +4 -0
pybond/pl.py
ADDED
|
@@ -0,0 +1,480 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
from .pybond import Ib, Sse
|
|
6
|
+
|
|
7
|
+
if TYPE_CHECKING:
|
|
8
|
+
from polars.type_aliases import IntoExpr
|
|
9
|
+
import polars as pl
|
|
10
|
+
|
|
11
|
+
from .polars_utils import parse_into_expr, register_plugin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class TfEvaluators:
|
|
15
|
+
"""A class for treasury futures evaluation using Polars expressions."""
|
|
16
|
+
|
|
17
|
+
def __init__(
|
|
18
|
+
self,
|
|
19
|
+
future: IntoExpr = "future",
|
|
20
|
+
bond: IntoExpr = "bond",
|
|
21
|
+
date: IntoExpr = "date",
|
|
22
|
+
future_price: IntoExpr = None,
|
|
23
|
+
bond_ytm: IntoExpr = None,
|
|
24
|
+
capital_rate: IntoExpr = None,
|
|
25
|
+
reinvest_rate=None,
|
|
26
|
+
):
|
|
27
|
+
"""
|
|
28
|
+
Initialize TfEvaluators with default column expressions.
|
|
29
|
+
|
|
30
|
+
Args:
|
|
31
|
+
future: Future contract code column expression
|
|
32
|
+
bond: Bond code column expression
|
|
33
|
+
date: Evaluation date column expression
|
|
34
|
+
future_price: Future price column expression
|
|
35
|
+
bond_ytm: Bond yield to maturity column expression
|
|
36
|
+
capital_rate: Capital cost rate column expression
|
|
37
|
+
reinvest_rate: Reinvestment rate (optional)
|
|
38
|
+
"""
|
|
39
|
+
self.future = parse_into_expr(
|
|
40
|
+
future if future is not None else pl.lit(None).cast(str)
|
|
41
|
+
)
|
|
42
|
+
self.bond = parse_into_expr(
|
|
43
|
+
bond if bond is not None else pl.lit(None).cast(str)
|
|
44
|
+
)
|
|
45
|
+
self.date = parse_into_expr(
|
|
46
|
+
date if date is not None else pl.lit(None).cast(pl.Date)
|
|
47
|
+
)
|
|
48
|
+
self.future_price = parse_into_expr(
|
|
49
|
+
future_price if future_price is not None else pl.lit(None)
|
|
50
|
+
)
|
|
51
|
+
self.bond_ytm = parse_into_expr(
|
|
52
|
+
bond_ytm if bond_ytm is not None else pl.lit(None)
|
|
53
|
+
)
|
|
54
|
+
self.capital_rate = parse_into_expr(
|
|
55
|
+
capital_rate if capital_rate is not None else pl.lit(None)
|
|
56
|
+
)
|
|
57
|
+
self.reinvest_rate = reinvest_rate
|
|
58
|
+
|
|
59
|
+
def _call_plugin(self, symbol: str):
|
|
60
|
+
"""Helper method to call plugin with consistent arguments."""
|
|
61
|
+
return register_plugin(
|
|
62
|
+
args=[
|
|
63
|
+
self.future,
|
|
64
|
+
self.bond,
|
|
65
|
+
self.date,
|
|
66
|
+
self.future_price,
|
|
67
|
+
self.bond_ytm,
|
|
68
|
+
self.capital_rate,
|
|
69
|
+
],
|
|
70
|
+
kwargs={"reinvest_rate": self.reinvest_rate},
|
|
71
|
+
symbol=symbol,
|
|
72
|
+
is_elementwise=False,
|
|
73
|
+
)
|
|
74
|
+
|
|
75
|
+
@property
|
|
76
|
+
def net_basis_spread(self):
|
|
77
|
+
"""
|
|
78
|
+
Calculate net basis spread (净基差).
|
|
79
|
+
|
|
80
|
+
Net basis spread = basis spread - carry return
|
|
81
|
+
|
|
82
|
+
Returns:
|
|
83
|
+
Polars expression for net basis spread
|
|
84
|
+
"""
|
|
85
|
+
return self._call_plugin("evaluators_net_basis_spread")
|
|
86
|
+
|
|
87
|
+
@property
|
|
88
|
+
def accrued_interest(self):
|
|
89
|
+
"""
|
|
90
|
+
Calculate accrued interest (应计利息).
|
|
91
|
+
|
|
92
|
+
Returns:
|
|
93
|
+
Polars expression for accrued interest
|
|
94
|
+
"""
|
|
95
|
+
return self._call_plugin("evaluators_accrued_interest")
|
|
96
|
+
|
|
97
|
+
@property
|
|
98
|
+
def deliver_accrued_interest(self):
|
|
99
|
+
"""
|
|
100
|
+
Calculate delivery accrued interest (国债期货交割应计利息).
|
|
101
|
+
|
|
102
|
+
Returns:
|
|
103
|
+
Polars expression for delivery accrued interest
|
|
104
|
+
"""
|
|
105
|
+
return self._call_plugin("evaluators_deliver_accrued_interest")
|
|
106
|
+
|
|
107
|
+
@property
|
|
108
|
+
def cf(self):
|
|
109
|
+
"""
|
|
110
|
+
Calculate conversion factor (转换因子).
|
|
111
|
+
|
|
112
|
+
Returns:
|
|
113
|
+
Polars expression for conversion factor
|
|
114
|
+
"""
|
|
115
|
+
return self._call_plugin("evaluators_cf")
|
|
116
|
+
|
|
117
|
+
@property
|
|
118
|
+
def dirty_price(self):
|
|
119
|
+
"""
|
|
120
|
+
Calculate bond dirty price (债券全价).
|
|
121
|
+
|
|
122
|
+
Returns:
|
|
123
|
+
Polars expression for bond dirty price
|
|
124
|
+
"""
|
|
125
|
+
return self._call_plugin("evaluators_dirty_price")
|
|
126
|
+
|
|
127
|
+
@property
|
|
128
|
+
def clean_price(self):
|
|
129
|
+
"""
|
|
130
|
+
Calculate bond clean price (债券净价).
|
|
131
|
+
|
|
132
|
+
Returns:
|
|
133
|
+
Polars expression for bond clean price
|
|
134
|
+
"""
|
|
135
|
+
return self._call_plugin("evaluators_clean_price")
|
|
136
|
+
|
|
137
|
+
@property
|
|
138
|
+
def future_dirty_price(self):
|
|
139
|
+
"""
|
|
140
|
+
Calculate future dirty price (期货全价/发票价格).
|
|
141
|
+
|
|
142
|
+
Returns:
|
|
143
|
+
Polars expression for future dirty price
|
|
144
|
+
"""
|
|
145
|
+
return self._call_plugin("evaluators_future_dirty_price")
|
|
146
|
+
|
|
147
|
+
@property
|
|
148
|
+
def deliver_cost(self):
|
|
149
|
+
"""
|
|
150
|
+
Calculate delivery cost (交割成本).
|
|
151
|
+
|
|
152
|
+
Delivery cost = bond dirty price - interim coupon payments
|
|
153
|
+
|
|
154
|
+
Returns:
|
|
155
|
+
Polars expression for delivery cost
|
|
156
|
+
"""
|
|
157
|
+
return self._call_plugin("evaluators_deliver_cost")
|
|
158
|
+
|
|
159
|
+
@property
|
|
160
|
+
def basis_spread(self):
|
|
161
|
+
"""
|
|
162
|
+
Calculate basis spread (基差).
|
|
163
|
+
|
|
164
|
+
Returns:
|
|
165
|
+
Polars expression for basis spread
|
|
166
|
+
"""
|
|
167
|
+
return self._call_plugin("evaluators_basis_spread")
|
|
168
|
+
|
|
169
|
+
@property
|
|
170
|
+
def f_b_spread(self):
|
|
171
|
+
"""
|
|
172
|
+
Calculate futures-bond spread (期现价差).
|
|
173
|
+
|
|
174
|
+
Returns:
|
|
175
|
+
Polars expression for futures-bond spread
|
|
176
|
+
"""
|
|
177
|
+
return self._call_plugin("evaluators_f_b_spread")
|
|
178
|
+
|
|
179
|
+
@property
|
|
180
|
+
def carry(self):
|
|
181
|
+
"""
|
|
182
|
+
Calculate carry return (持有收益).
|
|
183
|
+
|
|
184
|
+
Carry return = (delivery accrued - trading accrued + interim coupons) +
|
|
185
|
+
capital cost rate * (weighted average interim coupons - bond dirty price * remaining days / 365)
|
|
186
|
+
|
|
187
|
+
Returns:
|
|
188
|
+
Polars expression for carry return
|
|
189
|
+
"""
|
|
190
|
+
return self._call_plugin("evaluators_carry")
|
|
191
|
+
|
|
192
|
+
@property
|
|
193
|
+
def duration(self):
|
|
194
|
+
"""
|
|
195
|
+
Calculate modified duration (修正久期).
|
|
196
|
+
|
|
197
|
+
Returns:
|
|
198
|
+
Polars expression for modified duration
|
|
199
|
+
"""
|
|
200
|
+
return self._call_plugin("evaluators_duration")
|
|
201
|
+
|
|
202
|
+
@property
|
|
203
|
+
def irr(self):
|
|
204
|
+
"""
|
|
205
|
+
Calculate internal rate of return (内部收益率).
|
|
206
|
+
|
|
207
|
+
Returns:
|
|
208
|
+
Polars expression for internal rate of return
|
|
209
|
+
"""
|
|
210
|
+
return self._call_plugin("evaluators_irr")
|
|
211
|
+
|
|
212
|
+
@property
|
|
213
|
+
def future_ytm(self):
|
|
214
|
+
"""
|
|
215
|
+
Calculate futures implied yield to maturity (期货隐含收益率).
|
|
216
|
+
|
|
217
|
+
Returns:
|
|
218
|
+
Polars expression for futures implied yield to maturity
|
|
219
|
+
"""
|
|
220
|
+
return self._call_plugin("evaluators_future_ytm")
|
|
221
|
+
|
|
222
|
+
@property
|
|
223
|
+
def remain_cp_to_deliver(self):
|
|
224
|
+
"""
|
|
225
|
+
Calculate remaining coupon payments to delivery (到交割的期间付息).
|
|
226
|
+
|
|
227
|
+
Returns:
|
|
228
|
+
Polars expression for remaining coupon payments to delivery
|
|
229
|
+
"""
|
|
230
|
+
return self._call_plugin("evaluators_remain_cp_to_deliver")
|
|
231
|
+
|
|
232
|
+
@property
|
|
233
|
+
def remain_cp_to_deliver_wm(self):
|
|
234
|
+
"""
|
|
235
|
+
Calculate weighted average remaining coupon payments to delivery (加权平均到交割的期间付息).
|
|
236
|
+
|
|
237
|
+
Returns:
|
|
238
|
+
Polars expression for weighted average remaining coupon payments to delivery
|
|
239
|
+
"""
|
|
240
|
+
return self._call_plugin("evaluators_remain_cp_to_deliver_wm")
|
|
241
|
+
|
|
242
|
+
@property
|
|
243
|
+
def remain_cp_num(self):
|
|
244
|
+
"""
|
|
245
|
+
Calculate remaining number of coupon payments (债券剩余付息次数).
|
|
246
|
+
|
|
247
|
+
Returns:
|
|
248
|
+
Polars expression for remaining number of coupon payments
|
|
249
|
+
"""
|
|
250
|
+
return self._call_plugin("evaluators_remain_cp_num")
|
|
251
|
+
|
|
252
|
+
@property
|
|
253
|
+
def deliver_date(self):
|
|
254
|
+
"""
|
|
255
|
+
Calculate delivery date (交割日).
|
|
256
|
+
|
|
257
|
+
Returns:
|
|
258
|
+
Polars expression for delivery date
|
|
259
|
+
"""
|
|
260
|
+
return self._call_plugin("evaluators_deliver_date")
|
|
261
|
+
|
|
262
|
+
@property
|
|
263
|
+
def last_trading_date(self):
|
|
264
|
+
"""
|
|
265
|
+
Calculate last trading date (最后交易日).
|
|
266
|
+
|
|
267
|
+
Returns:
|
|
268
|
+
Polars expression for last trading date
|
|
269
|
+
"""
|
|
270
|
+
return self._call_plugin("evaluators_last_trading_date")
|
|
271
|
+
|
|
272
|
+
|
|
273
|
+
class Bonds:
|
|
274
|
+
"""
|
|
275
|
+
A class for bond-specific calculations using Polars expressions.
|
|
276
|
+
|
|
277
|
+
This class provides methods for calculating various bond metrics
|
|
278
|
+
without requiring futures contract information.
|
|
279
|
+
"""
|
|
280
|
+
|
|
281
|
+
def __init__(self, bond: IntoExpr = "symbol"):
|
|
282
|
+
"""
|
|
283
|
+
Initialize Bonds with bond identifier.
|
|
284
|
+
|
|
285
|
+
Args:
|
|
286
|
+
bond: Bond code column expression (default: "symbol")
|
|
287
|
+
"""
|
|
288
|
+
self.bond = bond
|
|
289
|
+
|
|
290
|
+
def _evaluator(
|
|
291
|
+
self, date: IntoExpr | None = None, ytm: IntoExpr | None = None
|
|
292
|
+
) -> TfEvaluators:
|
|
293
|
+
"""
|
|
294
|
+
Create a TfEvaluators instance for bond-only calculations.
|
|
295
|
+
|
|
296
|
+
Args:
|
|
297
|
+
date: Evaluation date column expression
|
|
298
|
+
ytm: Yield to maturity column expression
|
|
299
|
+
|
|
300
|
+
Returns:
|
|
301
|
+
TfEvaluators: Configured evaluator instance
|
|
302
|
+
"""
|
|
303
|
+
return TfEvaluators(
|
|
304
|
+
future=None,
|
|
305
|
+
bond=self.bond,
|
|
306
|
+
date=date,
|
|
307
|
+
bond_ytm=ytm,
|
|
308
|
+
future_price=None,
|
|
309
|
+
capital_rate=None,
|
|
310
|
+
reinvest_rate=None,
|
|
311
|
+
)
|
|
312
|
+
|
|
313
|
+
def accrued_interest(self, date: IntoExpr = "date"):
|
|
314
|
+
"""
|
|
315
|
+
Calculate accrued interest for the bond (应计利息).
|
|
316
|
+
|
|
317
|
+
Args:
|
|
318
|
+
date: Evaluation date column expression
|
|
319
|
+
|
|
320
|
+
Returns:
|
|
321
|
+
Polars expression for accrued interest
|
|
322
|
+
"""
|
|
323
|
+
return self._evaluator(date=date).accrued_interest
|
|
324
|
+
|
|
325
|
+
def clean_price(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
|
|
326
|
+
"""
|
|
327
|
+
Calculate bond clean price (债券净价).
|
|
328
|
+
|
|
329
|
+
Args:
|
|
330
|
+
ytm: Yield to maturity column expression
|
|
331
|
+
date: Evaluation date column expression
|
|
332
|
+
|
|
333
|
+
Returns:
|
|
334
|
+
Polars expression for bond clean price
|
|
335
|
+
"""
|
|
336
|
+
return self._evaluator(date=date, ytm=ytm).clean_price
|
|
337
|
+
|
|
338
|
+
def dirty_price(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
|
|
339
|
+
"""
|
|
340
|
+
Calculate bond dirty price (债券全价).
|
|
341
|
+
|
|
342
|
+
Args:
|
|
343
|
+
ytm: Yield to maturity column expression
|
|
344
|
+
date: Evaluation date column expression
|
|
345
|
+
|
|
346
|
+
Returns:
|
|
347
|
+
Polars expression for bond dirty price
|
|
348
|
+
"""
|
|
349
|
+
return self._evaluator(date=date, ytm=ytm).dirty_price
|
|
350
|
+
|
|
351
|
+
def duration(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
|
|
352
|
+
"""
|
|
353
|
+
Calculate modified duration (修正久期).
|
|
354
|
+
|
|
355
|
+
Args:
|
|
356
|
+
ytm: Yield to maturity column expression
|
|
357
|
+
date: Evaluation date column expression
|
|
358
|
+
|
|
359
|
+
Returns:
|
|
360
|
+
Polars expression for modified duration
|
|
361
|
+
"""
|
|
362
|
+
return self._evaluator(date=date, ytm=ytm).duration
|
|
363
|
+
|
|
364
|
+
def remain_cp_num(self, date: IntoExpr = "date"):
|
|
365
|
+
"""
|
|
366
|
+
Calculate remaining number of coupon payments (债券剩余付息次数).
|
|
367
|
+
|
|
368
|
+
Args:
|
|
369
|
+
date: Evaluation date column expression
|
|
370
|
+
|
|
371
|
+
Returns:
|
|
372
|
+
Polars expression for remaining number of coupon payments
|
|
373
|
+
"""
|
|
374
|
+
return self._evaluator(date=date).remain_cp_num
|
|
375
|
+
|
|
376
|
+
# TODO(Teamon): 实现向量化根据净价反推ytm的函数
|
|
377
|
+
|
|
378
|
+
|
|
379
|
+
class Futures:
|
|
380
|
+
def __init__(self, future: IntoExpr = "symbol"):
|
|
381
|
+
"""
|
|
382
|
+
Initialize Futures with future identifier.
|
|
383
|
+
|
|
384
|
+
Args:
|
|
385
|
+
future: Future code column expression (default: "symbol")
|
|
386
|
+
"""
|
|
387
|
+
self.future = future
|
|
388
|
+
|
|
389
|
+
def _evaluator(self, date: IntoExpr | None = None) -> TfEvaluators:
|
|
390
|
+
"""
|
|
391
|
+
Create a TfEvaluators instance for future-only calculations.
|
|
392
|
+
|
|
393
|
+
Args:
|
|
394
|
+
date: Evaluation date column expression
|
|
395
|
+
|
|
396
|
+
Returns:
|
|
397
|
+
TfEvaluators: Configured evaluator instance
|
|
398
|
+
"""
|
|
399
|
+
return TfEvaluators(
|
|
400
|
+
future=self.future,
|
|
401
|
+
bond=None,
|
|
402
|
+
date=date,
|
|
403
|
+
bond_ytm=None,
|
|
404
|
+
future_price=None,
|
|
405
|
+
capital_rate=None,
|
|
406
|
+
reinvest_rate=None,
|
|
407
|
+
)
|
|
408
|
+
|
|
409
|
+
def deliver_date(self):
|
|
410
|
+
"""
|
|
411
|
+
Calculate delivery date (交割日).
|
|
412
|
+
|
|
413
|
+
Args:
|
|
414
|
+
date: Evaluation date column expression
|
|
415
|
+
|
|
416
|
+
Returns:
|
|
417
|
+
Polars expression for delivery date
|
|
418
|
+
"""
|
|
419
|
+
return self._evaluator().deliver_date
|
|
420
|
+
|
|
421
|
+
def last_trading_date(self):
|
|
422
|
+
"""
|
|
423
|
+
Calculate last trading date (最后交易日).
|
|
424
|
+
|
|
425
|
+
Args:
|
|
426
|
+
date: Evaluation date column expression
|
|
427
|
+
|
|
428
|
+
Returns:
|
|
429
|
+
Polars expression for last trading date
|
|
430
|
+
"""
|
|
431
|
+
return self._evaluator().last_trading_date
|
|
432
|
+
|
|
433
|
+
|
|
434
|
+
def find_workday(date: IntoExpr, market: str | Ib | Sse, offset: int = 0):
|
|
435
|
+
"""
|
|
436
|
+
Find the workday based on the given date and market calendar.
|
|
437
|
+
|
|
438
|
+
Args:
|
|
439
|
+
date: Input date column expression
|
|
440
|
+
market: Market identifier (IB, SSE, or string)
|
|
441
|
+
offset: Number of workdays to offset (default: 0)
|
|
442
|
+
|
|
443
|
+
Returns:
|
|
444
|
+
Polars expression for the adjusted workday
|
|
445
|
+
"""
|
|
446
|
+
if market == Ib:
|
|
447
|
+
market = "IB"
|
|
448
|
+
elif market == Sse:
|
|
449
|
+
market = "SSE"
|
|
450
|
+
date = parse_into_expr(date)
|
|
451
|
+
return register_plugin(
|
|
452
|
+
args=[date],
|
|
453
|
+
kwargs={"market": market, "offset": offset},
|
|
454
|
+
symbol="calendar_find_workday",
|
|
455
|
+
is_elementwise=True,
|
|
456
|
+
)
|
|
457
|
+
|
|
458
|
+
|
|
459
|
+
def is_business_day(date: IntoExpr, market: str | Ib | Sse):
|
|
460
|
+
"""
|
|
461
|
+
Check if the given date is a business day for the specified market.
|
|
462
|
+
|
|
463
|
+
Args:
|
|
464
|
+
date: Input date column expression
|
|
465
|
+
market: Market identifier (IB, SSE, or string)
|
|
466
|
+
|
|
467
|
+
Returns:
|
|
468
|
+
Polars expression returning boolean values for business day check
|
|
469
|
+
"""
|
|
470
|
+
if market == Ib:
|
|
471
|
+
market = "IB"
|
|
472
|
+
elif market == Sse:
|
|
473
|
+
market = "SSE"
|
|
474
|
+
date = parse_into_expr(date)
|
|
475
|
+
return register_plugin(
|
|
476
|
+
args=[date],
|
|
477
|
+
kwargs={"market": market},
|
|
478
|
+
symbol="calendar_is_business_day",
|
|
479
|
+
is_elementwise=True,
|
|
480
|
+
)
|
pybond/pnl.py
ADDED
|
@@ -0,0 +1,110 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
5
|
+
if TYPE_CHECKING:
|
|
6
|
+
import polars as pl
|
|
7
|
+
from polars.type_aliases import IntoExpr
|
|
8
|
+
|
|
9
|
+
from .polars_utils import parse_into_expr, register_plugin
|
|
10
|
+
|
|
11
|
+
|
|
12
|
+
class Fee:
|
|
13
|
+
"""Represents a fee for a trade."""
|
|
14
|
+
|
|
15
|
+
def __init__(self, fee_str: str = ""):
|
|
16
|
+
self.str = fee_str
|
|
17
|
+
|
|
18
|
+
def __add__(self, other: Fee) -> Fee:
|
|
19
|
+
return Fee(self.str + "+" + other.str)
|
|
20
|
+
|
|
21
|
+
def __radd__(self, other: Fee) -> Fee:
|
|
22
|
+
return Fee(other.str + "+" + self.str)
|
|
23
|
+
|
|
24
|
+
@staticmethod
|
|
25
|
+
def trade(fee) -> TradeFee:
|
|
26
|
+
return TradeFee(fee)
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def qty(fee) -> QtyFee:
|
|
30
|
+
return QtyFee(fee)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def percent(fee) -> PercentFee:
|
|
34
|
+
return PercentFee(fee)
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
class TradeFee(Fee):
|
|
38
|
+
"""Represents a fixed fee for a trade."""
|
|
39
|
+
|
|
40
|
+
def __init__(self, fee: float):
|
|
41
|
+
self.str = f"Trade({fee})"
|
|
42
|
+
|
|
43
|
+
|
|
44
|
+
class QtyFee(Fee):
|
|
45
|
+
"""Represents a fee based on the quantity of a trade."""
|
|
46
|
+
|
|
47
|
+
def __init__(self, fee: float):
|
|
48
|
+
self.str = f"Qty({fee})"
|
|
49
|
+
|
|
50
|
+
|
|
51
|
+
class PercentFee(Fee):
|
|
52
|
+
"""Represents a fee based on a percentage of the trade amount."""
|
|
53
|
+
|
|
54
|
+
def __init__(self, fee: float):
|
|
55
|
+
self.str = f"Percent({fee})"
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
def calc_bond_trade_pnl(
|
|
59
|
+
symbol: IntoExpr,
|
|
60
|
+
settle_time: IntoExpr,
|
|
61
|
+
qty: IntoExpr,
|
|
62
|
+
clean_price: IntoExpr,
|
|
63
|
+
clean_close: IntoExpr,
|
|
64
|
+
# symbol: str = "",
|
|
65
|
+
bond_info_path: str | None = None,
|
|
66
|
+
multiplier: float = 1,
|
|
67
|
+
fee: str | Fee = "",
|
|
68
|
+
borrowing_cost: float = 0,
|
|
69
|
+
capital_rate: float = 0,
|
|
70
|
+
begin_state=None,
|
|
71
|
+
) -> pl.Expr:
|
|
72
|
+
if isinstance(fee, Fee):
|
|
73
|
+
fee = fee.str
|
|
74
|
+
symbol = parse_into_expr(symbol)
|
|
75
|
+
settle_time = parse_into_expr(settle_time)
|
|
76
|
+
qty = parse_into_expr(qty)
|
|
77
|
+
clean_price = parse_into_expr(clean_price)
|
|
78
|
+
clean_close = parse_into_expr(clean_close)
|
|
79
|
+
if bond_info_path is None:
|
|
80
|
+
from .bond import bonds_info_path as path
|
|
81
|
+
|
|
82
|
+
bond_info_path = str(path)
|
|
83
|
+
|
|
84
|
+
if begin_state is None:
|
|
85
|
+
begin_state = {
|
|
86
|
+
"pos": 0,
|
|
87
|
+
"avg_price": 0,
|
|
88
|
+
"pnl": 0,
|
|
89
|
+
"realized_pnl": 0,
|
|
90
|
+
"pos_price": 0,
|
|
91
|
+
"unrealized_pnl": 0,
|
|
92
|
+
"coupon_paid": 0,
|
|
93
|
+
"amt": 0,
|
|
94
|
+
"fee": 0,
|
|
95
|
+
}
|
|
96
|
+
kwargs = {
|
|
97
|
+
# "symbol": symbol,
|
|
98
|
+
"multiplier": multiplier,
|
|
99
|
+
"fee": fee,
|
|
100
|
+
"borrowing_cost": borrowing_cost,
|
|
101
|
+
"capital_rate": capital_rate,
|
|
102
|
+
"begin_state": begin_state,
|
|
103
|
+
"bond_info_path": bond_info_path,
|
|
104
|
+
}
|
|
105
|
+
return register_plugin(
|
|
106
|
+
args=[symbol, settle_time, qty, clean_price, clean_close],
|
|
107
|
+
kwargs=kwargs,
|
|
108
|
+
symbol="calc_bond_trade_pnl",
|
|
109
|
+
is_elementwise=False,
|
|
110
|
+
)
|
pybond/polars_utils.py
ADDED
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any, Sequence
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from polars.type_aliases import IntoExpr, PolarsDataType
|
|
11
|
+
|
|
12
|
+
|
|
13
|
+
def parse_into_expr(
|
|
14
|
+
expr: IntoExpr,
|
|
15
|
+
*,
|
|
16
|
+
str_as_lit: bool = False,
|
|
17
|
+
list_as_lit: bool = True,
|
|
18
|
+
dtype: PolarsDataType | None = None,
|
|
19
|
+
) -> pl.Expr:
|
|
20
|
+
"""
|
|
21
|
+
Parse a single input into an expression.
|
|
22
|
+
|
|
23
|
+
Parameters
|
|
24
|
+
----------
|
|
25
|
+
expr
|
|
26
|
+
The input to be parsed as an expression.
|
|
27
|
+
str_as_lit
|
|
28
|
+
Interpret string input as a string literal. If set to `False` (default),
|
|
29
|
+
strings are parsed as column names.
|
|
30
|
+
list_as_lit
|
|
31
|
+
Interpret list input as a lit literal, If set to `False`,
|
|
32
|
+
lists are parsed as `Series` literals.
|
|
33
|
+
dtype
|
|
34
|
+
If the input is expected to resolve to a literal with a known dtype, pass
|
|
35
|
+
this to the `lit` constructor.
|
|
36
|
+
|
|
37
|
+
Returns
|
|
38
|
+
-------
|
|
39
|
+
polars.Expr
|
|
40
|
+
"""
|
|
41
|
+
if isinstance(expr, pl.Expr):
|
|
42
|
+
pass
|
|
43
|
+
elif isinstance(expr, str) and not str_as_lit:
|
|
44
|
+
expr = pl.col(expr)
|
|
45
|
+
elif isinstance(expr, list) and not list_as_lit:
|
|
46
|
+
expr = pl.lit(pl.Series(expr), dtype=dtype)
|
|
47
|
+
else:
|
|
48
|
+
expr = pl.lit(expr, dtype=dtype)
|
|
49
|
+
|
|
50
|
+
return expr
|
|
51
|
+
|
|
52
|
+
|
|
53
|
+
def register_plugin(
|
|
54
|
+
*,
|
|
55
|
+
symbol: str,
|
|
56
|
+
is_elementwise: bool,
|
|
57
|
+
kwargs: dict[str, Any] | None = None,
|
|
58
|
+
args: list[IntoExpr],
|
|
59
|
+
# lib: str | Path,
|
|
60
|
+
) -> pl.Expr:
|
|
61
|
+
global lib
|
|
62
|
+
if parse_version(pl.__version__) < parse_version("0.20.16"):
|
|
63
|
+
assert isinstance(args[0], pl.Expr)
|
|
64
|
+
assert isinstance(lib, str)
|
|
65
|
+
return args[0].register_plugin(
|
|
66
|
+
lib=lib,
|
|
67
|
+
symbol=symbol,
|
|
68
|
+
args=args[1:],
|
|
69
|
+
kwargs=kwargs,
|
|
70
|
+
is_elementwise=is_elementwise,
|
|
71
|
+
)
|
|
72
|
+
from polars.plugins import register_plugin_function
|
|
73
|
+
|
|
74
|
+
return register_plugin_function(
|
|
75
|
+
args=args,
|
|
76
|
+
plugin_path=lib,
|
|
77
|
+
function_name=symbol,
|
|
78
|
+
kwargs=kwargs,
|
|
79
|
+
is_elementwise=is_elementwise,
|
|
80
|
+
)
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
def parse_version(version: Sequence[str | int]) -> tuple[int, ...]:
|
|
84
|
+
# Simple version parser; split into a tuple of ints for comparison.
|
|
85
|
+
# vendored from Polars
|
|
86
|
+
if isinstance(version, str):
|
|
87
|
+
version = version.split(".")
|
|
88
|
+
return tuple(int(re.sub(r"\D", "", str(v))) for v in version)
|
|
89
|
+
|
|
90
|
+
|
|
91
|
+
if parse_version(pl.__version__) < parse_version("0.20.16"):
|
|
92
|
+
from polars.utils.udfs import _get_shared_lib_location
|
|
93
|
+
|
|
94
|
+
lib: str | Path = _get_shared_lib_location(__file__)
|
|
95
|
+
else:
|
|
96
|
+
lib = Path(__file__).parent
|
pybond/pybond.abi3.so
ADDED
|
Binary file
|