tea-bond 0.3.10__cp38-abi3-win_amd64.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/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,108 @@
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
+ settle_time: IntoExpr,
60
+ qty: IntoExpr,
61
+ clean_price: IntoExpr,
62
+ clean_close: IntoExpr,
63
+ symbol: str = "",
64
+ bond_info_path: str | None = None,
65
+ multiplier: float = 1,
66
+ fee: str | Fee = "",
67
+ borrowing_cost: float = 0,
68
+ capital_rate: float = 0,
69
+ begin_state=None,
70
+ ) -> pl.Expr:
71
+ if isinstance(fee, Fee):
72
+ fee = fee.str
73
+ settle_time = parse_into_expr(settle_time)
74
+ qty = parse_into_expr(qty)
75
+ clean_price = parse_into_expr(clean_price)
76
+ clean_close = parse_into_expr(clean_close)
77
+ if bond_info_path is None:
78
+ from .bond import bonds_info_path as path
79
+
80
+ bond_info_path = str(path)
81
+
82
+ if begin_state is None:
83
+ begin_state = {
84
+ "pos": 0,
85
+ "avg_price": 0,
86
+ "pnl": 0,
87
+ "realized_pnl": 0,
88
+ "pos_price": 0,
89
+ "unrealized_pnl": 0,
90
+ "coupon_paid": 0,
91
+ "amt": 0,
92
+ "fee": 0,
93
+ }
94
+ kwargs = {
95
+ "symbol": symbol,
96
+ "multiplier": multiplier,
97
+ "fee": fee,
98
+ "borrowing_cost": borrowing_cost,
99
+ "capital_rate": capital_rate,
100
+ "begin_state": begin_state,
101
+ "bond_info_path": bond_info_path,
102
+ }
103
+ return register_plugin(
104
+ args=[settle_time, qty, clean_price, clean_close],
105
+ kwargs=kwargs,
106
+ symbol="calc_bond_trade_pnl",
107
+ is_elementwise=False,
108
+ )
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.pyd ADDED
Binary file