codestr 0.2.0__tar.gz → 0.3.0__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.
Files changed (47) hide show
  1. {codestr-0.2.0 → codestr-0.3.0}/PKG-INFO +25 -1
  2. {codestr-0.2.0 → codestr-0.3.0}/README.md +24 -0
  3. {codestr-0.2.0 → codestr-0.3.0}/docs/operators.md +36 -15
  4. codestr-0.3.0/docs/superpowers/plans/2026-07-17-min-samples.md +636 -0
  5. codestr-0.3.0/docs/superpowers/plans/2026-07-17-mixed-window-planner.md +1458 -0
  6. codestr-0.3.0/docs/superpowers/specs/2026-07-17-min-samples-design.md +200 -0
  7. codestr-0.3.0/docs/superpowers/specs/2026-07-17-mixed-window-planner-design.md +324 -0
  8. {codestr-0.2.0 → codestr-0.3.0}/pyproject.toml +1 -1
  9. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/compiler.py +10 -1
  10. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/engine.py +76 -32
  11. codestr-0.3.0/src/codestr/planner.py +257 -0
  12. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/syntax.py +2 -2
  13. codestr-0.3.0/src/codestr/udf/ts_udf.py +238 -0
  14. {codestr-0.2.0 → codestr-0.3.0}/tests/conftest.py +14 -0
  15. {codestr-0.2.0 → codestr-0.3.0}/tests/test_compiler.py +51 -2
  16. codestr-0.3.0/tests/test_engine.py +552 -0
  17. codestr-0.3.0/tests/test_planner.py +174 -0
  18. {codestr-0.2.0 → codestr-0.3.0}/tests/test_syntax.py +5 -2
  19. {codestr-0.2.0 → codestr-0.3.0}/tests/test_ts_udf.py +126 -0
  20. {codestr-0.2.0 → codestr-0.3.0}/uv.lock +1 -1
  21. codestr-0.2.0/src/codestr/udf/ts_udf.py +0 -110
  22. codestr-0.2.0/tests/test_engine.py +0 -285
  23. {codestr-0.2.0 → codestr-0.3.0}/.github/workflows/ci.yml +0 -0
  24. {codestr-0.2.0 → codestr-0.3.0}/.github/workflows/release.yml +0 -0
  25. {codestr-0.2.0 → codestr-0.3.0}/.gitignore +0 -0
  26. {codestr-0.2.0 → codestr-0.3.0}/.python-version +0 -0
  27. {codestr-0.2.0 → codestr-0.3.0}/CHANGELOG.md +0 -0
  28. {codestr-0.2.0 → codestr-0.3.0}/CLAUDE.md +0 -0
  29. {codestr-0.2.0 → codestr-0.3.0}/CONTRIBUTING.md +0 -0
  30. {codestr-0.2.0 → codestr-0.3.0}/LICENSE +0 -0
  31. {codestr-0.2.0 → codestr-0.3.0}/docs/superpowers/plans/2026-07-16-ts-ema-keyword-arguments.md +0 -0
  32. {codestr-0.2.0 → codestr-0.3.0}/docs/superpowers/specs/2026-07-16-polars-minimum-version-design.md +0 -0
  33. {codestr-0.2.0 → codestr-0.3.0}/docs/superpowers/specs/2026-07-16-ts-ema-keyword-arguments-design.md +0 -0
  34. {codestr-0.2.0 → codestr-0.3.0}/review.md +0 -0
  35. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/__init__.py +0 -0
  36. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/errors.py +0 -0
  37. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/parser.py +0 -0
  38. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/tokens.py +0 -0
  39. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/udf/__init__.py +0 -0
  40. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/udf/base_udf.py +0 -0
  41. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/udf/cs_udf.py +0 -0
  42. {codestr-0.2.0 → codestr-0.3.0}/src/codestr/udf/registry.py +0 -0
  43. {codestr-0.2.0 → codestr-0.3.0}/tests/__init__.py +0 -0
  44. {codestr-0.2.0 → codestr-0.3.0}/tests/test_base_udf.py +0 -0
  45. {codestr-0.2.0 → codestr-0.3.0}/tests/test_cs_udf.py +0 -0
  46. {codestr-0.2.0 → codestr-0.3.0}/tests/test_parser.py +0 -0
  47. {codestr-0.2.0 → codestr-0.3.0}/tests/test_registry.py +0 -0
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: codestr
3
- Version: 0.2.0
3
+ Version: 0.3.0
4
4
  Summary: DSL → Polars Expr compile engine for quantitative factor mining
5
5
  Project-URL: Repository, https://github.com/huangbogeng/codestr
6
6
  Author: huangbogeng
@@ -115,6 +115,23 @@ cs = CodeStr(df,
115
115
  | **TS (时序)** | `over(partition_by=partition_by, order_by=order_by)` |
116
116
  | **CS (截面)** | `over(partition_by=order_by, order_by=partition_by)` |
117
117
 
118
+ ### 混合窗口
119
+
120
+ `CodeStr.sql()` 会自动把 TS/CS 混合窗口拆成连续的 lazy projection:
121
+
122
+ ```python
123
+ cs.sql(
124
+ "close * 2 as scaled",
125
+ "ts_mean(cs_moderate(scaled), 60) as factor",
126
+ )
127
+ ```
128
+
129
+ 其语义等价于先生成 `cs_moderate(scaled)` 中间列,再沿资产时间轴
130
+ 计算 `ts_mean`。中间列不会出现在返回结果中。
131
+
132
+ 纯 `cs.compile()` 只能返回一个 `pl.Expr`,因此会明确拒绝需要多阶段
133
+ 执行的 TS/CS 混合窗口。TS→TS 和 CS→CS 同域嵌套不受影响。
134
+
118
135
  ## 自定义算子
119
136
 
120
137
  ```python
@@ -139,6 +156,13 @@ cs.sql("ts_ewm(close, 10) as ewm10")
139
156
 
140
157
  **时序算子** (`ts_udf`):`ts_mean`, `ts_ema`, `ts_sum`, `ts_std`, `ts_var`, `ts_skew`, `ts_kurt`, `ts_max`, `ts_min`, `ts_mid`, `ts_delay`, `ts_delta`, `ts_mad` 等
141
158
 
159
+ 滚动统计与 `ts_ema` 支持关键字参数 `min_samples`。滚动统计省略该参数时沿用 Polars 默认值 `None`(需要完整窗口),`ts_ema` 则沿用 Polars 默认值 `1`:
160
+
161
+ ```python
162
+ cs.sql("ts_mean(close, 20, min_samples=5) as ma20")
163
+ cs.sql("ts_ema(close, 10, min_samples=3) as ema10")
164
+ ```
165
+
142
166
  ## 项目结构
143
167
 
144
168
  ```
@@ -84,6 +84,23 @@ cs = CodeStr(df,
84
84
  | **TS (时序)** | `over(partition_by=partition_by, order_by=order_by)` |
85
85
  | **CS (截面)** | `over(partition_by=order_by, order_by=partition_by)` |
86
86
 
87
+ ### 混合窗口
88
+
89
+ `CodeStr.sql()` 会自动把 TS/CS 混合窗口拆成连续的 lazy projection:
90
+
91
+ ```python
92
+ cs.sql(
93
+ "close * 2 as scaled",
94
+ "ts_mean(cs_moderate(scaled), 60) as factor",
95
+ )
96
+ ```
97
+
98
+ 其语义等价于先生成 `cs_moderate(scaled)` 中间列,再沿资产时间轴
99
+ 计算 `ts_mean`。中间列不会出现在返回结果中。
100
+
101
+ 纯 `cs.compile()` 只能返回一个 `pl.Expr`,因此会明确拒绝需要多阶段
102
+ 执行的 TS/CS 混合窗口。TS→TS 和 CS→CS 同域嵌套不受影响。
103
+
87
104
  ## 自定义算子
88
105
 
89
106
  ```python
@@ -108,6 +125,13 @@ cs.sql("ts_ewm(close, 10) as ewm10")
108
125
 
109
126
  **时序算子** (`ts_udf`):`ts_mean`, `ts_ema`, `ts_sum`, `ts_std`, `ts_var`, `ts_skew`, `ts_kurt`, `ts_max`, `ts_min`, `ts_mid`, `ts_delay`, `ts_delta`, `ts_mad` 等
110
127
 
128
+ 滚动统计与 `ts_ema` 支持关键字参数 `min_samples`。滚动统计省略该参数时沿用 Polars 默认值 `None`(需要完整窗口),`ts_ema` 则沿用 Polars 默认值 `1`:
129
+
130
+ ```python
131
+ cs.sql("ts_mean(close, 20, min_samples=5) as ma20")
132
+ cs.sql("ts_ema(close, 10, min_samples=3) as ema10")
133
+ ```
134
+
111
135
  ## 项目结构
112
136
 
113
137
  ```
@@ -274,33 +274,35 @@ cs.sql("cs_midby(factor, sector, cap_group) as group_median")
274
274
 
275
275
  > 编译器自动注入窗口配置:`over(partition_by=asset_cols, order_by=datetime_cols)`
276
276
 
277
- 滚动、滞后与差分算子的通用签名为 `(expr, windows)`,其中 `windows` 是整数,表示回溯窗口大小。`ts_ema` 的签名为 `(expr, span)`,`span` 必须是正整数。
277
+ 滚动统计的通用签名为 `(expr, windows, *, min_samples=None)`,其中 `windows` 是整数,表示回溯窗口大小;`min_samples` 是仅限关键字的正整数参数,省略时沿用 Polars 默认值 `None`(需要完整窗口)。`ts_ema` 的签名为 `(expr, span, *, min_samples=1)`,`span` 和 `min_samples` 都必须是正整数。滞后与差分算子仍使用 `(expr, windows)`,不接收 `min_samples`。
278
278
 
279
279
  ### 滚动统计
280
280
 
281
281
  | 算子 | 说明 | 底层实现 |
282
282
  |------|------|------|
283
- | `ts_mean` | 滚动均值 | `rolling_mean(windows)` |
284
- | `ts_ema` | 递归指数移动平均 | `ewm_mean(span=span, adjust=False, min_samples=1)` |
285
- | `ts_sum` | 滚动求和 | `rolling_sum(windows)` |
286
- | `ts_std` | 滚动标准差 | `rolling_std(windows)` |
287
- | `ts_var` | 滚动方差 | `rolling_var(windows)` |
288
- | `ts_skew` | 滚动偏度 | `rolling_skew(windows)` |
289
- | `ts_kurt` | 滚动峰度 | `rolling_kurtosis(windows)` |
290
- | `ts_max` | 滚动最大值 | `rolling_max(windows)` |
291
- | `ts_min` | 滚动最小值 | `rolling_min(windows)` |
292
- | `ts_mid` | 滚动中位数 | `rolling_median(windows)` |
293
- | `ts_mad` | 滚动 MAD | `median(|x - rolling_median(x)|) × 1.4826` |
294
-
295
- > `ts_mad` 乘以常数 1.4826 使其在正态分布下与标准差一致(一致性缩放因子)。
283
+ | `ts_mean` | 滚动均值 | `rolling_mean(windows, min_samples=min_samples)` |
284
+ | `ts_ema` | 递归指数移动平均 | `ewm_mean(span=span, adjust=False, min_samples=min_samples)` |
285
+ | `ts_sum` | 滚动求和 | `rolling_sum(windows, min_samples=min_samples)` |
286
+ | `ts_std` | 滚动标准差 | `rolling_std(windows, min_samples=min_samples)` |
287
+ | `ts_var` | 滚动方差 | `rolling_var(windows, min_samples=min_samples)` |
288
+ | `ts_skew` | 滚动偏度 | `rolling_skew(windows, min_samples=min_samples)` |
289
+ | `ts_kurt` | 滚动峰度 | `rolling_kurtosis(windows, min_samples=min_samples)` |
290
+ | `ts_max` | 滚动最大值 | `rolling_max(windows, min_samples=min_samples)` |
291
+ | `ts_min` | 滚动最小值 | `rolling_min(windows, min_samples=min_samples)` |
292
+ | `ts_mid` | 滚动中位数 | `rolling_median(windows, min_samples=min_samples)` |
293
+ | `ts_mad` | 滚动 MAD | 内外两层 `rolling_median` 使用相同的 `min_samples` |
294
+
295
+ > `ts_mad` 计算 `median(|x - rolling_median(x)|) × 1.4826`。常数 1.4826 使其在正态分布下与标准差一致,内外两层滚动中位数共享同一个 `min_samples`。
296
296
 
297
297
  示例:
298
298
  ```python
299
299
  cs.sql("ts_mean(close, 5) as ma5")
300
+ cs.sql("ts_mean(close, 20, min_samples=5) as ma20_partial")
300
301
  cs.sql("ts_ema(close, 10) as ema10")
302
+ cs.sql("ts_ema(close, 10, min_samples=3) as ema10_warmup")
301
303
  cs.sql("ts_std(close, 20) as vol20")
302
304
  cs.sql("ts_max(high, 20) as hh20")
303
- cs.sql("ts_mad(close, 20) as mad20")
305
+ cs.sql("ts_mad(close, 20, min_samples=5) as mad20")
304
306
  ```
305
307
 
306
308
  ### 滞后与差分
@@ -373,6 +375,25 @@ cs = CodeStr(df,
373
375
 
374
376
  CS 与 TS 的 `partition_by` / `order_by` **刚好交换**,分别实现"按时间分组,沿实体统计"(CS)和"按实体分组,沿时间统计"(TS)。
375
377
 
378
+ ### 混合窗口规划
379
+
380
+ TS 与 CS 使用不同分组轴,不能安全地嵌套成一个 Polars window
381
+ expression。`CodeStr.sql()` 会检测窗口祖先链上的域变化,并生成多个
382
+ 连续 `with_columns` 阶段:
383
+
384
+ ```text
385
+ ts_mean(cs_moderate(x), 60)
386
+
387
+ stage 1: internal = cs_moderate(x)
388
+ stage 2: result = ts_mean(internal, 60)
389
+ ```
390
+
391
+ 该过程保持 lazy,不会在阶段间调用 `collect()`。同一次 `sql()` 中,
392
+ 后续表达式也可以引用前一条表达式创建的别名。
393
+
394
+ `CodeStr.compile()` 仍返回单个 `pl.Expr`,对混合窗口抛出
395
+ `CompileError`。同域嵌套和并列的 TS/CS 表达式继续正常编译。
396
+
376
397
  ---
377
398
 
378
399
  ## 自定义算子