tea-bond 0.4.3__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.
pybond/pl.py ADDED
@@ -0,0 +1,533 @@
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
+ @property
273
+ def remain_year(self):
274
+ """
275
+ Calculate bond remaining year (债券剩余期限).
276
+
277
+ Args:
278
+ date: Evaluation date column expression
279
+
280
+ Returns:
281
+ Polars expression for bond remaining year
282
+ """
283
+ return self._call_plugin("bonds_remain_year")
284
+
285
+ @property
286
+ def carry_date(self):
287
+ return self._call_plugin("bonds_carry_date")
288
+
289
+ @property
290
+ def maturity_date(self):
291
+ return self._call_plugin("bonds_maturity_date")
292
+
293
+
294
+ class Bonds:
295
+ """
296
+ A class for bond-specific calculations using Polars expressions.
297
+
298
+ This class provides methods for calculating various bond metrics
299
+ without requiring futures contract information.
300
+ """
301
+
302
+ def __init__(self, bond: IntoExpr = "symbol"):
303
+ """
304
+ Initialize Bonds with bond identifier.
305
+
306
+ Args:
307
+ bond: Bond code column expression (default: "symbol")
308
+ """
309
+ self.bond = bond
310
+
311
+ def _evaluator(
312
+ self, date: IntoExpr | None = None, ytm: IntoExpr | None = None
313
+ ) -> TfEvaluators:
314
+ """
315
+ Create a TfEvaluators instance for bond-only calculations.
316
+
317
+ Args:
318
+ date: Evaluation date column expression
319
+ ytm: Yield to maturity column expression
320
+
321
+ Returns:
322
+ TfEvaluators: Configured evaluator instance
323
+ """
324
+ return TfEvaluators(
325
+ future=None,
326
+ bond=self.bond,
327
+ date=date,
328
+ bond_ytm=ytm,
329
+ future_price=None,
330
+ capital_rate=None,
331
+ reinvest_rate=None,
332
+ )
333
+
334
+ def remain_year(self, date: IntoExpr = "date"):
335
+ """
336
+ Calculate remain year for the bond (剩余期限).
337
+ """
338
+ return self._evaluator(date=date).remain_year
339
+
340
+ def carry_date(self):
341
+ return self._evaluator().carry_date
342
+
343
+ def maturity_date(self):
344
+ return self._evaluator().maturity_date
345
+
346
+ def accrued_interest(self, date: IntoExpr = "date"):
347
+ """
348
+ Calculate accrued interest for the bond (应计利息).
349
+
350
+ Args:
351
+ date: Evaluation date column expression
352
+
353
+ Returns:
354
+ Polars expression for accrued interest
355
+ """
356
+ return self._evaluator(date=date).accrued_interest
357
+
358
+ def clean_price(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
359
+ """
360
+ Calculate bond clean price (债券净价).
361
+
362
+ Args:
363
+ ytm: Yield to maturity column expression
364
+ date: Evaluation date column expression
365
+
366
+ Returns:
367
+ Polars expression for bond clean price
368
+ """
369
+ return self._evaluator(date=date, ytm=ytm).clean_price
370
+
371
+ def dirty_price(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
372
+ """
373
+ Calculate bond dirty price (债券全价).
374
+
375
+ Args:
376
+ ytm: Yield to maturity column expression
377
+ date: Evaluation date column expression
378
+
379
+ Returns:
380
+ Polars expression for bond dirty price
381
+ """
382
+ return self._evaluator(date=date, ytm=ytm).dirty_price
383
+
384
+ def duration(self, ytm: IntoExpr = "ytm", date: IntoExpr = "date"):
385
+ """
386
+ Calculate modified duration (修正久期).
387
+
388
+ Args:
389
+ ytm: Yield to maturity column expression
390
+ date: Evaluation date column expression
391
+
392
+ Returns:
393
+ Polars expression for modified duration
394
+ """
395
+ return self._evaluator(date=date, ytm=ytm).duration
396
+
397
+ def remain_cp_num(self, date: IntoExpr = "date"):
398
+ """
399
+ Calculate remaining number of coupon payments (债券剩余付息次数).
400
+
401
+ Args:
402
+ date: Evaluation date column expression
403
+
404
+ Returns:
405
+ Polars expression for remaining number of coupon payments
406
+ """
407
+ return self._evaluator(date=date).remain_cp_num
408
+
409
+ def calc_ytm_with_price(
410
+ self,
411
+ date: IntoExpr = "date",
412
+ dirty_price: IntoExpr = "dirty_price",
413
+ clean_price: IntoExpr | None = None,
414
+ ):
415
+ bond = parse_into_expr(self.bond)
416
+ date = parse_into_expr(date)
417
+ if clean_price is None:
418
+ dirty_price = parse_into_expr(dirty_price)
419
+ else:
420
+ assert dirty_price == "dirty_price", (
421
+ "should not set dirty_price when clean_price is set"
422
+ )
423
+ clean_price = parse_into_expr(clean_price)
424
+ dirty_price = clean_price + self.accrued_interest(date)
425
+ return register_plugin(
426
+ args=[bond, date, dirty_price],
427
+ symbol="bonds_calc_ytm_with_price",
428
+ is_elementwise=False,
429
+ )
430
+
431
+
432
+ class Futures:
433
+ def __init__(self, future: IntoExpr = "symbol"):
434
+ """
435
+ Initialize Futures with future identifier.
436
+
437
+ Args:
438
+ future: Future code column expression (default: "symbol")
439
+ """
440
+ self.future = future
441
+
442
+ def _evaluator(self, date: IntoExpr | None = None) -> TfEvaluators:
443
+ """
444
+ Create a TfEvaluators instance for future-only calculations.
445
+
446
+ Args:
447
+ date: Evaluation date column expression
448
+
449
+ Returns:
450
+ TfEvaluators: Configured evaluator instance
451
+ """
452
+ return TfEvaluators(
453
+ future=self.future,
454
+ bond=None,
455
+ date=date,
456
+ bond_ytm=None,
457
+ future_price=None,
458
+ capital_rate=None,
459
+ reinvest_rate=None,
460
+ )
461
+
462
+ def deliver_date(self):
463
+ """
464
+ Calculate delivery date (交割日).
465
+
466
+ Args:
467
+ date: Evaluation date column expression
468
+
469
+ Returns:
470
+ Polars expression for delivery date
471
+ """
472
+ return self._evaluator().deliver_date
473
+
474
+ def last_trading_date(self):
475
+ """
476
+ Calculate last trading date (最后交易日).
477
+
478
+ Args:
479
+ date: Evaluation date column expression
480
+
481
+ Returns:
482
+ Polars expression for last trading date
483
+ """
484
+ return self._evaluator().last_trading_date
485
+
486
+
487
+ def find_workday(date: IntoExpr, market: str | Ib | Sse, offset: int = 0):
488
+ """
489
+ Find the workday based on the given date and market calendar.
490
+
491
+ Args:
492
+ date: Input date column expression
493
+ market: Market identifier (IB, SSE, or string)
494
+ offset: Number of workdays to offset (default: 0)
495
+
496
+ Returns:
497
+ Polars expression for the adjusted workday
498
+ """
499
+ if market == Ib:
500
+ market = "IB"
501
+ elif market == Sse:
502
+ market = "SSE"
503
+ date = parse_into_expr(date)
504
+ return register_plugin(
505
+ args=[date],
506
+ kwargs={"market": market, "offset": offset},
507
+ symbol="calendar_find_workday",
508
+ is_elementwise=True,
509
+ )
510
+
511
+
512
+ def is_business_day(date: IntoExpr, market: str | Ib | Sse):
513
+ """
514
+ Check if the given date is a business day for the specified market.
515
+
516
+ Args:
517
+ date: Input date column expression
518
+ market: Market identifier (IB, SSE, or string)
519
+
520
+ Returns:
521
+ Polars expression returning boolean values for business day check
522
+ """
523
+ if market == Ib:
524
+ market = "IB"
525
+ elif market == Sse:
526
+ market = "SSE"
527
+ date = parse_into_expr(date)
528
+ return register_plugin(
529
+ args=[date],
530
+ kwargs={"market": market},
531
+ symbol="calendar_is_business_day",
532
+ is_elementwise=True,
533
+ )