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.

Files changed (101) hide show
  1. {tea_bond-0.3.13 → tea_bond-0.3.15}/Cargo.lock +3 -3
  2. {tea_bond-0.3.13 → tea_bond-0.3.15}/Cargo.toml +1 -1
  3. {tea_bond-0.3.13 → tea_bond-0.3.15}/PKG-INFO +1 -1
  4. {tea_bond-0.3.13/pybond → tea_bond-0.3.15}/pybond/pl.py +36 -2
  5. {tea_bond-0.3.13/pybond → tea_bond-0.3.15}/pybond/pnl.py +6 -5
  6. {tea_bond-0.3.13 → tea_bond-0.3.15/pybond}/pybond/pl.py +36 -2
  7. {tea_bond-0.3.13 → tea_bond-0.3.15/pybond}/pybond/pnl.py +6 -5
  8. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/batch_eval.rs +100 -2
  9. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/pnl.rs +5 -2
  10. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/mod.rs +10 -1
  11. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/mod.rs +19 -16
  12. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/trade_from_signal.rs +10 -8
  13. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/evaluator.rs +7 -2
  14. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/.gitignore +0 -0
  15. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/Cargo.toml +0 -0
  16. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/__init__.py +0 -0
  17. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/bond.py +0 -0
  18. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/download.py +0 -0
  19. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/__init__.py +0 -0
  20. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/bond.py +0 -0
  21. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/datetime.py +0 -0
  22. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/duration.py +0 -0
  23. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/evaluators.py +0 -0
  24. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/ffi/lib.py +0 -0
  25. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/__init__.py +0 -0
  26. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/ir_utils.py +0 -0
  27. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_bond.py +0 -0
  28. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_date.py +0 -0
  29. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_datetime.py +0 -0
  30. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_duration.py +0 -0
  31. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_evaluators.py +0 -0
  32. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb/nb_time.py +0 -0
  33. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/nb_test.py +0 -0
  34. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pd.py +0 -0
  35. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/polars_utils.py +0 -0
  36. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/__init__.py +0 -0
  37. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/bond.py +0 -0
  38. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/download.py +0 -0
  39. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/__init__.py +0 -0
  40. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/bond.py +0 -0
  41. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/datetime.py +0 -0
  42. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/duration.py +0 -0
  43. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/evaluators.py +0 -0
  44. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/ffi/lib.py +0 -0
  45. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/__init__.py +0 -0
  46. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/ir_utils.py +0 -0
  47. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_bond.py +0 -0
  48. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_date.py +0 -0
  49. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_datetime.py +0 -0
  50. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_duration.py +0 -0
  51. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_evaluators.py +0 -0
  52. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/nb/nb_time.py +0 -0
  53. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/pd.py +0 -0
  54. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/polars_utils.py +0 -0
  55. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond/pybond.pyi +0 -0
  56. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/pybond.pyi +0 -0
  57. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/bond.rs +0 -0
  58. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/calendar.rs +0 -0
  59. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/bond.rs +0 -0
  60. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/datetime.rs +0 -0
  61. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/duration.rs +0 -0
  62. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/evaluators.rs +0 -0
  63. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/mod.rs +0 -0
  64. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/ffi/utils.rs +0 -0
  65. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/future.rs +0 -0
  66. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/lib.rs +0 -0
  67. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/tf_evaluator.rs +0 -0
  68. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/src/utils.rs +0 -0
  69. {tea_bond-0.3.13 → tea_bond-0.3.15}/pybond/test.py +0 -0
  70. {tea_bond-0.3.13 → tea_bond-0.3.15}/pyproject.toml +0 -0
  71. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/Cargo.toml +0 -0
  72. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/batch/evaluator.rs +0 -0
  73. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/batch/mod.rs +0 -0
  74. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/bond_ytm.rs +0 -0
  75. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/cached_bond.rs +0 -0
  76. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/china_money.rs +0 -0
  77. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/mod.rs +0 -0
  78. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/download/sse.rs +0 -0
  79. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/enums.rs +0 -0
  80. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/impl_convert.rs +0 -0
  81. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/impl_traits.rs +0 -0
  82. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/bond/io.rs +0 -0
  83. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/day_counter.rs +0 -0
  84. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/export.rs +0 -0
  85. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/future_price.rs +0 -0
  86. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/future_type.rs +0 -0
  87. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/impls.rs +0 -0
  88. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/future/mod.rs +0 -0
  89. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/lib.rs +0 -0
  90. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/pnl/fee.rs +0 -0
  91. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/impl_traits.rs +0 -0
  92. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/mod.rs +0 -0
  93. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/tf_evaluator/update_with_new_info.rs +0 -0
  94. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-bond/src/utils.rs +0 -0
  95. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/Cargo.toml +0 -0
  96. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/ib.rs +0 -0
  97. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/mod.rs +0 -0
  98. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/others.rs +0 -0
  99. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/china/sse.rs +0 -0
  100. {tea_bond-0.3.13 → tea_bond-0.3.15}/tea-calendar/src/calendars/mod.rs +0 -0
  101. {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.13"
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.13"
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.13"
3139
+ version = "0.3.15"
3140
3140
  dependencies = [
3141
3141
  "chrono",
3142
3142
  ]
@@ -4,7 +4,7 @@ default-members = ["tea-bond", "tea-calendar"]
4
4
  resolver = "2"
5
5
 
6
6
  [workspace.package]
7
- version = "0.3.13"
7
+ version = "0.3.15"
8
8
  edition = "2024"
9
9
 
10
10
  [workspace.dependencies]
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: tea-bond
3
- Version: 0.3.13
3
+ Version: 0.3.15
4
4
  Classifier: Programming Language :: Rust
5
5
  Classifier: Programming Language :: Python :: Implementation :: CPython
6
6
  Classifier: Programming Language :: Python :: Implementation :: PyPy
@@ -280,7 +280,15 @@ class TfEvaluators:
280
280
  Returns:
281
281
  Polars expression for bond remaining year
282
282
  """
283
- return self._call_plugin("evaluators_remain_year")
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
- # TODO(Teamon): 实现向量化根据净价反推ytm的函数
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: float = 1e8,
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": 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("evaluators_remain_year")
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
- # TODO(Teamon): 实现向量化根据净价反推ytm的函数
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: float = 1e8,
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": 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=Date)]
528
- fn evaluators_remain_year(
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 >= self.maturity_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 last_settle_time != Some(settle_time) {
92
- // 新的一天重新计算相关信息
93
- let cp_dates = bond.get_nearest_cp_date(settle_time).unwrap();
94
- accrued_interest = bond
95
- .calc_accrued_interest(settle_time, Some(cp_dates))
96
- .unwrap();
97
- last_cp_date = bond.mkt.find_workday(cp_dates.0, 0);
98
- // 当天初始仓位会产生的票息
99
- if settle_time == last_cp_date {
100
- state.coupon_paid += coupon_paid * multiplier * state.pos;
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
- // pub symbol: SmallStr,
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 * opt.cash / (p * opt.multiplier);
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 dpos.signum() == open_qty.signum() {
76
- open_price = (open_price * open_qty + qty * price) / (qty + open_qty)
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
- self.accrued_interest = Some(0.);
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
- self.dirty_price = Some(100.0 / (1.0+self.bond.ytm()).powf(remain_year));
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