tea-bond 0.3.11__tar.gz → 0.3.13__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.11 → tea_bond-0.3.13}/Cargo.lock +3 -3
- {tea_bond-0.3.11 → tea_bond-0.3.13}/Cargo.toml +1 -1
- {tea_bond-0.3.11 → tea_bond-0.3.13}/PKG-INFO +1 -1
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/bond.py +4 -2
- {tea_bond-0.3.11/pybond → tea_bond-0.3.13}/pybond/download.py +0 -2
- {tea_bond-0.3.11/pybond → tea_bond-0.3.13}/pybond/pd.py +17 -1
- {tea_bond-0.3.11/pybond → tea_bond-0.3.13}/pybond/pl.py +19 -0
- {tea_bond-0.3.11/pybond → tea_bond-0.3.13}/pybond/pnl.py +30 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/polars_utils.py +3 -1
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/bond.py +4 -2
- {tea_bond-0.3.11 → tea_bond-0.3.13/pybond}/pybond/download.py +0 -2
- {tea_bond-0.3.11 → tea_bond-0.3.13/pybond}/pybond/pd.py +17 -1
- {tea_bond-0.3.11 → tea_bond-0.3.13/pybond}/pybond/pl.py +19 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13/pybond}/pybond/pnl.py +30 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/polars_utils.py +3 -1
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/batch_eval.rs +31 -11
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/pnl.rs +76 -34
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/test.py +29 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/mod.rs +1 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/pnl/mod.rs +3 -0
- tea_bond-0.3.13/tea-bond/src/pnl/trade_from_signal.rs +112 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/tf_evaluator/evaluator.rs +21 -4
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/.gitignore +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/Cargo.toml +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/bond.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/datetime.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/duration.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/evaluators.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/ffi/lib.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/ir_utils.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_bond.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_date.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_datetime.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_duration.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_evaluators.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb/nb_time.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/nb_test.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/bond.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/datetime.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/duration.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/evaluators.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/ffi/lib.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/__init__.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/ir_utils.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_bond.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_date.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_datetime.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_duration.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_evaluators.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/nb/nb_time.py +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond/pybond.pyi +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/pybond.pyi +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/bond.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/calendar.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/bond.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/datetime.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/duration.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/evaluators.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/ffi/utils.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/future.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/lib.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/tf_evaluator.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pybond/src/utils.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/pyproject.toml +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/Cargo.toml +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/batch/evaluator.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/batch/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/bond_ytm.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/cached_bond.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/download/china_money.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/download/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/download/sse.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/enums.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/impl_convert.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/impl_traits.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/bond/io.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/day_counter.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/export.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/future/future_price.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/future/future_type.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/future/impls.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/future/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/lib.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/pnl/fee.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/tf_evaluator/impl_traits.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/tf_evaluator/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/tf_evaluator/update_with_new_info.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-bond/src/utils.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/Cargo.toml +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/src/calendars/china/ib.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/src/calendars/china/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/src/calendars/china/others.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/src/calendars/china/sse.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/tea-calendar/src/calendars/mod.rs +0 -0
- {tea_bond-0.3.11 → tea_bond-0.3.13}/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.13"
|
|
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.13"
|
|
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.13"
|
|
3140
3140
|
dependencies = [
|
|
3141
3141
|
"chrono",
|
|
3142
3142
|
]
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from datetime import date
|
|
5
4
|
from importlib.util import find_spec
|
|
6
5
|
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from .pybond import Bond as _BondRS
|
|
9
9
|
from .pybond import Future, download_bond
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from datetime import date
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
WIND_AVAILABLE = find_spec("WindPy") is not None
|
|
14
16
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
4
5
|
import polars as pl
|
|
5
6
|
|
|
6
7
|
from .pl import Bonds as PlBonds
|
|
7
8
|
from .pl import Futures as PlFutures
|
|
8
9
|
from .pl import TfEvaluators as PlTfEvaluators
|
|
9
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class TfEvaluators:
|
|
12
16
|
"""
|
|
@@ -287,6 +291,12 @@ class TfEvaluators:
|
|
|
287
291
|
"last_trading_date"
|
|
288
292
|
].to_pandas()
|
|
289
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
|
+
|
|
290
300
|
|
|
291
301
|
class Bonds:
|
|
292
302
|
"""
|
|
@@ -383,6 +393,12 @@ class Bonds:
|
|
|
383
393
|
"remain_cp_num"
|
|
384
394
|
].to_pandas()
|
|
385
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
|
+
|
|
386
402
|
|
|
387
403
|
class Futures:
|
|
388
404
|
def __init__(self, future: str | pd.Series):
|
|
@@ -269,6 +269,19 @@ class TfEvaluators:
|
|
|
269
269
|
"""
|
|
270
270
|
return self._call_plugin("evaluators_last_trading_date")
|
|
271
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("evaluators_remain_year")
|
|
284
|
+
|
|
272
285
|
|
|
273
286
|
class Bonds:
|
|
274
287
|
"""
|
|
@@ -310,6 +323,12 @@ class Bonds:
|
|
|
310
323
|
reinvest_rate=None,
|
|
311
324
|
)
|
|
312
325
|
|
|
326
|
+
def remain_year(self, date: IntoExpr = "date"):
|
|
327
|
+
"""
|
|
328
|
+
Calculate remain year for the bond (剩余期限).
|
|
329
|
+
"""
|
|
330
|
+
return self._evaluator(date=date).remain_year
|
|
331
|
+
|
|
313
332
|
def accrued_interest(self, date: IntoExpr = "date"):
|
|
314
333
|
"""
|
|
315
334
|
Calculate accrued interest for the bond (应计利息).
|
|
@@ -108,3 +108,33 @@ def calc_bond_trade_pnl(
|
|
|
108
108
|
symbol="calc_bond_trade_pnl",
|
|
109
109
|
is_elementwise=False,
|
|
110
110
|
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def trading_from_pos(
|
|
114
|
+
time: IntoExpr,
|
|
115
|
+
pos: IntoExpr,
|
|
116
|
+
open: IntoExpr,
|
|
117
|
+
finish_price: IntoExpr | None = None,
|
|
118
|
+
cash: float = 1e8,
|
|
119
|
+
multiplier: float = 1,
|
|
120
|
+
qty_tick: float = 1.0,
|
|
121
|
+
*,
|
|
122
|
+
stop_on_finish: bool = False,
|
|
123
|
+
) -> pl.Expr:
|
|
124
|
+
time = parse_into_expr(time)
|
|
125
|
+
pos = parse_into_expr(pos)
|
|
126
|
+
open = parse_into_expr(open)
|
|
127
|
+
finish_price = parse_into_expr(finish_price)
|
|
128
|
+
kwargs = {
|
|
129
|
+
"cash": cash,
|
|
130
|
+
"multiplier": multiplier,
|
|
131
|
+
"qty_tick": qty_tick,
|
|
132
|
+
"stop_on_finish": stop_on_finish,
|
|
133
|
+
"finish_price": None,
|
|
134
|
+
}
|
|
135
|
+
return register_plugin(
|
|
136
|
+
args=[time, pos, open, finish_price],
|
|
137
|
+
kwargs=kwargs,
|
|
138
|
+
symbol="trading_from_pos",
|
|
139
|
+
is_elementwise=False,
|
|
140
|
+
)
|
|
@@ -2,11 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
import polars as pl
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
10
12
|
from polars.type_aliases import IntoExpr, PolarsDataType
|
|
11
13
|
|
|
12
14
|
|
|
@@ -1,14 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
3
|
import os
|
|
4
|
-
from datetime import date
|
|
5
4
|
from importlib.util import find_spec
|
|
6
5
|
from pathlib import Path
|
|
6
|
+
from typing import TYPE_CHECKING
|
|
7
7
|
|
|
8
8
|
from .pybond import Bond as _BondRS
|
|
9
9
|
from .pybond import Future, download_bond
|
|
10
10
|
|
|
11
|
-
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
from datetime import date
|
|
13
|
+
|
|
12
14
|
|
|
13
15
|
WIND_AVAILABLE = find_spec("WindPy") is not None
|
|
14
16
|
|
|
@@ -1,12 +1,16 @@
|
|
|
1
1
|
from __future__ import annotations
|
|
2
2
|
|
|
3
|
-
|
|
3
|
+
from typing import TYPE_CHECKING
|
|
4
|
+
|
|
4
5
|
import polars as pl
|
|
5
6
|
|
|
6
7
|
from .pl import Bonds as PlBonds
|
|
7
8
|
from .pl import Futures as PlFutures
|
|
8
9
|
from .pl import TfEvaluators as PlTfEvaluators
|
|
9
10
|
|
|
11
|
+
if TYPE_CHECKING:
|
|
12
|
+
import pandas as pd
|
|
13
|
+
|
|
10
14
|
|
|
11
15
|
class TfEvaluators:
|
|
12
16
|
"""
|
|
@@ -287,6 +291,12 @@ class TfEvaluators:
|
|
|
287
291
|
"last_trading_date"
|
|
288
292
|
].to_pandas()
|
|
289
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
|
+
|
|
290
300
|
|
|
291
301
|
class Bonds:
|
|
292
302
|
"""
|
|
@@ -383,6 +393,12 @@ class Bonds:
|
|
|
383
393
|
"remain_cp_num"
|
|
384
394
|
].to_pandas()
|
|
385
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
|
+
|
|
386
402
|
|
|
387
403
|
class Futures:
|
|
388
404
|
def __init__(self, future: str | pd.Series):
|
|
@@ -269,6 +269,19 @@ class TfEvaluators:
|
|
|
269
269
|
"""
|
|
270
270
|
return self._call_plugin("evaluators_last_trading_date")
|
|
271
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("evaluators_remain_year")
|
|
284
|
+
|
|
272
285
|
|
|
273
286
|
class Bonds:
|
|
274
287
|
"""
|
|
@@ -310,6 +323,12 @@ class Bonds:
|
|
|
310
323
|
reinvest_rate=None,
|
|
311
324
|
)
|
|
312
325
|
|
|
326
|
+
def remain_year(self, date: IntoExpr = "date"):
|
|
327
|
+
"""
|
|
328
|
+
Calculate remain year for the bond (剩余期限).
|
|
329
|
+
"""
|
|
330
|
+
return self._evaluator(date=date).remain_year
|
|
331
|
+
|
|
313
332
|
def accrued_interest(self, date: IntoExpr = "date"):
|
|
314
333
|
"""
|
|
315
334
|
Calculate accrued interest for the bond (应计利息).
|
|
@@ -108,3 +108,33 @@ def calc_bond_trade_pnl(
|
|
|
108
108
|
symbol="calc_bond_trade_pnl",
|
|
109
109
|
is_elementwise=False,
|
|
110
110
|
)
|
|
111
|
+
|
|
112
|
+
|
|
113
|
+
def trading_from_pos(
|
|
114
|
+
time: IntoExpr,
|
|
115
|
+
pos: IntoExpr,
|
|
116
|
+
open: IntoExpr,
|
|
117
|
+
finish_price: IntoExpr | None = None,
|
|
118
|
+
cash: float = 1e8,
|
|
119
|
+
multiplier: float = 1,
|
|
120
|
+
qty_tick: float = 1.0,
|
|
121
|
+
*,
|
|
122
|
+
stop_on_finish: bool = False,
|
|
123
|
+
) -> pl.Expr:
|
|
124
|
+
time = parse_into_expr(time)
|
|
125
|
+
pos = parse_into_expr(pos)
|
|
126
|
+
open = parse_into_expr(open)
|
|
127
|
+
finish_price = parse_into_expr(finish_price)
|
|
128
|
+
kwargs = {
|
|
129
|
+
"cash": cash,
|
|
130
|
+
"multiplier": multiplier,
|
|
131
|
+
"qty_tick": qty_tick,
|
|
132
|
+
"stop_on_finish": stop_on_finish,
|
|
133
|
+
"finish_price": None,
|
|
134
|
+
}
|
|
135
|
+
return register_plugin(
|
|
136
|
+
args=[time, pos, open, finish_price],
|
|
137
|
+
kwargs=kwargs,
|
|
138
|
+
symbol="trading_from_pos",
|
|
139
|
+
is_elementwise=False,
|
|
140
|
+
)
|
|
@@ -2,11 +2,13 @@ from __future__ import annotations
|
|
|
2
2
|
|
|
3
3
|
import re
|
|
4
4
|
from pathlib import Path
|
|
5
|
-
from typing import TYPE_CHECKING, Any
|
|
5
|
+
from typing import TYPE_CHECKING, Any
|
|
6
6
|
|
|
7
7
|
import polars as pl
|
|
8
8
|
|
|
9
9
|
if TYPE_CHECKING:
|
|
10
|
+
from collections.abc import Sequence
|
|
11
|
+
|
|
10
12
|
from polars.type_aliases import IntoExpr, PolarsDataType
|
|
11
13
|
|
|
12
14
|
|
|
@@ -207,7 +207,7 @@ fn evaluators_net_basis_spread(
|
|
|
207
207
|
inputs,
|
|
208
208
|
kwargs,
|
|
209
209
|
|e: TfEvaluator| e.with_net_basis_spread().unwrap(),
|
|
210
|
-
|e: &TfEvaluator| e.net_basis_spread,
|
|
210
|
+
|e: &TfEvaluator| e.net_basis_spread.filter(|v| !v.is_nan()),
|
|
211
211
|
true,
|
|
212
212
|
true,
|
|
213
213
|
)?
|
|
@@ -273,7 +273,7 @@ fn evaluators_dirty_price(inputs: &[Series], kwargs: EvaluatorBatchParams) -> Po
|
|
|
273
273
|
inputs,
|
|
274
274
|
kwargs,
|
|
275
275
|
|e: TfEvaluator| e.with_dirty_price().unwrap(),
|
|
276
|
-
|e: &TfEvaluator| e.dirty_price,
|
|
276
|
+
|e: &TfEvaluator| e.dirty_price.filter(|v| !v.is_nan()),
|
|
277
277
|
false,
|
|
278
278
|
true,
|
|
279
279
|
)?
|
|
@@ -288,7 +288,7 @@ fn evaluators_clean_price(inputs: &[Series], kwargs: EvaluatorBatchParams) -> Po
|
|
|
288
288
|
inputs,
|
|
289
289
|
kwargs,
|
|
290
290
|
|e: TfEvaluator| e.with_clean_price().unwrap(),
|
|
291
|
-
|e: &TfEvaluator| e.clean_price,
|
|
291
|
+
|e: &TfEvaluator| e.clean_price.filter(|v| !v.is_nan()),
|
|
292
292
|
false,
|
|
293
293
|
true,
|
|
294
294
|
)?
|
|
@@ -306,7 +306,7 @@ fn evaluators_future_dirty_price(
|
|
|
306
306
|
inputs,
|
|
307
307
|
kwargs,
|
|
308
308
|
|e: TfEvaluator| e.with_future_dirty_price().unwrap(),
|
|
309
|
-
|e: &TfEvaluator| e.future_dirty_price,
|
|
309
|
+
|e: &TfEvaluator| e.future_dirty_price.filter(|v| !v.is_nan()),
|
|
310
310
|
true,
|
|
311
311
|
true,
|
|
312
312
|
)?
|
|
@@ -324,7 +324,7 @@ fn evaluators_deliver_cost(
|
|
|
324
324
|
inputs,
|
|
325
325
|
kwargs,
|
|
326
326
|
|e: TfEvaluator| e.with_deliver_cost().unwrap(),
|
|
327
|
-
|e: &TfEvaluator| e.deliver_cost,
|
|
327
|
+
|e: &TfEvaluator| e.deliver_cost.filter(|v| !v.is_nan()),
|
|
328
328
|
true,
|
|
329
329
|
true,
|
|
330
330
|
)?
|
|
@@ -342,7 +342,7 @@ fn evaluators_basis_spread(
|
|
|
342
342
|
inputs,
|
|
343
343
|
kwargs,
|
|
344
344
|
|e: TfEvaluator| e.with_basis_spread().unwrap(),
|
|
345
|
-
|e: &TfEvaluator| e.basis_spread,
|
|
345
|
+
|e: &TfEvaluator| e.basis_spread.filter(|v| !v.is_nan()),
|
|
346
346
|
true,
|
|
347
347
|
true,
|
|
348
348
|
)?
|
|
@@ -357,7 +357,7 @@ fn evaluators_f_b_spread(inputs: &[Series], kwargs: EvaluatorBatchParams) -> Pol
|
|
|
357
357
|
inputs,
|
|
358
358
|
kwargs,
|
|
359
359
|
|e: TfEvaluator| e.with_f_b_spread().unwrap(),
|
|
360
|
-
|e: &TfEvaluator| e.f_b_spread,
|
|
360
|
+
|e: &TfEvaluator| e.f_b_spread.filter(|v| !v.is_nan()),
|
|
361
361
|
true,
|
|
362
362
|
true,
|
|
363
363
|
)?
|
|
@@ -372,7 +372,7 @@ fn evaluators_carry(inputs: &[Series], kwargs: EvaluatorBatchParams) -> PolarsRe
|
|
|
372
372
|
inputs,
|
|
373
373
|
kwargs,
|
|
374
374
|
|e: TfEvaluator| e.with_carry().unwrap(),
|
|
375
|
-
|e: &TfEvaluator| e.carry,
|
|
375
|
+
|e: &TfEvaluator| e.carry.filter(|v| !v.is_nan()),
|
|
376
376
|
true,
|
|
377
377
|
true,
|
|
378
378
|
)?
|
|
@@ -387,7 +387,7 @@ fn evaluators_duration(inputs: &[Series], kwargs: EvaluatorBatchParams) -> Polar
|
|
|
387
387
|
inputs,
|
|
388
388
|
kwargs,
|
|
389
389
|
|e: TfEvaluator| e.with_duration().unwrap(),
|
|
390
|
-
|e: &TfEvaluator| e.duration,
|
|
390
|
+
|e: &TfEvaluator| e.duration.filter(|v| !v.is_nan()),
|
|
391
391
|
false,
|
|
392
392
|
true,
|
|
393
393
|
)?
|
|
@@ -402,7 +402,7 @@ fn evaluators_irr(inputs: &[Series], kwargs: EvaluatorBatchParams) -> PolarsResu
|
|
|
402
402
|
inputs,
|
|
403
403
|
kwargs,
|
|
404
404
|
|e: TfEvaluator| e.with_irr().unwrap(),
|
|
405
|
-
|e: &TfEvaluator| e.irr,
|
|
405
|
+
|e: &TfEvaluator| e.irr.filter(|v| !v.is_nan()),
|
|
406
406
|
true,
|
|
407
407
|
true,
|
|
408
408
|
)?
|
|
@@ -417,7 +417,7 @@ fn evaluators_future_ytm(inputs: &[Series], kwargs: EvaluatorBatchParams) -> Pol
|
|
|
417
417
|
inputs,
|
|
418
418
|
kwargs,
|
|
419
419
|
|e: TfEvaluator| e.with_future_ytm().unwrap(),
|
|
420
|
-
|e: &TfEvaluator| e.future_ytm,
|
|
420
|
+
|e: &TfEvaluator| e.future_ytm.filter(|v| !v.is_nan()),
|
|
421
421
|
true,
|
|
422
422
|
true,
|
|
423
423
|
)?
|
|
@@ -524,6 +524,26 @@ fn evaluators_last_trading_date(
|
|
|
524
524
|
Ok(result.into_date().into_series())
|
|
525
525
|
}
|
|
526
526
|
|
|
527
|
+
#[polars_expr(output_type=Date)]
|
|
528
|
+
fn evaluators_remain_year(
|
|
529
|
+
inputs: &[Series],
|
|
530
|
+
kwargs: EvaluatorBatchParams,
|
|
531
|
+
) -> PolarsResult<Series> {
|
|
532
|
+
let result: Float64Chunked = batch_eval(
|
|
533
|
+
inputs,
|
|
534
|
+
kwargs,
|
|
535
|
+
|e: TfEvaluator| e,
|
|
536
|
+
|e: &TfEvaluator| {
|
|
537
|
+
Some(e.bond.remain_year(e.date))
|
|
538
|
+
},
|
|
539
|
+
false,
|
|
540
|
+
true,
|
|
541
|
+
)?
|
|
542
|
+
.into_iter()
|
|
543
|
+
.collect_trusted();
|
|
544
|
+
Ok(result.into_series())
|
|
545
|
+
}
|
|
546
|
+
|
|
527
547
|
#[derive(Deserialize)]
|
|
528
548
|
struct FindWorkdayKwargs {
|
|
529
549
|
market: Market,
|
|
@@ -86,7 +86,7 @@ pub fn pnl_report_vec_to_series(reports: &[PnlReport]) -> Series {
|
|
|
86
86
|
res.into_series()
|
|
87
87
|
}
|
|
88
88
|
|
|
89
|
-
fn
|
|
89
|
+
fn get_pnl_output_type(_input_fields: &[Field]) -> PolarsResult<Field> {
|
|
90
90
|
let dtype = DataType::Struct(vec![
|
|
91
91
|
Field::new("pos".into(), DataType::Float64),
|
|
92
92
|
Field::new("avg_price".into(), DataType::Float64),
|
|
@@ -101,57 +101,99 @@ fn get_output_type(_input_fields: &[Field]) -> PolarsResult<Field> {
|
|
|
101
101
|
Ok(Field::new("pnl_report".into(), dtype))
|
|
102
102
|
}
|
|
103
103
|
|
|
104
|
-
|
|
105
|
-
// fn calc_bond_trade_pnl(inputs: &[Series], kwargs: BondTradePnlOpt) -> PolarsResult<Series> {
|
|
106
|
-
// let (time, qty, clean_price, clean_close) = (&inputs[0], &inputs[1], &inputs[2], &inputs[3]);
|
|
107
|
-
// let (qty, clean_price, clean_close) = auto_cast!(Float64(qty, clean_price, clean_close));
|
|
108
|
-
// let time = match time.dtype() {
|
|
109
|
-
// DataType::Date => time.clone(),
|
|
110
|
-
// _ => time.cast(&DataType::Date)?,
|
|
111
|
-
// };
|
|
112
|
-
// let profit_vec = pnl::calc_bond_trade_pnl(
|
|
113
|
-
// time.date()?.physical(),
|
|
114
|
-
// qty.f64()?,
|
|
115
|
-
// clean_price.f64()?,
|
|
116
|
-
// clean_close.f64()?,
|
|
117
|
-
// &kwargs,
|
|
118
|
-
// );
|
|
119
|
-
// let out = pnl_report_vec_to_series(&profit_vec);
|
|
120
|
-
// Ok(out)
|
|
121
|
-
// }
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
#[polars_expr(output_type_func=get_output_type)]
|
|
104
|
+
#[polars_expr(output_type_func=get_pnl_output_type)]
|
|
126
105
|
fn calc_bond_trade_pnl(inputs: &[Series], kwargs: BondTradePnlOpt) -> PolarsResult<Series> {
|
|
127
|
-
let (symbol, time, qty, clean_price, clean_close) =
|
|
106
|
+
let (symbol, time, qty, clean_price, clean_close) =
|
|
107
|
+
(&inputs[0], &inputs[1], &inputs[2], &inputs[3], &inputs[4]);
|
|
128
108
|
let symbol = auto_cast!(String(symbol));
|
|
129
109
|
let symbol = if let Some(s) = symbol.str()?.iter().next() {
|
|
130
110
|
s
|
|
131
111
|
} else {
|
|
132
|
-
return Ok(pnl_report_vec_to_series(&[]))
|
|
112
|
+
return Ok(pnl_report_vec_to_series(&[]));
|
|
133
113
|
};
|
|
134
114
|
let (qty, clean_price, clean_close) = auto_cast!(Float64(qty, clean_price, clean_close));
|
|
135
115
|
let time = match time.dtype() {
|
|
136
116
|
DataType::Date => time.clone(),
|
|
137
117
|
_ => time.cast(&DataType::Date)?,
|
|
138
118
|
};
|
|
139
|
-
let opt = BondTradePnlOpt {
|
|
140
|
-
bond_info_path: kwargs.bond_info_path,
|
|
141
|
-
multiplier: kwargs.multiplier,
|
|
142
|
-
fee: kwargs.fee,
|
|
143
|
-
borrowing_cost: kwargs.borrowing_cost,
|
|
144
|
-
capital_rate: kwargs.capital_rate,
|
|
145
|
-
begin_state: kwargs.begin_state
|
|
146
|
-
};
|
|
147
119
|
let profit_vec = pnl::calc_bond_trade_pnl(
|
|
148
120
|
symbol,
|
|
149
121
|
time.date()?.physical(),
|
|
150
122
|
qty.f64()?,
|
|
151
123
|
clean_price.f64()?,
|
|
152
124
|
clean_close.f64()?,
|
|
153
|
-
&
|
|
125
|
+
&kwargs,
|
|
154
126
|
);
|
|
155
127
|
let out = pnl_report_vec_to_series(&profit_vec);
|
|
156
128
|
Ok(out)
|
|
157
129
|
}
|
|
130
|
+
|
|
131
|
+
fn get_trading_output_type(input_fields: &[Field]) -> PolarsResult<Field> {
|
|
132
|
+
let dtype = DataType::Struct(vec![
|
|
133
|
+
input_fields[0].clone(), // time
|
|
134
|
+
Field::new("price".into(), DataType::Float64),
|
|
135
|
+
Field::new("qty".into(), DataType::Float64),
|
|
136
|
+
]);
|
|
137
|
+
Ok(Field::new("pnl_report".into(), dtype))
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
#[polars_expr(output_type_func=get_trading_output_type)]
|
|
141
|
+
fn trading_from_pos(inputs: &[Series], mut kwargs: pnl::TradeFromPosOpt) -> PolarsResult<Series> {
|
|
142
|
+
use pyo3_polars::export::polars_core::utils::CustomIterTools;
|
|
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));
|
|
146
|
+
if let Some(p) = finish_price.f64()?.iter().next() {
|
|
147
|
+
kwargs.finish_price = p
|
|
148
|
+
};
|
|
149
|
+
let res = match time.dtype() {
|
|
150
|
+
DataType::Date => {
|
|
151
|
+
let trade_vec =
|
|
152
|
+
pnl::trading_from_pos(time.date()?.physical(), pos.f64()?, open.f64()?, &kwargs);
|
|
153
|
+
let time: Int32Chunked = trade_vec.iter().map(|t| t.time).collect_trusted();
|
|
154
|
+
let time = time.into_date().into_series();
|
|
155
|
+
let price: Float64Chunked = trade_vec.iter().map(|t| Some(t.price)).collect_trusted();
|
|
156
|
+
let price = price.into_series();
|
|
157
|
+
let qty: Float64Chunked = trade_vec.iter().map(|t| Some(t.qty)).collect_trusted();
|
|
158
|
+
StructChunked::from_series(
|
|
159
|
+
"trade".into(),
|
|
160
|
+
time.len(),
|
|
161
|
+
[
|
|
162
|
+
time.with_name("time".into()),
|
|
163
|
+
price.into_series().with_name("price".into()),
|
|
164
|
+
qty.into_series().with_name("qty".into()),
|
|
165
|
+
]
|
|
166
|
+
.iter(),
|
|
167
|
+
)
|
|
168
|
+
.unwrap()
|
|
169
|
+
.into_series()
|
|
170
|
+
}
|
|
171
|
+
_ => {
|
|
172
|
+
let time_ca = time.datetime()?;
|
|
173
|
+
let time_unit = time_ca.time_unit();
|
|
174
|
+
let time_zone = time_ca.time_zone();
|
|
175
|
+
let trade_vec =
|
|
176
|
+
pnl::trading_from_pos(time_ca.physical(), pos.f64()?, open.f64()?, &kwargs);
|
|
177
|
+
let time: Int64Chunked = trade_vec.iter().map(|t| t.time).collect_trusted();
|
|
178
|
+
let time = time
|
|
179
|
+
.into_datetime(time_unit, time_zone.clone())
|
|
180
|
+
.into_series();
|
|
181
|
+
let price: Float64Chunked = trade_vec.iter().map(|t| Some(t.price)).collect_trusted();
|
|
182
|
+
let price = price.into_series();
|
|
183
|
+
let qty: Float64Chunked = trade_vec.iter().map(|t| Some(t.qty)).collect_trusted();
|
|
184
|
+
StructChunked::from_series(
|
|
185
|
+
"trade".into(),
|
|
186
|
+
time.len(),
|
|
187
|
+
[
|
|
188
|
+
time.with_name("time".into()),
|
|
189
|
+
price.into_series().with_name("price".into()),
|
|
190
|
+
qty.into_series().with_name("qty".into()),
|
|
191
|
+
]
|
|
192
|
+
.iter(),
|
|
193
|
+
)
|
|
194
|
+
.unwrap()
|
|
195
|
+
.into_series()
|
|
196
|
+
}
|
|
197
|
+
};
|
|
198
|
+
Ok(res)
|
|
199
|
+
}
|
|
@@ -2,6 +2,7 @@ import datetime
|
|
|
2
2
|
|
|
3
3
|
import numpy as np
|
|
4
4
|
import polars as pl
|
|
5
|
+
from IPython.display import display
|
|
5
6
|
|
|
6
7
|
# import os
|
|
7
8
|
# os.environ["POLARS_VERBOSE"] = "1"
|
|
@@ -10,6 +11,34 @@ from pybond.pd import Bonds as PdBonds
|
|
|
10
11
|
from pybond.pd import TfEvaluators as PdTfEvaluators
|
|
11
12
|
from pybond.pd import find_workday as pd_find_workday
|
|
12
13
|
from pybond.pl import Bonds, TfEvaluators, find_workday, is_business_day
|
|
14
|
+
from pybond.pnl import trading_from_pos
|
|
15
|
+
|
|
16
|
+
signal_df = (
|
|
17
|
+
pl.DataFrame(
|
|
18
|
+
{
|
|
19
|
+
"time": pl.date_range(
|
|
20
|
+
start=datetime.date(2025, 8, 1),
|
|
21
|
+
end=datetime.date(2025, 8, 8),
|
|
22
|
+
eager=True,
|
|
23
|
+
),
|
|
24
|
+
"pos": [1.0, 0.5, 0.2, 0.4, 0, -0.1, -0.1, 1],
|
|
25
|
+
"price": [100, 101, 102, 103, 104, 105, 106, 107],
|
|
26
|
+
}
|
|
27
|
+
)
|
|
28
|
+
.select(
|
|
29
|
+
trading_from_pos(
|
|
30
|
+
pl.col("time").cast(pl.Datetime("ms")),
|
|
31
|
+
"pos",
|
|
32
|
+
"price",
|
|
33
|
+
finish_price=110,
|
|
34
|
+
cash=10000,
|
|
35
|
+
stop_on_finish=True,
|
|
36
|
+
)
|
|
37
|
+
)
|
|
38
|
+
.unnest("time")
|
|
39
|
+
.with_columns(cum_qty=pl.col("qty").cum_sum())
|
|
40
|
+
)
|
|
41
|
+
display(signal_df)
|
|
13
42
|
|
|
14
43
|
e = TfEvaluator("T2509", 250205, "2025-07-15", 100, 0.02, 0.018)
|
|
15
44
|
e.net_basis_spread
|
|
@@ -0,0 +1,112 @@
|
|
|
1
|
+
use itertools::izip;
|
|
2
|
+
use serde::Deserialize;
|
|
3
|
+
use tevec::prelude::{EPS, IsNone, Number, Vec1View};
|
|
4
|
+
// use super::EPOCH;
|
|
5
|
+
|
|
6
|
+
pub struct Trade<T> {
|
|
7
|
+
pub time: T,
|
|
8
|
+
pub price: f64,
|
|
9
|
+
pub qty: f64,
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
#[derive(Deserialize)]
|
|
13
|
+
pub struct TradeFromPosOpt {
|
|
14
|
+
// pub symbol: SmallStr,
|
|
15
|
+
pub cash: f64,
|
|
16
|
+
pub multiplier: f64,
|
|
17
|
+
pub qty_tick: f64,
|
|
18
|
+
pub stop_on_finish: bool,
|
|
19
|
+
pub finish_price: Option<f64>,
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
const INIT_TRADE_COUNT: usize = 512;
|
|
23
|
+
|
|
24
|
+
fn quantize_inside(q: f64, tick: f64) -> f64 {
|
|
25
|
+
if tick <= 0.0 {
|
|
26
|
+
return q;
|
|
27
|
+
}
|
|
28
|
+
if q >= 0.0 {
|
|
29
|
+
(q / tick).floor() * tick // 买单向下取整
|
|
30
|
+
} else {
|
|
31
|
+
(q / tick).ceil() * tick // 卖单向上取整(数值更接近 0)
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
pub fn trading_from_pos<DT, T, VT, V>(
|
|
36
|
+
time_vec: &VT,
|
|
37
|
+
pos_vec: &V,
|
|
38
|
+
open_vec: &V,
|
|
39
|
+
opt: &TradeFromPosOpt,
|
|
40
|
+
) -> Vec<Trade<DT>>
|
|
41
|
+
where
|
|
42
|
+
DT: Clone,
|
|
43
|
+
T: IsNone,
|
|
44
|
+
T::Inner: Number,
|
|
45
|
+
VT: Vec1View<DT>,
|
|
46
|
+
V: Vec1View<T>,
|
|
47
|
+
{
|
|
48
|
+
let mut last_pos = 0.;
|
|
49
|
+
let mut open_price: f64 = 0.;
|
|
50
|
+
let mut open_qty: f64 = 0.;
|
|
51
|
+
let mut trades = Vec::with_capacity(INIT_TRADE_COUNT);
|
|
52
|
+
|
|
53
|
+
// 记录最后一个可用 (time, price),用于 stop_on_finish
|
|
54
|
+
let mut last_tp: Option<(DT, f64)> = None;
|
|
55
|
+
|
|
56
|
+
izip!(time_vec.titer(), pos_vec.titer(), open_vec.titer()).for_each(|(time, pos, open)| {
|
|
57
|
+
if pos.not_none() && open.not_none() {
|
|
58
|
+
let pos = pos.unwrap().f64();
|
|
59
|
+
let price = open.unwrap().f64();
|
|
60
|
+
|
|
61
|
+
// 记录最新可用的时间与价格
|
|
62
|
+
last_tp = Some((time.clone(), price));
|
|
63
|
+
|
|
64
|
+
let dpos = pos - last_pos;
|
|
65
|
+
if dpos.abs() > EPS {
|
|
66
|
+
// 目标名义 -> 成交量(正买负卖)
|
|
67
|
+
let qty = if pos.abs() > EPS {
|
|
68
|
+
let p = if open_price > 0. { open_price } else { price };
|
|
69
|
+
let raw_qty = dpos * opt.cash / (p * opt.multiplier);
|
|
70
|
+
// 量化到最小变动单位(朝 0 截断,避免超买/超卖)
|
|
71
|
+
quantize_inside(raw_qty, opt.qty_tick)
|
|
72
|
+
} else {
|
|
73
|
+
-open_qty
|
|
74
|
+
};
|
|
75
|
+
if dpos.signum() == open_qty.signum() {
|
|
76
|
+
open_price = (open_price * open_qty + qty * price) / (qty + open_qty)
|
|
77
|
+
} else if open_qty.abs() > qty.abs() {
|
|
78
|
+
// 反向加仓, 价格为新的开仓价格
|
|
79
|
+
open_price = price
|
|
80
|
+
};
|
|
81
|
+
// 减仓情况的价格不改变
|
|
82
|
+
|
|
83
|
+
// 若量化后仍非 0,则下单
|
|
84
|
+
if qty.abs() > 0.0 {
|
|
85
|
+
trades.push(Trade {
|
|
86
|
+
time: time.clone(),
|
|
87
|
+
price,
|
|
88
|
+
qty,
|
|
89
|
+
});
|
|
90
|
+
open_qty += qty;
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
// 维持 last_pos 为目标(策略)层面的持仓比例/规模
|
|
95
|
+
last_pos = pos;
|
|
96
|
+
}
|
|
97
|
+
});
|
|
98
|
+
|
|
99
|
+
// 收尾是否强制平仓
|
|
100
|
+
if opt.stop_on_finish && open_qty != 0.0 {
|
|
101
|
+
if let Some((t, p)) = last_tp {
|
|
102
|
+
let p = if let Some(p) = opt.finish_price { p } else { p };
|
|
103
|
+
trades.push(Trade {
|
|
104
|
+
time: t,
|
|
105
|
+
price: p,
|
|
106
|
+
qty: -open_qty,
|
|
107
|
+
});
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
trades
|
|
112
|
+
}
|
|
@@ -139,8 +139,12 @@ impl TfEvaluator {
|
|
|
139
139
|
}
|
|
140
140
|
/// 计算剩余付息次数
|
|
141
141
|
#[inline]
|
|
142
|
-
pub fn with_remain_cp_num(self) -> Result<Self> {
|
|
142
|
+
pub fn with_remain_cp_num(mut self) -> Result<Self> {
|
|
143
143
|
if self.remain_cp_num.is_none() {
|
|
144
|
+
if self.bond.is_zero_coupon() {
|
|
145
|
+
self.remain_cp_num = Some(0);
|
|
146
|
+
return Ok(self);
|
|
147
|
+
}
|
|
144
148
|
let mut out = self.with_nearest_cp_dates()?;
|
|
145
149
|
out.remain_cp_num = Some(
|
|
146
150
|
out.bond
|
|
@@ -154,8 +158,12 @@ impl TfEvaluator {
|
|
|
154
158
|
|
|
155
159
|
/// 计算应计利息
|
|
156
160
|
#[inline]
|
|
157
|
-
pub fn with_accrued_interest(self) -> Result<Self> {
|
|
161
|
+
pub fn with_accrued_interest(mut self) -> Result<Self> {
|
|
158
162
|
if self.accrued_interest.is_none() {
|
|
163
|
+
if self.bond.is_zero_coupon() {
|
|
164
|
+
self.accrued_interest = Some(0.);
|
|
165
|
+
return Ok(self);
|
|
166
|
+
}
|
|
159
167
|
let mut out = self.with_nearest_cp_dates()?;
|
|
160
168
|
out.accrued_interest = Some(
|
|
161
169
|
out.bond
|
|
@@ -169,8 +177,13 @@ impl TfEvaluator {
|
|
|
169
177
|
|
|
170
178
|
/// 计算债券全价
|
|
171
179
|
#[inline]
|
|
172
|
-
pub fn with_dirty_price(self) -> Result<Self> {
|
|
180
|
+
pub fn with_dirty_price(mut self) -> Result<Self> {
|
|
173
181
|
if self.dirty_price.is_none() {
|
|
182
|
+
if self.bond.is_zero_coupon() {
|
|
183
|
+
let remain_year = self.bond.remain_year(self.date);
|
|
184
|
+
self.dirty_price = Some(100.0 / (1.0+self.bond.ytm()).powf(remain_year));
|
|
185
|
+
return Ok(self);
|
|
186
|
+
}
|
|
174
187
|
let mut out = self.with_remain_cp_num()?;
|
|
175
188
|
out.dirty_price = Some(out.bond.calc_dirty_price_with_ytm(
|
|
176
189
|
out.bond.ytm(),
|
|
@@ -198,8 +211,12 @@ impl TfEvaluator {
|
|
|
198
211
|
|
|
199
212
|
/// 计算久期
|
|
200
213
|
#[inline]
|
|
201
|
-
pub fn with_duration(self) -> Result<Self> {
|
|
214
|
+
pub fn with_duration(mut self) -> Result<Self> {
|
|
202
215
|
if self.duration.is_none() {
|
|
216
|
+
if self.bond.is_zero_coupon() {
|
|
217
|
+
self.duration = Some(self.bond.remain_year(self.date));
|
|
218
|
+
return Ok(self);
|
|
219
|
+
}
|
|
203
220
|
let mut out = self.with_remain_cp_num()?;
|
|
204
221
|
out.duration = Some(out.bond.calc_duration(
|
|
205
222
|
out.bond.ytm(),
|
|
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
|