♻️ core 상태형 지표 nullable 입력 전환#16
Conversation
This stack of pull requests is managed by Graphite. Learn more about stacking. |
f327ee2 to
f266b37
Compare
f266b37 to
3bbbf2f
Compare
3bbbf2f to
4777d2d
Compare
sjquant
left a comment
There was a problem hiding this comment.
[P2] ULTOSC가 기존 1-pass rolling 계산에서 6번의 rolling_sum_strict pass로 바뀌면서 성능 회귀가 큽니다.
기존 구현은 short/medium/long의 BP/TR rolling sum을 한 루프에서 갱신했는데, 현재 구현은 buying_pressures/true_ranges를 만든 뒤 short/medium/long 각각에 대해 총 6번 rolling_sum_strict를 수행합니다. all-Some release 벤치 기준 200k rows에서 main 약 0.93ms, PR 약 2.90ms로 약 3.1배 느려졌습니다. nullable strict semantics는 각 window별 sum과 valid_count를 유지하는 방식으로도 한 pass에 처리할 수 있어 보입니다.
[P2] VR이 3개의 중간 벡터와 3번의 rolling_sum_strict scan을 사용하면서 성능 회귀가 큽니다.
기존 구현은 up/down/same rolling total을 한 루프에서 갱신했는데, 현재 구현은 up/down/same 전체 벡터를 먼저 만들고 각각 rolling_sum_strict를 돌린 뒤 다시 결과를 합칩니다. all-Some release 벤치 기준 200k rows에서 main 약 0.52ms, PR 약 1.70ms로 약 3.3배 느려졌습니다. 각 bucket의 rolling sum과 valid_count를 streaming으로 유지하면 strict window invalidation을 보존하면서 allocation과 반복 scan을 줄일 수 있습니다.
[P2] CMF가 기존 rolling sum 2개를 한 루프에서 유지하던 구조에서 여러 full-array pass 구조로 바뀌었습니다.
현재 구현은 money_flow_volume 전체 벡터를 만든 뒤 volume_sums와 money_flow_sums를 rolling_sum_strict로 각각 계산하고, 마지막에 한 번 더 순회해 결과를 만듭니다. 기존 CMF는 sum_money_flow_volume과 sum_volume을 한 루프에서 갱신했습니다. all-Some release 벤치 기준 200k rows에서 main 약 0.61ms, PR 약 1.23ms로 약 2.0배 느려졌습니다. nullable strict contract는 window valid_count와 두 rolling sum을 한 pass에서 유지하는 방식으로 구현할 수 있어 보입니다.
[P2] PSAR hot loop에서 길이 2짜리 상태를 Vec로 관리하면서 불필요한 동적 조작 비용이 들어갑니다.
현재 구현은 last_valid_highs/last_valid_lows를 Vec로 두고 매 유효 row마다 remove(0)와 push를 호출합니다. 길이가 2라 복잡도상 큰 문제는 아니지만, PSAR는 원래 매우 가벼운 state machine이라 이 비용이 상대적으로 크게 드러납니다. all-Some release 벤치 기준 200k rows에서 main 약 0.35ms, PR 약 0.90ms로 약 2.6배 느려졌습니다. prev_high_1/prev_high_2, prev_low_1/prev_low_2 같은 고정 슬롯 또는 [f64; 2] ring buffer로 바꾸면 nullable semantics를 유지하면서 비용을 줄일 수 있습니다.
[P3] CO는 AD 전체 벡터를 만든 뒤 다시 scan하고 있어 fusion 여지가 있습니다.
현재 구현은 먼저 ad(...)로 전체 AD series를 만든 다음 short/long EMA를 다시 계산합니다. 기존 구조와 크게 다르지는 않지만, nullable 전환 후 all-Some release 벤치 기준 200k rows에서 main 약 0.63ms, PR 약 0.95ms로 약 1.5배 느려졌습니다. CO는 AD 누적값을 순차적으로만 필요로 하므로 CLV/AD 누적과 short/long EMA 갱신을 한 루프에 합칠 수 있습니다. 특히 period_short == period_long 경로는 AD 값을 실제로 계산하지 않고 유효 row에만 Some(0.0)을 내도 됩니다.
======================
성능회귀 이슈가 있는 경우에 대해 AI 리뷰 코멘트를 받아서 공유드립니다.
c84e6f4 to
307a195
Compare
|
성능 회귀 코멘트 반영했습니다. 최신 307a195 에서 |
b00b2b2 to
1afb218
Compare
Merge activity
|
## 요약 - 이 PR은 `techr-core`의 nullable 입력 마이그레이션을 시작하는 bottom PR입니다. - 공개 indicator API의 결측치 계약을 `NaN`이 아닌 `&[Option<f64>]` aligned series로 정리하고, 이후 stacked PR인 `#16`, `#17`이 이 규칙 위에서 stateful/composite 지표를 순차적으로 올립니다. - 범위는 foundation helper와 단일 시계열 기반 지표 중심이며, `plugin/polars`의 실제 nullable adapter 마이그레이션은 이번 스택에서 제외합니다. ## 배경 - 기존 `core` 함수들은 대부분 dense `&[f64]` 입력을 전제하고 있었습니다. - 하지만 실제 시계열 처리에서는 결측이 섞인 aligned series를 직접 받을 수 있어야 하고, 표준 라이브러리 성격의 API라면 missing을 `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>]` 입력 기준으로 전환합니다. - foundation helper를 사용하는 일부 downstream indicator의 public 시그니처도 같은 계약에 맞게 정렬합니다 (`macd`, `ppo`, `pvo`, `massi`, `sonar` 등). - 공개 API의 결측치 표현으로 `NaN`을 새로 도입하지 않도록 정리합니다. ## 핵심 규칙 - 출력 길이는 입력 정렬을 그대로 유지합니다. - rolling 계산은 필요한 lookback window 안에 gap이 있으면 해당 row를 `None`으로 반환합니다. - recursive smoothing은 gap row에서 output만 `None`이고 내부 상태는 유지합니다. - valid 값만 따로 압축해서 계산한 뒤 다시 원래 위치에 펴는 방식을 public contract로 사용하지 않습니다. ## 범위 외 - `plugin/polars` nullable adapter 마이그레이션은 후속 작업으로 미룹니다. - 다만 현재 CI에서 `Polars CI`가 core-only PR에도 실행되기 때문에, 이 PR에는 non-polars 변경에서 `validate`를 성공적인 no-op으로 처리하도록 하는 최소한의 workflow 조정이 함께 포함됩니다. - 이 workflow 변경은 core nullable 전환 자체의 기능 스코프가 아니라, deferred plugin migration이 현재 스택을 막지 않도록 하는 CI 보정입니다. ## 리뷰 가이드 - 먼저 `core/src/utils.rs`, `sma.rs`, `ema.rs`, `wma.rs`, `bband.rs`, `disparity.rs`를 봐주시면 됩니다. - 그 다음 downstream 시그니처 정렬과 `.github/workflows/polars-ci.yml` 변경을 확인하시면 충분합니다. - 이 PR만 `main` 기준으로 리뷰하면 되고, 상위 PR인 `#16`, `#17`은 각각 이 PR을 base로 봐주시면 됩니다. ## 테스트 계획 - [x] `cargo fmt --package techr-core --check` - [x] `cargo test -p techr-core` - [x] GitHub Actions `Polars CI`가 non-polars 변경에서 성공적으로 통과하는지 확인
1afb218 to
aa4fc2b
Compare
## 요약 - 이 PR은 `#16` 위에 쌓인 stacked PR입니다. 리뷰는 `core-nullable-stateful` 대비로 봐주시면 됩니다. - 복합/파생 indicator를 nullable contract에 맞추고, `techr-core` 전반의 public API 정합성을 마무리합니다. - 하위 helper/stateful layer에서 정의된 gap semantics가 상위 지표에서도 그대로 보존되도록 정리합니다. ## 변경 사항 - `macd`, `ppo`, `pvo`, `massi`, `sonar`, `stochf`, `stochs`, `stochrsi` 등 composite indicator를 nullable 입력 기준으로 전환합니다. - `aroon`, `aroonosc`, `cci`, `cv`, `ichimoku`, `pchan`, `willr`, `roc`, `mom`, `psl`, `erbull`, `erbear` 등 남아 있던 파생 indicator를 같은 계약으로 맞춥니다. - 내부 helper 사용 경로에서 dense-only 가정이나 `NaN` 기반 우회가 남아 있던 부분을 정리합니다. - 길이 불일치와 gap 전파가 섞인 경계 케이스를 보강합니다. ## 리뷰 포인트 - composite indicator가 하위 indicator의 gap semantics를 그대로 전파하는지 - rolling extrema 계열이 gap이 들어간 window를 valid 값으로 오판하지 않는지 - signal/oscillator 계열이 nullable intermediate를 압축하지 않고 aligned output을 유지하는지 - `stoch*`, `macd/ppo/pvo`, `ichimoku`, `aroon` 계열이 대표적인 확인 포인트입니다. ## 범위 외 - `plugin/polars` nullable adapter 마이그레이션은 후속 작업입니다. ## 테스트 계획 - [x] `cargo fmt --package techr-core --check` - [x] `cargo test -p techr-core`

요약
#15위에 쌓인 stacked PR입니다. 리뷰는core-nullable-foundations대비로 봐주시면 됩니다.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를 이어서 재개하도록 맞춥니다.core/src/utils.rs에 추가합니다.리뷰 포인트
None이어야 합니다.psar,dmi,atr,rsi가 대표적인 확인 포인트입니다.범위 외
plugin/polars는 여전히 이번 stack 범위 밖입니다.#17에서 다룹니다.테스트 계획
cargo fmt --package techr-core --checkcargo test -p techr-core