tea-bond 0.3.13__tar.gz → 0.3.15__tar.gz
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.
- {tea_bond-0.3.13 → tea_bond-0.3.15}/Cargo.lock +3 -3
- {tea_bond-0.3.13 → tea_bond-0.3.15}/Cargo.toml +1 -1
- {tea_bond-0.3.13 → tea_bond-0.3.15}/PKG-INFO +1 -1
- {tea_bond-0.3.13/pybond → tea_bond-0.3.15}/pybond/pl.py +36 -2
- {tea_bond-0.3.13/pybond → tea_bond-0.3.15}/pybond/pnl.py +6 -5
- {tea_bond-0.3.13 → tea_bond-0.3.15/pybond}/pybond/pl.py +36 -2
- {tea_bond-0.3.13 → tea_bond-0.3.15/pybond}/pybond/pnl.py +6 -5
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/batch_eval.rs +100 -2
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/pnl.rs +5 -2
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/mod.rs +10 -1
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/mod.rs +19 -16
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/trade_from_signal.rs +10 -8
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/evaluator.rs +7 -2
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/.gitignore +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/Cargo.toml +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/download.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/datetime.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/duration.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/evaluators.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/lib.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/ir_utils.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_date.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_datetime.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_duration.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_evaluators.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_time.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb_test.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pd.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/polars_utils.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/download.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/datetime.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/duration.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/evaluators.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/lib.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/__init__.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/ir_utils.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_bond.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_date.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_datetime.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_duration.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_evaluators.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_time.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/pd.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/polars_utils.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/pybond.pyi +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond.pyi +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/bond.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/calendar.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/bond.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/datetime.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/duration.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/evaluators.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/utils.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/future.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/lib.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/tf_evaluator.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/utils.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/test.py +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/pyproject.toml +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/Cargo.toml +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/batch/evaluator.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/batch/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/bond_ytm.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/cached_bond.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/china_money.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/sse.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/enums.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/impl_convert.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/impl_traits.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/io.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/day_counter.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/export.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/future_price.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/future_type.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/impls.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/lib.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/fee.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/impl_traits.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/update_with_new_info.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/utils.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/Cargo.toml +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/ib.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/others.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/sse.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/mod.rs +0 -0
- {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/lib.rs +0 -0
|
@@ -2195,7 +2195,7 @@ dependencies = [
|
|
|
2195
2195
|
|
|
2196
2196
|
[[package]]
|
|
2197
2197
|
name = "pybond"
|
|
2198
|
-
version = "0.3.
|
|
2198
|
+
version = "0.3.15"
|
|
2199
2199
|
dependencies = [
|
|
2200
2200
|
"chrono",
|
|
2201
2201
|
"itertools 0.14.0",
|
|
@@ -3117,7 +3117,7 @@ dependencies = [
|
|
|
3117
3117
|
|
|
3118
3118
|
[[package]]
|
|
3119
3119
|
name = "tea-bond"
|
|
3120
|
-
version = "0.3.
|
|
3120
|
+
version = "0.3.15"
|
|
3121
3121
|
dependencies = [
|
|
3122
3122
|
"anyhow",
|
|
3123
3123
|
"chrono",
|
|
@@ -3136,7 +3136,7 @@ dependencies = [
|
|
|
3136
3136
|
|
|
3137
3137
|
[[package]]
|
|
3138
3138
|
name = "tea-calendar"
|
|
3139
|
-
version = "0.3.
|
|
3139
|
+
version = "0.3.15"
|
|
3140
3140
|
dependencies = [
|
|
3141
3141
|
"chrono",
|
|
3142
3142
|
]
|
|
@@ -280,7 +280,15 @@ class TfEvaluators:
|
|
|
280
280
|
Returns:
|
|
281
281
|
Polars expression for bond remaining year
|
|
282
282
|
"""
|
|
283
|
-
return self._call_plugin("
|
|
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")
|
|
284
292
|
|
|
285
293
|
|
|
286
294
|
class Bonds:
|
|
@@ -329,6 +337,12 @@ class Bonds:
|
|
|
329
337
|
"""
|
|
330
338
|
return self._evaluator(date=date).remain_year
|
|
331
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
|
+
|
|
332
346
|
def accrued_interest(self, date: IntoExpr = "date"):
|
|
333
347
|
"""
|
|
334
348
|
Calculate accrued interest for the bond (应计利息).
|
|
@@ -392,7 +406,27 @@ class Bonds:
|
|
|
392
406
|
"""
|
|
393
407
|
return self._evaluator(date=date).remain_cp_num
|
|
394
408
|
|
|
395
|
-
|
|
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
|
+
)
|
|
396
430
|
|
|
397
431
|
|
|
398
432
|
class Futures:
|
|
@@ -115,7 +115,7 @@ def trading_from_pos(
|
|
|
115
115
|
pos: IntoExpr,
|
|
116
116
|
open: IntoExpr,
|
|
117
117
|
finish_price: IntoExpr | None = None,
|
|
118
|
-
cash:
|
|
118
|
+
cash: IntoExpr = 1e8,
|
|
119
119
|
multiplier: float = 1,
|
|
120
120
|
qty_tick: float = 1.0,
|
|
121
121
|
*,
|
|
@@ -125,15 +125,16 @@ def trading_from_pos(
|
|
|
125
125
|
pos = parse_into_expr(pos)
|
|
126
126
|
open = parse_into_expr(open)
|
|
127
127
|
finish_price = parse_into_expr(finish_price)
|
|
128
|
+
cash = parse_into_expr(cash)
|
|
128
129
|
kwargs = {
|
|
129
|
-
"cash":
|
|
130
|
-
"multiplier": multiplier,
|
|
131
|
-
"qty_tick": qty_tick,
|
|
130
|
+
"cash": None,
|
|
131
|
+
"multiplier": float(multiplier),
|
|
132
|
+
"qty_tick": float(qty_tick),
|
|
132
133
|
"stop_on_finish": stop_on_finish,
|
|
133
134
|
"finish_price": None,
|
|
134
135
|
}
|
|
135
136
|
return register_plugin(
|
|
136
|
-
args=[time, pos, open, finish_price],
|
|
137
|
+
args=[time, pos, open, finish_price, cash],
|
|
137
138
|
kwargs=kwargs,
|
|
138
139
|
symbol="trading_from_pos",
|
|
139
140
|
is_elementwise=False,
|
|
@@ -280,7 +280,15 @@ class TfEvaluators:
|
|
|
280
280
|
Returns:
|
|
281
281
|
Polars expression for bond remaining year
|
|
282
282
|
"""
|
|
283
|
-
return self._call_plugin("
|
|
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")
|
|
284
292
|
|
|
285
293
|
|
|
286
294
|
class Bonds:
|
|
@@ -329,6 +337,12 @@ class Bonds:
|
|
|
329
337
|
"""
|
|
330
338
|
return self._evaluator(date=date).remain_year
|
|
331
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
|
+
|
|
332
346
|
def accrued_interest(self, date: IntoExpr = "date"):
|
|
333
347
|
"""
|
|
334
348
|
Calculate accrued interest for the bond (应计利息).
|
|
@@ -392,7 +406,27 @@ class Bonds:
|
|
|
392
406
|
"""
|
|
393
407
|
return self._evaluator(date=date).remain_cp_num
|
|
394
408
|
|
|
395
|
-
|
|
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
|
+
)
|
|
396
430
|
|
|
397
431
|
|
|
398
432
|
class Futures:
|
|
@@ -115,7 +115,7 @@ def trading_from_pos(
|
|
|
115
115
|
pos: IntoExpr,
|
|
116
116
|
open: IntoExpr,
|
|
117
117
|
finish_price: IntoExpr | None = None,
|
|
118
|
-
cash:
|
|
118
|
+
cash: IntoExpr = 1e8,
|
|
119
119
|
multiplier: float = 1,
|
|
120
120
|
qty_tick: float = 1.0,
|
|
121
121
|
*,
|
|
@@ -125,15 +125,16 @@ def trading_from_pos(
|
|
|
125
125
|
pos = parse_into_expr(pos)
|
|
126
126
|
open = parse_into_expr(open)
|
|
127
127
|
finish_price = parse_into_expr(finish_price)
|
|
128
|
+
cash = parse_into_expr(cash)
|
|
128
129
|
kwargs = {
|
|
129
|
-
"cash":
|
|
130
|
-
"multiplier": multiplier,
|
|
131
|
-
"qty_tick": qty_tick,
|
|
130
|
+
"cash": None,
|
|
131
|
+
"multiplier": float(multiplier),
|
|
132
|
+
"qty_tick": float(qty_tick),
|
|
132
133
|
"stop_on_finish": stop_on_finish,
|
|
133
134
|
"finish_price": None,
|
|
134
135
|
}
|
|
135
136
|
return register_plugin(
|
|
136
|
-
args=[time, pos, open, finish_price],
|
|
137
|
+
args=[time, pos, open, finish_price, cash],
|
|
137
138
|
kwargs=kwargs,
|
|
138
139
|
symbol="trading_from_pos",
|
|
139
140
|
is_elementwise=False,
|
|
@@ -183,6 +183,7 @@ where
|
|
|
183
183
|
let (future_price, bond_ytm, capital_rate) =
|
|
184
184
|
auto_cast!(Float64(future_price, bond_ytm, capital_rate));
|
|
185
185
|
let date = auto_cast!(Date(date));
|
|
186
|
+
let bond = auto_cast!(String(bond));
|
|
186
187
|
Ok(batch_eval_impl(
|
|
187
188
|
future.str()?,
|
|
188
189
|
bond.str()?,
|
|
@@ -524,8 +525,8 @@ fn evaluators_last_trading_date(
|
|
|
524
525
|
Ok(result.into_date().into_series())
|
|
525
526
|
}
|
|
526
527
|
|
|
527
|
-
#[polars_expr(output_type=
|
|
528
|
-
fn
|
|
528
|
+
#[polars_expr(output_type=Float64)]
|
|
529
|
+
fn bonds_remain_year(
|
|
529
530
|
inputs: &[Series],
|
|
530
531
|
kwargs: EvaluatorBatchParams,
|
|
531
532
|
) -> PolarsResult<Series> {
|
|
@@ -544,6 +545,103 @@ fn evaluators_remain_year(
|
|
|
544
545
|
Ok(result.into_series())
|
|
545
546
|
}
|
|
546
547
|
|
|
548
|
+
#[polars_expr(output_type=Date)]
|
|
549
|
+
fn bonds_carry_date(
|
|
550
|
+
inputs: &[Series],
|
|
551
|
+
kwargs: EvaluatorBatchParams,
|
|
552
|
+
) -> PolarsResult<Series> {
|
|
553
|
+
let result: Int32Chunked = batch_eval(
|
|
554
|
+
inputs,
|
|
555
|
+
kwargs,
|
|
556
|
+
|e: TfEvaluator| e,
|
|
557
|
+
|e: &TfEvaluator| {
|
|
558
|
+
Some(e.bond.carry_date.num_days_from_ce() - EPOCH_DAYS_FROM_CE)
|
|
559
|
+
},
|
|
560
|
+
false,
|
|
561
|
+
true,
|
|
562
|
+
)?
|
|
563
|
+
.into_iter()
|
|
564
|
+
.collect_trusted();
|
|
565
|
+
Ok(result.into_date().into_series())
|
|
566
|
+
}
|
|
567
|
+
|
|
568
|
+
#[polars_expr(output_type=Date)]
|
|
569
|
+
fn bonds_maturity_date(
|
|
570
|
+
inputs: &[Series],
|
|
571
|
+
kwargs: EvaluatorBatchParams,
|
|
572
|
+
) -> PolarsResult<Series> {
|
|
573
|
+
let result: Int32Chunked = batch_eval(
|
|
574
|
+
inputs,
|
|
575
|
+
kwargs,
|
|
576
|
+
|e: TfEvaluator| e,
|
|
577
|
+
|e: &TfEvaluator| {
|
|
578
|
+
Some(e.bond.maturity_date.num_days_from_ce() - EPOCH_DAYS_FROM_CE)
|
|
579
|
+
},
|
|
580
|
+
false,
|
|
581
|
+
true,
|
|
582
|
+
)?
|
|
583
|
+
.into_iter()
|
|
584
|
+
.collect_trusted();
|
|
585
|
+
Ok(result.into_date().into_series())
|
|
586
|
+
}
|
|
587
|
+
|
|
588
|
+
#[polars_expr(output_type=Float64)]
|
|
589
|
+
fn bonds_calc_ytm_with_price(
|
|
590
|
+
inputs: &[Series]
|
|
591
|
+
) -> PolarsResult<Series> {
|
|
592
|
+
let dirty_price_se = auto_cast!(Float64(&inputs[2]));
|
|
593
|
+
let bond_se = auto_cast!(String(&inputs[0]));
|
|
594
|
+
let date_se = auto_cast!(Date(&inputs[1]));
|
|
595
|
+
let len = dirty_price_se.len();
|
|
596
|
+
let bond = bond_se.str()?;
|
|
597
|
+
let date = date_se.date()?;
|
|
598
|
+
let dirty_price = dirty_price_se.f64()?;
|
|
599
|
+
let mut bond_iter = bond.iter();
|
|
600
|
+
let mut date_iter = date.physical().iter();
|
|
601
|
+
let mut bond = CachedBond::new(bond_iter.next().unwrap().unwrap_or(""), None).unwrap();
|
|
602
|
+
let mut dirty_price_iter = dirty_price.iter();
|
|
603
|
+
let mut date_physical = date_iter.next().unwrap().unwrap_or(0);
|
|
604
|
+
let mut date = EPOCH
|
|
605
|
+
.checked_add_days(Days::new(date_physical as u64))
|
|
606
|
+
.unwrap();
|
|
607
|
+
let mut dirty_price = dirty_price_iter.next().unwrap().unwrap_or(f64::NAN);
|
|
608
|
+
let mut result = Vec::with_capacity(len);
|
|
609
|
+
if bond.bond_code().is_empty() {
|
|
610
|
+
result.push(None)
|
|
611
|
+
} else {
|
|
612
|
+
result.push(bond.calc_ytm_with_price(dirty_price, date, None, None).ok().filter(|v| !v.is_nan()))
|
|
613
|
+
}
|
|
614
|
+
for _ in 1..len {
|
|
615
|
+
if let Some(dp) = dirty_price_iter.next() {
|
|
616
|
+
dirty_price = dp.unwrap_or(f64::NAN);
|
|
617
|
+
};
|
|
618
|
+
if let Some(dt) = date_iter.next() {
|
|
619
|
+
let dt = dt.unwrap_or(0);
|
|
620
|
+
if dt != date_physical {
|
|
621
|
+
date_physical = dt;
|
|
622
|
+
date = EPOCH.checked_add_days(Days::new(dt as u64)).unwrap()
|
|
623
|
+
}
|
|
624
|
+
};
|
|
625
|
+
if let Some(b) = bond_iter.next() {
|
|
626
|
+
if let Some(b) = b {
|
|
627
|
+
if b != bond.code() && bond.bond_code != b {
|
|
628
|
+
bond = CachedBond::new(b, None).unwrap();
|
|
629
|
+
}
|
|
630
|
+
} else {
|
|
631
|
+
result.push(None);
|
|
632
|
+
continue;
|
|
633
|
+
}
|
|
634
|
+
};
|
|
635
|
+
if bond.bond_code().is_empty() {
|
|
636
|
+
result.push(None);
|
|
637
|
+
} else {
|
|
638
|
+
result.push(bond.calc_ytm_with_price(dirty_price, date, None, None).ok().filter(|v| !v.is_nan()))
|
|
639
|
+
}
|
|
640
|
+
}
|
|
641
|
+
let result: Float64Chunked = result.into_iter().collect_trusted();
|
|
642
|
+
Ok(result.into_series())
|
|
643
|
+
}
|
|
644
|
+
|
|
547
645
|
#[derive(Deserialize)]
|
|
548
646
|
struct FindWorkdayKwargs {
|
|
549
647
|
market: Market,
|
|
@@ -141,11 +141,14 @@ fn get_trading_output_type(input_fields: &[Field]) -> PolarsResult<Field> {
|
|
|
141
141
|
fn trading_from_pos(inputs: &[Series], mut kwargs: pnl::TradeFromPosOpt) -> PolarsResult<Series> {
|
|
142
142
|
use pyo3_polars::export::polars_core::utils::CustomIterTools;
|
|
143
143
|
use tevec::export::polars::prelude::*;
|
|
144
|
-
let (time, pos, open, finish_price) = (&inputs[0], &inputs[1], &inputs[2], &inputs[3]);
|
|
145
|
-
let (pos, open, finish_price) = auto_cast!(Float64(pos, open, finish_price));
|
|
144
|
+
let (time, pos, open, finish_price, cash) = (&inputs[0], &inputs[1], &inputs[2], &inputs[3], &inputs[4]);
|
|
145
|
+
let (pos, open, finish_price, cash) = auto_cast!(Float64(pos, open, finish_price, cash));
|
|
146
146
|
if let Some(p) = finish_price.f64()?.iter().next() {
|
|
147
147
|
kwargs.finish_price = p
|
|
148
148
|
};
|
|
149
|
+
if let Some(c) = cash.f64()?.iter().next() {
|
|
150
|
+
kwargs.cash = c
|
|
151
|
+
};
|
|
149
152
|
let res = match time.dtype() {
|
|
150
153
|
DataType::Date => {
|
|
151
154
|
let trade_vec =
|
|
@@ -152,7 +152,7 @@ impl Bond {
|
|
|
152
152
|
self.carry_date
|
|
153
153
|
);
|
|
154
154
|
return Ok(self.carry_date);
|
|
155
|
-
} else if date
|
|
155
|
+
} else if date > self.maturity_date {
|
|
156
156
|
eprintln!(
|
|
157
157
|
"Calculating date {} is after the bond {} 's maturity date {}, the result may be incorrect",
|
|
158
158
|
date,
|
|
@@ -345,6 +345,15 @@ impl Bond {
|
|
|
345
345
|
) -> Result<f64> {
|
|
346
346
|
match self.interest_type {
|
|
347
347
|
InterestType::Fixed => {
|
|
348
|
+
if self.is_zero_coupon() {
|
|
349
|
+
let remain_year = self.remain_year(date);
|
|
350
|
+
if remain_year >= 1. {
|
|
351
|
+
return Ok((self.par_value / dirty_price).powf(1.0 / remain_year) - 1.)
|
|
352
|
+
} else {
|
|
353
|
+
return Ok((self.par_value / dirty_price - 1.) * remain_year)
|
|
354
|
+
}
|
|
355
|
+
|
|
356
|
+
}
|
|
348
357
|
let inst_freq = self.inst_freq as f64;
|
|
349
358
|
let coupon = self.get_coupon();
|
|
350
359
|
let (pre_cp_date, next_cp_date) =
|
|
@@ -88,24 +88,27 @@ where
|
|
|
88
88
|
.checked_add_days(Days::new(settle_time.unwrap() as u64))
|
|
89
89
|
.unwrap();
|
|
90
90
|
let (trade_price, close): (Option<f64>, Option<f64>) = if let Some(bond) = &symbol {
|
|
91
|
-
if
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
91
|
+
if !bond.is_zero_coupon() {
|
|
92
|
+
if last_settle_time != Some(settle_time) {
|
|
93
|
+
// 新的一天重新计算相关信息
|
|
94
|
+
let cp_dates = bond.get_nearest_cp_date(settle_time).unwrap();
|
|
95
|
+
accrued_interest = bond
|
|
96
|
+
.calc_accrued_interest(settle_time, Some(cp_dates))
|
|
97
|
+
.unwrap();
|
|
98
|
+
last_cp_date = bond.mkt.find_workday(cp_dates.0, 0);
|
|
99
|
+
// 当天初始仓位会产生的票息
|
|
100
|
+
if settle_time == last_cp_date {
|
|
101
|
+
// 调节应计利息
|
|
102
|
+
accrued_interest = coupon_paid;
|
|
103
|
+
state.coupon_paid += coupon_paid * multiplier * state.pos;
|
|
104
|
+
}
|
|
105
|
+
last_settle_time = Some(settle_time);
|
|
106
|
+
}
|
|
107
|
+
// 交易当天会产生付息
|
|
108
|
+
if (settle_time == last_cp_date) & (qty != 0.) {
|
|
109
|
+
state.coupon_paid += coupon_paid * multiplier * qty;
|
|
101
110
|
}
|
|
102
|
-
last_settle_time = Some(settle_time);
|
|
103
|
-
}
|
|
104
|
-
// 当前需要付息
|
|
105
|
-
if settle_time == last_cp_date {
|
|
106
|
-
state.coupon_paid += coupon_paid * multiplier * qty;
|
|
107
111
|
}
|
|
108
|
-
|
|
109
112
|
(
|
|
110
113
|
clean_price.map(|v| v.f64() + accrued_interest),
|
|
111
114
|
clean_close.map(|v| v.f64() + accrued_interest),
|
|
@@ -11,8 +11,7 @@ pub struct Trade<T> {
|
|
|
11
11
|
|
|
12
12
|
#[derive(Deserialize)]
|
|
13
13
|
pub struct TradeFromPosOpt {
|
|
14
|
-
|
|
15
|
-
pub cash: f64,
|
|
14
|
+
pub cash: Option<f64>,
|
|
16
15
|
pub multiplier: f64,
|
|
17
16
|
pub qty_tick: f64,
|
|
18
17
|
pub stop_on_finish: bool,
|
|
@@ -48,6 +47,7 @@ where
|
|
|
48
47
|
let mut last_pos = 0.;
|
|
49
48
|
let mut open_price: f64 = 0.;
|
|
50
49
|
let mut open_qty: f64 = 0.;
|
|
50
|
+
let cash = opt.cash.unwrap();
|
|
51
51
|
let mut trades = Vec::with_capacity(INIT_TRADE_COUNT);
|
|
52
52
|
|
|
53
53
|
// 记录最后一个可用 (time, price),用于 stop_on_finish
|
|
@@ -57,7 +57,6 @@ where
|
|
|
57
57
|
if pos.not_none() && open.not_none() {
|
|
58
58
|
let pos = pos.unwrap().f64();
|
|
59
59
|
let price = open.unwrap().f64();
|
|
60
|
-
|
|
61
60
|
// 记录最新可用的时间与价格
|
|
62
61
|
last_tp = Some((time.clone(), price));
|
|
63
62
|
|
|
@@ -66,17 +65,20 @@ where
|
|
|
66
65
|
// 目标名义 -> 成交量(正买负卖)
|
|
67
66
|
let qty = if pos.abs() > EPS {
|
|
68
67
|
let p = if open_price > 0. { open_price } else { price };
|
|
69
|
-
let raw_qty = dpos *
|
|
68
|
+
let raw_qty = dpos * cash / (p * opt.multiplier);
|
|
70
69
|
// 量化到最小变动单位(朝 0 截断,避免超买/超卖)
|
|
71
70
|
quantize_inside(raw_qty, opt.qty_tick)
|
|
72
71
|
} else {
|
|
73
72
|
-open_qty
|
|
74
73
|
};
|
|
75
|
-
if
|
|
76
|
-
|
|
74
|
+
if open_qty == 0. {
|
|
75
|
+
// 开仓情况
|
|
76
|
+
open_price = price;
|
|
77
|
+
} else if dpos.signum() == open_qty.signum() {
|
|
78
|
+
open_price = (open_price * open_qty + qty * price) / (qty + open_qty);
|
|
77
79
|
} else if open_qty.abs() > qty.abs() {
|
|
78
80
|
// 反向加仓, 价格为新的开仓价格
|
|
79
|
-
open_price = price
|
|
81
|
+
open_price = price;
|
|
80
82
|
};
|
|
81
83
|
// 减仓情况的价格不改变
|
|
82
84
|
|
|
@@ -97,7 +99,7 @@ where
|
|
|
97
99
|
});
|
|
98
100
|
|
|
99
101
|
// 收尾是否强制平仓
|
|
100
|
-
if opt.stop_on_finish && open_qty != 0.0 {
|
|
102
|
+
if opt.stop_on_finish && (open_qty != 0.0) {
|
|
101
103
|
if let Some((t, p)) = last_tp {
|
|
102
104
|
let p = if let Some(p) = opt.finish_price { p } else { p };
|
|
103
105
|
trades.push(Trade {
|
|
@@ -161,7 +161,8 @@ impl TfEvaluator {
|
|
|
161
161
|
pub fn with_accrued_interest(mut self) -> Result<Self> {
|
|
162
162
|
if self.accrued_interest.is_none() {
|
|
163
163
|
if self.bond.is_zero_coupon() {
|
|
164
|
-
|
|
164
|
+
let days = ACTUAL.count_days(self.bond.carry_date, self.date);
|
|
165
|
+
self.accrued_interest = Some(self.bond.cp_rate * self.bond.par_value * days as f64 / 365.);
|
|
165
166
|
return Ok(self);
|
|
166
167
|
}
|
|
167
168
|
let mut out = self.with_nearest_cp_dates()?;
|
|
@@ -181,7 +182,11 @@ impl TfEvaluator {
|
|
|
181
182
|
if self.dirty_price.is_none() {
|
|
182
183
|
if self.bond.is_zero_coupon() {
|
|
183
184
|
let remain_year = self.bond.remain_year(self.date);
|
|
184
|
-
|
|
185
|
+
if remain_year > 1. {
|
|
186
|
+
self.dirty_price = Some(self.bond.par_value / (1.0+self.bond.ytm()).powf(remain_year));
|
|
187
|
+
} else {
|
|
188
|
+
self.dirty_price = Some(self.bond.par_value / (1.0+self.bond.ytm() * remain_year));
|
|
189
|
+
}
|
|
185
190
|
return Ok(self);
|
|
186
191
|
}
|
|
187
192
|
let mut out = self.with_remain_cp_num()?;
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|
|
File without changes
|