From 1c72362c6b0fd686ac436e3c2d732f148afbf4fb Mon Sep 17 00:00:00 2001 From: nym21 Date: Mon, 31 Mar 2025 10:02:59 +0200 Subject: [PATCH] kibo: part 4 --- Cargo.lock | 69 +++-- Cargo.toml | 6 +- .../brk_computer/src/storage/vecs/indexes.rs | 130 ++++++++- crates/brk_core/src/structs/timestamp.rs | 18 +- crates/brk_server/Cargo.toml | 2 +- crates/brk_server/src/api/query/dts.rs | 21 +- websites/kibo.money/index.html | 31 +-- .../packages/lightweight-charts/wrapper.js | 201 +++++++++++--- websites/kibo.money/scripts/chart.js | 147 +++++++--- websites/kibo.money/scripts/main.js | 177 +++--------- websites/kibo.money/scripts/options.js | 5 +- websites/kibo.money/scripts/types/self.d.ts | 32 +-- .../kibo.money/scripts/vecid-to-indexes.js | 262 +++++++++--------- 13 files changed, 664 insertions(+), 437 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 67bea0ff5..78392e721 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -168,9 +168,9 @@ checksum = "ace50bade8e6234aa140d9a2f552bbee1db4d353f69b8217bc503490fc1a9f26" [[package]] name = "axum" -version = "0.8.1" +version = "0.8.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6d6fd624c75e18b3b4c6b9caf42b1afe24437daaee904069137d8bab077be8b8" +checksum = "de45108900e1f9b9242f7f2e254aa3e2c029c921c258fe9e6b4217eeebd54288" dependencies = [ "axum-core", "bytes", @@ -202,12 +202,12 @@ dependencies = [ [[package]] name = "axum-core" -version = "0.5.0" +version = "0.5.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "df1362f362fd16024ae199c1970ce98f9661bf5ef94b9808fee734bc3698b733" +checksum = "68464cd0412f486726fb3373129ef5d2993f90c34bc2bc1c1e9943b2f4fc7ca6" dependencies = [ "bytes", - "futures-util", + "futures-core", "http", "http-body", "http-body-util", @@ -425,7 +425,7 @@ version = "0.0.10" dependencies = [ "bitcoin", "bitcoincore-rpc", - "byteview 0.6.0", + "byteview", "derive_deref", "jiff", "log", @@ -469,7 +469,7 @@ dependencies = [ "brk_logger", "brk_parser", "brk_vec", - "byteview 0.6.0", + "byteview", "color-eyre", "fjall", "log", @@ -605,15 +605,9 @@ checksum = "d71b6127be86fdcfddb610f7182ac57211d4b18a3e9c82eb2d17662f2227ad6a" [[package]] name = "byteview" -version = "0.5.4" +version = "0.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7a4516a8561bff0598c45512f90ee04ed62cee2cb36839e650a0a0704d5f741f" - -[[package]] -name = "byteview" -version = "0.6.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0c081862625f9a597e441ceb0ae11aeabfbe2c05ed4ec5091d328d69d0f6b0f3" +checksum = "6236364b88b9b6d0bc181ba374cf1ab55ba3ef97a1cb6f8cddad48a273767fb5" [[package]] name = "bzip2" @@ -691,9 +685,9 @@ dependencies = [ [[package]] name = "clap" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6088f3ae8c3608d19260cd7445411865a485688711b78b5be70d78cd96136f83" +checksum = "e958897981290da2a852763fe9cdb89cd36977a5d729023127095fa94d95e2ff" dependencies = [ "clap_builder", "clap_derive", @@ -701,9 +695,9 @@ dependencies = [ [[package]] name = "clap_builder" -version = "4.5.32" +version = "4.5.34" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22a7ef7f676155edfb82daa97f99441f3ebf4a58d5e32f295a56259f1b6facc8" +checksum = "83b0f35019843db2160b5bb19ae09b4e6411ac33fc6a712003c33e03090e2489" dependencies = [ "anstream", "anstyle", @@ -921,9 +915,9 @@ dependencies = [ [[package]] name = "darling" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6f63b86c8a8826a49b8c21f08a2d07338eec8d900540f8630dc76284be802989" +checksum = "fc7f46116c46ff9ab3eb1597a45688b6715c6e628b5c133e288e709a29bcb4ee" dependencies = [ "darling_core", "darling_macro", @@ -931,9 +925,9 @@ dependencies = [ [[package]] name = "darling_core" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "95133861a8032aaea082871032f5815eb9e98cef03fa916ab4500513994df9e5" +checksum = "0d00b9596d185e565c2207a0b01f8bd1a135483d02d9b7b0a54b11da8d53412e" dependencies = [ "fnv", "ident_case", @@ -945,9 +939,9 @@ dependencies = [ [[package]] name = "darling_macro" -version = "0.20.10" +version = "0.20.11" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d336a2a514f6ccccaa3e09b02d41d35330c07ddf03a62165fcec10bb561c7806" +checksum = "fc34b93ccb385b40dc71c6fceac4b2ad23662c7eeb248cf10d529b7e055b6ead" dependencies = [ "darling_core", "quote", @@ -1104,12 +1098,12 @@ checksum = "1d674e81391d1e1ab681a28d99df07927c6d4aa5b027d7da16ba32d1d21ecd99" [[package]] name = "fjall" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f6d38d014c22756201667b0b2f84ded6f54688d538879659a9c450b4fdfbab57" +checksum = "26b2ced3483989a62b3533c9f99054d73b527c6c0045cf22b00fe87956f1a46f" dependencies = [ "byteorder", - "byteview 0.5.4", + "byteview", "dashmap", "log", "lsm-tree", @@ -1579,9 +1573,9 @@ checksum = "13dc2df351e3202783a1fe0d44375f7295ffb4049267b0f3018346dc122a1d94" [[package]] name = "lsm-tree" -version = "2.7.0" +version = "2.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0327fc4b44c1212e87f44141029ab2d05a7e607b758a0f6cf661121f3101b737" +checksum = "d0a63a5e98a38b51765274137d8aedfbd848da5f4d016867e186b673fcc06a8c" dependencies = [ "byteorder", "crossbeam-skiplist", @@ -1761,9 +1755,9 @@ dependencies = [ [[package]] name = "once_cell" -version = "1.21.1" +version = "1.21.3" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d75b0bedcc4fe52caa0e03d9f1151a323e4aa5e2d78ba3580400cd3c9e2bc4bc" +checksum = "42f5e15c9953c5e4ccceeb2e7382a716482c34515315f7b03532b8b4e8393d2d" [[package]] name = "outref" @@ -2749,9 +2743,9 @@ checksum = "b7c388c1b5e93756d0c740965c41e8822f866621d41acbdf6336a6a168f8840c" [[package]] name = "socket2" -version = "0.5.8" +version = "0.5.9" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c970269d99b64e60ec3bd6ad27270092a5394c4e309314b18ae3fe575695fbe8" +checksum = "4f5fd57c80058a56cf5c777ab8a126398ece8e442983605d280a44ce79d0edef" dependencies = [ "libc", "windows-sys 0.52.0", @@ -3132,16 +3126,15 @@ checksum = "ba73ea9cf16a25df0c8caa16c51acb937d5712a8429db78a3ee29d5dcacd3a65" [[package]] name = "value-log" -version = "1.7.2" +version = "1.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d65573c63cf768179763226edb8d614d8b314130a3f50422d6d375d3947c529f" +checksum = "fd29b17c041f94e0885179637289815cd038f0c9fc19c4549d5a97017404fb7d" dependencies = [ "byteorder", - "byteview 0.5.4", + "byteview", "interval-heap", "log", "path-absolutize", - "quick_cache", "rustc-hash", "tempfile", "varint-rs", diff --git a/Cargo.toml b/Cargo.toml index 8999800ea..01925d0dc 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -26,11 +26,11 @@ brk_parser = { version = "0", path = "crates/brk_parser" } brk_query = { version = "0", path = "crates/brk_query" } brk_server = { version = "0", path = "crates/brk_server" } brk_vec = { version = "0", path = "crates/brk_vec" } -byteview = "0.6.0" -clap = { version = "4.5.32", features = ["derive", "string"] } +byteview = "0.6.1" +clap = { version = "4.5.34", features = ["derive", "string"] } color-eyre = "0.6.3" derive_deref = "1.1.1" -fjall = "2.7.0" +fjall = "2.8.0" jiff = "0.2.5" log = { version = "0.4.27" } minreq = { version = "2.13.3", features = ["https", "serde_json"] } diff --git a/crates/brk_computer/src/storage/vecs/indexes.rs b/crates/brk_computer/src/storage/vecs/indexes.rs index 82c99ce3c..9ab3d2a19 100644 --- a/crates/brk_computer/src/storage/vecs/indexes.rs +++ b/crates/brk_computer/src/storage/vecs/indexes.rs @@ -1,8 +1,8 @@ use std::{fs, ops::Deref, path::Path}; use brk_core::{ - Date, Dateindex, Decadeindex, Difficultyepoch, Halvingepoch, Height, Monthindex, Txindex, - Txinindex, Txoutindex, Weekindex, Yearindex, + Date, Dateindex, Decadeindex, Difficultyepoch, Halvingepoch, Height, Monthindex, Timestamp, + Txindex, Txinindex, Txoutindex, Weekindex, Yearindex, }; use brk_exit::Exit; use brk_indexer::Indexer; @@ -19,18 +19,23 @@ pub struct Vecs { pub dateindex_to_first_height: StorableVec, pub dateindex_to_last_height: StorableVec, pub dateindex_to_monthindex: StorableVec, + pub dateindex_to_timestamp: StorableVec, pub dateindex_to_weekindex: StorableVec, pub decadeindex_to_decadeindex: StorableVec, pub decadeindex_to_first_yearindex: StorableVec, pub decadeindex_to_last_yearindex: StorableVec, + pub decadeindex_to_timestamp: StorableVec, pub difficultyepoch_to_difficultyepoch: StorableVec, pub difficultyepoch_to_first_height: StorableVec, pub difficultyepoch_to_last_height: StorableVec, + pub difficultyepoch_to_timestamp: StorableVec, pub halvingepoch_to_first_height: StorableVec, pub halvingepoch_to_halvingepoch: StorableVec, pub halvingepoch_to_last_height: StorableVec, + pub halvingepoch_to_timestamp: StorableVec, pub height_to_dateindex: StorableVec, pub height_to_difficultyepoch: StorableVec, + pub height_to_fixed_timestamp: StorableVec, pub height_to_fixed_date: StorableVec, pub height_to_halvingepoch: StorableVec, pub height_to_height: StorableVec, @@ -39,15 +44,18 @@ pub struct Vecs { pub monthindex_to_first_dateindex: StorableVec, pub monthindex_to_last_dateindex: StorableVec, pub monthindex_to_monthindex: StorableVec, + pub monthindex_to_timestamp: StorableVec, pub monthindex_to_yearindex: StorableVec, pub txindex_to_last_txinindex: StorableVec, pub txindex_to_last_txoutindex: StorableVec, pub weekindex_to_first_dateindex: StorableVec, pub weekindex_to_last_dateindex: StorableVec, + pub weekindex_to_timestamp: StorableVec, pub weekindex_to_weekindex: StorableVec, pub yearindex_to_decadeindex: StorableVec, pub yearindex_to_first_monthindex: StorableVec, pub yearindex_to_last_monthindex: StorableVec, + pub yearindex_to_timestamp: StorableVec, pub yearindex_to_yearindex: StorableVec, } @@ -231,6 +239,46 @@ impl Vecs { Version::from(1), compressed, )?, + dateindex_to_timestamp: StorableVec::forced_import( + &path.join("dateindex_to_timestamp"), + Version::from(1), + compressed, + )?, + decadeindex_to_timestamp: StorableVec::forced_import( + &path.join("decadeindex_to_timestamp"), + Version::from(1), + compressed, + )?, + difficultyepoch_to_timestamp: StorableVec::forced_import( + &path.join("difficultyepoch_to_timestamp"), + Version::from(1), + compressed, + )?, + halvingepoch_to_timestamp: StorableVec::forced_import( + &path.join("halvingepoch_to_timestamp"), + Version::from(1), + compressed, + )?, + monthindex_to_timestamp: StorableVec::forced_import( + &path.join("monthindex_to_timestamp"), + Version::from(1), + compressed, + )?, + weekindex_to_timestamp: StorableVec::forced_import( + &path.join("weekindex_to_timestamp"), + Version::from(1), + compressed, + )?, + yearindex_to_timestamp: StorableVec::forced_import( + &path.join("yearindex_to_timestamp"), + Version::from(1), + compressed, + )?, + height_to_fixed_timestamp: StorableVec::forced_import( + &path.join("height_to_fixed_timestamp"), + Version::from(1), + compressed, + )?, }) } @@ -261,9 +309,9 @@ impl Vecs { exit, )?; - self.height_to_fixed_date.compute_transform( + self.height_to_fixed_timestamp.compute_transform( starting_indexes.height, - self.height_to_real_date.mut_vec(), + indexer_vecs.height_to_timestamp.mut_vec(), |(h, d, s, ..)| { let d = h .decremented() @@ -278,6 +326,13 @@ impl Vecs { exit, )?; + self.height_to_fixed_date.compute_transform( + starting_indexes.height, + self.height_to_fixed_timestamp.mut_vec(), + |(h, t, ..)| (h, Date::from(t)), + exit, + )?; + let starting_dateindex = self .height_to_dateindex .get(starting_indexes.height.decremented().unwrap_or_default())? @@ -332,6 +387,13 @@ impl Vecs { exit, )?; + self.dateindex_to_timestamp.compute_transform( + starting_dateindex, + self.dateindex_to_date.mut_vec(), + |(di, d, ..)| (di, Timestamp::from(d)), + exit, + )?; + self.txindex_to_last_txinindex .compute_last_index_from_first( starting_indexes.txindex, @@ -392,6 +454,13 @@ impl Vecs { exit, )?; + self.weekindex_to_timestamp.compute_transform( + starting_weekindex, + self.weekindex_to_first_dateindex.mut_vec(), + |(i, d, ..)| (i, *self.dateindex_to_timestamp.get(d).unwrap().unwrap()), + exit, + )?; + // --- let starting_monthindex = self @@ -431,6 +500,13 @@ impl Vecs { exit, )?; + self.monthindex_to_timestamp.compute_transform( + starting_monthindex, + self.monthindex_to_first_dateindex.mut_vec(), + |(i, d, ..)| (i, *self.dateindex_to_timestamp.get(d).unwrap().unwrap()), + exit, + )?; + // --- let starting_yearindex = self @@ -470,6 +546,13 @@ impl Vecs { exit, )?; + self.yearindex_to_timestamp.compute_transform( + starting_yearindex, + self.yearindex_to_first_monthindex.mut_vec(), + |(i, m, ..)| (i, *self.monthindex_to_timestamp.get(m).unwrap().unwrap()), + exit, + )?; + // --- let starting_decadeindex = self @@ -507,6 +590,13 @@ impl Vecs { exit, )?; + self.decadeindex_to_timestamp.compute_transform( + starting_decadeindex, + self.decadeindex_to_first_yearindex.mut_vec(), + |(i, y, ..)| (i, *self.yearindex_to_timestamp.get(y).unwrap().unwrap()), + exit, + )?; + // --- let starting_difficultyepoch = self @@ -544,6 +634,18 @@ impl Vecs { exit, )?; + self.difficultyepoch_to_timestamp.compute_transform( + starting_difficultyepoch, + self.difficultyepoch_to_first_height.mut_vec(), + |(i, h, ..)| { + ( + i, + *indexer_vecs.height_to_timestamp.get(h).unwrap().unwrap(), + ) + }, + exit, + )?; + // --- let starting_halvingepoch = self @@ -581,6 +683,18 @@ impl Vecs { exit, )?; + // self.difficultyepoch_to_timestamp.compute_transform( + // starting_difficultyepoch, + // self.difficultyepoch_to_first_height.mut_vec(), + // |(i, h, ..)| { + // ( + // i, + // *indexer_vecs.height_to_timestamp.get(h).unwrap().unwrap(), + // ) + // }, + // exit, + // )?; + Ok(Indexes { indexes: starting_indexes, dateindex: starting_dateindex, @@ -630,6 +744,14 @@ impl Vecs { self.decadeindex_to_decadeindex.any_vec(), self.difficultyepoch_to_difficultyepoch.any_vec(), self.halvingepoch_to_halvingepoch.any_vec(), + self.dateindex_to_timestamp.any_vec(), + self.decadeindex_to_timestamp.any_vec(), + self.difficultyepoch_to_timestamp.any_vec(), + self.halvingepoch_to_timestamp.any_vec(), + self.monthindex_to_timestamp.any_vec(), + self.weekindex_to_timestamp.any_vec(), + self.yearindex_to_timestamp.any_vec(), + self.height_to_fixed_timestamp.any_vec(), ] } } diff --git a/crates/brk_core/src/structs/timestamp.rs b/crates/brk_core/src/structs/timestamp.rs index 26e82ec37..1d9adee47 100644 --- a/crates/brk_core/src/structs/timestamp.rs +++ b/crates/brk_core/src/structs/timestamp.rs @@ -1,12 +1,17 @@ use std::ops::{Add, Div}; use derive_deref::Deref; -use jiff::{civil::date, tz::TimeZone}; +use jiff::{ + civil::{Time, date}, + tz::TimeZone, +}; use serde::Serialize; use zerocopy::{FromBytes, Immutable, IntoBytes, KnownLayout}; use crate::CheckedSub; +use super::Date; + #[derive( Debug, Deref, @@ -75,6 +80,17 @@ impl From for Timestamp { } } +impl From for Timestamp { + fn from(value: Date) -> Self { + Self::from( + jiff::civil::Date::from(value) + .to_zoned(TimeZone::UTC) + .unwrap() + .timestamp(), + ) + } +} + impl CheckedSub for Timestamp { fn checked_sub(self, rhs: Self) -> Option { self.0.checked_sub(rhs.0).map(Self) diff --git a/crates/brk_server/Cargo.toml b/crates/brk_server/Cargo.toml index 359ac9cfa..112bc3d29 100644 --- a/crates/brk_server/Cargo.toml +++ b/crates/brk_server/Cargo.toml @@ -7,7 +7,7 @@ license.workspace = true repository.workspace = true [dependencies] -axum = "0.8.1" +axum = "0.8.3" brk_computer = { workspace = true } brk_exit = { workspace = true } brk_fetcher = { workspace = true } diff --git a/crates/brk_server/src/api/query/dts.rs b/crates/brk_server/src/api/query/dts.rs index 868ecb389..3163543f1 100644 --- a/crates/brk_server/src/api/query/dts.rs +++ b/crates/brk_server/src/api/query/dts.rs @@ -36,7 +36,7 @@ impl DTS for Query<'static> { .enumerate() .map(|(i_of_i, i)| { // let lowered = i.to_string().to_lowercase(); - format!("const {i} = {i_of_i};\n/** @typedef {{typeof {i}}} {i} */",) + format!("/** @typedef {{{i_of_i}}} {i} */",) }) .collect::>() .join("\n"); @@ -50,7 +50,19 @@ impl DTS for Query<'static> { .join(" | ") ); - contents += "\n\nexport const VecIdToIndexes = {\n"; + contents += "\n\nexport function createVecIdToIndexes() {\n"; + + contents += &indexes + .iter() + .enumerate() + .map(|(i_of_i, i)| { + // let lowered = i.to_string().to_lowercase(); + format!(" const {i} = /** @satisfies {{{i}}} */ ({i_of_i});",) + }) + .collect::>() + .join("\n"); + + contents += "\n\n return {\n"; self.vecid_to_index_to_vec .iter() @@ -62,7 +74,7 @@ impl DTS for Query<'static> { .join(", "); contents += &format!( - " {}: [{indexes}],\n", + " {}: [{indexes}],\n", if id.contains("-") { format!("\"{id}\"") } else { @@ -71,9 +83,10 @@ impl DTS for Query<'static> { ); }); + contents += " }\n"; contents.push('}'); - contents += "\n/** @typedef {typeof VecIdToIndexes} VecIdToIndexes */"; + contents += "\n/** @typedef {ReturnType} VecIdToIndexes */"; contents += "\n/** @typedef {keyof VecIdToIndexes} VecId */\n"; fs::write(path, contents) diff --git a/websites/kibo.money/index.html b/websites/kibo.money/index.html index 9fb12d09d..32814b3cc 100644 --- a/websites/kibo.money/index.html +++ b/websites/kibo.money/index.html @@ -217,7 +217,7 @@ --white: oklch(99% 0.01 44); --light-gray: oklch(90% 0.01 44); --gray: oklch(60% 0.01 44); - --dark-gray: oklch(25% 0.01 44); + --dark-gray: oklch(30% 0.01 44); --black: oklch(17.5% 0.005 44); --red: oklch(0.607 0.241 26.328); --orange: oklch(67.64% 0.191 44.41); @@ -236,8 +236,8 @@ --purple: oklch(0.5925 0.2765 303.1105); --fuchsia: oklch(0.629 0.294 322.523); --pink: oklch(0.624 0.245 357.444); - --rose: oklch(0.6155 0.2495 17.012) - --background-color: light-dark(var(--white), var(--black)); + --rose: oklch(0.6155 0.2495 17.012); + --background-color: light-dark(var(--white), var(--black)); --color: light-dark(var(--black), var(--white)); --off-color: var(--gray); --border-color: light-dark(var(--light-gray), var(--dark-gray)); @@ -256,6 +256,8 @@ --line-height-xl: calc(1.75 / 1.25); --font-size-2xl: 1.5rem; --line-height-2xl: calc(2 / 1.5); + --font-size-3xl: 1.75rem; + --line-height-3xl: calc(2.25 / 1.75); --main-padding: 2rem; @@ -265,7 +267,7 @@ --negative-main-padding: calc(-1 * var(--main-padding)); - --font-weight-base: 450; + --font-weight-base: 400; --font-weight-bold: 700; --transform-scale-active: scaleY(0.9); @@ -446,16 +448,11 @@ } } - field, h1 { text-transform: capitalize; - } - - h1, - h2 { - font-size: var(--font-size-2xl); - line-height: var(--line-height-2xl); - font-weight: 400; + font-size: var(--font-size-3xl); + line-height: var(--line-height-3xl); + font-weight: 300; } h3 { @@ -889,7 +886,7 @@ transparent, var(--background-color) ); - height: var(--main-padding); + height: calc(var(--main-padding) * 10); bottom: 0; left: 0; right: 0; @@ -936,6 +933,7 @@ } > div.field { + text-transform: lowercase; display: flex; align-items: center; /* font-size: var(--font-size-xs); @@ -948,17 +946,17 @@ } > hr { - min-width: 1rem; + min-width: 2rem; } label { padding: 0.5rem; - /* margin: -0.5rem; */ + margin: -0.5rem; } > div { display: flex; - gap: 0.5rem; + gap: 1.5rem; } } } @@ -968,6 +966,7 @@ flex-direction: column; min-height: 0; z-index: 20; + margin-bottom: 2rem; > legend { display: flex; diff --git a/websites/kibo.money/packages/lightweight-charts/wrapper.js b/websites/kibo.money/packages/lightweight-charts/wrapper.js index ba80a0a46..93f3f9e50 100644 --- a/websites/kibo.money/packages/lightweight-charts/wrapper.js +++ b/websites/kibo.money/packages/lightweight-charts/wrapper.js @@ -1,5 +1,7 @@ // @ts-check +/** @import {SeriesDefinition} from './v5.0.5/types' */ + export default import("./v5.0.5/script.js").then((lc) => { /** * @param {Object} args @@ -23,26 +25,18 @@ export default import("./v5.0.5/script.js").then((lc) => { autoSize: true, layout: { fontFamily: "Satoshi", - fontSize: 13.5, + fontSize: 14, background: { color: "transparent" }, attributionLogo: false, colorSpace: "display-p3", colorParsers: [oklchToRGBA], - panes: {}, }, grid: { vertLines: { visible: false }, horzLines: { visible: false }, }, timeScale: { - minBarSpacing: 0.1, - shiftVisibleRangeOnNewBar: false, - allowShiftVisibleRangeOnWhitespaceReplacement: false, - }, - handleScale: { - axisDoubleClickReset: { - time: false, - }, + minBarSpacing: 2.1, }, localization: { priceFormatter: utils.locale.numberToShortUSFormat, @@ -51,6 +45,7 @@ export default import("./v5.0.5/script.js").then((lc) => { ..._options, }; + /** @type {IChartApi} */ const chart = lc.createChart(element, options); chart.priceScale("right").applyOptions({ @@ -65,13 +60,22 @@ export default import("./v5.0.5/script.js").then((lc) => { () => ({ defaultColor: colors.default(), offColor: colors.off(), + borderColor: colors.border(), }), - ({ defaultColor, offColor }) => { - console.log(defaultColor, offColor); + ({ defaultColor, offColor, borderColor }) => { + console.log( + defaultColor, + offColor, + borderColor, + `rgba(${oklchToRGBA(borderColor).join(", ")})`, + ); chart.applyOptions({ layout: { textColor: offColor, + panes: { + separatorColor: borderColor, + }, }, rightPriceScale: { borderVisible: false, @@ -82,13 +86,14 @@ export default import("./v5.0.5/script.js").then((lc) => { }, crosshair: { horzLine: { - color: defaultColor, + color: offColor, labelBackgroundColor: defaultColor, }, vertLine: { - color: defaultColor, + color: offColor, labelBackgroundColor: defaultColor, }, + mode: 3, }, }); }, @@ -103,9 +108,9 @@ export default import("./v5.0.5/script.js").then((lc) => { const defaultSeriesOptions = { // @ts-ignore lineWidth: 1.5, - priceLineVisible: false, - baseLineVisible: false, - baseLineColor: "", + // priceLineVisible: false, + // baseLineVisible: false, + // baseLineColor: "", }; /** @@ -116,15 +121,16 @@ export default import("./v5.0.5/script.js").then((lc) => { * @param {Colors} args.colors * @param {"static" | "scrollable"} args.kind * @param {Utilities} args.utils + * @param {VecsResources} args.vecsResources * @param {Owner | null} [args.owner] */ function createChartElement({ parent, signals, colors, - id: chartId, kind, utils, + vecsResources, owner: _owner, }) { let owner = _owner || signals.getOwner(); @@ -133,14 +139,16 @@ export default import("./v5.0.5/script.js").then((lc) => { div.classList.add("chart"); parent.append(div); - const legendElement = window.document.createElement("legend"); - div.append(legendElement); + const legend = createLegend({ + parent: div, + }); const chartDiv = window.document.createElement("div"); chartDiv.classList.add("lightweight-chart"); div.append(chartDiv); let ichart = /** @type {IChartApi | null} */ (null); + let timeScaleSet = false; if (kind === "static") { new ResizeObserver(() => ichart?.timeScale().fitContent()).observe( @@ -148,17 +156,82 @@ export default import("./v5.0.5/script.js").then((lc) => { ); } + /** @type {Index} */ + let index = 0; + + let timeResource = /** @type {VecResource| null} */ (null); + + /** + * @param {ISeriesApi} series + * @param {VecResource} valuesResource + */ + function createSetDataEffect(series, valuesResource) { + signals.createEffect( + () => [timeResource?.fetched(), valuesResource.fetched()], + ([indexes, _ohlcs]) => { + if (!ichart) throw Error("IChart should be initialized"); + + if (!indexes || !_ohlcs) return; + const ohlcs = /** @type {OHLCTuple[]} */ (_ohlcs); + let length = Math.min(indexes.length, ohlcs.length); + const data = new Array(length); + let prevTime = null; + let offset = 0; + for (let i = 0; i < length; i++) { + const time = indexes[i]; + if (prevTime && prevTime === time) { + offset += 1; + } + const v = ohlcs[i]; + if (typeof v === "number") { + data[i - offset] = { + time, + value: v, + }; + } else { + data[i - offset] = { + time, + open: v[0], + high: v[1], + low: v[2], + close: v[3], + }; + } + prevTime = time; + } + data.length -= offset; + series.setData(data); + if ( + !timeScaleSet && + (index === /** @satisfies {Yearindex} */ (15) || + index === /** @satisfies {Decadeindex} */ (16)) + ) { + ichart + .timeScale() + .setVisibleLogicalRange({ from: -1, to: data.length }); + } + timeScaleSet = true; + }, + ); + } + return { - chart() { - return ichart; - }, /** - * @param {Index} index + * @param {Index} _index */ - createChart(index) { + create(_index) { + index = _index; if (ichart) throw Error("IChart shouldn't be initialized"); - createLightweightChart({ + timeResource = vecsResources.getOrCreate( + index, + index === /** @satisfies {Height} */ (2) + ? "fixed-timestamp" + : "timestamp", + ); + timeResource.fetch(); + + ichart = createLightweightChart({ index, element: chartDiv, signals, @@ -166,6 +239,56 @@ export default import("./v5.0.5/script.js").then((lc) => { utils, }); }, + /** + * @param {VecId} id + * @param {number} [paneNumber] + */ + addCandlestickSeries(id, paneNumber) { + if (!ichart || !timeResource) throw Error("Chart not fully set"); + + const valuesResource = vecsResources.getOrCreate(index, id); + valuesResource.fetch(); + + const green = colors.green(); + const red = colors.red(); + const series = ichart.addSeries( + /** @type {SeriesDefinition<'Candlestick'>} */ (lc.CandlestickSeries), + { + upColor: green, + downColor: red, + wickUpColor: green, + wickDownColor: red, + borderVisible: false, + }, + paneNumber, + ); + + createSetDataEffect(series, valuesResource); + + return series; + }, + /** + * @param {VecId} id + * @param {number} [paneNumber] + */ + addLineSeries(id, paneNumber) { + if (!ichart || !timeResource) throw Error("Chart not fully set"); + + const valuesResource = vecsResources.getOrCreate(index, id); + valuesResource.fetch(); + + const series = ichart.addSeries( + /** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries), + { + lineWidth: /** @type {any} */ (1.5), + }, + paneNumber, + ); + + createSetDataEffect(series, valuesResource); + + return series; + }, /** * * @param {Object} args @@ -173,21 +296,35 @@ export default import("./v5.0.5/script.js").then((lc) => { */ reset({ owner: _owner }) { owner = _owner; - if (ichart !== null) { - ichart.remove(); - } else { - throw Error("IChart should be initialized"); - } - legendElement.innerHTML = ""; + ichart?.remove(); + ichart = null; + timeScaleSet = false; + legend.reset(); }, }; } return { + inner: lc, createChartElement, }; }); +/** + * @param {Object} args + * @param {Element} args.parent + */ +function createLegend({ parent }) { + const legendElement = window.document.createElement("legend"); + parent.append(legendElement); + + return { + reset() { + legendElement.innerHTML = ""; + }, + }; +} + const oklchToRGBA = (() => { { /** diff --git a/websites/kibo.money/scripts/chart.js b/websites/kibo.money/scripts/chart.js index c2cff95f5..94e83182d 100644 --- a/websites/kibo.money/scripts/chart.js +++ b/websites/kibo.money/scripts/chart.js @@ -10,6 +10,7 @@ * @param {WebSockets} args.webSockets * @param {Elements} args.elements * @param {VecsResources} args.vecsResources + * @param {VecIdToIndexes} args.vecIdToIndexes */ export function init({ colors, @@ -20,6 +21,7 @@ export function init({ utils, webSockets, vecsResources, + vecIdToIndexes, }) { console.log("init chart state"); @@ -28,56 +30,61 @@ export function init({ const { headerElement, titleElement } = utils.dom.createHeader({}); elements.charts.append(headerElement); - signals.createEffect(selected, (option) => { - titleElement.innerHTML = option.title; - }); - const chartElement = lightweightCharts.createChartElement({ + const chart = lightweightCharts.createChartElement({ parent: elements.charts, signals, colors, id: "chart", kind: "scrollable", utils, + vecsResources, }); - const indexes = utils.dom.createHorizontalChoiceField({ - title: "Index", - selected: "date", - choices: [ - "Timestamp", - "Date", - "Week", - "Difficulty Epoch", - "Month", - "Year", - "Halving Epoch", - "Decade", - ], - id: "index", - signals, + const index = createIndexSelector({ elements, signals, utils }); + + // const vecs = signals.createSignal( + // /** @type {Set} */ (new Set()), + // { + // equals: false, + // }, + // ); + + signals.createEffect(selected, (option) => { + titleElement.innerHTML = option.title; + signals.createEffect(index, (index) => { + chart.reset({ owner: signals.getOwner() }); + + chart.create(index); + + const candles = chart.addCandlestickSeries("ohlc"); + + signals.createEffect(webSockets.kraken1dCandle.latest, (latest) => { + if (!latest) return; + const last = /** @type { CandlestickData | undefined} */ ( + candles.data().at(-1) + ); + if (!last) return; + candles?.update({ ...last, close: latest.close }); + }); + + [ + { blueprints: option.top, paneIndex: 0 }, + { blueprints: option.bottom, paneIndex: 1 }, + ].forEach(({ blueprints, paneIndex }) => { + blueprints?.forEach((blueprint) => { + if (vecIdToIndexes[blueprint.key].includes(index)) { + const series = chart.addLineSeries(blueprint.key, paneIndex); + series.applyOptions({ + visible: blueprint.defaultActive !== false, + color: blueprint.color?.(), + }); + } + }); + }); + }); }); - const fieldset = window.document.createElement("fieldset"); - fieldset.append(indexes); - - elements.charts.append(fieldset); - - const vecs = signals.createSignal( - /** @type {Set>} */ (new Set()), - { - equals: false, - }, - ); - - const index = /** @satisfies {Dateindex} */ (1); - - chartElement.createChart(index); - - const ohlc = vecsResources.getOrCreate(index, "ohlc"); - const date = vecsResources.getOrCreate(index, "date"); - date.fetch(-10_000); - // function createFetchChunksOfVisibleDatasetsEffect() { // signals.createEffect( // () => ({ @@ -185,3 +192,65 @@ export function init({ // } // createApplyChartOptionEffect(); } + +/** + * @param {Object} args + * @param {Elements} args.elements + * @param {Signals} args.signals + * @param {Utilities} args.utils + */ +function createIndexSelector({ elements, signals, utils }) { + const indexLSKey = "chart-index"; + const indexChoices = /**@type {const} */ ([ + "timestamp", + "date", + "week", + // "difficulty epoch", + "month", + "year", + // "halving epoch", + "decade", + ]); + /** @typedef {(typeof indexChoices)[number]} SerializedIndex */ + const serializedIndex = signals.createSignal( + /** @type {SerializedIndex} */ (localStorage.getItem(indexLSKey) || "date"), + ); + const indexesField = utils.dom.createHorizontalChoiceField({ + title: "Index", + selected: serializedIndex(), + choices: indexChoices, + id: "index", + signals, + }); + indexesField.addEventListener("change", (event) => { + // @ts-ignore + const value = event.target.value; + localStorage.setItem(indexLSKey, value); + serializedIndex.set(value); + }); + + const fieldset = window.document.createElement("fieldset"); + fieldset.append(indexesField); + elements.charts.append(fieldset); + + const index = signals.createMemo( + /** @returns {Index} */ () => { + switch (serializedIndex()) { + case "timestamp": + return /** @satisfies {Height} */ (2); + case "date": + return /** @satisfies {Dateindex} */ (1); + case "week": + return /** @satisfies {Weekindex} */ (13); + case "month": + return /** @satisfies {Monthindex} */ (14); + case "year": + return /** @satisfies {Yearindex} */ (15); + case "decade": + return /** @satisfies {Decadeindex} */ (16); + } + }, + ); + + return index; +} diff --git a/websites/kibo.money/scripts/main.js b/websites/kibo.money/scripts/main.js index 95b1b4258..25c89b34a 100644 --- a/websites/kibo.money/scripts/main.js +++ b/websites/kibo.money/scripts/main.js @@ -1,7 +1,7 @@ // @ts-check /** - * @import { Option, TimeRange, Weighted, OHLC, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, VecResource, Valued, FetchedVecRange, SingleValueData, CandlestickData, ChartData } from "./types/self" + * @import { Option, Weighted, Color, DatasetCandlestickData, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple } from "./types/self" * @import { Marker, CreatePaneParameters, HoveredLegend, ChartPane, SplitSeries, SingleSeries, CreateSplitSeriesParameters, LineSeriesBlueprint, CandlestickSeriesBlueprint, BaselineSeriesBlueprint, CreateBaseSeriesParameters, BaseSeries, RemoveSeriesBlueprintFluff, SplitSeriesBlueprint, AnySeries, PriceSeriesType } from "../packages/lightweight-charts/types"; * @import * as _ from "../packages/ufuzzy/v1.0.14/types" * @import { createChart as CreateClassicChart, createChartEx as CreateCustomChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, SeriesMarker, SeriesType, BaselineStyleOptions, SeriesOptionsCommon } from "../packages/lightweight-charts/v5.0.5/types" @@ -9,7 +9,7 @@ * @import { getOwner as GetOwner, onCleanup as OnCleanup, Owner } from "../packages/solid-signals/2024-11-02/types/core/owner" * @import { createSignal as CreateSignal, createEffect as CreateEffect, Accessor, Setter, createMemo as CreateMemo, createRoot as CreateRoot, runWithOwner as RunWithOwner } from "../packages/solid-signals/2024-11-02/types/signals"; * @import {Signal, Signals} from "../packages/solid-signals/types"; - * @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex} from "./vecid-to-indexes" + * @import {Addressindex, Dateindex, Decadeindex, Difficultyepoch, Index, Halvingepoch, Height, Monthindex, P2PK33index, P2PK65index, P2PKHindex, P2SHindex, P2TRindex, P2WPKHindex, P2WSHindex, Txindex, Txinindex, Txoutindex, VecId, Weekindex, Yearindex, VecIdToIndexes} from "./vecid-to-indexes" */ function initPackages() { @@ -301,14 +301,6 @@ function createUtils() { a.click(); a.remove(); }, - /** - * @param {string} text - */ - createItalic(text) { - const italic = window.document.createElement("i"); - italic.innerHTML = text; - return italic; - }, /** * @param {string} href */ @@ -375,12 +367,6 @@ function createUtils() { return field; }, - createUlElement() { - return window.document.createElement("ul"); - }, - createLiElement() { - return window.document.createElement("li"); - }, /** * @param {Object} args * @param {string} args.id @@ -1204,7 +1190,7 @@ function createUtils() { return `/api${genPath(index, vecId)}`; }, /** - * @template {number | OHLC} [T=number] + * @template {number | OHLCTuple} [T=number] * @param {(v: T[]) => void} callback * @param {Index} index * @param {VecId} vecId @@ -1215,7 +1201,7 @@ function createUtils() { return fetchApi(callback, genPath(index, vecId, from, to)); }, /** - * @template {number | OHLC} [T=number] + * @template {number | OHLCTuple} [T=number] * @param {(v: T) => void} callback * @param {Index} index * @param {VecId} vecId @@ -1252,14 +1238,10 @@ function createUtils() { * @param {Utilities} utils */ function createVecsResources(signals, utils) { - /** @type {Map} */ - const map = new Map(); const owner = signals.getOwner(); - const STEP = 1000; - /** - * @template {number | OHLC} [T=number] + * @template {number | OHLCTuple} [T=number] * @param {Index} index * @param {VecId} id */ @@ -1267,134 +1249,45 @@ function createVecsResources(signals, utils) { return signals.runWithOwner(owner, () => { /** @typedef {T extends number ? SingleValueData : CandlestickData} Value */ - // interface VecResource { - // url: string; - // fetch: (from: number, to: number) => Promise; - // ranges: Map>; - // } - const vec = { + const fetched = signals.createSignal(/** @type {T[] | null} */ (null)); + let loading = false; + let at = /** @type {Date | null} */ (null); + + return { url: utils.api.genUrl(index, id), - /** - * - * @param {number} [from] - * @param {number} [to] - * @returns - */ - async fetch(from, to) { - const range = getOrCreate(from); - if (range.loading) return range.fetched(); - if (range.at) { + fetched, + async fetch() { + if (loading) return fetched(); + if (at) { + const diff = new Date().getTime() - at.getTime(); const ONE_MINUTE_IN_MS = 60_000; - const ONE_HOUR_IN_MS = 3_600_000; - const diff = new Date().getTime() - range.at.getTime(); - if (diff < ONE_MINUTE_IN_MS) return range.fetched(); - /** @type {number | null} */ - let lastFrom = null; - for (const key of vec.ranges.keys()) { - lastFrom = key; - } - if (lastFrom && from < lastFrom && diff < ONE_HOUR_IN_MS) { - return range.fetched(); - } + if (diff < ONE_MINUTE_IN_MS) return fetched(); } - range.loading = true; + loading = true; const res = /** @type {T[] | null} */ ( await utils.api.fetchVec( (values) => { - range.fetched.set(/** @type {T[]} */ (values)); + fetched.set(/** @type {T[]} */ (values)); }, index, id, - from, - to, + -10_000, ) ); - range.at = new Date(); - range.loading = false; + at = new Date(); + loading = false; return res; }, - ranges: new Map(), }; - - /** - * @param {number} from - */ - function getOrCreate(from) { - const found = vec.ranges.get(from); - if (found) return found; - - const fetched = signals.createSignal(/** @type {T[] | null} */ (null)); - - /** - * @param {number} i - */ - function computeTime(i) { - switch (index) { - case /** @satisfies {Dateindex} */ (1): { - const index = from + i; - if (index === 0) { - return new Date(Date.UTC(2009, 1, 3)); - } else { - let d = new Date(Date.UTC(2009, 1, 9)); - d.setUTCDate(d.getUTCDate() + index - 1); - return d; - } - } - case /** @satisfies {Height} */ (2): { - // vecs.getOrCreate(/** @satisfies {Height} */ (2), "timestamp"); - } - default: { - throw Error("todo!"); - } - } - } - - /** @type {FetchedVecRange} */ - const range = { - at: null, - fetched, - transformed: signals.createMemo(() => { - const vec = fetched(); - if (!vec) return null; - const values = /** @type {Value[]} */ (new Array(vec.length)); - for (let i = 0; i < vec.length; i++) { - const v = vec[i]; - const time = /** @type {Time} */ (computeTime(i).valueOf()); - /** @satisfies {SingleValueData} */ - const value = { - time, - index: from + i, - ...(Array.isArray(v) && v !== null - ? { - open: v[0], - high: v[1], - low: v[2], - close: v[3], - value: v[3], - } - : { - value: v === null ? NaN : /** @type {number} */ (v), - }), - }; - values[i] = /** @type {Value} */ (value); - } - return values; - }), - loading: false, - }; - - vec.ranges.set(from, range); - - return range; - } - - return vec; }); } + /** @type {Map>>} */ + const map = new Map(); + const vecs = { - STEP, /** + * @template {number | OHLCTuple} [T=number] * @param {Index} index * @param {VecId} id */ @@ -1406,9 +1299,10 @@ function createVecsResources(signals, utils) { return found; } console.log("not found"); + const vec = createVecResource(index, id); if (!vec) throw Error("vec is undefined"); - map.set(key, vec); + map.set(key, /** @type {any} */ (vec)); return vec; }, }; @@ -1416,6 +1310,7 @@ function createVecsResources(signals, utils) { return vecs; } /** @typedef {ReturnType} VecsResources */ +/** @typedef {ReturnType} VecResource */ function initEnv() { const standalone = @@ -1487,7 +1382,6 @@ function createColors(dark, elements) { */ function getColor(color) { return elements.style.getPropertyValue(`--${color}`); - // return utils.color.oklch2hex(elements.style.getPropertyValue(`--${color}`)); } function red() { return getColor("red"); @@ -1559,10 +1453,14 @@ function createColors(dark, elements) { function textColor() { return getLightDarkValue("--color"); } + function borderColor() { + return getLightDarkValue("--border-color"); + } return { default: textColor, off, + border: borderColor, lightBitcoin: yellow, bitcoin: orange, offBitcoin: red, @@ -1834,8 +1732,8 @@ function initWebSockets(signals, utils) { /** @typedef {ReturnType} WebSockets */ function main() { - const options = import("./options.js"); - const vecidToIndexes = import("./vecid-to-indexes.js"); + const optionsPromise = import("./options.js"); + const vecidToIndexesPromise = import("./vecid-to-indexes.js"); const packages = initPackages(); const env = initEnv(); const utils = createUtils(); @@ -1948,8 +1846,10 @@ function main() { createKeyDownEventListener(); packages.signals().then((signals) => - vecidToIndexes.then(({ VecIdToIndexes }) => - options.then(async ({ initOptions }) => { + vecidToIndexesPromise.then(({ createVecIdToIndexes }) => + optionsPromise.then(async ({ initOptions }) => { + const vecIdToIndexes = createVecIdToIndexes(); + function initDark() { const preferredColorSchemeMatchMedia = window.matchMedia( "(prefers-color-scheme: dark)", @@ -2071,6 +1971,7 @@ function main() { utils, webSockets, vecsResources, + vecIdToIndexes: vecIdToIndexes, }), ), ), diff --git a/websites/kibo.money/scripts/options.js b/websites/kibo.money/scripts/options.js index f3c3ccc38..c631aad78 100644 --- a/websites/kibo.money/scripts/options.js +++ b/websites/kibo.money/scripts/options.js @@ -4926,7 +4926,8 @@ function createPartialOptions(colors) { // name: "Market", // tree: [ { - name: "Dollars Per Bitcoin", + name: "Price", + title: "Bitcoin Price in US Dollars", }, { name: "Blocks", @@ -4948,11 +4949,13 @@ function createPartialOptions(colors) { key: "block-interval-max", title: "Max", color: colors.pink, + defaultActive: false, }, { key: "block-interval-min", title: "Min", color: colors.green, + defaultActive: false, }, { key: "block-interval-90p", diff --git a/websites/kibo.money/scripts/types/self.d.ts b/websites/kibo.money/scripts/types/self.d.ts index 9f53afa75..2d3083d17 100644 --- a/websites/kibo.money/scripts/types/self.d.ts +++ b/websites/kibo.money/scripts/types/self.d.ts @@ -17,19 +17,6 @@ import { BaselineData, } from "../../packages/lightweight-charts/v5.0.5/types"; import { AnyPossibleCohortId, Groups } from "../options"; -import { Signal } from "../../packages/solid-signals/types"; - -// type TimeScale = "date" | "height"; - -type TimeRange = IRange