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/pd.py ADDED
@@ -0,0 +1,474 @@
1
+ from __future__ import annotations
2
+
3
+ from typing import TYPE_CHECKING
4
+
5
+ import polars as pl
6
+
7
+ from .pl import Bonds as PlBonds
8
+ from .pl import Futures as PlFutures
9
+ from .pl import TfEvaluators as PlTfEvaluators
10
+
11
+ if TYPE_CHECKING:
12
+ import pandas as pd
13
+
14
+
15
+ class TfEvaluators:
16
+ """
17
+ Treasury Futures Evaluators for pandas DataFrames.
18
+
19
+ A pandas-compatible wrapper around the Polars-based TfEvaluators that provides
20
+ various financial calculations for treasury futures and bonds analysis.
21
+
22
+ This class converts pandas inputs to Polars for computation and returns
23
+ pandas Series results for seamless integration with pandas workflows.
24
+ """
25
+
26
+ def __init__(
27
+ self,
28
+ future: str | pd.Series,
29
+ bond: str | pd.Series,
30
+ date: str | pd.Series,
31
+ future_price: pd.Series | None = None,
32
+ bond_ytm: pd.Series | None = None,
33
+ capital_rate: float | pd.Series | None = None,
34
+ reinvest_rate: float | None = None,
35
+ ):
36
+ """
37
+ Initialize TfEvaluators with market data.
38
+
39
+ Args:
40
+ future: Future contract code(s)
41
+ bond: Bond code(s)
42
+ date: Evaluation date(s)
43
+ future_price: Future price(s)
44
+ bond_ytm: Bond yield to maturity
45
+ capital_rate: Capital cost rate for carry calculations
46
+ reinvest_rate: Reinvestment rate for coupon payments (optional)
47
+ """
48
+ self.pl_df = pl.DataFrame(
49
+ {
50
+ "future": future,
51
+ "bond": bond,
52
+ "date": date,
53
+ "future_price": future_price,
54
+ "bond_ytm": bond_ytm,
55
+ "capital_rate": capital_rate,
56
+ }
57
+ )
58
+ self._evaluators = PlTfEvaluators(
59
+ future_price="future_price",
60
+ bond_ytm="bond_ytm",
61
+ capital_rate="capital_rate",
62
+ reinvest_rate=reinvest_rate,
63
+ )
64
+
65
+ @property
66
+ def net_basis_spread(self):
67
+ """
68
+ Calculate net basis spread (净基差).
69
+
70
+ Net basis spread = basis spread - carry return
71
+
72
+ Returns:
73
+ pd.Series: Net basis spread values
74
+ """
75
+ return self.pl_df.select(net_basis_spread=self._evaluators.net_basis_spread)[
76
+ "net_basis_spread"
77
+ ].to_pandas()
78
+
79
+ @property
80
+ def accrued_interest(self):
81
+ """
82
+ Calculate accrued interest (应计利息).
83
+
84
+ Returns:
85
+ pd.Series: Accrued interest values
86
+ """
87
+ return self.pl_df.select(accrued_interest=self._evaluators.accrued_interest)[
88
+ "accrued_interest"
89
+ ].to_pandas()
90
+
91
+ @property
92
+ def deliver_accrued_interest(self):
93
+ """
94
+ Calculate delivery accrued interest (国债期货交割应计利息).
95
+
96
+ Returns:
97
+ pd.Series: Delivery accrued interest values
98
+ """
99
+ return self.pl_df.select(
100
+ deliver_accrued_interest=self._evaluators.deliver_accrued_interest
101
+ )["deliver_accrued_interest"].to_pandas()
102
+
103
+ @property
104
+ def cf(self):
105
+ """
106
+ Calculate conversion factor (转换因子).
107
+
108
+ Returns:
109
+ pd.Series: Conversion factor values
110
+ """
111
+ return self.pl_df.select(cf=self._evaluators.cf)["cf"].to_pandas()
112
+
113
+ @property
114
+ def dirty_price(self):
115
+ """
116
+ Calculate bond dirty price (债券全价).
117
+
118
+ Returns:
119
+ pd.Series: Bond dirty price values
120
+ """
121
+ return self.pl_df.select(dirty_price=self._evaluators.dirty_price)[
122
+ "dirty_price"
123
+ ].to_pandas()
124
+
125
+ @property
126
+ def clean_price(self):
127
+ """
128
+ Calculate bond clean price (债券净价).
129
+
130
+ Returns:
131
+ pd.Series: Bond clean price values
132
+ """
133
+ return self.pl_df.select(clean_price=self._evaluators.clean_price)[
134
+ "clean_price"
135
+ ].to_pandas()
136
+
137
+ @property
138
+ def future_dirty_price(self):
139
+ """
140
+ Calculate future dirty price (期货全价/发票价格).
141
+
142
+ Returns:
143
+ pd.Series: Future dirty price values
144
+ """
145
+ return self.pl_df.select(
146
+ future_dirty_price=self._evaluators.future_dirty_price
147
+ )["future_dirty_price"].to_pandas()
148
+
149
+ @property
150
+ def deliver_cost(self):
151
+ """
152
+ Calculate delivery cost (交割成本).
153
+
154
+ Delivery cost = bond dirty price - interim coupon payments
155
+
156
+ Returns:
157
+ pd.Series: Delivery cost values
158
+ """
159
+ return self.pl_df.select(deliver_cost=self._evaluators.deliver_cost)[
160
+ "deliver_cost"
161
+ ].to_pandas()
162
+
163
+ @property
164
+ def basis_spread(self):
165
+ """
166
+ Calculate basis spread (基差).
167
+
168
+ Returns:
169
+ pd.Series: Basis spread values
170
+ """
171
+ return self.pl_df.select(basis_spread=self._evaluators.basis_spread)[
172
+ "basis_spread"
173
+ ].to_pandas()
174
+
175
+ @property
176
+ def f_b_spread(self):
177
+ """
178
+ Calculate futures-bond spread (期现价差).
179
+
180
+ Returns:
181
+ pd.Series: Futures-bond spread values
182
+ """
183
+ return self.pl_df.select(f_b_spread=self._evaluators.f_b_spread)[
184
+ "f_b_spread"
185
+ ].to_pandas()
186
+
187
+ @property
188
+ def carry(self):
189
+ """
190
+ Calculate carry return (持有收益).
191
+
192
+ Carry return = (delivery accrued - trading accrued + interim coupons) +
193
+ capital cost rate * (weighted average interim coupons - bond dirty price * remaining days / 365)
194
+
195
+ Returns:
196
+ pd.Series: Carry return values
197
+ """
198
+ return self.pl_df.select(carry=self._evaluators.carry)["carry"].to_pandas()
199
+
200
+ @property
201
+ def duration(self):
202
+ """
203
+ Calculate modified duration (修正久期).
204
+
205
+ Returns:
206
+ pd.Series: Modified duration values
207
+ """
208
+ return self.pl_df.select(duration=self._evaluators.duration)[
209
+ "duration"
210
+ ].to_pandas()
211
+
212
+ @property
213
+ def irr(self):
214
+ """
215
+ Calculate internal rate of return (内部收益率).
216
+
217
+ Returns:
218
+ pd.Series: Internal rate of return values
219
+ """
220
+ return self.pl_df.select(irr=self._evaluators.irr)["irr"].to_pandas()
221
+
222
+ @property
223
+ def future_ytm(self):
224
+ """
225
+ Calculate futures implied yield to maturity (期货隐含收益率).
226
+
227
+ Returns:
228
+ pd.Series: Futures implied yield to maturity values
229
+ """
230
+ return self.pl_df.select(future_ytm=self._evaluators.future_ytm)[
231
+ "future_ytm"
232
+ ].to_pandas()
233
+
234
+ @property
235
+ def remain_cp_to_deliver(self):
236
+ """
237
+ Calculate remaining coupon payments to delivery (到交割的期间付息).
238
+
239
+ Returns:
240
+ pd.Series: Remaining coupon payments to delivery values
241
+ """
242
+ return self.pl_df.select(
243
+ remain_cp_to_deliver=self._evaluators.remain_cp_to_deliver
244
+ )["remain_cp_to_deliver"].to_pandas()
245
+
246
+ @property
247
+ def remain_cp_to_deliver_wm(self):
248
+ """
249
+ Calculate weighted average remaining coupon payments to delivery (加权平均到交割的期间付息).
250
+
251
+ Returns:
252
+ pd.Series: Weighted average remaining coupon payments to delivery values
253
+ """
254
+ return self.pl_df.select(
255
+ remain_cp_to_deliver_wm=self._evaluators.remain_cp_to_deliver_wm
256
+ )["remain_cp_to_deliver_wm"].to_pandas()
257
+
258
+ @property
259
+ def remain_cp_num(self):
260
+ """
261
+ Calculate remaining number of coupon payments (债券剩余付息次数).
262
+
263
+ Returns:
264
+ pd.Series: Remaining number of coupon payments values
265
+ """
266
+ return self.pl_df.select(remain_cp_num=self._evaluators.remain_cp_num)[
267
+ "remain_cp_num"
268
+ ].to_pandas()
269
+
270
+ @property
271
+ def deliver_date(self):
272
+ """
273
+ Calculate delivery date (交割日).
274
+
275
+ Returns:
276
+ pd.Series: Delivery date values
277
+ """
278
+ return self.pl_df.select(deliver_date=self._evaluators.deliver_date)[
279
+ "deliver_date"
280
+ ].to_pandas()
281
+
282
+ @property
283
+ def last_trading_date(self):
284
+ """
285
+ Calculate last trading date (最后交易日).
286
+
287
+ Returns:
288
+ pd.Series: Last trading date values
289
+ """
290
+ return self.pl_df.select(last_trading_date=self._evaluators.last_trading_date)[
291
+ "last_trading_date"
292
+ ].to_pandas()
293
+
294
+ @property
295
+ def remain_year(self):
296
+ return self.pl_df.select(remain_year=self._evaluators.remain_year)[
297
+ "remain_year"
298
+ ].to_pandas()
299
+
300
+
301
+ class Bonds:
302
+ """
303
+ Bond calculations for pandas DataFrames.
304
+
305
+ A pandas-compatible wrapper around the Polars-based Bonds class that provides
306
+ bond-specific financial calculations without requiring futures contract information.
307
+ """
308
+
309
+ def __init__(self, bond: str | pd.Series):
310
+ """
311
+ Initialize Bonds with bond identifier.
312
+
313
+ Args:
314
+ bond: Bond code(s)
315
+ """
316
+ self.bond = bond
317
+
318
+ def accrued_interest(self, date: str | pd.Series):
319
+ """
320
+ Calculate accrued interest for the bond (应计利息).
321
+
322
+ Args:
323
+ date: Evaluation date(s)
324
+
325
+ Returns:
326
+ pd.Series: Accrued interest values
327
+ """
328
+ df = pl.DataFrame({"bond": self.bond, "date": date})
329
+ return df.select(accrued_interest=PlBonds("bond").accrued_interest("date"))[
330
+ "accrued_interest"
331
+ ].to_pandas()
332
+
333
+ def clean_price(self, ytm: float | pd.Series, date: str | pd.Series):
334
+ """
335
+ Calculate bond clean price (债券净价).
336
+
337
+ Args:
338
+ ytm: Yield to maturity
339
+ date: Evaluation date(s)
340
+
341
+ Returns:
342
+ pd.Series: Bond clean price values
343
+ """
344
+ df = pl.DataFrame({"bond": self.bond, "ytm": ytm, "date": date})
345
+ return df.select(clean_price=PlBonds("bond").clean_price("ytm", "date"))[
346
+ "clean_price"
347
+ ].to_pandas()
348
+
349
+ def dirty_price(self, ytm: float | pd.Series, date: str | pd.Series):
350
+ """
351
+ Calculate bond dirty price (债券全价).
352
+
353
+ Args:
354
+ ytm: Yield to maturity
355
+ date: Evaluation date(s)
356
+
357
+ Returns:
358
+ pd.Series: Bond dirty price values
359
+ """
360
+ df = pl.DataFrame({"bond": self.bond, "ytm": ytm, "date": date})
361
+ return df.select(dirty_price=PlBonds("bond").dirty_price("ytm", "date"))[
362
+ "dirty_price"
363
+ ].to_pandas()
364
+
365
+ def duration(self, ytm: float | pd.Series, date: str | pd.Series):
366
+ """
367
+ Calculate modified duration (修正久期).
368
+
369
+ Args:
370
+ ytm: Yield to maturity
371
+ date: Evaluation date(s)
372
+
373
+ Returns:
374
+ pd.Series: Modified duration values
375
+ """
376
+ df = pl.DataFrame({"bond": self.bond, "ytm": ytm, "date": date})
377
+ return df.select(duration=PlBonds("bond").duration("ytm", "date"))[
378
+ "duration"
379
+ ].to_pandas()
380
+
381
+ def remain_cp_num(self, date: str | pd.Series):
382
+ """
383
+ Calculate remaining number of coupon payments (债券剩余付息次数).
384
+
385
+ Args:
386
+ date: Evaluation date(s)
387
+
388
+ Returns:
389
+ pd.Series: Remaining number of coupon payments values
390
+ """
391
+ df = pl.DataFrame({"bond": self.bond, "date": date})
392
+ return df.select(remain_cp_num=PlBonds("bond").remain_cp_num("date"))[
393
+ "remain_cp_num"
394
+ ].to_pandas()
395
+
396
+ def remain_year(self, date: str | pd.Series):
397
+ df = pl.DataFrame({"bond": self.bond, "date": date})
398
+ return df.select(remain_year=PlBonds("bond").remain_year("date"))[
399
+ "remain_year"
400
+ ].to_pandas()
401
+
402
+
403
+ class Futures:
404
+ def __init__(self, future: str | pd.Series):
405
+ self.future = future
406
+
407
+ def deliver_date(self):
408
+ """
409
+ Calculate delivery date (交割日).
410
+
411
+ Args:
412
+ date: Evaluation date(s)
413
+
414
+ Returns:
415
+ pd.Series: Delivery date values
416
+ """
417
+ df = pl.DataFrame({"future": self.future})
418
+ return df.select(deliver_date=PlFutures("future").deliver_date())[
419
+ "deliver_date"
420
+ ].to_pandas()
421
+
422
+ def last_trading_date(self):
423
+ """
424
+ Calculate last trading date (最后交易日).
425
+
426
+ Args:
427
+ date: Evaluation date(s)
428
+
429
+ Returns:
430
+ pd.Series: Last trading date values
431
+ """
432
+ df = pl.DataFrame({"future": self.future})
433
+ return df.select(last_trading_date=PlFutures("future").last_trading_date())[
434
+ "last_trading_date"
435
+ ].to_pandas()
436
+
437
+
438
+ def find_workday(date: str | pd.Series, market: str, offset: int = 0):
439
+ """
440
+ Find the workday based on the given date and market calendar.
441
+
442
+ Args:
443
+ date: Input date(s)
444
+ market: Market identifier ("IB" or "SSE")
445
+ offset: Number of workdays to offset (default: 0)
446
+
447
+ Returns:
448
+ pd.Series: Adjusted workday values
449
+ """
450
+ from .pl import find_workday as pl_find_workday
451
+
452
+ df = pl.DataFrame({"date": date}).select(pl.col("date").dt.date())
453
+ return df.select(workday=pl_find_workday("date", market, offset))[
454
+ "workday"
455
+ ].to_pandas()
456
+
457
+
458
+ def is_business_day(date: str | pd.Series, market: str):
459
+ """
460
+ Check if the given date is a business day for the specified market.
461
+
462
+ Args:
463
+ date: Input date(s)
464
+ market: Market identifier ("IB" or "SSE")
465
+
466
+ Returns:
467
+ pd.Series: Boolean values indicating if dates are business days
468
+ """
469
+ from .pl import is_business_day as pl_is_business_day
470
+
471
+ df = pl.DataFrame({"date": date}).select(pl.col("date").dt.date())
472
+ return df.select(is_business=pl_is_business_day("date", market))[
473
+ "is_business"
474
+ ].to_pandas()