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/__init__.py +26 -0
- pybond/bond.py +209 -0
- pybond/download.py +145 -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 +474 -0
- pybond/pl.py +533 -0
- pybond/pnl.py +280 -0
- pybond/polars_utils.py +98 -0
- pybond/pybond.abi3.so +0 -0
- pybond/pybond.pyi +390 -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.4.3.dist-info/METADATA +7 -0
- tea_bond-0.4.3.dist-info/RECORD +36 -0
- tea_bond-0.4.3.dist-info/WHEEL +5 -0
pybond/pnl.py
ADDED
|
@@ -0,0 +1,280 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
from dataclasses import dataclass, field
|
|
4
|
+
from typing import TYPE_CHECKING, Any
|
|
5
|
+
|
|
6
|
+
import polars as pl
|
|
7
|
+
|
|
8
|
+
if TYPE_CHECKING:
|
|
9
|
+
from polars.type_aliases import IntoExpr
|
|
10
|
+
|
|
11
|
+
from .polars_utils import parse_into_expr, register_plugin
|
|
12
|
+
|
|
13
|
+
|
|
14
|
+
class Fee:
|
|
15
|
+
def to_dict(self) -> dict[str, Any]:
|
|
16
|
+
raise NotImplementedError
|
|
17
|
+
|
|
18
|
+
def __add__(self, other: Fee) -> FeeSum:
|
|
19
|
+
if isinstance(self, FeeSum):
|
|
20
|
+
if isinstance(other, FeeSum):
|
|
21
|
+
return FeeSum(items=self.items + other.items)
|
|
22
|
+
return FeeSum(items=[*self.items, other])
|
|
23
|
+
else:
|
|
24
|
+
if isinstance(other, FeeSum):
|
|
25
|
+
return FeeSum(items=[self, *other.items])
|
|
26
|
+
return FeeSum(items=[self, other])
|
|
27
|
+
|
|
28
|
+
@staticmethod
|
|
29
|
+
def trade(fee: float) -> TradeFee:
|
|
30
|
+
return TradeFee(fee)
|
|
31
|
+
|
|
32
|
+
@staticmethod
|
|
33
|
+
def qty(fee: float) -> QtyFee:
|
|
34
|
+
return QtyFee(fee)
|
|
35
|
+
|
|
36
|
+
@staticmethod
|
|
37
|
+
def percent(fee: float) -> PercentFee:
|
|
38
|
+
return PercentFee(fee)
|
|
39
|
+
|
|
40
|
+
def zero(self) -> FeeZero:
|
|
41
|
+
return FeeZero()
|
|
42
|
+
|
|
43
|
+
def min(self, fee: float) -> MinFee:
|
|
44
|
+
return MinFee(fee, self)
|
|
45
|
+
|
|
46
|
+
def max(self, fee: float) -> MaxFee:
|
|
47
|
+
return MaxFee(fee, self)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
@dataclass
|
|
51
|
+
class FeeZero(Fee):
|
|
52
|
+
def to_dict(self):
|
|
53
|
+
return {"kind": "zero"}
|
|
54
|
+
|
|
55
|
+
|
|
56
|
+
@dataclass
|
|
57
|
+
class PercentFee(Fee):
|
|
58
|
+
"""Represents a fee based on a percentage of the trade amount."""
|
|
59
|
+
|
|
60
|
+
rate: float
|
|
61
|
+
|
|
62
|
+
def to_dict(self):
|
|
63
|
+
return {"kind": "percent", "rate": self.rate}
|
|
64
|
+
|
|
65
|
+
|
|
66
|
+
@dataclass
|
|
67
|
+
class QtyFee(Fee):
|
|
68
|
+
"""Represents a fee based on the quantity of a trade."""
|
|
69
|
+
|
|
70
|
+
per_qty: float
|
|
71
|
+
|
|
72
|
+
def to_dict(self):
|
|
73
|
+
return {"kind": "per_qty", "per_qty": self.per_qty}
|
|
74
|
+
|
|
75
|
+
|
|
76
|
+
@dataclass
|
|
77
|
+
class TradeFee(Fee):
|
|
78
|
+
"""Represents a fixed fee for a trade."""
|
|
79
|
+
|
|
80
|
+
per_trade: float
|
|
81
|
+
|
|
82
|
+
def to_dict(self):
|
|
83
|
+
return {"kind": "per_trade", "per_trade": self.per_trade}
|
|
84
|
+
|
|
85
|
+
|
|
86
|
+
@dataclass
|
|
87
|
+
class FeeSum(Fee):
|
|
88
|
+
items: list[Fee] = field(default_factory=list)
|
|
89
|
+
|
|
90
|
+
def to_dict(self):
|
|
91
|
+
return {
|
|
92
|
+
"kind": "sum",
|
|
93
|
+
"items": [f.to_dict() for f in self.items],
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
|
|
97
|
+
@dataclass
|
|
98
|
+
class MinFee(Fee):
|
|
99
|
+
"""Represents a minimum fee for a trade."""
|
|
100
|
+
|
|
101
|
+
cap: float
|
|
102
|
+
fee: Fee
|
|
103
|
+
|
|
104
|
+
def to_dict(self):
|
|
105
|
+
return {"kind": "min", "cap": self.cap, "fee": self.fee.to_dict()}
|
|
106
|
+
|
|
107
|
+
|
|
108
|
+
@dataclass
|
|
109
|
+
class MaxFee(Fee):
|
|
110
|
+
"""Represents a maximum fee for a trade."""
|
|
111
|
+
|
|
112
|
+
floor: float
|
|
113
|
+
fee: Fee
|
|
114
|
+
|
|
115
|
+
def to_dict(self):
|
|
116
|
+
return {"kind": "max", "floor": self.floor, "fee": self.fee.to_dict()}
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
def calc_bond_trade_pnl(
|
|
120
|
+
symbol: IntoExpr,
|
|
121
|
+
settle_time: IntoExpr,
|
|
122
|
+
qty: IntoExpr | None = None,
|
|
123
|
+
clean_price: IntoExpr | None = None,
|
|
124
|
+
clean_close: IntoExpr = "close",
|
|
125
|
+
bond_info_path: str | None = None,
|
|
126
|
+
multiplier: float = 1,
|
|
127
|
+
fee: Fee | None = None,
|
|
128
|
+
borrowing_cost: float = 0,
|
|
129
|
+
capital_rate: float = 0,
|
|
130
|
+
begin_state: IntoExpr | None = None,
|
|
131
|
+
) -> pl.Expr:
|
|
132
|
+
"""
|
|
133
|
+
计算债券交易pnl
|
|
134
|
+
symbol: 交易的标的名称, 如果不是债券传⼊空字符串即可。
|
|
135
|
+
settle_time: 结算时间,
|
|
136
|
+
如果settle_time传入代表Trade的struct Series(包含time, price, qty三个field), 则可以不传qty和clean_price
|
|
137
|
+
qty: 成交量, 正负号表⽰⽅向
|
|
138
|
+
clean_price: 成交的净价
|
|
139
|
+
clean_close: 当前时间段的最新价格(净价)
|
|
140
|
+
bond_info_path: 可以指定债券信息的存放⽂件夹, 不传⼊则使⽤默认路径.
|
|
141
|
+
multiplier: 合约乘数, 例如对于债券, 1000的成交对应1000w, 合约乘数应为100
|
|
142
|
+
fee: 交易费⽤
|
|
143
|
+
费⽤设置说明:
|
|
144
|
+
TradeFee: 每笔成交⽀付的费⽤
|
|
145
|
+
QtyFee: 每⼿需要⽀付的费⽤
|
|
146
|
+
PercentFee: 按照成交⾦额百分⽐⽀付的费⽤
|
|
147
|
+
费⽤⽀持相加, 例如 QtyFee(120) + TradeFee(20)
|
|
148
|
+
"""
|
|
149
|
+
assert clean_close is not None
|
|
150
|
+
if fee is None:
|
|
151
|
+
fee = FeeZero()
|
|
152
|
+
fee = fee.to_dict()
|
|
153
|
+
symbol = parse_into_expr(symbol)
|
|
154
|
+
settle_time = parse_into_expr(settle_time)
|
|
155
|
+
clean_close = parse_into_expr(clean_close)
|
|
156
|
+
if begin_state is not None and not isinstance(begin_state, dict):
|
|
157
|
+
begin_state = parse_into_expr(begin_state)
|
|
158
|
+
if bond_info_path is None:
|
|
159
|
+
from .bond import bonds_info_path as path
|
|
160
|
+
|
|
161
|
+
bond_info_path = str(path)
|
|
162
|
+
|
|
163
|
+
if begin_state is None:
|
|
164
|
+
begin_state = pl.lit(
|
|
165
|
+
{
|
|
166
|
+
"pos": 0,
|
|
167
|
+
"avg_price": 0,
|
|
168
|
+
"pnl": 0,
|
|
169
|
+
"realized_pnl": 0,
|
|
170
|
+
"pos_price": 0,
|
|
171
|
+
"unrealized_pnl": 0,
|
|
172
|
+
"coupon_paid": 0,
|
|
173
|
+
"amt": 0,
|
|
174
|
+
"fee": 0,
|
|
175
|
+
}
|
|
176
|
+
)
|
|
177
|
+
kwargs = {
|
|
178
|
+
"multiplier": multiplier,
|
|
179
|
+
"fee": fee,
|
|
180
|
+
"borrowing_cost": borrowing_cost,
|
|
181
|
+
"capital_rate": capital_rate,
|
|
182
|
+
"bond_info_path": bond_info_path,
|
|
183
|
+
}
|
|
184
|
+
if all(x is None for x in [qty, clean_price]):
|
|
185
|
+
# struct settle_time, contains trade info
|
|
186
|
+
args = [symbol, settle_time, clean_close, begin_state]
|
|
187
|
+
else:
|
|
188
|
+
qty = parse_into_expr(qty)
|
|
189
|
+
clean_price = parse_into_expr(clean_price)
|
|
190
|
+
args = [symbol, settle_time, qty, clean_price, clean_close, begin_state]
|
|
191
|
+
return register_plugin(
|
|
192
|
+
args=args,
|
|
193
|
+
kwargs=kwargs,
|
|
194
|
+
symbol="calc_bond_trade_pnl",
|
|
195
|
+
is_elementwise=False,
|
|
196
|
+
)
|
|
197
|
+
|
|
198
|
+
|
|
199
|
+
def calc_trade_pnl(
|
|
200
|
+
time: IntoExpr,
|
|
201
|
+
qty: IntoExpr | None = None,
|
|
202
|
+
price: IntoExpr | None = None,
|
|
203
|
+
close: IntoExpr = "close",
|
|
204
|
+
multiplier: float = 1,
|
|
205
|
+
fee: Fee | None = None,
|
|
206
|
+
begin_state: IntoExpr | None = None,
|
|
207
|
+
):
|
|
208
|
+
"""
|
|
209
|
+
计算交易pnl
|
|
210
|
+
symbol: 交易的标的名称, 如果不是债券传⼊空字符串即可。
|
|
211
|
+
time: 交易时间,
|
|
212
|
+
如果time传入代表Trade的struct Series(包含time, price, qty三个field), 则可以不传qty和clean_price
|
|
213
|
+
qty: 成交量, 正负号表⽰⽅向
|
|
214
|
+
clean_price: 成交的净价
|
|
215
|
+
clean_close: 当前时间段的最新价格(净价)
|
|
216
|
+
multiplier: 合约乘数, 例如对于债券, 1000的成交对应1000w, 合约乘数应为100
|
|
217
|
+
fee: 交易费⽤
|
|
218
|
+
费⽤设置说明:
|
|
219
|
+
TradeFee: 每笔成交⽀付的费⽤
|
|
220
|
+
QtyFee: 每⼿需要⽀付的费⽤
|
|
221
|
+
PercentFee: 按照成交⾦额百分⽐⽀付的费⽤
|
|
222
|
+
费⽤⽀持相加, 例如 QtyFee(120) + TradeFee(20)
|
|
223
|
+
"""
|
|
224
|
+
return calc_bond_trade_pnl(
|
|
225
|
+
symbol=pl.lit(""),
|
|
226
|
+
settle_time=time,
|
|
227
|
+
qty=qty,
|
|
228
|
+
clean_price=price,
|
|
229
|
+
clean_close=close,
|
|
230
|
+
multiplier=multiplier,
|
|
231
|
+
fee=fee,
|
|
232
|
+
begin_state=begin_state,
|
|
233
|
+
)
|
|
234
|
+
|
|
235
|
+
|
|
236
|
+
def trading_from_pos(
|
|
237
|
+
time: IntoExpr,
|
|
238
|
+
pos: IntoExpr,
|
|
239
|
+
open: IntoExpr,
|
|
240
|
+
finish_price: IntoExpr | None = None,
|
|
241
|
+
cash: IntoExpr = 1e8,
|
|
242
|
+
multiplier: float = 1,
|
|
243
|
+
qty_tick: float = 1.0,
|
|
244
|
+
min_adjust_amt: float = 0.0,
|
|
245
|
+
*,
|
|
246
|
+
stop_on_finish: bool = False,
|
|
247
|
+
keep_shape: bool = False,
|
|
248
|
+
) -> pl.Expr:
|
|
249
|
+
"""
|
|
250
|
+
生成交易记录
|
|
251
|
+
time: ⽤于⽣成成交时间, ⽀持任意可以转为polars表达式的输⼊
|
|
252
|
+
pos: 当前时间的实际仓位, -1 ~ 1, 表⽰百分⽐
|
|
253
|
+
open: 当前周期的开仓价格
|
|
254
|
+
cash: 总资⾦, ⽤于计算实际开仓⼿数
|
|
255
|
+
multiplier: 合约乘数
|
|
256
|
+
qty_tick: 最⼩开仓⼿数, 例如0.01, 0.1, 1, 100
|
|
257
|
+
stop_on_finish: 当前标的没有数据后是否平仓
|
|
258
|
+
finish_price: 当前标的没数据时的平仓价格, ⽀持polars表达式
|
|
259
|
+
keep_shape: 是否维持表达式的长度, 不保留则只返回实际发生的交易
|
|
260
|
+
"""
|
|
261
|
+
time = parse_into_expr(time)
|
|
262
|
+
pos = parse_into_expr(pos)
|
|
263
|
+
open = parse_into_expr(open)
|
|
264
|
+
finish_price = parse_into_expr(finish_price)
|
|
265
|
+
cash = parse_into_expr(cash)
|
|
266
|
+
kwargs = {
|
|
267
|
+
"cash": None, # 会从表达式中获取
|
|
268
|
+
"multiplier": float(multiplier),
|
|
269
|
+
"qty_tick": float(qty_tick),
|
|
270
|
+
"stop_on_finish": stop_on_finish,
|
|
271
|
+
"finish_price": None, # 会从表达式中获取
|
|
272
|
+
"min_adjust_amt": float(min_adjust_amt),
|
|
273
|
+
"keep_shape": bool(keep_shape),
|
|
274
|
+
}
|
|
275
|
+
return register_plugin(
|
|
276
|
+
args=[time, pos, open, finish_price, cash],
|
|
277
|
+
kwargs=kwargs,
|
|
278
|
+
symbol="trading_from_pos",
|
|
279
|
+
is_elementwise=False,
|
|
280
|
+
)
|
pybond/polars_utils.py
ADDED
|
@@ -0,0 +1,98 @@
|
|
|
1
|
+
from __future__ import annotations
|
|
2
|
+
|
|
3
|
+
import re
|
|
4
|
+
from pathlib import Path
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
|
+
|
|
7
|
+
import polars as pl
|
|
8
|
+
|
|
9
|
+
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
12
|
+
from polars.type_aliases import IntoExpr, PolarsDataType
|
|
13
|
+
|
|
14
|
+
|
|
15
|
+
def parse_into_expr(
|
|
16
|
+
expr: IntoExpr,
|
|
17
|
+
*,
|
|
18
|
+
str_as_lit: bool = False,
|
|
19
|
+
list_as_lit: bool = True,
|
|
20
|
+
dtype: PolarsDataType | None = None,
|
|
21
|
+
) -> pl.Expr:
|
|
22
|
+
"""
|
|
23
|
+
Parse a single input into an expression.
|
|
24
|
+
|
|
25
|
+
Parameters
|
|
26
|
+
----------
|
|
27
|
+
expr
|
|
28
|
+
The input to be parsed as an expression.
|
|
29
|
+
str_as_lit
|
|
30
|
+
Interpret string input as a string literal. If set to `False` (default),
|
|
31
|
+
strings are parsed as column names.
|
|
32
|
+
list_as_lit
|
|
33
|
+
Interpret list input as a lit literal, If set to `False`,
|
|
34
|
+
lists are parsed as `Series` literals.
|
|
35
|
+
dtype
|
|
36
|
+
If the input is expected to resolve to a literal with a known dtype, pass
|
|
37
|
+
this to the `lit` constructor.
|
|
38
|
+
|
|
39
|
+
Returns
|
|
40
|
+
-------
|
|
41
|
+
polars.Expr
|
|
42
|
+
"""
|
|
43
|
+
if isinstance(expr, pl.Expr):
|
|
44
|
+
pass
|
|
45
|
+
elif isinstance(expr, str) and not str_as_lit:
|
|
46
|
+
expr = pl.col(expr)
|
|
47
|
+
elif isinstance(expr, list) and not list_as_lit:
|
|
48
|
+
expr = pl.lit(pl.Series(expr), dtype=dtype)
|
|
49
|
+
else:
|
|
50
|
+
expr = pl.lit(expr, dtype=dtype)
|
|
51
|
+
|
|
52
|
+
return expr
|
|
53
|
+
|
|
54
|
+
|
|
55
|
+
def register_plugin(
|
|
56
|
+
*,
|
|
57
|
+
symbol: str,
|
|
58
|
+
is_elementwise: bool,
|
|
59
|
+
kwargs: dict[str, Any] | None = None,
|
|
60
|
+
args: list[IntoExpr],
|
|
61
|
+
# lib: str | Path,
|
|
62
|
+
) -> pl.Expr:
|
|
63
|
+
global lib
|
|
64
|
+
if parse_version(pl.__version__) < parse_version("0.20.16"):
|
|
65
|
+
assert isinstance(args[0], pl.Expr)
|
|
66
|
+
assert isinstance(lib, str)
|
|
67
|
+
return args[0].register_plugin(
|
|
68
|
+
lib=lib,
|
|
69
|
+
symbol=symbol,
|
|
70
|
+
args=args[1:],
|
|
71
|
+
kwargs=kwargs,
|
|
72
|
+
is_elementwise=is_elementwise,
|
|
73
|
+
)
|
|
74
|
+
from polars.plugins import register_plugin_function
|
|
75
|
+
|
|
76
|
+
return register_plugin_function(
|
|
77
|
+
args=args,
|
|
78
|
+
plugin_path=lib,
|
|
79
|
+
function_name=symbol,
|
|
80
|
+
kwargs=kwargs,
|
|
81
|
+
is_elementwise=is_elementwise,
|
|
82
|
+
)
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def parse_version(version: Sequence[str | int]) -> tuple[int, ...]:
|
|
86
|
+
# Simple version parser; split into a tuple of ints for comparison.
|
|
87
|
+
# vendored from Polars
|
|
88
|
+
if isinstance(version, str):
|
|
89
|
+
version = version.split(".")
|
|
90
|
+
return tuple(int(re.sub(r"\D", "", str(v))) for v in version)
|
|
91
|
+
|
|
92
|
+
|
|
93
|
+
if parse_version(pl.__version__) < parse_version("0.20.16"):
|
|
94
|
+
from polars.utils.udfs import _get_shared_lib_location
|
|
95
|
+
|
|
96
|
+
lib: str | Path = _get_shared_lib_location(__file__)
|
|
97
|
+
else:
|
|
98
|
+
lib = Path(__file__).parent
|
pybond/pybond.abi3.so
ADDED
|
Binary file
|