Files
sanguo_vnpy_v2/vnpy_v4.4.0/vnpy/alpha/dataset/utility.py
T
claude_dev 3fbe6c9be5 feat: 纳入 VeighNa 4.4.0 上游源码作为版本参考
- 添加 vnpy_v4.4.0/ 目录到版本控制
- 删除嵌入的 .git 目录避免嵌套仓库
- 添加 README_SANGUO.md 说明来源和用途
- 更新 .gitignore 移除 vnpy_v4.4.0/ 忽略规则

上游代码信息:
- 项目: VeighNa 量化交易平台
- 版本: 4.4.0
- 来源: https://github.com/vnpy/vnpy

用途: 版本参考、代码学习、差异对比、离线开发

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
2026-07-01 21:40:21 +08:00

286 lines
9.4 KiB
Python

from datetime import datetime
from enum import Enum
from numbers import Real
from typing import Union, cast
from collections.abc import Callable
import polars as pl
EXPRESSION_FUNCTIONS: dict[str, Callable] = {}
def register_functions(functions: list[Callable]) -> None:
"""Register custom expression functions by function name."""
for func in functions:
EXPRESSION_FUNCTIONS[func.__name__] = func
class DataProxy:
"""Feature data proxy"""
def __init__(self, df: pl.DataFrame) -> None:
"""Constructor"""
self.name: str = df.columns[-1]
self.df: pl.DataFrame = df.rename({self.name: "data"})
# Note that for numerical expressions, variables should be placed before numbers. e.g. a * 2
@staticmethod
def _as_series(value: object) -> pl.Series:
"""Normalize an operator result to a Polars series."""
if isinstance(value, pl.Series):
return value
return cast(pl.Series, value)
def _comparison_series(self, value: object) -> pl.Series:
"""Normalize comparison results to an Int32 series."""
if isinstance(value, pl.Series):
return value.cast(pl.Int32)
if isinstance(value, bool):
return pl.Series(name="data", values=[int(value)] * len(self.df))
if isinstance(value, Real):
return pl.Series(name="data", values=[int(bool(value))] * len(self.df))
raise TypeError(f"Unsupported comparison result type: {type(value)!r}")
def result(self, s: pl.Series) -> "DataProxy":
"""Convert series data to feature object"""
result: pl.DataFrame = self.df[["datetime", "vt_symbol"]]
result = result.with_columns(other=s)
return DataProxy(result)
def __add__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Addition operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] + other.df["data"])
else:
s = self._as_series(self.df["data"] + other)
return self.result(s)
def __radd__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Right addition operation"""
if isinstance(other, DataProxy):
s = self._as_series(other.df["data"] + self.df["data"])
else:
s = self._as_series(other + self.df["data"])
return self.result(s)
def __sub__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Subtraction operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] - other.df["data"])
else:
s = self._as_series(self.df["data"] - other)
return self.result(s)
def __rsub__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Right subtraction operation"""
if isinstance(other, DataProxy):
s = self._as_series(other.df["data"] - self.df["data"])
else:
s = self._as_series(other - self.df["data"])
return self.result(s)
def __mul__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Multiplication operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] * other.df["data"])
else:
s = self._as_series(self.df["data"] * other)
return self.result(s)
def __rmul__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Right multiplication operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] * other.df["data"])
else:
s = self._as_series(self.df["data"] * other)
return self.result(s)
def __truediv__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Division operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] / other.df["data"])
else:
s = self._as_series(self.df["data"] / other)
return self.result(s)
def __rtruediv__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Right division operation"""
if isinstance(other, DataProxy):
s = self._as_series(other.df["data"] / self.df["data"])
else:
s = self._as_series(other / self.df["data"])
return self.result(s)
def __floordiv__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Floor division operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] // other.df["data"])
else:
s = self._as_series(self.df["data"] // other)
return self.result(s)
def __mod__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Modulo operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"] % other.df["data"])
else:
s = self._as_series(self.df["data"] % other)
return self.result(s)
def __pow__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Power operation"""
if isinstance(other, DataProxy):
s = self._as_series(self.df["data"].pow(other.df["data"]))
else:
s = self._as_series(self.df["data"].pow(cast(int | float, other)))
return self.result(s)
def __abs__(self) -> "DataProxy":
"""Get absolute value"""
s: pl.Series = self.df["data"].abs()
return self.result(s)
def __neg__(self) -> "DataProxy":
"""Negation operation"""
s: pl.Series = -self.df["data"]
return self.result(s)
def __gt__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Greater than comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] > other.df["data"]
else:
s = self.df["data"] > other
return self.result(self._comparison_series(s))
def __ge__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Greater than or equal comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] >= other.df["data"]
else:
s = self.df["data"] >= other
return self.result(self._comparison_series(s))
def __lt__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Less than comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] < other.df["data"]
else:
s = self.df["data"] < other
return self.result(self._comparison_series(s))
def __le__(self, other: Union["DataProxy", Real]) -> "DataProxy":
"""Less than or equal comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] <= other.df["data"]
else:
s = self.df["data"] <= other
return self.result(self._comparison_series(s))
def __eq__(self, other: Union["DataProxy", Real]) -> "DataProxy": # type: ignore[override]
"""Equal comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] == other.df["data"]
else:
s = self.df["data"] == other
return self.result(self._comparison_series(s))
def __ne__(self, other: Union["DataProxy", Real]) -> "DataProxy": # type: ignore[override]
"""Not equal comparison"""
if isinstance(other, DataProxy):
s: object = self.df["data"] != other.df["data"]
else:
s = self.df["data"] != other
return self.result(self._comparison_series(s))
def calculate_by_expression(df: pl.DataFrame, expression: str) -> pl.DataFrame:
"""Execute calculation based on expression"""
# Import operators locally to avoid polluting global namespace
from .ts_function import ( # noqa
ts_delay,
ts_min, ts_max,
ts_argmax, ts_argmin,
ts_rank, ts_sum,
ts_mean, ts_std,
ts_slope, ts_quantile,
ts_rsquare, ts_resi,
ts_corr,
ts_less, ts_greater,
ts_log, ts_abs,
ts_delta, ts_cov,
ts_decay_linear,
ts_product
)
from .cs_function import ( # noqa
cs_rank,
cs_mean,
cs_std,
cs_sum,
cs_scale
)
from .ta_function import ( # noqa
ta_rsi,
ta_atr
)
from .math_function import ( # noqa
less, greater, log, abs,
sign, pow1, pow2,
quesval, quesval2
)
# Extract feature objects to local space
d: dict = locals()
d.update(EXPRESSION_FUNCTIONS)
for column in df.columns:
# Filter index columns
if column in {"datetime", "vt_symbol"}:
continue
# Cache feature df
column_df = df[["datetime", "vt_symbol", column]]
d[column] = DataProxy(column_df)
# Use eval to execute calculation
other: DataProxy = eval(expression, {}, d)
# Return result DataFrame
return other.df
def calculate_by_polars(df: pl.DataFrame, expression: pl.expr.expr.Expr) -> pl.DataFrame:
"""Execute calculation based on Polars expression"""
return df.select([
"datetime",
"vt_symbol",
expression.alias("data")
])
def to_datetime(arg: datetime | str) -> datetime:
"""Convert time data type"""
if isinstance(arg, str):
if "-" in arg:
fmt: str = "%Y-%m-%d"
else:
fmt = "%Y%m%d"
return datetime.strptime(arg, fmt)
else:
return arg
class Segment(Enum):
"""Data segment enumeration values"""
TRAIN = 1
VALID = 2
TEST = 3