refactor(monorepo): reorganización lógica + renames + SDDs + split CHANGELOG
Reorganización física de crates/: - core/ (mezclaba 6 propósitos) se divide en protocol/, init/, runtime/, compat/ - shared/ (3 crates) se redistribuye en protocol/ e init/ - lapaloma (sub-módulo de ui_engine) se promueve a modules/pineal/ Renames de proyectos: - shipote → shuma (runtime de sandboxes) - nouser → akasha (explorador de Mónadas) - yahweh → nahual (motor GPUI, antes ui_engine/) - lapaloma → pineal (data-viz agnóstica) Fraccionamiento UI → core agnóstico: - vista-core (DeckState + snap, 175 LOC, 5 tests verdes) - barra-core (Task + render_html + sanitize, 90 LOC, 5 tests verdes) - vista-web y barra-web ahora son thin DOM bindings Documentación nueva: - 16 SDDs por subdirectorio (≤80 LOC c/u): protocol/init/runtime/compat + 10 módulos + apps/ - docs/STATUS.md con cifras reales por proyecto - docs/ROADMAP.md con plan a finalización (6 hitos, ~6-8 semanas) - CHANGELOG.md particionado en docs/changelog/<proyecto>.md (7 buckets) Automatización: - scripts/reorg.py — script idempotente que: git mv directorios, renombra package names, recomputa path = refs, reescribe imports rust, actualiza workspace Cargo.toml. Soporta --dry-run. - scripts/split-changelog.py — particiona CHANGELOG por componente. Validación: - cargo check --workspace pasa (124 crates + 2 nuevos cores). - 10 tests adicionales (5 en vista-core + 5 en barra-core) verdes. Co-Authored-By: Claude Opus 4.7 <noreply@anthropic.com>
This commit is contained in:
@@ -0,0 +1,231 @@
|
||||
//! `pineal-financial-demo` — chart OHLC con random walk.
|
||||
//!
|
||||
//! Genera 120 "días" de bars con un random walk determinístico
|
||||
//! (sin RNG runtime — derivado de un seed fijo + xorshift32 inline)
|
||||
//! y los pinta con `LapalomaCandlestickElement`. Pan + zoom igual
|
||||
//! al cartesian demo.
|
||||
|
||||
use gpui::{
|
||||
div, prelude::*, px, ClickEvent, Context, IntoElement, MouseButton, MouseDownEvent,
|
||||
MouseMoveEvent, MouseUpEvent, Point, Render, ScrollDelta, ScrollWheelEvent, Window,
|
||||
};
|
||||
|
||||
use pineal_cartesian::ChartViewport;
|
||||
use pineal_financial::{
|
||||
lapaloma_candlestick, Bar, CandlestickStyle, OhlcBuffer,
|
||||
};
|
||||
use pineal_render::Color;
|
||||
use nahual_launcher::launch_app;
|
||||
use nahual_theme::Theme;
|
||||
|
||||
const N_BARS: usize = 120;
|
||||
const WHEEL_SENSITIVITY: f64 = 0.0015;
|
||||
|
||||
fn main() {
|
||||
launch_app(
|
||||
"Lapaloma — candlesticks (drag = pan, wheel = zoom, dbl-click = reset)",
|
||||
(960., 560.),
|
||||
FinancialDemo::new,
|
||||
);
|
||||
}
|
||||
|
||||
struct FinancialDemo {
|
||||
data: OhlcBuffer,
|
||||
viewport: ChartViewport,
|
||||
initial_viewport: ChartViewport,
|
||||
drag: Option<DragAnchor>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy)]
|
||||
struct DragAnchor {
|
||||
start_position: Point<gpui::Pixels>,
|
||||
viewport_at_start: ChartViewport,
|
||||
}
|
||||
|
||||
impl FinancialDemo {
|
||||
fn new(_cx: &mut Context<Self>) -> Self {
|
||||
let data = synth_random_walk(N_BARS, 100.0, 0xc0ffee);
|
||||
let (lo, hi) = data.price_range().unwrap_or((0.0, 1.0));
|
||||
let pad = (hi - lo) * 0.08;
|
||||
let viewport = ChartViewport::new(
|
||||
-0.5,
|
||||
N_BARS as f64 - 0.5,
|
||||
(lo - pad) as f64,
|
||||
(hi + pad) as f64,
|
||||
);
|
||||
Self {
|
||||
data,
|
||||
viewport,
|
||||
initial_viewport: viewport,
|
||||
drag: None,
|
||||
}
|
||||
}
|
||||
|
||||
fn on_mouse_down(&mut self, e: &MouseDownEvent, _w: &mut Window, cx: &mut Context<Self>) {
|
||||
self.drag = Some(DragAnchor {
|
||||
start_position: e.position,
|
||||
viewport_at_start: self.viewport,
|
||||
});
|
||||
cx.notify();
|
||||
}
|
||||
fn on_mouse_move(&mut self, e: &MouseMoveEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let Some(anchor) = self.drag else { return };
|
||||
let win = window.viewport_size();
|
||||
let w: f32 = win.width.into();
|
||||
let h: f32 = win.height.into();
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let sx: f32 = e.position.x.into();
|
||||
let sy: f32 = e.position.y.into();
|
||||
let ax: f32 = anchor.start_position.x.into();
|
||||
let ay: f32 = anchor.start_position.y.into();
|
||||
let dfx = ((sx - ax) / w) as f64;
|
||||
let dfy = ((sy - ay) / h) as f64;
|
||||
let mut vp = anchor.viewport_at_start;
|
||||
vp.pan_fraction(dfx, dfy);
|
||||
self.viewport = vp;
|
||||
cx.notify();
|
||||
}
|
||||
fn on_mouse_up(&mut self, _e: &MouseUpEvent, _w: &mut Window, cx: &mut Context<Self>) {
|
||||
if self.drag.take().is_some() {
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
fn on_scroll(&mut self, e: &ScrollWheelEvent, window: &mut Window, cx: &mut Context<Self>) {
|
||||
let win = window.viewport_size();
|
||||
let w: f32 = win.width.into();
|
||||
let h: f32 = win.height.into();
|
||||
if w <= 0.0 || h <= 0.0 {
|
||||
return;
|
||||
}
|
||||
let dy_px: f32 = match e.delta {
|
||||
ScrollDelta::Pixels(p) => p.y.into(),
|
||||
ScrollDelta::Lines(p) => p.y * 16.0,
|
||||
};
|
||||
let factor = (-dy_px as f64 * WHEEL_SENSITIVITY).exp();
|
||||
let sx: f32 = e.position.x.into();
|
||||
let sy: f32 = e.position.y.into();
|
||||
let ax = (sx / w).clamp(0.0, 1.0) as f64;
|
||||
let ay = (1.0 - sy / h).clamp(0.0, 1.0) as f64;
|
||||
self.viewport.zoom_uniform(factor, (ax, ay));
|
||||
cx.notify();
|
||||
}
|
||||
fn on_click(&mut self, e: &ClickEvent, _w: &mut Window, cx: &mut Context<Self>) {
|
||||
if let ClickEvent::Mouse(m) = e {
|
||||
if m.up.click_count >= 2 {
|
||||
self.viewport = self.initial_viewport;
|
||||
cx.notify();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl Render for FinancialDemo {
|
||||
fn render(&mut self, _w: &mut Window, cx: &mut Context<Self>) -> impl IntoElement {
|
||||
let theme = Theme::global(cx).clone();
|
||||
let plot_bg = Color::rgba(0.06, 0.08, 0.10, 1.0);
|
||||
|
||||
let style = CandlestickStyle {
|
||||
bull_color: Color::from_hex(0xa3be8c),
|
||||
bear_color: Color::from_hex(0xbf616a),
|
||||
..CandlestickStyle::default()
|
||||
};
|
||||
|
||||
let chart = lapaloma_candlestick(self.data.clone(), self.viewport)
|
||||
.background(plot_bg)
|
||||
.style(style);
|
||||
|
||||
let (lo, hi) = self.data.price_range().unwrap_or((0.0, 0.0));
|
||||
let drag_active = self.drag.is_some();
|
||||
|
||||
div()
|
||||
.id("pineal-financial-root")
|
||||
.size_full()
|
||||
.bg(theme.bg_app.clone())
|
||||
.p(px(16.))
|
||||
.flex()
|
||||
.flex_col()
|
||||
.gap(px(10.))
|
||||
.child(
|
||||
div()
|
||||
.text_color(theme.fg_text)
|
||||
.text_size(px(18.))
|
||||
.child("Lapaloma — candlesticks"),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.flex()
|
||||
.gap(px(16.))
|
||||
.text_size(px(11.))
|
||||
.text_color(theme.fg_muted)
|
||||
.child(format!("{} bars (random walk)", N_BARS))
|
||||
.child(format!("price [{:.2}, {:.2}]", lo, hi))
|
||||
.child(if drag_active { "· dragging" } else { "" }),
|
||||
)
|
||||
.child(
|
||||
div()
|
||||
.id("pineal-financial-chart")
|
||||
.w_full()
|
||||
.flex_grow()
|
||||
.child(chart)
|
||||
.on_mouse_down(MouseButton::Left, cx.listener(Self::on_mouse_down))
|
||||
.on_mouse_move(cx.listener(Self::on_mouse_move))
|
||||
.on_mouse_up(MouseButton::Left, cx.listener(Self::on_mouse_up))
|
||||
.on_scroll_wheel(cx.listener(Self::on_scroll))
|
||||
.on_click(cx.listener(Self::on_click)),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
/// xorshift32 inline — RNG determinístico mínimo. No criptográfico,
|
||||
/// pero perfecto para series sintéticas reproducibles.
|
||||
fn xorshift32(state: &mut u32) -> u32 {
|
||||
let mut x = *state;
|
||||
x ^= x << 13;
|
||||
x ^= x >> 17;
|
||||
x ^= x << 5;
|
||||
*state = x;
|
||||
x
|
||||
}
|
||||
|
||||
fn rand_f32(state: &mut u32) -> f32 {
|
||||
xorshift32(state) as f32 / u32::MAX as f32
|
||||
}
|
||||
|
||||
fn synth_random_walk(n: usize, start_price: f32, seed: u32) -> OhlcBuffer {
|
||||
let mut rng = seed.max(1);
|
||||
let mut buf = OhlcBuffer::with_capacity(n);
|
||||
let mut close = start_price;
|
||||
let drift = 0.05; // tendencia mínima alcista
|
||||
let vol = 1.2;
|
||||
for i in 0..n {
|
||||
let r1 = rand_f32(&mut rng) - 0.5;
|
||||
let r2 = rand_f32(&mut rng) - 0.5;
|
||||
let r3 = rand_f32(&mut rng) - 0.5;
|
||||
let r4 = rand_f32(&mut rng) - 0.5;
|
||||
|
||||
let open = close;
|
||||
let move_close = drift + r1 * vol * 2.0;
|
||||
let new_close = (open + move_close).max(1.0);
|
||||
// Wicks: ruido por encima/debajo del rango open-close.
|
||||
let body_hi = open.max(new_close);
|
||||
let body_lo = open.min(new_close);
|
||||
let wick_up = (r2.abs() * vol * 1.2).max(0.05);
|
||||
let wick_dn = (r3.abs() * vol * 1.2).max(0.05);
|
||||
let high = body_hi + wick_up;
|
||||
let low = (body_lo - wick_dn).max(0.1);
|
||||
let volume = 1000.0 + r4.abs() * 8000.0;
|
||||
|
||||
buf.push_bar(Bar {
|
||||
t: i as f32,
|
||||
o: open,
|
||||
h: high,
|
||||
l: low,
|
||||
c: new_close,
|
||||
v: volume,
|
||||
});
|
||||
close = new_close;
|
||||
}
|
||||
buf
|
||||
}
|
||||
Reference in New Issue
Block a user