deriweave-python 0.3.0__cp312-cp312-win_amd64.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,560 @@
1
+ """Composable contract descriptions for the native DeriWeave pricing engine.
2
+
3
+ Objects here are *recipes*, not wrappers around live C++ pointers. They can be
4
+ reused in as many products as needed. On price(), the recipes are converted to
5
+ fresh, exclusively owned DeriWeave C++ expressions and products.
6
+ """
7
+ from __future__ import annotations
8
+
9
+ from dataclasses import dataclass
10
+ from datetime import date
11
+ from math import isfinite
12
+ from numbers import Real
13
+ from enum import Enum
14
+ from types import MappingProxyType
15
+ from typing import TYPE_CHECKING
16
+
17
+ if TYPE_CHECKING:
18
+ from ._core import PricingResult, SimulationSettings, MarketPath, Cashflow
19
+ from typing import Any, Mapping, Sequence
20
+
21
+
22
+ def _number(value: float, name: str) -> float:
23
+ if isinstance(value, bool) or not isinstance(value, Real):
24
+ raise TypeError(f"{name} must be a real number")
25
+ v = float(value)
26
+ if not isfinite(v):
27
+ raise ValueError(f"{name} must be finite")
28
+ return v
29
+
30
+
31
+ class _ExerciseTime(Enum):
32
+ CURRENT = "exercise"
33
+
34
+
35
+ ExerciseTime = _ExerciseTime.CURRENT
36
+ """Symbolic decision date for Spot/Variance inside an American contract."""
37
+
38
+
39
+ def _time(value: float, name: str) -> float:
40
+ result = _number(value, name)
41
+ if result < 0:
42
+ raise ValueError(f"{name} must be non-negative")
43
+ return result
44
+
45
+
46
+ def _observation_time(value: float | _ExerciseTime) -> float | str:
47
+ return "exercise" if value is ExerciseTime else _time(value, "observation date")
48
+
49
+
50
+ def _freeze(obj: Any) -> Any:
51
+ if isinstance(obj, Mapping):
52
+ return MappingProxyType({k: _freeze(v) for k, v in obj.items()})
53
+ if isinstance(obj, (list, tuple)):
54
+ return tuple(_freeze(v) for v in obj)
55
+ return obj
56
+
57
+
58
+ @dataclass(frozen=True)
59
+ class Asset:
60
+ identifier: str
61
+
62
+ def __post_init__(self) -> None:
63
+ if not isinstance(self.identifier, str) or not self.identifier:
64
+ raise ValueError("Asset identifier must be a non-empty string")
65
+
66
+ def __str__(self) -> str:
67
+ return self.identifier
68
+
69
+
70
+ def _asset_name(asset: Asset | str) -> str:
71
+ if isinstance(asset, Asset):
72
+ return asset.identifier
73
+ if not isinstance(asset, str) or not asset:
74
+ raise TypeError("asset must be an Asset or a non-empty string")
75
+ return asset
76
+
77
+
78
+ @dataclass(frozen=True)
79
+ class ValueExpression:
80
+ """Declarative description of a DeriWeave ValueExpression."""
81
+
82
+ kind: str
83
+ fields: Mapping[str, Any]
84
+
85
+ def __post_init__(self) -> None:
86
+ object.__setattr__(self, "fields", _freeze(self.fields))
87
+
88
+ def to_spec(self) -> dict[str, Any]:
89
+ return {"kind": self.kind, **_recursive_copy(self.fields)}
90
+
91
+ def __mul__(self, factor: float) -> ValueExpression:
92
+ return Scaled(self, factor)
93
+
94
+ def __rmul__(self, factor: float) -> ValueExpression:
95
+ return Scaled(self, factor)
96
+
97
+
98
+ def _recursive_copy(obj: Any) -> Any:
99
+ if isinstance(obj, Mapping):
100
+ return {k: _recursive_copy(v) for k, v in obj.items()}
101
+ if isinstance(obj, (list, tuple)):
102
+ return [_recursive_copy(v) for v in obj]
103
+ return obj
104
+
105
+
106
+ def _expr(value: ValueExpression) -> dict[str, Any]:
107
+ if not isinstance(value, ValueExpression):
108
+ raise TypeError("Expected ValueExpression (use Constant(number) for a constant)")
109
+ return value.to_spec()
110
+
111
+
112
+ def Spot(asset: Asset | str, at: float | _ExerciseTime) -> ValueExpression:
113
+ return ValueExpression("spot", {"asset": _asset_name(asset), "at": _observation_time(at)})
114
+
115
+
116
+ def Variance(asset: Asset | str, at: float | _ExerciseTime) -> ValueExpression:
117
+ """Heston's instantaneous variance V_t, *not* its volatility."""
118
+ return ValueExpression("variance", {"asset": _asset_name(asset), "at": _observation_time(at)})
119
+
120
+
121
+ def Constant(value: float) -> ValueExpression:
122
+ return ValueExpression("constant", {"value": _number(value, "value")})
123
+
124
+
125
+ def Scaled(value: ValueExpression, factor: float) -> ValueExpression:
126
+ return ValueExpression("scaled", {"value": _expr(value), "factor": _number(factor, "factor")})
127
+
128
+
129
+ def _strike(strike: float | ValueExpression) -> float | dict[str, Any]:
130
+ if isinstance(strike, ValueExpression):
131
+ return _expr(strike)
132
+ value = _number(strike, "strike")
133
+ if value < 0:
134
+ raise ValueError("Fixed strike must be non-negative; use Constant for a floating expression")
135
+ return value
136
+
137
+
138
+ def Call(value: ValueExpression, strike: float | ValueExpression) -> ValueExpression:
139
+ return ValueExpression("call", {"value": _expr(value), "strike": _strike(strike)})
140
+
141
+
142
+ def Put(value: ValueExpression, strike: float | ValueExpression) -> ValueExpression:
143
+ return ValueExpression("put", {"value": _expr(value), "strike": _strike(strike)})
144
+
145
+
146
+ def Digital(value: ValueExpression, threshold: float | ValueExpression,
147
+ amount: float = 1.0, comparison: str = ">=") -> ValueExpression:
148
+ if comparison not in ("<", "<=", ">", ">="):
149
+ raise ValueError("comparison must be one of <, <=, >, >=")
150
+ if _number(amount, "amount") < 0:
151
+ raise ValueError("Digital amount must be non-negative")
152
+ return ValueExpression("digital", {
153
+ "value": _expr(value), "threshold": (_expr(threshold) if isinstance(threshold, ValueExpression) else _number(threshold, "threshold")),
154
+ "amount": _number(amount, "amount"), "comparison": comparison,
155
+ })
156
+
157
+
158
+ def Basket(items: Sequence[ValueExpression], weights: Sequence[float]) -> ValueExpression:
159
+ parts = [_expr(item) for item in items]
160
+ w = [_number(weight, "weight") for weight in weights]
161
+ if any(weight < 0 for weight in w) or (w and not any(weight > 0 for weight in w)):
162
+ raise ValueError("Basket weights must be non-negative with at least one positive weight")
163
+ if not parts or len(parts) != len(w):
164
+ raise ValueError("Basket items and weights must have the same nonzero length")
165
+ return ValueExpression("basket", {"items": parts, "weights": w})
166
+
167
+
168
+ def Average(items: Sequence[ValueExpression] | Asset | str, *,
169
+ dates: Sequence[float] | None = None, method: str = "arithmetic") -> ValueExpression:
170
+ """Average a list of expressions, or one asset observed at multiple dates."""
171
+ if isinstance(items, (Asset, str)):
172
+ if dates is None:
173
+ raise ValueError("dates are required when Average receives an asset")
174
+ values = [Spot(items, t) for t in dates]
175
+ else:
176
+ if dates is not None:
177
+ raise ValueError("dates are only valid when Average receives an asset")
178
+ values = list(items)
179
+ if not values:
180
+ raise ValueError("Average requires at least one observation")
181
+ if method not in ("arithmetic", "geometric"):
182
+ raise ValueError("Average method must be arithmetic or geometric")
183
+ return ValueExpression("average", {"items": [_expr(v) for v in values], "method": method})
184
+
185
+
186
+ def WorstOf(items: Sequence[ValueExpression]) -> ValueExpression:
187
+ parts = [_expr(item) for item in items]
188
+ if not parts:
189
+ raise ValueError("WorstOf requires at least one expression")
190
+ return ValueExpression("worst_of", {"items": parts})
191
+
192
+
193
+ def BestOf(items: Sequence[ValueExpression]) -> ValueExpression:
194
+ parts = [_expr(item) for item in items]
195
+ if not parts:
196
+ raise ValueError("BestOf requires at least one expression")
197
+ return ValueExpression("best_of", {"items": parts})
198
+
199
+
200
+ def Conditional(condition: ValueExpression, if_true: ValueExpression,
201
+ if_false: ValueExpression) -> ValueExpression:
202
+ """Condition must evaluate to exactly 0 or 1 (typically a Digital)."""
203
+ return ValueExpression("conditional", {
204
+ "condition": _expr(condition), "if_true": _expr(if_true), "if_false": _expr(if_false),
205
+ })
206
+
207
+
208
+ @dataclass(frozen=True)
209
+ class CashflowRule:
210
+ payment_date: float
211
+ amount: ValueExpression
212
+
213
+ def __post_init__(self) -> None:
214
+ object.__setattr__(self, "payment_date", _time(self.payment_date, "payment_date"))
215
+ _require_expr(self.amount)
216
+
217
+ def to_spec(self) -> dict[str, Any]:
218
+ return {"payment_date": _number(self.payment_date, "payment_date"),
219
+ "amount": _expr(self.amount)}
220
+
221
+
222
+ @dataclass(frozen=True, init=False)
223
+ class ComposedProduct:
224
+ rules: tuple[CashflowRule, ...]
225
+
226
+ def __init__(self, rules: Sequence[CashflowRule]):
227
+ items = tuple(rules)
228
+ if not items or any(not isinstance(r, CashflowRule) for r in items):
229
+ raise ValueError("ComposedProduct needs one or more CashflowRule objects")
230
+ object.__setattr__(self, "rules", items)
231
+
232
+ def to_spec(self) -> dict[str, Any]:
233
+ return {"kind": "composed", "rules": [rule.to_spec() for rule in self.rules]}
234
+
235
+
236
+ def Product(payoff: ValueExpression | Sequence[CashflowRule], *,
237
+ payment_date: float | None = None) -> ComposedProduct:
238
+ """Convenience: Product(Call(...), payment_date=1.0) or Product([rules...])."""
239
+ if isinstance(payoff, ValueExpression):
240
+ if payment_date is None:
241
+ raise ValueError("payment_date is mandatory for a single payoff")
242
+ return ComposedProduct([CashflowRule(payment_date, payoff)])
243
+ if payment_date is not None:
244
+ raise ValueError("Do not provide payment_date when passing CashflowRule objects")
245
+ return ComposedProduct(payoff)
246
+
247
+
248
+ @dataclass(frozen=True, init=False)
249
+ class DiscreteBarrier:
250
+ """Discrete knock-in/out wrapper; no rebate.
251
+
252
+ Monitor either one underlying on monitoring_dates, or arbitrary expressions
253
+ with observations=[(date, expression), ...]. These modes are exclusive.
254
+ """
255
+ product: ComposedProduct | DiscreteBarrier
256
+ level: float
257
+ direction: str
258
+ activation: str
259
+ underlying: str | None
260
+ monitoring_dates: tuple[float, ...] | None
261
+ observations: tuple[tuple[float, ValueExpression], ...] | None
262
+
263
+ def __init__(self, product: ComposedProduct | DiscreteBarrier, *, level: float,
264
+ direction: str = "down", activation: str = "out",
265
+ underlying: Asset | str | None = None,
266
+ monitoring_dates: Sequence[float] | None = None,
267
+ observations: Sequence[tuple[float, ValueExpression]] | None = None):
268
+ _product_spec(product)
269
+ if direction not in ("down", "up") or activation not in ("in", "out"):
270
+ raise ValueError("direction must be down/up and activation must be in/out")
271
+ direct = underlying is not None and monitoring_dates is not None and observations is None
272
+ custom = observations is not None and underlying is None and monitoring_dates is None
273
+ if not (direct or custom):
274
+ raise ValueError("Provide underlying+monitoring_dates OR observations, not both")
275
+ obs = None if observations is None else tuple((
276
+ _time(t, "observation date"), _require_expr(v)) for t, v in observations)
277
+ dates = None if monitoring_dates is None else tuple(_time(t, "monitoring date") for t in monitoring_dates)
278
+ if (obs is not None and not obs) or (dates is not None and not dates):
279
+ raise ValueError("Barrier needs at least one monitoring observation")
280
+ object.__setattr__(self, "product", product)
281
+ object.__setattr__(self, "level", _number(level, "level"))
282
+ object.__setattr__(self, "direction", direction)
283
+ object.__setattr__(self, "activation", activation)
284
+ object.__setattr__(self, "underlying", None if underlying is None else _asset_name(underlying))
285
+ object.__setattr__(self, "monitoring_dates", dates)
286
+ object.__setattr__(self, "observations", obs)
287
+
288
+ def to_spec(self) -> dict[str, Any]:
289
+ result = {"kind": "barrier", "product": self.product.to_spec(),
290
+ "level": self.level, "direction": self.direction, "activation": self.activation}
291
+ if self.observations is not None:
292
+ result["observations"] = [
293
+ {"date": t, "value": _expr(expr)} for t, expr in self.observations]
294
+ else:
295
+ result["asset"] = self.underlying
296
+ result["monitoring_dates"] = list(self.monitoring_dates or ())
297
+ return result
298
+
299
+
300
+ def _require_expr(value: ValueExpression) -> ValueExpression:
301
+ _expr(value)
302
+ return value
303
+
304
+
305
+ @dataclass(frozen=True)
306
+ class SpotControl:
307
+ asset: Asset | str
308
+ at: float
309
+
310
+ def to_spec(self) -> dict[str, Any]:
311
+ return {"kind": "spot", "asset": _asset_name(self.asset), "at": _number(self.at, "at")}
312
+
313
+
314
+ @dataclass(frozen=True)
315
+ class Market:
316
+ """Quotes mapped by asset name. Insertion order is the model's asset order."""
317
+ spots: Mapping[Asset | str, float]
318
+ rate: float
319
+ dividend_yields: Mapping[Asset | str, float] | None = None
320
+ valuation_date: date | None = None
321
+
322
+ def __post_init__(self) -> None:
323
+ if not isinstance(self.spots, Mapping):
324
+ raise TypeError("spots must map asset names to quotes")
325
+ def snapshot(values: Mapping, label: str) -> Mapping:
326
+ if not isinstance(values, Mapping):
327
+ raise TypeError(f"{label} must map asset names to values")
328
+ result: dict[str, float] = {}
329
+ for key, value in values.items():
330
+ name = _asset_name(key)
331
+ if name in result:
332
+ raise ValueError(f"Duplicate asset name in {label}: {name}")
333
+ result[name] = _number(value, label)
334
+ return MappingProxyType(result)
335
+ object.__setattr__(self, "spots", snapshot(self.spots, "spots"))
336
+ if self.dividend_yields is not None:
337
+ object.__setattr__(self, "dividend_yields", snapshot(self.dividend_yields, "dividend_yields"))
338
+
339
+ def to_spec(self) -> dict[str, Any]:
340
+ if not self.spots:
341
+ raise ValueError("Market requires at least one asset")
342
+ names = [_asset_name(name) for name in self.spots]
343
+ if len(set(names)) != len(names):
344
+ raise ValueError("Duplicate asset names")
345
+ divs = {} if self.dividend_yields is None else self.dividend_yields
346
+ if set(divs) - set(names):
347
+ raise ValueError("dividend_yields contains an unknown asset")
348
+ d = self.valuation_date if self.valuation_date is not None else date.today()
349
+ if not isinstance(d, date):
350
+ raise TypeError("valuation_date must be datetime.date or None")
351
+ return {"assets": names,
352
+ "spots": [_number(v, "spot") for v in self.spots.values()],
353
+ "dividend_yields": [_number(divs.get(name, 0.0), "dividend yield") for name in names],
354
+ "rate": _number(self.rate, "rate"),
355
+ "year": d.year, "month": d.month, "day": d.day}
356
+
357
+
358
+ def _vector(values: float | Sequence[float] | Mapping[str, float],
359
+ assets: Sequence[str], label: str) -> list[float]:
360
+ if isinstance(values, Mapping):
361
+ if set(values) != set(assets):
362
+ raise ValueError(f"{label} keys must match market assets")
363
+ return [_number(values[name], label) for name in assets]
364
+ if isinstance(values, Real):
365
+ if len(assets) != 1:
366
+ raise ValueError(f"Scalar {label} only valid for one asset")
367
+ return [_number(values, label)]
368
+ result = [_number(v, label) for v in values]
369
+ if len(result) != len(assets):
370
+ raise ValueError(f"{label} length must equal the market asset count")
371
+ return result
372
+
373
+
374
+ def _matrix(correlation: Sequence[Sequence[float]] | None, size: int) -> list[list[float]]:
375
+ if correlation is None:
376
+ return [[float(i == j) for j in range(size)] for i in range(size)]
377
+ result = [[_number(x, "correlation") for x in row] for row in correlation]
378
+ if len(result) != size or any(len(row) != size for row in result):
379
+ raise ValueError(f"Correlation matrix must be {size}x{size}")
380
+ return result
381
+
382
+
383
+ def _params(values: Mapping[str, float] | Sequence[Mapping[str, float]] |
384
+ Mapping[str, Mapping[str, float]], assets: Sequence[str],
385
+ fields: Sequence[str]) -> list[list[float]]:
386
+ if isinstance(values, Mapping) and set(values) == set(fields) and len(assets) == 1:
387
+ rows = [values]
388
+ elif isinstance(values, Mapping):
389
+ if set(values) != set(assets):
390
+ raise ValueError("Model parameter asset names must match market")
391
+ rows = [values[name] for name in assets]
392
+ else:
393
+ rows = list(values)
394
+ if len(rows) != len(assets):
395
+ raise ValueError("Model parameter count must match market assets")
396
+ result = []
397
+ for row in rows:
398
+ if not isinstance(row, Mapping) or set(row) != set(fields):
399
+ raise ValueError(f"Model parameters must have fields {tuple(fields)}")
400
+ result.append([_number(row[field], field) for field in fields])
401
+ return result
402
+
403
+
404
+ @dataclass(frozen=True)
405
+ class BlackScholes:
406
+ volatilities: float | Sequence[float] | Mapping[str, float]
407
+ correlation: Sequence[Sequence[float]] | None = None
408
+
409
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
410
+ return {"kind": "black_scholes", "volatilities": _vector(self.volatilities, assets, "volatility"),
411
+ "correlation": _matrix(self.correlation, len(assets))}
412
+
413
+
414
+ @dataclass(frozen=True)
415
+ class Heston:
416
+ parameters: Mapping | Sequence[Mapping]
417
+ rho: float | None = None
418
+ correlation: Sequence[Sequence[float]] | None = None
419
+
420
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
421
+ spec = {"kind": "heston", "parameters": _params(self.parameters, assets, ("v0", "kappa", "theta", "xi"))}
422
+ if self.rho is not None:
423
+ if self.correlation is not None:
424
+ raise ValueError("Provide rho or a full correlation matrix, not both")
425
+ if len(assets) != 1:
426
+ raise ValueError("rho is only valid for single-asset Heston")
427
+ spec["rho"] = _number(self.rho, "rho")
428
+ else:
429
+ if self.correlation is None:
430
+ raise ValueError("Heston requires rho (single asset) or a full 2N x 2N correlation")
431
+ spec["correlation"] = _matrix(self.correlation, 2 * len(assets))
432
+ return spec
433
+
434
+
435
+ @dataclass(frozen=True)
436
+ class Merton:
437
+ parameters: Mapping | Sequence[Mapping]
438
+ correlation: Sequence[Sequence[float]] | None = None
439
+
440
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
441
+ return {"kind": "merton", "parameters": _params(self.parameters, assets, ("sigma", "lambda", "mu", "delta")),
442
+ "correlation": _matrix(self.correlation, len(assets))}
443
+
444
+
445
+ @dataclass(frozen=True)
446
+ class Kou:
447
+ parameters: Mapping | Sequence[Mapping]
448
+ correlation: Sequence[Sequence[float]] | None = None
449
+
450
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
451
+ return {"kind": "kou", "parameters": _params(self.parameters, assets, ("sigma", "lambda", "p", "eta_plus", "eta_minus")),
452
+ "correlation": _matrix(self.correlation, len(assets))}
453
+
454
+
455
+ @dataclass(frozen=True)
456
+ class VarianceGamma:
457
+ sigmas: float | Sequence[float] | Mapping[str, float]
458
+ thetas: float | Sequence[float] | Mapping[str, float]
459
+ nu: float
460
+ correlation: Sequence[Sequence[float]] | None = None
461
+
462
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
463
+ return {"kind": "variance_gamma", "sigmas": _vector(self.sigmas, assets, "sigma"),
464
+ "thetas": _vector(self.thetas, assets, "theta"), "nu": _number(self.nu, "nu"),
465
+ "correlation": _matrix(self.correlation, len(assets))}
466
+
467
+
468
+ @dataclass(frozen=True)
469
+ class NIG:
470
+ sigmas: float | Sequence[float] | Mapping[str, float]
471
+ thetas: float | Sequence[float] | Mapping[str, float]
472
+ nu: float
473
+ correlation: Sequence[Sequence[float]] | None = None
474
+
475
+ def to_spec(self, assets: Sequence[str]) -> dict[str, Any]:
476
+ return {"kind": "nig", "sigmas": _vector(self.sigmas, assets, "sigma"),
477
+ "thetas": _vector(self.thetas, assets, "theta"), "nu": _number(self.nu, "nu"),
478
+ "correlation": _matrix(self.correlation, len(assets))}
479
+
480
+
481
+ def _product_spec(product: Any) -> dict[str, Any]:
482
+ from .structured import AutocallProduct
483
+ if not isinstance(product, (ComposedProduct, DiscreteBarrier, AutocallProduct)):
484
+ raise TypeError("Expected ComposedProduct, DiscreteBarrier or AutocallProduct")
485
+ return product.to_spec()
486
+
487
+
488
+ def _settings(settings: SimulationSettings | None) -> SimulationSettings:
489
+ from . import _core
490
+ if settings is not None and not isinstance(settings, _core.SimulationSettings):
491
+ raise TypeError("settings must be SimulationSettings or None")
492
+ return settings if settings is not None else _core.SimulationSettings()
493
+
494
+
495
+ def _market_model(market: Market, model: Any) -> tuple[dict[str, Any], dict[str, Any]]:
496
+ if not isinstance(market, Market):
497
+ raise TypeError("market must be Market")
498
+ if not isinstance(model, (BlackScholes, Heston, Merton, Kou, VarianceGamma, NIG)):
499
+ raise TypeError("Unknown model type")
500
+ ms = market.to_spec()
501
+ return ms, model.to_spec(ms["assets"])
502
+
503
+
504
+ def price(product: Any, *, market: Market,
505
+ model: BlackScholes | Heston | Merton | Kou | VarianceGamma | NIG,
506
+ settings: SimulationSettings | None = None,
507
+ control: SpotControl | None = None) -> PricingResult:
508
+ """Price a composed contract or evaluate a frozen exercise policy in C++.
509
+
510
+ n_simulations counts observations (two paths per antithetic observation).
511
+ A SpotControl requires settings.use_control_variate=True. Training a policy
512
+ is a separate explicit operation; this function never learns a policy.
513
+ """
514
+ from . import _core
515
+ if control is not None and not isinstance(control, SpotControl):
516
+ raise TypeError("control must be SpotControl or None")
517
+ mc = _settings(settings)
518
+ ms, model_spec = _market_model(market, model)
519
+ ctrl = None if control is None else control.to_spec()
520
+ if isinstance(product, _core.ExercisePolicy):
521
+ return _core.price_exercise_policy(product, ms, model_spec, mc, ctrl)
522
+ return _core.price_composed(_product_spec(product), ms, model_spec, mc, ctrl)
523
+
524
+
525
+ def simulate_path(*, market: Market, model: Any, dates: Sequence[float],
526
+ seed: int = 42) -> MarketPath:
527
+ """Generate one native path on exact dates starting at zero; requires NumPy."""
528
+ from . import _core
529
+ ms, model_spec = _market_model(market, model)
530
+ return _core.simulate_path(ms, model_spec, list(dates), seed)
531
+
532
+
533
+ def evaluate(product: Any, path: MarketPath) -> list[Cashflow]:
534
+ """Return undiscounted native Cashflow objects on a supplied MarketPath."""
535
+ from . import _core
536
+ return _core.evaluate(_product_spec(product), path)
537
+
538
+
539
+ def evaluate_expression(value: ValueExpression, path: MarketPath) -> float:
540
+ """Evaluate an expression on a native MarketPath without simulation."""
541
+ from . import _core
542
+ return _core.evaluate_expression(_expr(value), path)
543
+
544
+
545
+ def simulation_dates(product: Any, *, settings: SimulationSettings | None = None,
546
+ control: SpotControl | None = None) -> list[float]:
547
+ """Inspect the native numerical date union, independently of payment dates."""
548
+ from . import _core
549
+ if control is not None and not isinstance(control, SpotControl):
550
+ raise TypeError("control must be SpotControl or None")
551
+ return _core.simulation_dates(_product_spec(product), _settings(settings),
552
+ None if control is None else control.to_spec())
553
+
554
+
555
+ def has_finite_spot_moment(*, market: Market, model: Kou | VarianceGamma | NIG,
556
+ asset: Asset | str, order: float = 2) -> bool:
557
+ """Use the engine's moment-domain check (Kou, VG and NIG only)."""
558
+ from . import _core
559
+ ms, model_spec = _market_model(market, model)
560
+ return _core.has_finite_spot_moment(model_spec, ms, _asset_name(asset), _number(order, "order"))
@@ -0,0 +1,26 @@
1
+ /*
2
+ Copyright (c) 2011, Intel Corporation. All rights reserved.
3
+
4
+ Redistribution and use in source and binary forms, with or without modification,
5
+ are permitted provided that the following conditions are met:
6
+
7
+ * Redistributions of source code must retain the above copyright notice, this
8
+ list of conditions and the following disclaimer.
9
+ * Redistributions in binary form must reproduce the above copyright notice,
10
+ this list of conditions and the following disclaimer in the documentation
11
+ and/or other materials provided with the distribution.
12
+ * Neither the name of Intel Corporation nor the names of its contributors may
13
+ be used to endorse or promote products derived from this software without
14
+ specific prior written permission.
15
+
16
+ THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
17
+ ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18
+ WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19
+ DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR
20
+ ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21
+ (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22
+ LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON
23
+ ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24
+ (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25
+ SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26
+ */
@@ -0,0 +1,51 @@
1
+ Minpack Copyright Notice (1999) University of Chicago. All rights reserved
2
+
3
+ Redistribution and use in source and binary forms, with or
4
+ without modification, are permitted provided that the
5
+ following conditions are met:
6
+
7
+ 1. Redistributions of source code must retain the above
8
+ copyright notice, this list of conditions and the following
9
+ disclaimer.
10
+
11
+ 2. Redistributions in binary form must reproduce the above
12
+ copyright notice, this list of conditions and the following
13
+ disclaimer in the documentation and/or other materials
14
+ provided with the distribution.
15
+
16
+ 3. The end-user documentation included with the
17
+ redistribution, if any, must include the following
18
+ acknowledgment:
19
+
20
+ "This product includes software developed by the
21
+ University of Chicago, as Operator of Argonne National
22
+ Laboratory.
23
+
24
+ Alternately, this acknowledgment may appear in the software
25
+ itself, if and wherever such third-party acknowledgments
26
+ normally appear.
27
+
28
+ 4. WARRANTY DISCLAIMER. THE SOFTWARE IS SUPPLIED "AS IS"
29
+ WITHOUT WARRANTY OF ANY KIND. THE COPYRIGHT HOLDER, THE
30
+ UNITED STATES, THE UNITED STATES DEPARTMENT OF ENERGY, AND
31
+ THEIR EMPLOYEES: (1) DISCLAIM ANY WARRANTIES, EXPRESS OR
32
+ IMPLIED, INCLUDING BUT NOT LIMITED TO ANY IMPLIED WARRANTIES
33
+ OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE, TITLE
34
+ OR NON-INFRINGEMENT, (2) DO NOT ASSUME ANY LEGAL LIABILITY
35
+ OR RESPONSIBILITY FOR THE ACCURACY, COMPLETENESS, OR
36
+ USEFULNESS OF THE SOFTWARE, (3) DO NOT REPRESENT THAT USE OF
37
+ THE SOFTWARE WOULD NOT INFRINGE PRIVATELY OWNED RIGHTS, (4)
38
+ DO NOT WARRANT THAT THE SOFTWARE WILL FUNCTION
39
+ UNINTERRUPTED, THAT IT IS ERROR-FREE OR THAT ANY ERRORS WILL
40
+ BE CORRECTED.
41
+
42
+ 5. LIMITATION OF LIABILITY. IN NO EVENT WILL THE COPYRIGHT
43
+ HOLDER, THE UNITED STATES, THE UNITED STATES DEPARTMENT OF
44
+ ENERGY, OR THEIR EMPLOYEES: BE LIABLE FOR ANY INDIRECT,
45
+ INCIDENTAL, CONSEQUENTIAL, SPECIAL OR PUNITIVE DAMAGES OF
46
+ ANY KIND OR NATURE, INCLUDING BUT NOT LIMITED TO LOSS OF
47
+ PROFITS OR LOSS OF DATA, FOR ANY REASON WHATSOEVER, WHETHER
48
+ SUCH LIABILITY IS ASSERTED ON THE BASIS OF CONTRACT, TORT
49
+ (INCLUDING NEGLIGENCE OR STRICT LIABILITY), OR OTHERWISE,
50
+ EVEN IF ANY OF SAID PARTIES HAS BEEN WARNED OF THE
51
+ POSSIBILITY OF SUCH LOSS OR DAMAGES.