Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
There was a problem hiding this comment.
지금 당장은 polars plugin쪽에서 non-nullable을 막고 있어서, core쪽만 nullable을 허용하게 되면 CI가 깨지게 됩니다.
그래서 일단 core 작업하는 동안은 임시로 비활성화 해두고, 그 다음 작업으로 바로 plugin쪽으로 넘어가서, 이 파일 변경사항을 되돌리고 작업 재개하려고 합니다
| /// Computes an exponential moving average over an aligned nullable series. | ||
| /// | ||
| /// The returned vector keeps the same length as the input and emits `None` | ||
| /// until the first contiguous run of `period` valid observations has been | ||
| /// observed. Once seeded, gaps emit `None` without resetting the EMA state. | ||
| pub fn ema(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| ema_impl(data, period) | ||
| } | ||
|
|
||
| pub(crate) fn ema_aligned(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| ema_impl(data, period) | ||
| } | ||
|
|
There was a problem hiding this comment.
말씀 주신 방향으로 정리했습니다. 8847f9d 에서 중복 구현은 제거하고 pub(crate) use ema as ema_aligned; alias만 남겨 internal call-site semantics만 유지했습니다.
| let input = testutils::load_data(&format!("../data/{}.json", symbol), "c") | ||
| .into_iter() | ||
| .map(Some) | ||
| .collect::<Vec<_>>(); |
There was a problem hiding this comment.
load_data마다 이 과정을 해주지 않고 load_data의 함수를 바꾸면 좋을거 같네요.
There was a problem hiding this comment.
기존 dense fixture loader는 다른 테스트들과의 호환성 때문에 그대로 유지하고, testutils::load_data_nullable() helper를 추가해서, 이번 nullable 테스트들에서 반복되던 map(Some)를 걷어내도록 처리했습니다! (8847f9d)
| /// The returned vector keeps the same length as the input and emits `None` | ||
| /// until the first full `period` window has been observed. | ||
| pub fn sma(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| sma_impl(data, period) | ||
| } | ||
|
|
||
| result | ||
| pub(crate) fn sma_aligned(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| sma_impl(data, period) | ||
| } |
There was a problem hiding this comment.
추가로 후속 stack에서 sma_dense가 실제로 dead가 된 뒤에는 제거까지 반영했습니다. #15 범위에서는 dense EOM 경로가 아직 있어서 유지가 필요했지만, 그 경로가 정리된 다음 #16 head b00b2b2와 top stack head 21c77cd에서 sma_dense를 삭제했고 cargo test -p techr-core도 다시 통과했습니다.
There was a problem hiding this comment.
pub(crate) use sma as sma_aligned;
이런 alias도 없이 그냥 sma를 외부에서 써도 될 거 같습니다!
| /// Computes a rolling mean that only emits a value when the full window contains valid values. | ||
| pub fn rolling_mean_strict(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| rolling_mean_strict_impl(data, period) | ||
| } | ||
|
|
||
| /// Computes a rolling weighted mean that only emits a value when the full window is valid. | ||
| fn rolling_weighted_mean_strict_impl(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| let len = data.len(); | ||
| let mut result = vec![None; len]; | ||
|
|
||
| if len < period || period == 0 { | ||
| return result; | ||
| } | ||
|
|
||
| let weight_sum = (period * (period + 1)) as f64 / 2.0; | ||
|
|
||
| for end in (period - 1)..len { | ||
| let start = end + 1 - period; | ||
| let mut weighted_sum = 0.0; | ||
| let mut valid = true; | ||
|
|
||
| for (offset, item) in data[start..=end].iter().enumerate() { | ||
| let Some(value) = *item else { | ||
| valid = false; | ||
| break; | ||
| }; | ||
| weighted_sum += value * (offset + 1) as f64; | ||
| } | ||
|
|
||
| if valid { | ||
| result[end] = Some(weighted_sum / weight_sum); | ||
| } | ||
| } | ||
|
|
||
| result | ||
| } | ||
|
|
||
| /// Computes a rolling weighted mean that only emits a value when the full window is valid. | ||
| pub fn rolling_weighted_mean_strict(data: &[Option<f64>], period: usize) -> Vec<Option<f64>> { | ||
| rolling_weighted_mean_strict_impl(data, period) | ||
| } |
There was a problem hiding this comment.
sma (sma_aligend)와 차이가 있는지 궁금합니다!
There was a problem hiding this comment.
지금은 behavioral difference가 없습니다. 실제 구현은 rolling_mean_strict이고, sma/sma_aligned는 indicator API layer였는데, 최신 head(8847f9d)에서는 sma_aligned도 alias로 정리해서 같은 경로만 타도록 맞췄습니다.
There was a problem hiding this comment.
sma와 구현이 같다면 해당 코드 없이 sma를 다른 곳에서 쓰면 어떨까요?
ema나 일부 sma는 그렇게 사용되고 있는거 같아서요.
|
WMA 성능 코멘트도 반영했습니다. 최신 head( |
Merge activity
|
## 요약 - 이 PR은 `#15` 위에 쌓인 stacked PR입니다. 리뷰는 `core-nullable-foundations` 대비로 봐주시면 됩니다. - foundation layer에서 정의한 nullable contract를 상태형, pairwise, cumulative 지표에 적용합니다. - gap을 건너뛰며 series를 압축하지 않고, 현재 row, 이전 row, 내부 state의 유효성에 따라 `None`을 반환하는 규칙을 맞춥니다. ## 변경 사항 - `rsi`, `atr`, `dmi`, `adx`, `adxr`를 nullable 입력 기준으로 전환합니다. - `ad`, `cmf`, `co`, `efi`, `eom`, `nvi`, `obv`, `pvi`, `ultosc`, `vr` 등 pairwise/cumulative family를 같은 계약으로 정리합니다. - `psar`를 포함한 stateful indicator가 gap row에서 output만 `None`을 내고, 다음 valid row에서 이전 state를 이어서 재개하도록 맞춥니다. - 필요한 nullable helper를 `core/src/utils.rs`에 추가합니다. ## 리뷰 포인트 - current row나 required predecessor가 비면 해당 row는 `None`이어야 합니다. - recursive/stateful 계산은 gap을 사이에 두고 series를 압축하지 않은 채 정렬을 유지해야 합니다. - accumulator 기반 지표는 invalid row에서 상태를 억지로 갱신하지 않아야 합니다. - `psar`, `dmi`, `atr`, `rsi`가 대표적인 확인 포인트입니다. ## 범위 외 - `plugin/polars`는 여전히 이번 stack 범위 밖입니다. - composite/derived indicator의 최종 정리는 상위 PR `#17`에서 다룹니다. ## 테스트 계획 - [x] `cargo fmt --package techr-core --check` - [x] `cargo test -p techr-core`


요약
techr-core의 nullable 입력 마이그레이션을 시작하는 bottom PR입니다.NaN이 아닌&[Option<f64>]aligned series로 정리하고, 이후 stacked PR인#16,#17이 이 규칙 위에서 stateful/composite 지표를 순차적으로 올립니다.plugin/polars의 실제 nullable adapter 마이그레이션은 이번 스택에서 제외합니다.배경
core함수들은 대부분 dense&[f64]입력을 전제하고 있었습니다.NaN이 아니라 명시적인 타입으로 다루는 편이 계약이 더 분명합니다.techr-core전체에서 nullable aligned input을 표준 계약으로 만들고, gap을 건너뛰며 series를 압축하지 않는 일관된 의미론을 정착시키는 것입니다.이 PR에서 하는 일
core/src/utils.rs에 nullable rolling/helper primitive를 추가해 foundation semantics를 먼저 고정합니다.sma,ema,wma,bband,env,disparity를&[Option<f64>]입력 기준으로 전환합니다.macd,ppo,pvo,massi,sonar등).NaN을 새로 도입하지 않도록 정리합니다.핵심 규칙
None으로 반환합니다.None이고 내부 상태는 유지합니다.범위 외
plugin/polarsnullable adapter 마이그레이션은 후속 작업으로 미룹니다.Polars CI가 core-only PR에도 실행되기 때문에, 이 PR에는 non-polars 변경에서validate를 성공적인 no-op으로 처리하도록 하는 최소한의 workflow 조정이 함께 포함됩니다.리뷰 가이드
core/src/utils.rs,sma.rs,ema.rs,wma.rs,bband.rs,disparity.rs를 봐주시면 됩니다..github/workflows/polars-ci.yml변경을 확인하시면 충분합니다.main기준으로 리뷰하면 되고, 상위 PR인#16,#17은 각각 이 PR을 base로 봐주시면 됩니다.테스트 계획
cargo fmt --package techr-core --checkcargo test -p techr-corePolars CI가 non-polars 변경에서 성공적으로 통과하는지 확인