Compare commits

...

14 Commits

Author SHA1 Message Date
nym21 31110a740d release: v0.0.54 2025-06-12 22:18:36 +02:00
nym21 b64d8b1d7f release: v0.0.53 2025-06-12 22:16:33 +02:00
nym21 c46006aacc web: filter possible index choices in charts 2025-06-12 22:09:33 +02:00
nym21 92f81b1493 web: fix css 2025-06-12 20:23:23 +02:00
nym21 70213cfc8f websites: default: add auto price series type 2025-06-12 18:41:56 +02:00
nym21 8a82bf5c50 websites: default: add live price 2025-06-12 18:10:24 +02:00
nym21 37405384a2 vec: fixed compressed, still slow par read, cli: made raw the default 2025-06-12 16:31:54 +02:00
nym21 54ea6cc53b indexer: only raw format + global: fixes 2025-06-12 12:33:43 +02:00
nym21 339c00d815 release: v0.0.52 2025-06-11 21:19:41 +02:00
nym21 ea6b4dcde2 websites: default: remove scrollToSelected 2025-06-11 21:19:22 +02:00
nym21 2b84623d1e release: v0.0.51 2025-06-11 21:09:07 +02:00
nym21 c8b3afa56b websites: default: fix sw adn co 2025-06-11 21:08:42 +02:00
nym21 1348f3c24c release: v0.0.50 2025-06-11 18:11:22 +02:00
nym21 62208ce3e1 websites: default: fix minBarSpacing 2025-06-11 18:11:11 +02:00
32 changed files with 898 additions and 579 deletions
Generated
+18 -18
View File
@@ -388,7 +388,7 @@ dependencies = [
[[package]] [[package]]
name = "brk" name = "brk"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"brk_cli", "brk_cli",
"brk_computer", "brk_computer",
@@ -407,7 +407,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_cli" name = "brk_cli"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bitcoincore-rpc", "bitcoincore-rpc",
"brk_computer", "brk_computer",
@@ -432,7 +432,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_computer" name = "brk_computer"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -453,7 +453,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_core" name = "brk_core"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bincode", "bincode",
"bitcoin", "bitcoin",
@@ -474,7 +474,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_exit" name = "brk_exit"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"brk_logger", "brk_logger",
"ctrlc", "ctrlc",
@@ -483,7 +483,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_fetcher" name = "brk_fetcher"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"brk_core", "brk_core",
"brk_logger", "brk_logger",
@@ -496,7 +496,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_indexer" name = "brk_indexer"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -514,7 +514,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_logger" name = "brk_logger"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"color-eyre", "color-eyre",
"env_logger", "env_logger",
@@ -524,7 +524,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_parser" name = "brk_parser"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bitcoin", "bitcoin",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -539,7 +539,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_query" name = "brk_query"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"brk_computer", "brk_computer",
"brk_core", "brk_core",
@@ -557,7 +557,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_server" name = "brk_server"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"axum", "axum",
"bitcoincore-rpc", "bitcoincore-rpc",
@@ -586,7 +586,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_state" name = "brk_state"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"bincode", "bincode",
"brk_core", "brk_core",
@@ -600,7 +600,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_store" name = "brk_store"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"brk_core", "brk_core",
@@ -610,7 +610,7 @@ dependencies = [
[[package]] [[package]]
name = "brk_vec" name = "brk_vec"
version = "0.0.49" version = "0.0.54"
dependencies = [ dependencies = [
"arc-swap", "arc-swap",
"brk_core", "brk_core",
@@ -1698,9 +1698,9 @@ checksum = "47e1ffaa40ddd1f3ed91f717a33c8c0ee23fff369e3aa8772b9605cc1d22f4c3"
[[package]] [[package]]
name = "memchr" name = "memchr"
version = "2.7.4" version = "2.7.5"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" checksum = "32a282da65faaf38286cf3be983213fcf1d2e2a58700e808f83f4ea9a4804bc0"
[[package]] [[package]]
name = "memmap2" name = "memmap2"
@@ -3352,9 +3352,9 @@ dependencies = [
[[package]] [[package]]
name = "windows-link" name = "windows-link"
version = "0.1.1" version = "0.1.2"
source = "registry+https://github.com/rust-lang/crates.io-index" source = "registry+https://github.com/rust-lang/crates.io-index"
checksum = "76840935b766e1b0a05c0066835fb9ec80071d4c09a16f6bd5f7e655e3c14c38" checksum = "d3bfe459f85da17560875b8bf1423d6f113b7a87a5d942e7da0ac71be7c61f8b"
[[package]] [[package]]
name = "windows-result" name = "windows-result"
+14 -14
View File
@@ -4,7 +4,7 @@ members = ["crates/*"]
package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node" package.description = "The Bitcoin Research Kit is a suite of tools designed to extract, compute and display data stored on a Bitcoin Core node"
package.license = "MIT" package.license = "MIT"
package.edition = "2024" package.edition = "2024"
package.version = "0.0.49" package.version = "0.0.54"
package.homepage = "https://bitcoinresearchkit.org" package.homepage = "https://bitcoinresearchkit.org"
package.repository = "https://github.com/bitcoinresearchkit/brk" package.repository = "https://github.com/bitcoinresearchkit/brk"
@@ -22,19 +22,19 @@ axum = "0.8.4"
bincode = { version = "2.0.1", features = ["serde"] } bincode = { version = "2.0.1", features = ["serde"] }
bitcoin = { version = "0.32.6", features = ["serde"] } bitcoin = { version = "0.32.6", features = ["serde"] }
bitcoincore-rpc = "0.19.0" bitcoincore-rpc = "0.19.0"
brk_cli = { version = "0.0.49", path = "crates/brk_cli" } brk_cli = { version = "0.0.54", path = "crates/brk_cli" }
brk_computer = { version = "0.0.49", path = "crates/brk_computer" } brk_computer = { version = "0.0.54", path = "crates/brk_computer" }
brk_core = { version = "0.0.49", path = "crates/brk_core" } brk_core = { version = "0.0.54", path = "crates/brk_core" }
brk_exit = { version = "0.0.49", path = "crates/brk_exit" } brk_exit = { version = "0.0.54", path = "crates/brk_exit" }
brk_fetcher = { version = "0.0.49", path = "crates/brk_fetcher" } brk_fetcher = { version = "0.0.54", path = "crates/brk_fetcher" }
brk_indexer = { version = "0.0.49", path = "crates/brk_indexer" } brk_indexer = { version = "0.0.54", path = "crates/brk_indexer" }
brk_logger = { version = "0.0.49", path = "crates/brk_logger" } brk_logger = { version = "0.0.54", path = "crates/brk_logger" }
brk_parser = { version = "0.0.49", path = "crates/brk_parser" } brk_parser = { version = "0.0.54", path = "crates/brk_parser" }
brk_query = { version = "0.0.49", path = "crates/brk_query" } brk_query = { version = "0.0.54", path = "crates/brk_query" }
brk_server = { version = "0.0.49", path = "crates/brk_server" } brk_server = { version = "0.0.54", path = "crates/brk_server" }
brk_state = { version = "0.0.49", path = "crates/brk_state" } brk_state = { version = "0.0.54", path = "crates/brk_state" }
brk_store = { version = "0.0.49", path = "crates/brk_store" } brk_store = { version = "0.0.54", path = "crates/brk_store" }
brk_vec = { version = "0.0.49", path = "crates/brk_vec" } brk_vec = { version = "0.0.54", path = "crates/brk_vec" }
byteview = "=0.6.1" byteview = "=0.6.1"
clap = { version = "4.5.40", features = ["string"] } clap = { version = "4.5.40", features = ["string"] }
clap_derive = "4.5.40" clap_derive = "4.5.40"
+2 -2
View File
@@ -20,9 +20,9 @@ struct Cli {
#[derive(Subcommand, Debug)] #[derive(Subcommand, Debug)]
enum Commands { enum Commands {
/// Run the indexer, computer and server /// Run the indexer, computer and server, use `run -h` for more information
Run(RunConfig), Run(RunConfig),
/// Query generated datasets via the `run` command in a similar fashion as the server's API /// Query generated datasets via the `run` command in a similar fashion as the server's API, use `query -h` for more information
Query(QueryArgs), Query(QueryArgs),
} }
+1 -1
View File
@@ -10,7 +10,7 @@ pub fn query(params: QueryParams) -> color_eyre::Result<()> {
let format = config.format(); let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?; let mut indexer = Indexer::new(&config.outputsdir(), config.check_collisions())?;
indexer.import_vecs()?; indexer.import_vecs()?;
let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format); let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format);
+30 -15
View File
@@ -7,7 +7,7 @@ use std::{
use bitcoincore_rpc::{self, Auth, Client, RpcApi}; use bitcoincore_rpc::{self, Auth, Client, RpcApi};
use brk_computer::Computer; use brk_computer::Computer;
use brk_core::{default_bitcoin_path, default_brk_path, dot_brk_path}; use brk_core::{default_bitcoin_path, default_brk_path, default_on_error, dot_brk_path};
use brk_exit::Exit; use brk_exit::Exit;
use brk_fetcher::Fetcher; use brk_fetcher::Fetcher;
use brk_indexer::Indexer; use brk_indexer::Indexer;
@@ -29,7 +29,7 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
let format = config.format(); let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?; let mut indexer = Indexer::new(&config.outputsdir(), config.check_collisions())?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
@@ -109,62 +109,77 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
#[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)] #[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct RunConfig { pub struct RunConfig {
/// Bitcoin main directory path, defaults: ~/.bitcoin, ~/Library/Application\ Support/Bitcoin, saved /// Bitcoin main directory path, defaults: ~/.bitcoin, ~/Library/Application\ Support/Bitcoin, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
bitcoindir: Option<String>, bitcoindir: Option<String>,
/// Bitcoin blocks directory path, default: --bitcoindir/blocks, saved /// Bitcoin blocks directory path, default: --bitcoindir/blocks, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
blocksdir: Option<String>, blocksdir: Option<String>,
/// Bitcoin Research Kit outputs directory path, default: ~/.brk, saved /// Bitcoin Research Kit outputs directory path, default: ~/.brk, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
brkdir: Option<String>, brkdir: Option<String>,
/// Executed by the runner, default: all, saved /// Activated services, default: all, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
mode: Option<Mode>, services: Option<Services>,
/// Computation mode for compatible datasets, `lazy` computes data whenever requested without saving it, `eager` computes the data once and saves it to disk, default: Lazy, saved /// Computation of computed datasets, `lazy` computes data whenever requested without saving it, `eager` computes the data once and saves it to disk, default: `lazy`, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
computation: Option<Computation>, computation: Option<Computation>,
/// Activate compression of datasets, set to true to save disk space or false if prioritize speed, default: compressed, saved /// Format of computed datasets, `compressed` to save disk space (experimental), `raw` to prioritize speed, default: `raw`, saved
#[arg(short, long, value_name = "FORMAT")] #[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)]
format: Option<Format>, format: Option<Format>,
/// Activate fetching prices from exchanges APIs and the computation of all related datasets, default: true, saved /// Activate fetching prices from exchanges APIs and the computation of all related datasets, default: true, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short = 'F', long, value_name = "BOOL")] #[arg(short = 'F', long, value_name = "BOOL")]
fetch: Option<bool>, fetch: Option<bool>,
/// Website served by the server (if active), default: default, saved /// Website served by the server (if active), default: default, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)] #[arg(short, long)]
website: Option<Website>, website: Option<Website>,
/// Bitcoin RPC ip, default: localhost, saved /// Bitcoin RPC ip, default: localhost, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "IP")] #[arg(long, value_name = "IP")]
rpcconnect: Option<String>, rpcconnect: Option<String>,
/// Bitcoin RPC port, default: 8332, saved /// Bitcoin RPC port, default: 8332, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PORT")] #[arg(long, value_name = "PORT")]
rpcport: Option<u16>, rpcport: Option<u16>,
/// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved /// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")] #[arg(long, value_name = "PATH")]
rpccookiefile: Option<String>, rpccookiefile: Option<String>,
/// Bitcoin RPC username, saved /// Bitcoin RPC username, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "USERNAME")] #[arg(long, value_name = "USERNAME")]
rpcuser: Option<String>, rpcuser: Option<String>,
/// Bitcoin RPC password, saved /// Bitcoin RPC password, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PASSWORD")] #[arg(long, value_name = "PASSWORD")]
rpcpassword: Option<String>, rpcpassword: Option<String>,
/// Delay between runs, default: 0, saved /// Delay between runs, default: 0, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "SECONDS")] #[arg(long, value_name = "SECONDS")]
delay: Option<u64>, delay: Option<u64>,
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved /// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "BOOL")] #[arg(long, value_name = "BOOL")]
check_collisions: Option<bool>, check_collisions: Option<bool>,
} }
@@ -192,8 +207,8 @@ impl RunConfig {
config_saved.brkdir = Some(brkdir); config_saved.brkdir = Some(brkdir);
} }
if let Some(mode) = config_args.mode.take() { if let Some(services) = config_args.services.take() {
config_saved.mode = Some(mode); config_saved.services = Some(services);
} }
if let Some(computation) = config_args.computation.take() { if let Some(computation) = config_args.computation.take() {
@@ -255,7 +270,7 @@ impl RunConfig {
// info!("Configuration {{"); // info!("Configuration {{");
// info!(" bitcoindir: {:?}", config.bitcoindir); // info!(" bitcoindir: {:?}", config.bitcoindir);
// info!(" brkdir: {:?}", config.brkdir); // info!(" brkdir: {:?}", config.brkdir);
// info!(" mode: {:?}", config.mode); // info!(" services: {:?}", config.services);
// info!(" website: {:?}", config.website); // info!(" website: {:?}", config.website);
// info!(" rpcconnect: {:?}", config.rpcconnect); // info!(" rpcconnect: {:?}", config.rpcconnect);
// info!(" rpcport: {:?}", config.rpcport); // info!(" rpcport: {:?}", config.rpcport);
@@ -375,13 +390,13 @@ impl RunConfig {
} }
pub fn process(&self) -> bool { pub fn process(&self) -> bool {
self.mode self.services
.is_none_or(|m| m == Mode::All || m == Mode::Processor) .is_none_or(|m| m == Services::All || m == Services::Processor)
} }
pub fn serve(&self) -> bool { pub fn serve(&self) -> bool {
self.mode self.services
.is_none_or(|m| m == Mode::All || m == Mode::Server) .is_none_or(|m| m == Services::All || m == Services::Server)
} }
fn path_cookiefile(&self) -> PathBuf { fn path_cookiefile(&self) -> PathBuf {
@@ -449,7 +464,7 @@ impl RunConfig {
PartialOrd, PartialOrd,
Ord, Ord,
)] )]
pub enum Mode { pub enum Services {
#[default] #[default]
All, All,
Processor, Processor,
+1 -1
View File
@@ -33,7 +33,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Raw; let format = Format::Raw;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
@@ -226,7 +226,11 @@ impl ComputedValueVecsFromTxindex {
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> { pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[ [
self.sats.vecs(), self.sats.vecs(),
vec![&self.bitcoin_txindex as &dyn AnyCollectableVec],
self.bitcoin.vecs(), self.bitcoin.vecs(),
self.dollars_txindex
.as_ref()
.map_or(vec![], |v| vec![v as &dyn AnyCollectableVec]),
self.dollars.as_ref().map_or(vec![], |v| v.vecs()), self.dollars.as_ref().map_or(vec![], |v| v.vecs()),
] ]
.into_iter() .into_iter()
+1
View File
@@ -124,6 +124,7 @@ impl Vecs {
fetcher: Option<&mut Fetcher>, fetcher: Option<&mut Fetcher>,
exit: &Exit, exit: &Exit,
) -> color_eyre::Result<()> { ) -> color_eyre::Result<()> {
info!("Computing indexes...");
let starting_indexes = self.indexes.compute(indexer, starting_indexes, exit)?; let starting_indexes = self.indexes.compute(indexer, starting_indexes, exit)?;
info!("Computing constants..."); info!("Computing constants...");
+2
View File
@@ -3,9 +3,11 @@ mod checked_sub;
mod paths; mod paths;
mod pause; mod pause;
mod rlimit; mod rlimit;
mod serde;
pub use bytes::*; pub use bytes::*;
pub use checked_sub::*; pub use checked_sub::*;
pub use paths::*; pub use paths::*;
pub use pause::*; pub use pause::*;
pub use rlimit::*; pub use rlimit::*;
pub use serde::*;
+12
View File
@@ -0,0 +1,12 @@
use serde::{Deserialize, Deserializer};
pub fn default_on_error<'de, D, T>(deserializer: D) -> Result<T, D::Error>
where
D: Deserializer<'de>,
T: Deserialize<'de> + Default,
{
match T::deserialize(deserializer) {
Ok(v) => Ok(v),
Err(_) => Ok(T::default()),
}
}
+1 -2
View File
@@ -4,7 +4,6 @@ use brk_core::default_bitcoin_path;
use brk_exit::Exit; use brk_exit::Exit;
use brk_indexer::Indexer; use brk_indexer::Indexer;
use brk_parser::Parser; use brk_parser::Parser;
use brk_vec::Format;
fn main() -> color_eyre::Result<()> { fn main() -> color_eyre::Result<()> {
color_eyre::install()?; color_eyre::install()?;
@@ -25,7 +24,7 @@ fn main() -> color_eyre::Result<()> {
let outputs = Path::new("../../_outputs"); let outputs = Path::new("../../_outputs");
let mut indexer = Indexer::new(outputs, Format::Raw, false)?; let mut indexer = Indexer::new(outputs, false)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
+2 -9
View File
@@ -19,7 +19,7 @@ use brk_core::{
use bitcoin::{Transaction, TxIn, TxOut}; use bitcoin::{Transaction, TxIn, TxOut};
use brk_exit::Exit; use brk_exit::Exit;
use brk_parser::Parser; use brk_parser::Parser;
use brk_vec::{AnyVec, Format, VecIterator}; use brk_vec::{AnyVec, VecIterator};
use color_eyre::eyre::{ContextCompat, eyre}; use color_eyre::eyre::{ContextCompat, eyre};
use fjall::TransactionalKeyspace; use fjall::TransactionalKeyspace;
use log::{error, info}; use log::{error, info};
@@ -42,21 +42,15 @@ pub struct Indexer {
vecs: Option<Vecs>, vecs: Option<Vecs>,
stores: Option<Stores>, stores: Option<Stores>,
check_collisions: bool, check_collisions: bool,
format: Format,
} }
impl Indexer { impl Indexer {
pub fn new( pub fn new(outputs_dir: &Path, check_collisions: bool) -> color_eyre::Result<Self> {
outputs_dir: &Path,
format: Format,
check_collisions: bool,
) -> color_eyre::Result<Self> {
setrlimit()?; setrlimit()?;
Ok(Self { Ok(Self {
path: outputs_dir.to_owned(), path: outputs_dir.to_owned(),
vecs: None, vecs: None,
stores: None, stores: None,
format,
check_collisions, check_collisions,
}) })
} }
@@ -65,7 +59,6 @@ impl Indexer {
self.vecs = Some(Vecs::forced_import( self.vecs = Some(Vecs::forced_import(
&self.path.join("vecs/indexed"), &self.path.join("vecs/indexed"),
VERSION + Version::ZERO, VERSION + Version::ZERO,
self.format,
)?); )?);
Ok(()) Ok(())
} }
+34 -38
View File
@@ -66,11 +66,7 @@ pub struct Vecs {
} }
impl Vecs { impl Vecs {
pub fn forced_import( pub fn forced_import(path: &Path, version: Version) -> color_eyre::Result<Self> {
path: &Path,
version: Version,
format: Format,
) -> color_eyre::Result<Self> {
fs::create_dir_all(path)?; fs::create_dir_all(path)?;
Ok(Self { Ok(Self {
@@ -78,7 +74,7 @@ impl Vecs {
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_blockhash: IndexedVec::forced_import( height_to_blockhash: IndexedVec::forced_import(
path, path,
@@ -90,145 +86,145 @@ impl Vecs {
path, path,
"difficulty", "difficulty",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_emptyoutputindex: IndexedVec::forced_import( height_to_first_emptyoutputindex: IndexedVec::forced_import(
path, path,
"first_emptyoutputindex", "first_emptyoutputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_inputindex: IndexedVec::forced_import( height_to_first_inputindex: IndexedVec::forced_import(
path, path,
"first_inputindex", "first_inputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_opreturnindex: IndexedVec::forced_import( height_to_first_opreturnindex: IndexedVec::forced_import(
path, path,
"first_opreturnindex", "first_opreturnindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_outputindex: IndexedVec::forced_import( height_to_first_outputindex: IndexedVec::forced_import(
path, path,
"first_outputindex", "first_outputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2aindex: IndexedVec::forced_import( height_to_first_p2aindex: IndexedVec::forced_import(
path, path,
"first_p2aindex", "first_p2aindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2msindex: IndexedVec::forced_import( height_to_first_p2msindex: IndexedVec::forced_import(
path, path,
"first_p2msindex", "first_p2msindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pk33index: IndexedVec::forced_import( height_to_first_p2pk33index: IndexedVec::forced_import(
path, path,
"first_p2pk33index", "first_p2pk33index",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pk65index: IndexedVec::forced_import( height_to_first_p2pk65index: IndexedVec::forced_import(
path, path,
"first_p2pk65index", "first_p2pk65index",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2pkhindex: IndexedVec::forced_import( height_to_first_p2pkhindex: IndexedVec::forced_import(
path, path,
"first_p2pkhindex", "first_p2pkhindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2shindex: IndexedVec::forced_import( height_to_first_p2shindex: IndexedVec::forced_import(
path, path,
"first_p2shindex", "first_p2shindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2trindex: IndexedVec::forced_import( height_to_first_p2trindex: IndexedVec::forced_import(
path, path,
"first_p2trindex", "first_p2trindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2wpkhindex: IndexedVec::forced_import( height_to_first_p2wpkhindex: IndexedVec::forced_import(
path, path,
"first_p2wpkhindex", "first_p2wpkhindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_p2wshindex: IndexedVec::forced_import( height_to_first_p2wshindex: IndexedVec::forced_import(
path, path,
"first_p2wshindex", "first_p2wshindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_txindex: IndexedVec::forced_import( height_to_first_txindex: IndexedVec::forced_import(
path, path,
"first_txindex", "first_txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_first_unknownoutputindex: IndexedVec::forced_import( height_to_first_unknownoutputindex: IndexedVec::forced_import(
path, path,
"first_unknownoutputindex", "first_unknownoutputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_timestamp: IndexedVec::forced_import( height_to_timestamp: IndexedVec::forced_import(
path, path,
"timestamp", "timestamp",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_total_size: IndexedVec::forced_import( height_to_total_size: IndexedVec::forced_import(
path, path,
"total_size", "total_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
height_to_weight: IndexedVec::forced_import( height_to_weight: IndexedVec::forced_import(
path, path,
"weight", "weight",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
inputindex_to_outputindex: IndexedVec::forced_import( inputindex_to_outputindex: IndexedVec::forced_import(
path, path,
"outputindex", "outputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
opreturnindex_to_txindex: IndexedVec::forced_import( opreturnindex_to_txindex: IndexedVec::forced_import(
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_outputtype: IndexedVec::forced_import( outputindex_to_outputtype: IndexedVec::forced_import(
path, path,
"outputtype", "outputtype",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_outputtypeindex: IndexedVec::forced_import( outputindex_to_outputtypeindex: IndexedVec::forced_import(
path, path,
"outputtypeindex", "outputtypeindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
outputindex_to_value: IndexedVec::forced_import( outputindex_to_value: IndexedVec::forced_import(
path, path,
"value", "value",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
p2aindex_to_p2abytes: IndexedVec::forced_import( p2aindex_to_p2abytes: IndexedVec::forced_import(
path, path,
@@ -240,7 +236,7 @@ impl Vecs {
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
p2pk33index_to_p2pk33bytes: IndexedVec::forced_import( p2pk33index_to_p2pk33bytes: IndexedVec::forced_import(
path, path,
@@ -288,13 +284,13 @@ impl Vecs {
path, path,
"base_size", "base_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_first_inputindex: IndexedVec::forced_import( txindex_to_first_inputindex: IndexedVec::forced_import(
path, path,
"first_inputindex", "first_inputindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_first_outputindex: IndexedVec::forced_import( txindex_to_first_outputindex: IndexedVec::forced_import(
path, path,
@@ -306,19 +302,19 @@ impl Vecs {
path, path,
"is_explicitly_rbf", "is_explicitly_rbf",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_rawlocktime: IndexedVec::forced_import( txindex_to_rawlocktime: IndexedVec::forced_import(
path, path,
"rawlocktime", "rawlocktime",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_total_size: IndexedVec::forced_import( txindex_to_total_size: IndexedVec::forced_import(
path, path,
"total_size", "total_size",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
txindex_to_txid: IndexedVec::forced_import( txindex_to_txid: IndexedVec::forced_import(
path, path,
@@ -330,13 +326,13 @@ impl Vecs {
path, path,
"txversion", "txversion",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
unknownoutputindex_to_txindex: IndexedVec::forced_import( unknownoutputindex_to_txindex: IndexedVec::forced_import(
path, path,
"txindex", "txindex",
version + VERSION + Version::ZERO, version + VERSION + Version::ZERO,
format, Format::Raw,
)?, )?,
}) })
} }
+1 -1
View File
@@ -12,7 +12,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Compressed; let format = Format::Compressed;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_vecs()?; indexer.import_vecs()?;
let mut computer = Computer::new(outputs_dir, None, format); let mut computer = Computer::new(outputs_dir, None, format);
+1 -1
View File
@@ -31,7 +31,7 @@ pub fn main() -> color_eyre::Result<()> {
let format = Format::Compressed; let format = Format::Compressed;
let mut indexer = Indexer::new(outputs_dir, format, true)?; let mut indexer = Indexer::new(outputs_dir, true)?;
indexer.import_stores()?; indexer.import_stores()?;
indexer.import_vecs()?; indexer.import_vecs()?;
+1
View File
@@ -124,6 +124,7 @@ pub async fn variant_handler(
Query(params_opt): Query<ParamsOpt>, Query(params_opt): Query<ParamsOpt>,
state: State<AppState>, state: State<AppState>,
) -> Response { ) -> Response {
let variant = variant.replace("_", "-");
let mut split = variant.split(TO_SEPARATOR); let mut split = variant.split(TO_SEPARATOR);
let params = Params::from(( let params = Params::from((
( (
+1 -1
View File
@@ -8,8 +8,8 @@ use serde::{Deserialize, Serialize};
Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ValueEnum, Default, Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Serialize, Deserialize, ValueEnum,
)] )]
pub enum Format { pub enum Format {
#[default]
Compressed, Compressed,
#[default]
Raw, Raw,
} }
+2 -1
View File
@@ -31,7 +31,7 @@ where
} }
#[inline] #[inline]
fn get_or_read_(&self, index: usize, mmap: &Mmap) -> Result<Option<Value<T>>> { fn get_or_read_(&self, index: usize, mmap: &Mmap) -> Result<Option<Value<T>>> {
let stored_len = mmap.len() / Self::SIZE_OF_T; let stored_len = self.stored_len_(mmap);
if index >= stored_len { if index >= stored_len {
let pushed = self.pushed(); let pushed = self.pushed();
@@ -53,6 +53,7 @@ where
fn mmap(&self) -> &ArcSwap<Mmap>; fn mmap(&self) -> &ArcSwap<Mmap>;
fn stored_len(&self) -> usize; fn stored_len(&self) -> usize;
fn stored_len_(&self, mmap: &Mmap) -> usize;
fn pushed(&self) -> &[T]; fn pushed(&self) -> &[T];
#[inline] #[inline]
+13 -5
View File
@@ -21,7 +21,9 @@ use crate::{
const ONE_KIB: usize = 1024; const ONE_KIB: usize = 1024;
const ONE_MIB: usize = ONE_KIB * ONE_KIB; const ONE_MIB: usize = ONE_KIB * ONE_KIB;
pub const MAX_CACHE_SIZE: usize = 100 * ONE_MIB; pub const MAX_CACHE_SIZE: usize = 100 * ONE_MIB;
pub const MAX_PAGE_SIZE: usize = 16 * ONE_KIB; pub const MAX_PAGE_SIZE: usize = 64 * ONE_KIB;
const VERSION: Version = Version::ONE;
#[derive(Debug)] #[derive(Debug)]
pub struct CompressedVec<I, T> { pub struct CompressedVec<I, T> {
@@ -39,7 +41,9 @@ where
pub const CACHE_LENGTH: usize = MAX_CACHE_SIZE / Self::PAGE_SIZE; pub const CACHE_LENGTH: usize = MAX_CACHE_SIZE / Self::PAGE_SIZE;
/// Same as import but will reset the folder under certain errors, so be careful ! /// Same as import but will reset the folder under certain errors, so be careful !
pub fn forced_import(path: &Path, version: Version) -> Result<Self> { pub fn forced_import(path: &Path, mut version: Version) -> Result<Self> {
version = version + VERSION;
let res = Self::import(path, version); let res = Self::import(path, version);
match res { match res {
Err(Error::WrongEndian) Err(Error::WrongEndian)
@@ -129,7 +133,7 @@ where
page_index * Self::PER_PAGE page_index * Self::PER_PAGE
} }
fn stored_len_(pages_meta: &Guard<Arc<CompressedPagesMetadata>>) -> usize { fn stored_len__(pages_meta: &Guard<Arc<CompressedPagesMetadata>>) -> usize {
if let Some(last) = pages_meta.last() { if let Some(last) = pages_meta.last() {
(pages_meta.len() - 1) * Self::PER_PAGE + last.values_len as usize (pages_meta.len() - 1) * Self::PER_PAGE + last.values_len as usize
} else { } else {
@@ -178,7 +182,11 @@ where
#[inline] #[inline]
fn stored_len(&self) -> usize { fn stored_len(&self) -> usize {
Self::stored_len_(&self.pages_meta.load()) Self::stored_len__(&self.pages_meta.load())
}
#[inline]
fn stored_len_(&self, _: &Mmap) -> usize {
self.stored_len()
} }
#[inline] #[inline]
@@ -477,7 +485,7 @@ where
fn into_iter(self) -> Self::IntoIter { fn into_iter(self) -> Self::IntoIter {
let pages_meta = self.pages_meta.load(); let pages_meta = self.pages_meta.load();
let stored_len = CompressedVec::<I, T>::stored_len_(&pages_meta); let stored_len = CompressedVec::<I, T>::stored_len__(&pages_meta);
CompressedVecIterator { CompressedVecIterator {
vec: self, vec: self,
guard: self.mmap().load(), guard: self.mmap().load(),
+5 -1
View File
@@ -104,7 +104,11 @@ where
#[inline] #[inline]
fn stored_len(&self) -> usize { fn stored_len(&self) -> usize {
self.mmap.load().len() / Self::SIZE_OF_T self.stored_len_(&self.mmap.load())
}
#[inline]
fn stored_len_(&self, mmap: &Mmap) -> usize {
mmap.len() / Self::SIZE_OF_T
} }
#[inline] #[inline]
+7
View File
@@ -73,6 +73,13 @@ where
StoredVec::Compressed(v) => v.stored_len(), StoredVec::Compressed(v) => v.stored_len(),
} }
} }
#[inline]
fn stored_len_(&self, mmap: &Mmap) -> usize {
match self {
StoredVec::Raw(v) => v.stored_len_(mmap),
StoredVec::Compressed(v) => v.stored_len_(mmap),
}
}
#[inline] #[inline]
fn pushed(&self) -> &[T] { fn pushed(&self) -> &[T] {
+8 -6
View File
@@ -1,4 +1,4 @@
<!doctype html> <!DOCTYPE html>
<html lang="en"> <html lang="en">
<head> <head>
<meta charset="utf-8" /> <meta charset="utf-8" />
@@ -965,7 +965,7 @@
display: flex; display: flex;
align-items: center; align-items: center;
gap: 1.5rem; gap: 1.5rem;
margin: -0.75rem var(--negative-main-padding); margin: -0.5rem var(--negative-main-padding);
padding: 0.75rem var(--main-padding); padding: 0.75rem var(--main-padding);
overflow-x: auto; overflow-x: auto;
min-width: 0; min-width: 0;
@@ -1113,7 +1113,7 @@
// @ts-check // @ts-check
const preferredColorSchemeMatchMedia = window.matchMedia( const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)", "(prefers-color-scheme: dark)"
); );
const themeColor = window.document.createElement("meta"); const themeColor = window.document.createElement("meta");
@@ -1123,7 +1123,7 @@
/** @param {boolean} dark */ /** @param {boolean} dark */
function updateThemeColor(dark) { function updateThemeColor(dark) {
const theme = getComputedStyle( const theme = getComputedStyle(
window.document.documentElement, window.document.documentElement
).getPropertyValue(dark ? "--black" : "--white"); ).getPropertyValue(dark ? "--black" : "--white");
themeColor.content = theme; themeColor.content = theme;
} }
@@ -1133,7 +1133,7 @@
"change", "change",
({ matches }) => { ({ matches }) => {
updateThemeColor(matches); updateThemeColor(matches);
}, }
); );
if ("standalone" in window.navigator && !!window.navigator.standalone) { if ("standalone" in window.navigator && !!window.navigator.standalone) {
@@ -1141,7 +1141,9 @@
} }
if ("serviceWorker" in navigator) { if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("/service-worker.js"); window.addEventListener("load", () => {
navigator.serviceWorker.register("/service-worker.js");
});
} }
</script> </script>
@@ -14,9 +14,10 @@
/** /**
* @typedef {Object} Series * @typedef {Object} Series
* @property {ISeriesApi<SeriesType>} inner * @property {ISeriesApi<SeriesType, number>} inner
* @property {string} id * @property {string} id
* @property {Signal<boolean>} active * @property {Signal<boolean>} active
* @property {Signal<boolean>} hasData
* @property {VoidFunction} remove * @property {VoidFunction} remove
*/ */
@@ -26,8 +27,12 @@
*/ */
/** /**
* @typedef {ChartData<_SingleValueData>} SingleValueData * @typedef {ChartData<_SingleValueData<number>>} SingleValueData
* @typedef {ChartData<_CandlestickData>} CandlestickData * @typedef {ChartData<_CandlestickData<number>>} CandlestickData
*/
/**
* @typedef {function({ iseries: ISeriesApi<any, number>; unit: Unit; index: Index }): void} SetDataCallback
*/ */
export default import("./v5.0.7-treeshaked/script.js").then((lc) => { export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
@@ -123,7 +128,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
} }
: {}), : {}),
// ..._options, // ..._options,
}), })
); );
ichart.priceScale("right").applyOptions({ ichart.priceScale("right").applyOptions({
@@ -155,7 +160,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
}, },
}, },
}); });
}, }
); );
let timeScaleSet = false; let timeScaleSet = false;
@@ -163,30 +168,30 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
signals.createEffect(index, (index) => { signals.createEffect(index, (index) => {
timeScaleSet = false; timeScaleSet = false;
const minBarSpacing =
index === /** @satisfies {MonthIndex} */ (7)
? 1
: index === /** @satisfies {QuarterIndex} */ (19)
? 3
: index === /** @satisfies {YearIndex} */ (23)
? 12
: index === /** @satisfies {DecadeIndex} */ (1)
? 120
: 0.5;
ichart.applyOptions({ ichart.applyOptions({
timeScale: { timeScale: {
timeVisible: timeVisible:
index === /** @satisfies {Height} */ (5) || index === /** @satisfies {Height} */ (5) ||
index === /** @satisfies {DifficultyEpoch} */ (2) || index === /** @satisfies {DifficultyEpoch} */ (2) ||
index === /** @satisfies {HalvingEpoch} */ (4), index === /** @satisfies {HalvingEpoch} */ (4),
...(!fitContent
? {
minBarSpacing,
}
: {}),
}, },
}); });
if (!fitContent) {
ichart.applyOptions({
timeScale: {
minBarSpacing:
index === /** @satisfies {MonthIndex} */ (7)
? 1
: index === /** @satisfies {QuarterIndex} */ (19)
? 3
: index === /** @satisfies {YearIndex} */ (23)
? 12
: index === /** @satisfies {DecadeIndex} */ (1)
? 120
: undefined,
},
});
}
}); });
const activeResources = /** @type {Set<VecResource>} */ (new Set()); const activeResources = /** @type {Set<VecResource>} */ (new Set());
@@ -195,12 +200,12 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
activeResources.forEach((v) => { activeResources.forEach((v) => {
v.fetch(); v.fetch();
}); });
}), })
); );
if (fitContent) { if (fitContent) {
new ResizeObserver(() => ichart.timeScale().fitContent()).observe( new ResizeObserver(() => ichart.timeScale().fitContent()).observe(
chartDiv, chartDiv
); );
} }
@@ -229,7 +234,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
const children = Array.from(parent.childNodes).filter( const children = Array.from(parent.childNodes).filter(
(element) => (element) =>
/** @type {HTMLElement} */ (element).dataset.position === /** @type {HTMLElement} */ (element).dataset.position ===
position, position
); );
if (children.length === 1) { if (children.length === 1) {
@@ -251,7 +256,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
fieldset.append(createChild(pane)); fieldset.append(createChild(pane));
}), }),
paneIndex ? 50 : 0, paneIndex ? 50 : 0
); );
} }
@@ -293,14 +298,15 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
/** /**
* @param {Object} args * @param {Object} args
* @param {ISeriesApi<SeriesType>} args.iseries * @param {ISeriesApi<SeriesType, number>} args.iseries
* @param {string} args.name * @param {string} args.name
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {number} args.order * @param {number} args.order
* @param {Color[]} args.colors * @param {Color[]} args.colors
* @param {SeriesType} args.seriesType * @param {SeriesType} args.seriesType
* @param {VecId} [args.vecId] * @param {VecId} [args.vecId]
* @param {Accessor<WhitespaceData[]>} [args.data] * @param {SetDataCallback} [args.setDataCallback]
* @param {Accessor<WhitespaceData<number>[]>} [args.data]
* @param {number} args.paneIndex * @param {number} args.paneIndex
* @param {boolean} [args.defaultActive] * @param {boolean} [args.defaultActive]
*/ */
@@ -311,6 +317,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
unit, unit,
order, order,
seriesType, seriesType,
setDataCallback,
paneIndex, paneIndex,
defaultActive, defaultActive,
colors, colors,
@@ -327,13 +334,15 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
}, },
}); });
const hasData = signals.createSignal(false);
let url = /** @type {string | undefined} */ (undefined); let url = /** @type {string | undefined} */ (undefined);
signals.createEffect(active, (active) => signals.createEffect(active, (active) =>
// Or remove ? // Or remove ?
iseries.applyOptions({ iseries.applyOptions({
visible: active, visible: active,
}), })
); );
iseries.setSeriesOrder(order); iseries.setSeriesOrder(order);
@@ -345,9 +354,11 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
const series = { const series = {
inner: iseries, inner: iseries,
active, active,
hasData,
id, id,
remove() { remove() {
dispose(); dispose();
// @ts-ignore
chart.inner.removeSeries(iseries); chart.inner.removeSeries(iseries);
if (_valuesResource) { if (_valuesResource) {
activeResources.delete(_valuesResource); activeResources.delete(_valuesResource);
@@ -361,7 +372,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
index, index,
index === /** @satisfies {Height} */ (5) index === /** @satisfies {Height} */ (5)
? "timestamp-fixed" ? "timestamp-fixed"
: "timestamp", : "timestamp"
); );
timeResource.fetch(); timeResource.fetch();
@@ -377,13 +388,13 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
const fetchedKey = vecsResources.defaultFetchedKey; const fetchedKey = vecsResources.defaultFetchedKey;
signals.createEffect( signals.createEffect(
() => [ () => [
timeResource.fetched[fetchedKey].vec(), timeResource.fetched().get(fetchedKey)?.vec(),
valuesResource.fetched[fetchedKey].vec(), valuesResource.fetched().get(fetchedKey)?.vec(),
], ],
([indexes, _ohlcs]) => { ([indexes, _ohlcs]) => {
if (!indexes || !_ohlcs) return; if (!indexes?.length || !_ohlcs?.length) return;
const seriesData = series.inner.data(); // const seriesData = series.inner.data();
// const set = seriesData.length === 0; // const set = seriesData.length === 0;
const ohlcs = /** @type {OHLCTuple[]} */ (_ohlcs); const ohlcs = /** @type {OHLCTuple[]} */ (_ohlcs);
@@ -446,6 +457,12 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
// iseries.update(bar, true); // iseries.update(bar, true);
// }); // });
// } // }
hasData.set(true);
setDataCallback?.({
iseries,
index,
unit,
});
timeScaleSetCallback?.(() => { timeScaleSetCallback?.(() => {
if ( if (
@@ -461,7 +478,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
} }
}); });
timeScaleSet = true; timeScaleSet = true;
}, }
); );
} else { } else {
activeResources.delete(valuesResource); activeResources.delete(valuesResource);
@@ -471,6 +488,12 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
} else if (data) { } else if (data) {
signals.createEffect(data, (data) => { signals.createEffect(data, (data) => {
iseries.setData(data); iseries.setData(data);
hasData.set(true);
setDataCallback?.({
iseries,
index: index(),
unit,
});
if (fitContent) { if (fitContent) {
ichart.timeScale().fitContent(); ichart.timeScale().fitContent();
@@ -520,6 +543,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
* @param {number} [args.paneIndex] * @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive] * @param {boolean} [args.defaultActive]
* @param {boolean} [args.inverse] * @param {boolean} [args.inverse]
* @param {SetDataCallback} [args.setDataCallback]
* @param {DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options] * @param {DeepPartial<CandlestickStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options]
*/ */
addCandlestickSeries({ addCandlestickSeries({
@@ -529,22 +553,29 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
order, order,
paneIndex = 0, paneIndex = 0,
defaultActive, defaultActive,
setDataCallback,
data, data,
inverse, inverse,
}) { }) {
const green = inverse ? colors.red : colors.green; const green = inverse ? colors.red : colors.green;
const red = inverse ? colors.green : colors.red; const red = inverse ? colors.green : colors.red;
const iseries = ichart.addSeries(
/** @type {SeriesDefinition<'Candlestick'>} */ (lc.CandlestickSeries), /** @type {ISeriesApi<'Candlestick', number>} */
{ const iseries = /** @type {any} */ (
upColor: green(), ichart.addSeries(
downColor: red(), /** @type {SeriesDefinition<'Candlestick'>} */ (
wickUpColor: green(), lc.CandlestickSeries
wickDownColor: red(), ),
borderVisible: false, {
visible: defaultActive !== false, upColor: green(),
}, downColor: red(),
paneIndex, wickUpColor: green(),
wickDownColor: red(),
borderVisible: false,
visible: defaultActive !== false,
},
paneIndex
)
); );
return addSeries({ return addSeries({
@@ -556,6 +587,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
seriesType: "Candlestick", seriesType: "Candlestick",
unit, unit,
data, data,
setDataCallback,
defaultActive, defaultActive,
vecId, vecId,
}); });
@@ -565,9 +597,10 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
* @param {string} args.name * @param {string} args.name
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {number} args.order * @param {number} args.order
* @param {Accessor<LineData[]>} [args.data] * @param {Accessor<LineData<number>[]>} [args.data]
* @param {VecId} [args.vecId] * @param {VecId} [args.vecId]
* @param {Color} [args.color] * @param {Color} [args.color]
* @param {SetDataCallback} [args.setDataCallback]
* @param {number} [args.paneIndex] * @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive] * @param {boolean} [args.defaultActive]
* @param {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options] * @param {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options]
@@ -577,6 +610,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
name, name,
unit, unit,
order, order,
setDataCallback,
color, color,
paneIndex = 0, paneIndex = 0,
defaultActive, defaultActive,
@@ -585,16 +619,19 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
}) { }) {
color ||= unit === "USD" ? colors.green : colors.orange; color ||= unit === "USD" ? colors.green : colors.orange;
const iseries = ichart.addSeries( /** @type {ISeriesApi<'Line', number>} */
/** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries), const iseries = /** @type {any} */ (
{ ichart.addSeries(
lineWidth: /** @type {any} */ (1.5), /** @type {SeriesDefinition<'Line'>} */ (lc.LineSeries),
visible: defaultActive !== false, {
priceLineVisible: false, lineWidth: /** @type {any} */ (1.5),
color: color(), visible: defaultActive !== false,
...options, priceLineVisible: false,
}, color: color(),
paneIndex, ...options,
},
paneIndex
)
); );
const priceLineOptions = options?.createPriceLine; const priceLineOptions = options?.createPriceLine;
@@ -610,6 +647,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
paneIndex, paneIndex,
seriesType: "Line", seriesType: "Line",
unit, unit,
setDataCallback,
data, data,
defaultActive, defaultActive,
vecId, vecId,
@@ -620,8 +658,9 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
* @param {string} args.name * @param {string} args.name
* @param {Unit} args.unit * @param {Unit} args.unit
* @param {number} args.order * @param {number} args.order
* @param {Accessor<BaselineData[]>} [args.data] * @param {Accessor<BaselineData<number>[]>} [args.data]
* @param {VecId} [args.vecId] * @param {VecId} [args.vecId]
* @param {SetDataCallback} [args.setDataCallback]
* @param {number} [args.paneIndex] * @param {number} [args.paneIndex]
* @param {boolean} [args.defaultActive] * @param {boolean} [args.defaultActive]
* @param {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options] * @param {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [args.options]
@@ -632,31 +671,35 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
unit, unit,
order, order,
paneIndex: _paneIndex, paneIndex: _paneIndex,
setDataCallback,
defaultActive, defaultActive,
data, data,
options, options,
}) { }) {
const paneIndex = _paneIndex ?? 0; const paneIndex = _paneIndex ?? 0;
const iseries = ichart.addSeries( /** @type {ISeriesApi<'Baseline', number>} */
/** @type {SeriesDefinition<'Baseline'>} */ (lc.BaselineSeries), const iseries = /** @type {any} */ (
{ ichart.addSeries(
lineWidth: /** @type {any} */ (1.5), /** @type {SeriesDefinition<'Baseline'>} */ (lc.BaselineSeries),
visible: defaultActive !== false, {
baseValue: { lineWidth: /** @type {any} */ (1.5),
price: options?.createPriceLine?.value ?? 0, visible: defaultActive !== false,
baseValue: {
price: options?.createPriceLine?.value ?? 0,
},
...options,
topLineColor: options?.topLineColor ?? colors.green(),
bottomLineColor: options?.bottomLineColor ?? colors.red(),
priceLineVisible: false,
bottomFillColor1: "transparent",
bottomFillColor2: "transparent",
topFillColor1: "transparent",
topFillColor2: "transparent",
lineVisible: true,
}, },
...options, paneIndex
topLineColor: options?.topLineColor ?? colors.green(), )
bottomLineColor: options?.bottomLineColor ?? colors.red(),
priceLineVisible: false,
bottomFillColor1: "transparent",
bottomFillColor2: "transparent",
topFillColor1: "transparent",
topFillColor2: "transparent",
lineVisible: true,
},
paneIndex,
); );
const priceLineOptions = options?.createPriceLine; const priceLineOptions = options?.createPriceLine;
@@ -674,6 +717,7 @@ export default import("./v5.0.7-treeshaked/script.js").then((lc) => {
order, order,
paneIndex, paneIndex,
seriesType: "Baseline", seriesType: "Baseline",
setDataCallback,
unit, unit,
data, data,
defaultActive, defaultActive,
@@ -824,7 +868,7 @@ function createLegend({ signals, utils }) {
} else { } else {
spanColor.style.backgroundColor = tameColor(color); spanColor.style.backgroundColor = tameColor(color);
} }
}, }
); );
}); });
@@ -930,7 +974,7 @@ function createPaneHeightObserver({ ichart, paneIndex, signals, utils }) {
} }
/** /**
* @param {ISeriesApi<SeriesType>} series * @param {ISeriesApi<SeriesType, number>} series
* @param {DeepPartial<CreatePriceLine>} options * @param {DeepPartial<CreatePriceLine>} options
* @param {Colors} colors * @param {Colors} colors
*/ */
@@ -944,18 +988,21 @@ function createPriceLine(series, options, colors) {
}); });
} }
/** @param {number} value */ /**
function numberToShortUSFormat(value) { * @param {number} value
* @param {0 | 2} [digits]
*/
function numberToShortUSFormat(value, digits) {
const absoluteValue = Math.abs(value); const absoluteValue = Math.abs(value);
if (isNaN(value)) { if (isNaN(value)) {
return ""; return "";
} else if (absoluteValue < 10) { } else if (absoluteValue < 10) {
return numberToUSFormat(value, 3); return numberToUSFormat(value, Math.min(3, digits || 10));
} else if (absoluteValue < 1_000) { } else if (absoluteValue < 1_000) {
return numberToUSFormat(value, 2); return numberToUSFormat(value, Math.min(2, digits || 10));
} else if (absoluteValue < 10_000) { } else if (absoluteValue < 10_000) {
return numberToUSFormat(value, 1); return numberToUSFormat(value, Math.min(1, digits || 10));
} else if (absoluteValue < 1_000_000) { } else if (absoluteValue < 1_000_000) {
return numberToUSFormat(value, 0); return numberToUSFormat(value, 0);
} else if (absoluteValue >= 900_000_000_000_000_000) { } else if (absoluteValue >= 900_000_000_000_000_000) {
@@ -978,17 +1025,17 @@ function numberToShortUSFormat(value) {
if (modulused === 0) { if (modulused === 0) {
return `${numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
3, 3
)}${letter}`; )}${letter}`;
} else if (modulused === 1) { } else if (modulused === 1) {
return `${numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
2, 2
)}${letter}`; )}${letter}`;
} else { } else {
return `${numberToUSFormat( return `${numberToUSFormat(
value / (1_000_000 * 1_000 ** letterIndex), value / (1_000_000 * 1_000 ** letterIndex),
1, 1
)}${letter}`; )}${letter}`;
} }
} }
@@ -1038,7 +1085,7 @@ function createOklchToRGBA() {
return rgb.map((c) => return rgb.map((c) =>
Math.abs(c) > 0.0031308 Math.abs(c) > 0.0031308
? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055) ? (c < 0 ? -1 : 1) * (1.055 * Math.abs(c) ** (1 / 2.4) - 0.055)
: 12.92 * c, : 12.92 * c
); );
} }
/** /**
@@ -1050,7 +1097,7 @@ function createOklchToRGBA() {
1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586, 1, 0.3963377773761749, 0.2158037573099136, 1, -0.1055613458156586,
-0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092, -0.0638541728258133, 1, -0.0894841775298119, -1.2914855480194092,
]), ]),
lab, lab
); );
const LMS = /** @type {[number, number, number]} */ ( const LMS = /** @type {[number, number, number]} */ (
LMSg.map((val) => val ** 3) LMSg.map((val) => val ** 3)
@@ -1061,7 +1108,7 @@ function createOklchToRGBA() {
-0.0405757452148008, 1.112286803280317, -0.0717110580655164, -0.0405757452148008, 1.112286803280317, -0.0717110580655164,
-0.0763729366746601, -0.4214933324022432, 1.5869240198367816, -0.0763729366746601, -0.4214933324022432, 1.5869240198367816,
]), ]),
LMS, LMS
); );
} }
/** /**
@@ -1074,7 +1121,7 @@ function createOklchToRGBA() {
-0.9692436362808796, 1.8759675015077202, 0.04155505740717559, -0.9692436362808796, 1.8759675015077202, 0.04155505740717559,
0.05563007969699366, -0.20397695888897652, 1.0569715142428786, 0.05563007969699366, -0.20397695888897652, 1.0569715142428786,
], ],
xyz, xyz
); );
} }
@@ -1097,8 +1144,8 @@ function createOklchToRGBA() {
}); });
const rgb = srgbLinear2rgb( const rgb = srgbLinear2rgb(
xyz2rgbLinear( xyz2rgbLinear(
oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch))), oklab2xyz(oklch2oklab(/** @type {[number, number, number]} */ (lch)))
), )
).map((v) => { ).map((v) => {
return Math.max(Math.min(Math.round(v * 255), 255), 0); return Math.max(Math.min(Math.round(v * 255), 255), 0);
}); });
@@ -31,13 +31,13 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
if (dispose) { if (dispose) {
dispose(); dispose();
dispose = null; dispose = null;
console.debug("effectCount = ", --effectCount); // console.log("effectCount = ", --effectCount);
} }
} }
// @ts-ignore // @ts-ignore
_signals.createEffect(compute, (v, oldV) => { _signals.createEffect(compute, (v, oldV) => {
console.debug("effectCount = ", ++effectCount); // console.log("effectCount = ", ++effectCount);
cleanup(); cleanup();
signals.createRoot((_dispose) => { signals.createRoot((_dispose) => {
dispose = _dispose; dispose = _dispose;
@@ -62,7 +62,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
createSignal(initialValue, options) { createSignal(initialValue, options) {
const [get, set] = this.createSolidSignal( const [get, set] = this.createSolidSignal(
/** @type {any} */ (initialValue), /** @type {any} */ (initialValue),
options, options
); );
// @ts-ignore // @ts-ignore
@@ -77,13 +77,17 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
const paramKey = save.key; const paramKey = save.key;
const storageKey = this.createMemo( const storageKey = this.createMemo(
() => () =>
`${typeof save.keyPrefix === "string" ? save.keyPrefix : save.keyPrefix()}-${paramKey}`, `${
typeof save.keyPrefix === "string"
? save.keyPrefix
: save.keyPrefix()
}-${paramKey}`
); );
let serialized = /** @type {string | null} */ (null); let serialized = /** @type {string | null} */ (null);
if (options.save.serializeParam !== false) { if (options.save.serializeParam !== false) {
serialized = new URLSearchParams(window.location.search).get( serialized = new URLSearchParams(window.location.search).get(
paramKey, paramKey
); );
} }
if (serialized === null) { if (serialized === null) {
@@ -91,7 +95,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
} }
if (serialized) { if (serialized) {
set(() => set(() =>
serialized ? save.deserialize(serialized) : initialValue, serialized ? save.deserialize(serialized) : initialValue
); );
} }
@@ -100,7 +104,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
if (!firstRun1) { if (!firstRun1) {
serialized = localStorage.getItem(storageKey); serialized = localStorage.getItem(storageKey);
set(() => set(() =>
serialized ? save.deserialize(serialized) : initialValue, serialized ? save.deserialize(serialized) : initialValue
); );
} }
firstRun1 = false; firstRun1 = false;
@@ -146,7 +150,7 @@ const importSignals = import("./v0.3.2-treeshaked/script.js").then(
}; };
return signals; return signals;
}, }
); );
/** /**
@@ -166,7 +170,7 @@ function writeParam(key, value) {
window.history.replaceState( window.history.replaceState(
null, null,
"", "",
`${window.location.pathname}?${urlParams.toString()}`, `${window.location.pathname}?${urlParams.toString()}`
); );
} catch (_) {} } catch (_) {}
} }
+201 -70
View File
@@ -1,12 +1,16 @@
// @ts-check // @ts-check
const keyPrefix = "chart"; const keyPrefix = "chart";
const ONE_BTC_IN_SATS = 100_000_000;
const AUTO = "auto";
const LINE = "line";
const CANDLE = "candle";
/** /**
* @param {Object} args * @param {Object} args
* @param {Colors} args.colors * @param {Colors} args.colors
* @param {LightweightCharts} args.lightweightCharts * @param {LightweightCharts} args.lightweightCharts
* @param {Accessor<ChartOption>} args.selected * @param {Accessor<ChartOption>} args.option
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
* @param {WebSockets} args.webSockets * @param {WebSockets} args.webSockets
@@ -18,7 +22,7 @@ export function init({
colors, colors,
elements, elements,
lightweightCharts, lightweightCharts,
selected, option,
signals, signals,
utils, utils,
webSockets, webSockets,
@@ -31,10 +35,15 @@ export function init({
const { headerElement, headingElement } = utils.dom.createHeader(); const { headerElement, headingElement } = utils.dom.createHeader();
elements.charts.append(headerElement); elements.charts.append(headerElement);
const { index, fieldset } = createIndexSelector({ signals, utils }); const { index, fieldset } = createIndexSelector({
option,
vecIdToIndexes,
signals,
utils,
});
const TIMERANGE_LS_KEY = signals.createMemo( const TIMERANGE_LS_KEY = signals.createMemo(
() => `chart-timerange-${index()}`, () => `chart-timerange-${index()}`
); );
let firstRun = true; let firstRun = true;
@@ -89,20 +98,37 @@ export function init({
from.set(t.from); from.set(t.from);
to.set(t.to); to.set(t.to);
} }
}), })
); );
elements.charts.append(fieldset); elements.charts.append(fieldset);
const { field: seriesTypeField, selected: topSeriesType } = const { field: seriesTypeField, selected: topSeriesType_ } =
utils.dom.createHorizontalChoiceField({ utils.dom.createHorizontalChoiceField({
defaultValue: "Line", defaultValue: CANDLE,
keyPrefix, keyPrefix,
key: "seriestype-0", key: "seriestype-0",
choices: /** @type {const} */ (["Candles", "Line"]), choices: /** @type {const} */ ([AUTO, CANDLE, LINE]),
signals, signals,
}); });
const topSeriesType = signals.createMemo(() => {
const topSeriesType = topSeriesType_();
if (topSeriesType === AUTO) {
const t = to();
const f = from();
if (!t || !f) return null;
const diff = t - f;
if (diff / chart.inner.paneSize().width <= 0.5) {
return CANDLE;
} else {
return LINE;
}
} else {
return topSeriesType;
}
});
const { field: topUnitField, selected: topUnit } = const { field: topUnitField, selected: topUnit } =
utils.dom.createHorizontalChoiceField({ utils.dom.createHorizontalChoiceField({
defaultValue: "USD", defaultValue: "USD",
@@ -128,7 +154,89 @@ export function init({
const seriesListTop = /** @type {Series[]} */ ([]); const seriesListTop = /** @type {Series[]} */ ([]);
const seriesListBottom = /** @type {Series[]} */ ([]); const seriesListBottom = /** @type {Series[]} */ ([]);
signals.createEffect(selected, (option) => { /**
* @param {Object} params
* @param {ISeriesApi<any, number>} params.iseries
* @param {Unit} params.unit
* @param {Index} params.index
*/
function printLatest({ iseries, unit, index }) {
const _latest = webSockets.kraken1dCandle.latest();
if (!_latest) return;
const latest = { ..._latest };
if (unit === "Sats") {
latest.open = Math.floor(ONE_BTC_IN_SATS / latest.open);
latest.high = Math.floor(ONE_BTC_IN_SATS / latest.high);
latest.low = Math.floor(ONE_BTC_IN_SATS / latest.low);
latest.close = Math.floor(ONE_BTC_IN_SATS / latest.close);
latest.value = Math.floor(ONE_BTC_IN_SATS / latest.value);
}
const last_ = iseries.data().at(-1);
if (!last_) return;
const last = { ...last_ };
if ("close" in last) {
last.close = latest.close;
}
if ("value" in last) {
last.value = latest.value;
}
const date = new Date(latest.time * 1000);
switch (index) {
case /** @satisfies {Height} */ (5): {
if ("close" in last) {
last.low = Math.min(last.low, latest.close);
last.high = Math.max(last.high, latest.close);
}
iseries.update(last);
break;
}
case /** @satisfies {DateIndex} */ (0): {
iseries.update(latest);
break;
}
default: {
if (index === /** @satisfies {WeekIndex} */ (22)) {
date.setUTCDate(date.getUTCDate() - ((date.getUTCDay() + 6) % 7));
} else if (index === /** @satisfies {MonthIndex} */ (7)) {
date.setUTCDate(1);
} else if (index === /** @satisfies {QuarterIndex} */ (19)) {
const month = date.getUTCMonth();
date.setUTCMonth(month - (month % 3), 1);
} else if (index === /** @satisfies {YearIndex} */ (23)) {
date.setUTCMonth(0, 1);
} else if (index === /** @satisfies {DecadeIndex} */ (1)) {
date.setUTCFullYear(
Math.floor(date.getUTCFullYear() / 10) * 10,
0,
1
);
} else {
throw Error("Unsupported");
}
const time = date.valueOf() / 1000;
if (time === last.time) {
if ("close" in last) {
last.low = Math.min(last.low, latest.low);
last.high = Math.max(last.high, latest.high);
}
iseries.update(last);
} else {
latest.time = time;
iseries.update(latest);
}
}
}
}
signals.createEffect(option, (option) => {
headingElement.innerHTML = option.title; headingElement.innerHTML = option.title;
const bottomUnits = /** @type {readonly Unit[]} */ ( const bottomUnits = /** @type {readonly Unit[]} */ (
@@ -166,80 +274,92 @@ export function init({
signals.createEffect(index, (index) => { signals.createEffect(index, (index) => {
signals.createEffect( signals.createEffect(
() => [topUnit(), topSeriesType()], () => ({
([topUnit, topSeriesType]) => { topUnit: topUnit(),
topSeriesType: topSeriesType(),
}),
({ topUnit, topSeriesType }) => {
/** @type {Series | undefined} */
let series;
switch (topUnit) { switch (topUnit) {
case "USD": { case "USD": {
switch (topSeriesType) { switch (topSeriesType) {
case "Candles": { case CANDLE: {
const series = chart.addCandlestickSeries({ series = chart.addCandlestickSeries({
vecId: "ohlc", vecId: "ohlc",
name: "Price", name: "Price",
unit: topUnit, unit: topUnit,
setDataCallback: printLatest,
order: 0, order: 0,
}); });
seriesListTop[0]?.remove();
seriesListTop[0] = series;
break; break;
} }
case "Line": { case LINE: {
const series = chart.addLineSeries({ series = chart.addLineSeries({
vecId: "close", vecId: "close",
name: "Price", name: "Price",
unit: topUnit, unit: topUnit,
color: colors.default, color: colors.default,
setDataCallback: printLatest,
options: { options: {
priceLineVisible: true, priceLineVisible: true,
}, },
order: 0, order: 0,
}); });
seriesListTop[0]?.remove();
seriesListTop[0] = series;
} }
} }
// 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 });
// });
break; break;
} }
case "Sats": { case "Sats": {
switch (topSeriesType) { switch (topSeriesType) {
case "Candles": { case CANDLE: {
const series = chart.addCandlestickSeries({ series = chart.addCandlestickSeries({
vecId: "ohlc-in-sats", vecId: "ohlc-in-sats",
name: "Price", name: "Price",
unit: topUnit, unit: topUnit,
inverse: true, inverse: true,
setDataCallback: printLatest,
order: 0, order: 0,
}); });
seriesListTop[0]?.remove();
seriesListTop[0] = series;
break; break;
} }
case "Line": { case LINE: {
const series = chart.addLineSeries({ series = chart.addLineSeries({
vecId: "close-in-sats", vecId: "close-in-sats",
name: "Price", name: "Price",
unit: topUnit, unit: topUnit,
color: colors.default, color: colors.default,
setDataCallback: printLatest,
options: { options: {
priceLineVisible: true, priceLineVisible: true,
}, },
order: 0, order: 0,
}); });
seriesListTop[0]?.remove();
seriesListTop[0] = series;
} }
} }
break; break;
} }
} }
},
if (!series) throw Error("Unreachable");
seriesListTop[0]?.remove();
seriesListTop[0] = series;
// setDataCallback insimport("./options").tead of hasData
signals.createEffect(
() => ({
latest: webSockets.kraken1dCandle.latest(),
hasData: series.hasData(),
}),
({ latest, hasData }) => {
if (!series || !latest || !hasData) return;
printLatest({ iseries: series.inner, unit: topUnit, index });
}
);
}
); );
[ [
@@ -300,7 +420,7 @@ export function init({
blueprint.color?.() ?? blueprint.colors?.[1](), blueprint.color?.() ?? blueprint.colors?.[1](),
}, },
order, order,
}), })
); );
break; break;
} }
@@ -318,13 +438,13 @@ export function init({
paneIndex, paneIndex,
options: blueprint.options, options: blueprint.options,
order, order,
}), })
); );
} }
} }
}); });
}); });
}, }
); );
firstRun = false; firstRun = false;
@@ -334,25 +454,53 @@ export function init({
/** /**
* @param {Object} args * @param {Object} args
* @param {Accessor<ChartOption>} args.option
* @param {VecIdToIndexes} args.vecIdToIndexes
* @param {Signals} args.signals * @param {Signals} args.signals
* @param {Utilities} args.utils * @param {Utilities} args.utils
*/ */
function createIndexSelector({ signals, utils }) { function createIndexSelector({ option, vecIdToIndexes, signals, utils }) {
const choices_ = /** @type {const} */ ([
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]);
/** @type {Accessor<typeof choices_>} */
const choices = signals.createMemo(() => {
const o = option();
if (!Object.keys(o.top).length && !Object.keys(o.bottom).length) {
return [...choices_];
}
const rawIndexes = new Set(
[Object.values(o.top), Object.values(o.bottom)]
.flat(2)
.map((blueprint) => vecIdToIndexes[blueprint.key])
.flat()
);
const serializedIndexes = [...rawIndexes].flatMap((index) => {
const c = utils.serde.chartableIndex.serialize(index);
return c ? [c] : [];
});
return /** @type {any} */ (
choices_.filter((choice) => serializedIndexes.includes(choice))
);
});
const { field, selected } = utils.dom.createHorizontalChoiceField({ const { field, selected } = utils.dom.createHorizontalChoiceField({
defaultValue: "date", defaultValue: "date",
keyPrefix, keyPrefix,
key: "index", key: "index",
choices: /**@type {const} */ ([ choices,
"timestamp",
"date",
"week",
// "difficulty epoch",
"month",
"quarter",
"year",
// "halving epoch",
"decade",
]),
id: "index", id: "index",
signals, signals,
}); });
@@ -361,25 +509,8 @@ function createIndexSelector({ signals, utils }) {
fieldset.append(field); fieldset.append(field);
fieldset.dataset.size = "sm"; fieldset.dataset.size = "sm";
const index = signals.createMemo( const index = signals.createMemo(() =>
/** @returns {ChartableIndex} */ () => { utils.serde.chartableIndex.deserialize(selected())
switch (selected()) {
case "timestamp":
return /** @satisfies {Height} */ (5);
case "date":
return /** @satisfies {DateIndex} */ (0);
case "week":
return /** @satisfies {WeekIndex} */ (22);
case "month":
return /** @satisfies {MonthIndex} */ (7);
case "quarter":
return /** @satisfies {QuarterIndex} */ (19);
case "year":
return /** @satisfies {YearIndex} */ (23);
case "decade":
return /** @satisfies {DecadeIndex} */ (1);
}
},
); );
return { fieldset, index }; return { fieldset, index };
+257 -183
View File
@@ -1,7 +1,7 @@
// @ts-check // @ts-check
/** /**
* @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, ChartableIndex,CreatePriceLineOptions, CreatePriceLine } from "./options" * @import { Option, PartialChartOption, ChartOption, AnyPartialOption, ProcessedOptionAddons, OptionsTree, SimulationOption, AnySeriesBlueprint, ChartableIndex,CreatePriceLineOptions, CreatePriceLine, SeriesType } from "./options"
* @import { Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple, Series } from "../packages/lightweight-charts/wrapper" * @import { Valued, SingleValueData, CandlestickData, ChartData, OHLCTuple, Series } from "../packages/lightweight-charts/wrapper"
* @import * as _ from "../packages/ufuzzy/v1.0.18/types" * @import * as _ from "../packages/ufuzzy/v1.0.18/types"
* @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.7-treeshaked/types" * @import { createChart as CreateClassicChart, LineStyleOptions, DeepPartial, ChartOptions, IChartApi, IHorzScaleBehavior, WhitespaceData, ISeriesApi, Time, LineData, LogicalRange, BaselineStyleOptions, SeriesOptionsCommon, BaselineData, CandlestickStyleOptions } from "../packages/lightweight-charts/v5.0.7-treeshaked/types"
@@ -67,14 +67,14 @@ function initPackages() {
const imports = { const imports = {
async signals() { async signals() {
return import("../packages/solid-signals/wrapper.js").then((d) => return import("../packages/solid-signals/wrapper.js").then((d) =>
d.default.then((d) => d), d.default.then((d) => d)
); );
}, },
async lightweightCharts() { async lightweightCharts() {
return window.document.fonts.ready.then(() => return window.document.fonts.ready.then(() =>
import("../packages/lightweight-charts/wrapper.js").then((d) => import("../packages/lightweight-charts/wrapper.js").then((d) =>
d.default.then((d) => d), d.default.then((d) => d)
), )
); );
}, },
async leanQr() { async leanQr() {
@@ -82,7 +82,7 @@ function initPackages() {
}, },
async ufuzzy() { async ufuzzy() {
return import("../packages/ufuzzy/v1.0.18/script.js").then( return import("../packages/ufuzzy/v1.0.18/script.js").then(
({ default: d }) => d, ({ default: d }) => d
); );
}, },
}; };
@@ -362,11 +362,11 @@ function createUtils() {
* @param {Object} args * @param {Object} args
* @param {T[number]} args.defaultValue * @param {T[number]} args.defaultValue
* @param {string} [args.id] * @param {string} [args.id]
* @param {T} args.choices * @param {T | Accessor<T>} args.choices
* @param {string} [args.keyPrefix] * @param {string} [args.keyPrefix]
* @param {string} args.key * @param {string} args.key
* @param {boolean} [args.sorted] * @param {boolean} [args.sorted]
* @param {{createEffect: CreateEffect, createSignal: Signals["createSignal"]}} args.signals * @param {{createEffect: CreateEffect, createMemo: CreateMemo, createSignal: Signals["createSignal"]}} args.signals
*/ */
createHorizontalChoiceField({ createHorizontalChoiceField({
id, id,
@@ -377,13 +377,21 @@ function createUtils() {
signals, signals,
sorted, sorted,
}) { }) {
const choices = sorted const choices = signals.createMemo(() => {
? /** @type {T} */ ( /** @type {T} */
/** @type {any} */ ( let c;
unsortedChoices.toSorted((a, b) => a.localeCompare(b)) if (typeof unsortedChoices === "function") {
c = unsortedChoices();
} else {
c = unsortedChoices;
}
return sorted
? /** @type {T} */ (
/** @type {any} */ (c.toSorted((a, b) => a.localeCompare(b)))
) )
) : c;
: unsortedChoices; });
/** @type {Signal<T[number]>} */ /** @type {Signal<T[number]>} */
const selected = signals.createSignal(defaultValue, { const selected = signals.createSignal(defaultValue, {
@@ -394,31 +402,39 @@ function createUtils() {
}, },
}); });
if (!choices.includes(selected())) {
console.log(choices, "don't include", selected());
selected.set(() => defaultValue);
}
const field = window.document.createElement("div"); const field = window.document.createElement("div");
field.classList.add("field"); field.classList.add("field");
const div = window.document.createElement("div"); const div = window.document.createElement("div");
field.append(div); field.append(div);
choices.forEach((choice) => { signals.createEffect(choices, (choices) => {
const inputValue = choice; const s = selected();
const { label } = this.createLabeledInput({ if (!choices.includes(s)) {
inputId: `${id ?? key}-${choice.toLowerCase()}`, if (choices.includes(defaultValue)) {
inputName: id ?? key, selected.set(() => defaultValue);
inputValue, } else if (choices.length) {
inputChecked: inputValue === selected(), selected.set(() => choices[0]);
labelTitle: choice, }
type: "radio", }
});
const text = window.document.createTextNode(choice); div.innerHTML = "";
label.append(text);
div.append(label); choices.forEach((choice) => {
const inputValue = choice;
const { label } = this.createLabeledInput({
inputId: `${id ?? key}-${choice.toLowerCase()}`,
inputName: id ?? key,
inputValue,
inputChecked: inputValue === selected(),
labelTitle: choice,
type: "radio",
});
const text = window.document.createTextNode(choice);
label.append(text);
div.append(label);
});
}); });
field.addEventListener("change", (event) => { field.addEventListener("change", (event) => {
@@ -580,7 +596,7 @@ function createUtils() {
window.history.pushState( window.history.pushState(
null, null,
"", "",
`${pathname}?${urlParams.toString()}`, `${pathname}?${urlParams.toString()}`
); );
} catch (_) {} } catch (_) {}
}, },
@@ -597,7 +613,7 @@ function createUtils() {
window.history.replaceState( window.history.replaceState(
null, null,
"", "",
`${pathname}?${urlParams.toString()}`, `${pathname}?${urlParams.toString()}`
); );
} catch (_) {} } catch (_) {}
}, },
@@ -1093,6 +1109,116 @@ function createUtils() {
} }
}, },
}, },
index: {
/**
* @param {Index} v
*/
serialize(v) {
switch (v) {
case /** @satisfies {DateIndex} */ (0):
return "dateindex";
case /** @satisfies {DecadeIndex} */ (1):
return "decadeindex";
case /** @satisfies {DifficultyEpoch} */ (2):
return "difficultyepoch";
case /** @satisfies {EmptyOutputIndex} */ (3):
return "emptyoutputindex";
case /** @satisfies {HalvingEpoch} */ (4):
return "halvingepoch";
case /** @satisfies {Height} */ (5):
return "height";
case /** @satisfies {InputIndex} */ (6):
return "inputindex";
case /** @satisfies {MonthIndex} */ (7):
return "monthindex";
case /** @satisfies {OpReturnIndex} */ (8):
return "opreturnindex";
case /** @satisfies {OutputIndex} */ (9):
return "outputindex";
case /** @satisfies {P2AIndex} */ (10):
return "p2aindex";
case /** @satisfies {P2MSIndex} */ (11):
return "p2msindex";
case /** @satisfies {P2PK33Index} */ (12):
return "p2pk33index";
case /** @satisfies {P2PK65Index} */ (13):
return "p2pk65index";
case /** @satisfies {P2PKHIndex} */ (14):
return "p2pkhindex";
case /** @satisfies {P2SHIndex} */ (15):
return "p2shindex";
case /** @satisfies {P2TRIndex} */ (16):
return "p2trindex";
case /** @satisfies {P2WPKHIndex} */ (17):
return "p2wpkhindex";
case /** @satisfies {P2WSHIndex} */ (18):
return "p2wshindex";
case /** @satisfies {QuarterIndex} */ (19):
return "quarterindex";
case /** @satisfies {TxIndex} */ (20):
return "txindex";
case /** @satisfies {UnknownOutputIndex} */ (21):
return "unknownoutputindex";
case /** @satisfies {WeekIndex} */ (22):
return "weekindex";
case /** @satisfies {YearIndex} */ (23):
return "yearindex";
}
},
},
chartableIndex: {
/**
* @param {Index} v
*/
serialize(v) {
switch (v) {
case /** @satisfies {DateIndex} */ (0):
return "date";
case /** @satisfies {DecadeIndex} */ (1):
return "decade";
// case /** @satisfies {DifficultyEpoch} */ (2):
// return "difficulty";
// case /** @satisfies {HalvingEpoch} */ (4):
// return "halving";
case /** @satisfies {Height} */ (5):
return "timestamp";
case /** @satisfies {MonthIndex} */ (7):
return "month";
case /** @satisfies {QuarterIndex} */ (19):
return "quarter";
case /** @satisfies {WeekIndex} */ (22):
return "week";
case /** @satisfies {YearIndex} */ (23):
return "year";
default:
return null;
}
},
/**
* @param {string} v
* @returns {ChartableIndex}
*/
deserialize(v) {
switch (v) {
case "timestamp":
return /** @satisfies {Height} */ (5);
case "date":
return /** @satisfies {DateIndex} */ (0);
case "week":
return /** @satisfies {WeekIndex} */ (22);
case "month":
return /** @satisfies {MonthIndex} */ (7);
case "quarter":
return /** @satisfies {QuarterIndex} */ (19);
case "year":
return /** @satisfies {YearIndex} */ (23);
case "decade":
return /** @satisfies {DecadeIndex} */ (1);
default:
throw Error("Unsupported");
}
},
},
}; };
const formatters = { const formatters = {
@@ -1120,8 +1246,8 @@ function createUtils() {
today.getUTCDate(), today.getUTCDate(),
0, 0,
0, 0,
0, 0
), )
); );
}, },
/** /**
@@ -1223,7 +1349,7 @@ function createUtils() {
*/ */
function getNumberOfDaysBetweenTwoDates(oldest, youngest) { function getNumberOfDaysBetweenTwoDates(oldest, youngest) {
return Math.round( return Math.round(
Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS), Math.abs((youngest.getTime() - oldest.getTime()) / date.ONE_DAY_IN_MS)
); );
} }
@@ -1333,62 +1459,6 @@ function createUtils() {
} }
} }
/**
* @param {Index} index
*/
function vecIndexToString(index) {
switch (index) {
case /** @satisfies {DateIndex} */ (0):
return "dateindex";
case /** @satisfies {DecadeIndex} */ (1):
return "decadeindex";
case /** @satisfies {DifficultyEpoch} */ (2):
return "difficultyepoch";
case /** @satisfies {EmptyOutputIndex} */ (3):
return "emptyoutputindex";
case /** @satisfies {HalvingEpoch} */ (4):
return "halvingepoch";
case /** @satisfies {Height} */ (5):
return "height";
case /** @satisfies {InputIndex} */ (6):
return "inputindex";
case /** @satisfies {MonthIndex} */ (7):
return "monthindex";
case /** @satisfies {OpReturnIndex} */ (8):
return "opreturnindex";
case /** @satisfies {OutputIndex} */ (9):
return "outputindex";
case /** @satisfies {P2AIndex} */ (10):
return "p2aindex";
case /** @satisfies {P2MSIndex} */ (11):
return "p2msindex";
case /** @satisfies {P2PK33Index} */ (12):
return "p2pk33index";
case /** @satisfies {P2PK65Index} */ (13):
return "p2pk65index";
case /** @satisfies {P2PKHIndex} */ (14):
return "p2pkhindex";
case /** @satisfies {P2SHIndex} */ (15):
return "p2shindex";
case /** @satisfies {P2TRIndex} */ (16):
return "p2trindex";
case /** @satisfies {P2WPKHIndex} */ (17):
return "p2wpkhindex";
case /** @satisfies {P2WSHIndex} */ (18):
return "p2wshindex";
case /** @satisfies {QuarterIndex} */ (19):
return "quarterindex";
case /** @satisfies {TxIndex} */ (20):
return "txindex";
case /** @satisfies {UnknownOutputIndex} */ (21):
return "unknownoutputindex";
case /** @satisfies {WeekIndex} */ (22):
return "weekindex";
case /** @satisfies {YearIndex} */ (23):
return "yearindex";
}
}
/** /**
* @param {Index} index * @param {Index} index
* @param {VecId} vecId * @param {VecId} vecId
@@ -1396,7 +1466,7 @@ function createUtils() {
* @param {number} [to] * @param {number} [to]
*/ */
function genPath(index, vecId, from, to) { function genPath(index, vecId, from, to) {
let path = `/query?index=${vecIndexToString(index)}&values=${vecId}`; let path = `/query?index=${serde.index.serialize(index)}&values=${vecId}`;
if (from !== undefined) { if (from !== undefined) {
path += `&from=${from}`; path += `&from=${from}`;
} }
@@ -1494,8 +1564,11 @@ function createVecsResources(signals, utils) {
return signals.runWithOwner(owner, () => { return signals.runWithOwner(owner, () => {
/** @typedef {T extends number ? SingleValueData : CandlestickData} Value */ /** @typedef {T extends number ? SingleValueData : CandlestickData} Value */
const fetchedRecord = const fetchedRecord = signals.createSignal(
/** @type {Record<string, {loading: boolean, at: Date | null, vec: Signal<T[] | null>}>} */ ({}); /** @type {Map<string, {loading: boolean, at: Date | null, vec: Signal<T[] | null>}>} */ (
new Map()
)
);
return { return {
url: utils.api.genUrl(index, id, defaultFrom), url: utils.api.genUrl(index, id, defaultFrom),
@@ -1513,12 +1586,18 @@ function createVecsResources(signals, utils) {
const from = args?.from ?? defaultFrom; const from = args?.from ?? defaultFrom;
const to = args?.to ?? defaultTo; const to = args?.to ?? defaultTo;
const fetchedKey = genFetchedKey({ from, to }); const fetchedKey = genFetchedKey({ from, to });
fetchedRecord[fetchedKey] ??= { if (!fetchedRecord().has(fetchedKey)) {
loading: false, fetchedRecord.set((map) => {
at: null, map.set(fetchedKey, {
vec: signals.createSignal(/** @type {T[] | null} */ (null)), loading: false,
}; at: null,
const fetched = fetchedRecord[fetchedKey]; vec: signals.createSignal(/** @type {T[] | null} */ (null)),
});
return map;
});
}
const fetched = fetchedRecord().get(fetchedKey);
if (!fetched) throw Error("Unreachable");
if (fetched.loading) return fetched.vec(); if (fetched.loading) return fetched.vec();
if (fetched.at) { if (fetched.at) {
const diff = new Date().getTime() - fetched.at.getTime(); const diff = new Date().getTime() - fetched.at.getTime();
@@ -1529,12 +1608,14 @@ function createVecsResources(signals, utils) {
const res = /** @type {T[] | null} */ ( const res = /** @type {T[] | null} */ (
await utils.api.fetchVec( await utils.api.fetchVec(
(values) => { (values) => {
fetched.vec.set(/** @type {T[]} */ (values)); if (values.length || !fetched.vec()) {
fetched.vec.set(values);
}
}, },
index, index,
id, id,
from, from,
to, to
) )
); );
fetched.at = new Date(); fetched.at = new Date();
@@ -1795,7 +1876,7 @@ function initWebSockets(signals, utils) {
window.document.addEventListener( window.document.addEventListener(
"visibilitychange", "visibilitychange",
reinitWebSocketIfDocumentNotHidden, reinitWebSocketIfDocumentNotHidden
); );
window.document.addEventListener("online", reinitWebSocket); window.document.addEventListener("online", reinitWebSocket);
@@ -1804,7 +1885,7 @@ function initWebSockets(signals, utils) {
ws?.close(); ws?.close();
window.document.removeEventListener( window.document.removeEventListener(
"visibilitychange", "visibilitychange",
reinitWebSocketIfDocumentNotHidden, reinitWebSocketIfDocumentNotHidden
); );
window.document.removeEventListener("online", reinitWebSocket); window.document.removeEventListener("online", reinitWebSocket);
live.set(false); live.set(false);
@@ -1817,9 +1898,8 @@ function initWebSockets(signals, utils) {
/** /**
* @param {(candle: CandlestickData) => void} callback * @param {(candle: CandlestickData) => void} callback
* @param {number} interval
*/ */
function krakenCandleWebSocketCreator(callback, interval) { function krakenCandleWebSocketCreator(callback) {
const ws = new WebSocket("wss://ws.kraken.com/v2"); const ws = new WebSocket("wss://ws.kraken.com/v2");
ws.addEventListener("open", () => { ws.addEventListener("open", () => {
@@ -1831,7 +1911,7 @@ function initWebSockets(signals, utils) {
symbol: ["BTC/USD"], symbol: ["BTC/USD"],
interval: 1440, interval: 1440,
}, },
}), })
); );
}); });
@@ -1842,14 +1922,10 @@ function initWebSockets(signals, utils) {
const { interval_begin, open, high, low, close } = result.data.at(-1); const { interval_begin, open, high, low, close } = result.data.at(-1);
const date = new Date(interval_begin);
const dateStr = utils.date.toString(date);
/** @type {CandlestickData} */ /** @type {CandlestickData} */
const candle = { const candle = {
index: -1, index: -1,
time: dateStr, time: new Date(interval_begin).valueOf() / 1000,
open: Number(open), open: Number(open),
high: Number(high), high: Number(high),
low: Number(low), low: Number(low),
@@ -1863,8 +1939,9 @@ function initWebSockets(signals, utils) {
return ws; return ws;
} }
/** @type {ReturnType<typeof createWebsocket<CandlestickData>>} */
const kraken1dCandle = createWebsocket((callback) => const kraken1dCandle = createWebsocket((callback) =>
krakenCandleWebSocketCreator(callback, 1440), krakenCandleWebSocketCreator(callback)
); );
kraken1dCandle.open(); kraken1dCandle.open();
@@ -1923,7 +2000,7 @@ function main() {
} }
const frame = window.document.getElementById( const frame = window.document.getElementById(
/** @type {string} */ (input.value), /** @type {string} */ (input.value)
); );
if (!frame) { if (!frame) {
@@ -2021,23 +2098,23 @@ function main() {
function initDark() { function initDark() {
const preferredColorSchemeMatchMedia = window.matchMedia( const preferredColorSchemeMatchMedia = window.matchMedia(
"(prefers-color-scheme: dark)", "(prefers-color-scheme: dark)"
); );
const dark = signals.createSignal( const dark = signals.createSignal(
preferredColorSchemeMatchMedia.matches, preferredColorSchemeMatchMedia.matches
); );
preferredColorSchemeMatchMedia.addEventListener( preferredColorSchemeMatchMedia.addEventListener(
"change", "change",
({ matches }) => { ({ matches }) => {
dark.set(matches); dark.set(matches);
}, }
); );
return dark; return dark;
} }
const dark = initDark(); const dark = initDark();
const qrcode = signals.createSignal( const qrcode = signals.createSignal(
/** @type {string | null} */ (null), /** @type {string | null} */ (null)
); );
function createLastHeightResource() { function createLastHeightResource() {
@@ -2048,7 +2125,7 @@ function main() {
lastHeight.set(h); lastHeight.set(h);
}, },
/** @satisfies {Height} */ (5), /** @satisfies {Height} */ (5),
"height", "height"
); );
} }
fetchLastHeight(); fetchLastHeight();
@@ -2095,10 +2172,10 @@ function main() {
const owner = signals.getOwner(); const owner = signals.getOwner();
const chartOption = signals.createSignal( const chartOption = signals.createSignal(
/** @type {ChartOption | null} */ (null), /** @type {ChartOption | null} */ (null)
); );
const simOption = signals.createSignal( const simOption = signals.createSignal(
/** @type {SimulationOption | null} */ (null), /** @type {SimulationOption | null} */ (null)
); );
let previousElement = /** @type {HTMLElement | undefined} */ ( let previousElement = /** @type {HTMLElement | undefined} */ (
@@ -2137,7 +2214,7 @@ function main() {
colors, colors,
elements, elements,
lightweightCharts, lightweightCharts,
selected: /** @type {Accessor<ChartOption>} */ ( option: /** @type {Accessor<ChartOption>} */ (
chartOption chartOption
), ),
signals, signals,
@@ -2145,10 +2222,10 @@ function main() {
webSockets, webSockets,
vecsResources, vecsResources,
vecIdToIndexes, vecIdToIndexes,
}), })
), )
), )
), )
); );
} }
firstTimeLoadingChart = false; firstTimeLoadingChart = false;
@@ -2171,9 +2248,9 @@ function main() {
vecsResources, vecsResources,
option, option,
vecIdToIndexes, vecIdToIndexes,
}), })
), )
), )
); );
} }
firstTimeLoadingTable = false; firstTimeLoadingTable = false;
@@ -2201,10 +2278,10 @@ function main() {
signals, signals,
utils, utils,
vecsResources, vecsResources,
}), })
), )
), )
), )
); );
} }
firstTimeLoadingSimulation = false; firstTimeLoadingSimulation = false;
@@ -2233,55 +2310,52 @@ function main() {
createMobileSwitchEffect(); createMobileSwitchEffect();
utils.dom.onFirstIntersection(elements.aside, () => utils.dom.onFirstIntersection(elements.aside, () =>
signals.runWithOwner(owner, initSelectedFrame), signals.runWithOwner(owner, initSelectedFrame)
); );
} }
initSelected(); initSelected();
function initFolders() { function initFolders() {
function initTreeElement() { // async function scrollToSelected() {
// if (!options.selected()) throw "Selected should be set by now";
// const selectedId = options.selected().id;
// const path = options.selected().path;
// let i = 0;
// while (i !== path.length) {
// try {
// const id = path[i];
// const details = /** @type {HTMLDetailsElement} */ (
// utils.dom.getElementById(id)
// );
// details.open = true;
// i++;
// } catch {
// await utils.next();
// }
// }
// await utils.next();
// await utils.next();
// utils.dom
// .getElementById(`${selectedId}-nav-selector`)
// .scrollIntoView({
// behavior: "instant",
// block: "center",
// });
// }
utils.dom.onFirstIntersection(elements.nav, () => {
options.treeElement.set(() => { options.treeElement.set(() => {
const treeElement = window.document.createElement("div"); const treeElement = window.document.createElement("div");
treeElement.classList.add("tree"); treeElement.classList.add("tree");
elements.nav.append(treeElement); elements.nav.append(treeElement);
return treeElement; return treeElement;
}); });
}
async function scrollToSelected() { // setTimeout(scrollToSelected, 10);
if (!options.selected()) throw "Selected should be set by now";
const selectedId = options.selected().id;
const path = options.selected().path;
let i = 0;
while (i !== path.length) {
try {
const id = path[i];
const details = /** @type {HTMLDetailsElement} */ (
utils.dom.getElementById(id)
);
details.open = true;
i++;
} catch {
await utils.next();
}
}
await utils.next();
utils.dom
.getElementById(`${selectedId}-nav-selector`)
.scrollIntoView({
behavior: "instant",
block: "center",
});
}
utils.dom.onFirstIntersection(elements.nav, () => {
console.log("nav: init");
initTreeElement();
scrollToSelected();
}); });
} }
initFolders(); initFolders();
@@ -2314,7 +2388,7 @@ function main() {
if (indexes?.length) { if (indexes?.length) {
const maxIndex = Math.min( const maxIndex = Math.min(
(order || indexes).length - 1, (order || indexes).length - 1,
minIndex + RESULTS_PER_PAGE - 1, minIndex + RESULTS_PER_PAGE - 1
); );
list = Array(maxIndex - minIndex + 1); list = Array(maxIndex - minIndex + 1);
@@ -2390,7 +2464,7 @@ function main() {
haystack, haystack,
needle, needle,
undefined, undefined,
infoThresh, infoThresh
); );
if (!result?.[0]?.length || !result?.[1]) { if (!result?.[0]?.length || !result?.[1]) {
@@ -2398,7 +2472,7 @@ function main() {
haystack, haystack,
needle, needle,
outOfOrder, outOfOrder,
infoThresh, infoThresh
); );
} }
@@ -2407,7 +2481,7 @@ function main() {
haystack, haystack,
needle, needle,
outOfOrder, outOfOrder,
infoThresh, infoThresh
); );
} }
@@ -2416,7 +2490,7 @@ function main() {
haystack, haystack,
needle, needle,
outOfOrder, outOfOrder,
infoThresh, infoThresh
); );
} }
@@ -2425,7 +2499,7 @@ function main() {
haystack, haystack,
needle, needle,
undefined, undefined,
infoThresh, infoThresh
); );
} }
@@ -2434,7 +2508,7 @@ function main() {
haystack, haystack,
needle, needle,
outOfOrder, outOfOrder,
infoThresh, infoThresh
); );
} }
@@ -2517,7 +2591,7 @@ function main() {
shareDiv.hidden = false; shareDiv.hidden = false;
}); });
}), })
); );
} }
initShare(); initShare();
@@ -2539,7 +2613,7 @@ function main() {
localStorage.setItem(barWidthLocalStorageKey, String(width)); localStorage.setItem(barWidthLocalStorageKey, String(width));
} else { } else {
elements.main.style.width = elements.style.getPropertyValue( elements.main.style.width = elements.style.getPropertyValue(
"--default-main-width", "--default-main-width"
); );
localStorage.removeItem(barWidthLocalStorageKey); localStorage.removeItem(barWidthLocalStorageKey);
} }
@@ -2575,9 +2649,9 @@ function main() {
window.addEventListener("mouseleave", setResizeFalse); window.addEventListener("mouseleave", setResizeFalse);
} }
initDesktopResizeBar(); initDesktopResizeBar();
}), })
), )
), )
); );
} }
main(); main();
+30 -24
View File
@@ -1,7 +1,7 @@
// @ts-check // @ts-check
/** /**
* @typedef {Height | DateIndex | WeekIndex | DifficultyEpoch | MonthIndex | QuarterIndex | YearIndex | HalvingEpoch | DecadeIndex} ChartableIndex * @typedef {Height | DateIndex | WeekIndex | MonthIndex | QuarterIndex | YearIndex | DecadeIndex} ChartableIndex
*/ */
/** /**
* @template {readonly unknown[]} T * @template {readonly unknown[]} T
@@ -27,7 +27,7 @@
* @property {Color} [color] * @property {Color} [color]
* @property {[Color, Color]} [colors] * @property {[Color, Color]} [colors]
* @property {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options] * @property {DeepPartial<BaselineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
* @property {Accessor<BaselineData[]>} [data] * @property {Accessor<BaselineData<number>[]>} [data]
* @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint * @typedef {BaseSeriesBlueprint & BaselineSeriesBlueprintSpecific} BaselineSeriesBlueprint
* *
* @typedef {Object} CandlestickSeriesBlueprintSpecific * @typedef {Object} CandlestickSeriesBlueprintSpecific
@@ -41,7 +41,7 @@
* @property {"Line"} [type] * @property {"Line"} [type]
* @property {Color} [color] * @property {Color} [color]
* @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options] * @property {DeepPartial<LineStyleOptions & SeriesOptionsCommon & CreatePriceLineOptions>} [options]
* @property {Accessor<LineData[]>} [data] * @property {Accessor<LineData<number>[]>} [data]
* @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint * @typedef {BaseSeriesBlueprint & LineSeriesBlueprintSpecific} LineSeriesBlueprint
* *
* @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint * @typedef {BaselineSeriesBlueprint | CandlestickSeriesBlueprint | LineSeriesBlueprint} AnySeriesBlueprint
@@ -1430,7 +1430,7 @@ function createPartialOptions(colors) {
key: `${fixKey(key)}realized-price`, key: `${fixKey(key)}realized-price`,
name, name,
color, color,
}), })
), ),
} }
: createPriceWithRatio({ : createPriceWithRatio({
@@ -1512,7 +1512,9 @@ function createPartialOptions(colors) {
}), }),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}net-realized-profit-and-loss-relative-to-realized-cap`, key: `${fixKey(
key
)}net-realized-profit-and-loss-relative-to-realized-cap`,
title: useGroupName ? name : "Net", title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1581,7 +1583,9 @@ function createPartialOptions(colors) {
bottom: list.flatMap(({ color, name, key }) => [ bottom: list.flatMap(({ color, name, key }) => [
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}adjusted-spent-output-profit-ratio`, key: `${fixKey(
key
)}adjusted-spent-output-profit-ratio`,
title: useGroupName ? name : "asopr", title: useGroupName ? name : "asopr",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1603,7 +1607,7 @@ function createPartialOptions(colors) {
key: `${fixKey(key)}sell-side-risk-ratio`, key: `${fixKey(key)}sell-side-risk-ratio`,
name: useGroupName ? name : "Risk", name: useGroupName ? name : "Risk",
color: color, color: color,
}), })
), ),
}, },
], ],
@@ -1691,7 +1695,9 @@ function createPartialOptions(colors) {
}), }),
/** @satisfies {FetchedBaselineSeriesBlueprint} */ ({ /** @satisfies {FetchedBaselineSeriesBlueprint} */ ({
type: "Baseline", type: "Baseline",
key: `${fixKey(key)}net-unrealized-profit-and-loss-relative-to-market-cap`, key: `${fixKey(
key
)}net-unrealized-profit-and-loss-relative-to-market-cap`,
title: useGroupName ? name : "Net", title: useGroupName ? name : "Net",
color: useGroupName ? color : undefined, color: useGroupName ? color : undefined,
options: { options: {
@@ -1868,7 +1874,7 @@ function createPartialOptions(colors) {
key: `${key}-sma`, key: `${key}-sma`,
name: key, name: key,
color, color,
}), })
), ),
}, },
...averages.map(({ key, name, color }) => ...averages.map(({ key, name, color }) =>
@@ -1878,7 +1884,7 @@ function createPartialOptions(colors) {
title: `${name} Market Price Moving Average`, title: `${name} Market Price Moving Average`,
legend: "average", legend: "average",
color, color,
}), })
), ),
], ],
}, },
@@ -1969,7 +1975,7 @@ function createPartialOptions(colors) {
}, },
}), }),
], ],
}), })
), ),
.../** @type {const} */ ([ .../** @type {const} */ ([
{ name: "2 Year", key: "2y" }, { name: "2 Year", key: "2y" },
@@ -2040,7 +2046,7 @@ function createPartialOptions(colors) {
}, },
}), }),
], ],
}), })
), ),
], ],
}, },
@@ -2056,7 +2062,7 @@ function createPartialOptions(colors) {
name: `${year}`, name: `${year}`,
color, color,
defaultActive, defaultActive,
}), })
), ),
}, },
...dcaClasses.map( ...dcaClasses.map(
@@ -2083,7 +2089,7 @@ function createPartialOptions(colors) {
}, },
}), }),
], ],
}), })
), ),
], ],
}, },
@@ -2144,10 +2150,10 @@ function createPartialOptions(colors) {
bottom: [ bottom: [
...createAverageSumCumulativeMinMaxPercentilesSeries("fee"), ...createAverageSumCumulativeMinMaxPercentilesSeries("fee"),
...createAverageSumCumulativeMinMaxPercentilesSeries( ...createAverageSumCumulativeMinMaxPercentilesSeries(
"fee-in-btc", "fee-in-btc"
), ),
...createAverageSumCumulativeMinMaxPercentilesSeries( ...createAverageSumCumulativeMinMaxPercentilesSeries(
"fee-in-usd", "fee-in-usd"
), ),
], ],
}, },
@@ -2911,7 +2917,7 @@ export function initOptions({
const detailsList = []; const detailsList = [];
const treeElement = signals.createSignal( const treeElement = signals.createSignal(
/** @type {HTMLDivElement | null} */ (null), /** @type {HTMLDivElement | null} */ (null)
); );
/** @type {string[] | undefined} */ /** @type {string[] | undefined} */
@@ -3030,7 +3036,7 @@ export function initOptions({
return null; return null;
} }
}, },
null, null
); );
partialTree.forEach((anyPartial, partialIndex) => { partialTree.forEach((anyPartial, partialIndex) => {
@@ -3053,7 +3059,7 @@ export function initOptions({
if ("tree" in anyPartial) { if ("tree" in anyPartial) {
const folderId = utils.stringToId( const folderId = utils.stringToId(
`${(path || []).join(" ")} ${anyPartial.name} folder`, `${(path || []).join(" ")} ${anyPartial.name} folder`
); );
/** @type {Omit<OptionsGroup, keyof PartialOptionsGroup>} */ /** @type {Omit<OptionsGroup, keyof PartialOptionsGroup>} */
@@ -3068,13 +3074,13 @@ export function initOptions({
const thisPath = groupAddons.id; const thisPath = groupAddons.id;
const passedDetails = signals.createSignal( const passedDetails = signals.createSignal(
/** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null), /** @type {HTMLDivElement | HTMLDetailsElement | null} */ (null)
); );
const childOptionsCount = recursiveProcessPartialTree( const childOptionsCount = recursiveProcessPartialTree(
anyPartial.tree, anyPartial.tree,
passedDetails, passedDetails,
[...(path || []), thisPath], [...(path || []), thisPath]
); );
listForSum.push(childOptionsCount); listForSum.push(childOptionsCount);
@@ -3106,7 +3112,7 @@ export function initOptions({
summary.append(supCount); summary.append(supCount);
signals.createEffect(childOptionsCount, (childOptionsCount) => { signals.createEffect(childOptionsCount, (childOptionsCount) => {
supCount.innerHTML = childOptionsCount.toLocaleString(); supCount.innerHTML = childOptionsCount.toLocaleString("en-us");
}); });
details.addEventListener("toggle", () => { details.addEventListener("toggle", () => {
@@ -3206,7 +3212,7 @@ export function initOptions({
}); });
return signals.createMemo(() => return signals.createMemo(() =>
listForSum.reduce((acc, s) => acc + s(), 0), listForSum.reduce((acc, s) => acc + s(), 0)
); );
} }
recursiveProcessPartialTree(partialOptions, treeElement); recursiveProcessPartialTree(partialOptions, treeElement);
@@ -3233,7 +3239,7 @@ export function initOptions({
console.log( console.log(
[...m.entries()] [...m.entries()]
.filter(([_, value]) => value > 1) .filter(([_, value]) => value > 1)
.map(([key, _]) => key), .map(([key, _]) => key)
); );
throw Error("ID duplicate"); throw Error("ID duplicate");
+72 -66
View File
@@ -76,7 +76,7 @@ export function init({
input.value = value; input.value = value;
stateValue = value; stateValue = value;
} }
}, }
); );
input.addEventListener("input", () => { input.addEventListener("input", () => {
@@ -139,7 +139,7 @@ export function init({
input.value = value; input.value = value;
stateValue = value; stateValue = value;
} }
}, }
); );
input.addEventListener("change", () => { input.addEventListener("change", () => {
@@ -328,7 +328,7 @@ export function init({
keyPrefix, keyPrefix,
key: "top-up-freq", key: "top-up-freq",
}, },
}, }
), ),
}, },
}, },
@@ -356,7 +356,7 @@ export function init({
keyPrefix, keyPrefix,
key: "swap-freq", key: "swap-freq",
}, },
}, }
), ),
}, },
}, },
@@ -369,7 +369,7 @@ export function init({
keyPrefix, keyPrefix,
key: "interval-start", key: "interval-start",
}, },
}, }
), ),
end: signals.createSignal(/** @type {Date | null} */ (new Date()), { end: signals.createSignal(/** @type {Date | null} */ (new Date()), {
save: { save: {
@@ -391,7 +391,7 @@ export function init({
}; };
parametersElement.append( parametersElement.append(
utils.dom.createHeader("Save in Bitcoin").headerElement, utils.dom.createHeader("Save in Bitcoin").headerElement
); );
/** /**
@@ -410,7 +410,9 @@ export function init({
* @param {string} param0.text * @param {string} param0.text
*/ */
function createColoredSpan({ color, text }) { function createColoredSpan({ color, text }) {
return `<span style="color: ${colors[color]()}; font-weight: 500; text-transform: uppercase; return `<span style="color: ${colors[
color
]()}; font-weight: 500; text-transform: uppercase;
font-size: var(--font-size-sm);">${text}</span>`; font-size: var(--font-size-sm);">${text}</span>`;
} }
@@ -429,9 +431,9 @@ export function init({
title: "Initial Dollar Amount", title: "Initial Dollar Amount",
signal: settings.dollars.initial.amount, signal: settings.dollars.initial.amount,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -449,9 +451,9 @@ export function init({
list: frequencies.list, list: frequencies.list,
signal: settings.dollars.topUp.frenquency, signal: settings.dollars.topUp.frenquency,
deep: true, deep: true,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -469,9 +471,9 @@ export function init({
title: "Top Up Dollar Amount", title: "Top Up Dollar Amount",
signal: settings.dollars.topUp.amount, signal: settings.dollars.topUp.amount,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -489,9 +491,9 @@ export function init({
title: "Initial Swap Amount", title: "Initial Swap Amount",
signal: settings.bitcoin.investment.initial, signal: settings.bitcoin.investment.initial,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -508,9 +510,9 @@ export function init({
list: frequencies.list, list: frequencies.list,
signal: settings.bitcoin.investment.frequency, signal: settings.bitcoin.investment.frequency,
deep: true, deep: true,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -528,9 +530,9 @@ export function init({
title: "Bitcoin Recurrent Investment", title: "Bitcoin Recurrent Investment",
signal: settings.bitcoin.investment.recurrent, signal: settings.bitcoin.investment.recurrent,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -547,9 +549,9 @@ export function init({
title: "First Simulation Date", title: "First Simulation Date",
signal: settings.interval.start, signal: settings.interval.start,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -566,9 +568,9 @@ export function init({
title: "Last Simulation Day", title: "Last Simulation Day",
signal: settings.interval.end, signal: settings.interval.end,
signals, signals,
}), })
), ),
}), })
); );
parametersElement.append( parametersElement.append(
@@ -589,9 +591,9 @@ export function init({
step: 0.01, step: 0.01,
signals, signals,
placeholder: "Fees", placeholder: "Fees",
}), })
), ),
}), })
); );
const p1 = window.document.createElement("p"); const p1 = window.document.createElement("p");
@@ -606,94 +608,94 @@ export function init({
const owner = signals.getOwner(); const owner = signals.getOwner();
const totalInvestedAmountData = signals.createSignal( const totalInvestedAmountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinValueData = signals.createSignal( const bitcoinValueData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinData = signals.createSignal( const bitcoinData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const resultData = signals.createSignal( const resultData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const dollarsLeftData = signals.createSignal( const dollarsLeftData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const totalValueData = signals.createSignal( const totalValueData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const investmentData = signals.createSignal( const investmentData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinAddedData = signals.createSignal( const bitcoinAddedData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const averagePricePaidData = signals.createSignal( const averagePricePaidData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const bitcoinPriceData = signals.createSignal( const bitcoinPriceData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const buyCountData = signals.createSignal( const buyCountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const totalFeesPaidData = signals.createSignal( const totalFeesPaidData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const daysCountData = signals.createSignal( const daysCountData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const profitableDaysRatioData = signals.createSignal( const profitableDaysRatioData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const unprofitableDaysRatioData = signals.createSignal( const unprofitableDaysRatioData = signals.createSignal(
/** @type {LineData<Time>[]} */ ([]), /** @type {LineData<number>[]} */ ([]),
{ {
equals: false, equals: false,
}, }
); );
const index = () => /** @type {DateIndex} */ (0); const index = () => /** @type {DateIndex} */ (0);
@@ -933,8 +935,7 @@ export function init({
let lastSatsAdded = 0; let lastSatsAdded = 0;
range.forEach((date, index) => { range.forEach((date, index) => {
const year = date.getUTCFullYear(); const time = date.valueOf() / 1000;
const time = utils.date.toString(date);
if (topUpFrequency.isTriggerDay(date)) { if (topUpFrequency.isTriggerDay(date)) {
dollars += topUpAmount; dollars += topUpAmount;
@@ -1095,16 +1096,21 @@ export function init({
p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`; p1.innerHTML = `After exchanging ${serInvestedAmount} in the span of ${serDaysCount} days, you would have accumulated ${serSats} Satoshis (${serBitcoin} Bitcoin) worth today ${serBitcoinValue} at an average price of ${serAveragePricePaid} per Bitcoin with a return of investment of ${serRoi}, have ${serDollars} left and paid a total of ${serTotalFeesPaid} in fees.`;
const dayDiff = Math.floor( const dayDiff = Math.floor(
utils.date.differenceBetween(new Date(), lastInvestDay), utils.date.differenceBetween(new Date(), lastInvestDay)
); );
const serDailyInvestment = c("emerald", fd(dailyInvestment)); const serDailyInvestment = c("emerald", fd(dailyInvestment));
const setLastSatsAdded = c("orange", f(lastSatsAdded)); const setLastSatsAdded = c("orange", f(lastSatsAdded));
p2.innerHTML = `You would've last bought ${c("blue", dayDiff ? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago` : "today")} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`; p2.innerHTML = `You would've last bought ${c(
"blue",
dayDiff
? `${f(dayDiff)} ${dayDiff > 1 ? "days" : "day"} ago`
: "today"
)} and exchanged ${serDailyInvestment} for approximately ${setLastSatsAdded} Satoshis`;
const serProfitableDaysRatio = c("green", fp(profitableDaysRatio)); const serProfitableDaysRatio = c("green", fp(profitableDaysRatio));
const serUnprofitableDaysRatio = c( const serUnprofitableDaysRatio = c(
"red", "red",
fp(unprofitableDaysRatio), fp(unprofitableDaysRatio)
); );
p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`; p3.innerHTML = `You would've been ${serProfitableDaysRatio} of the time profitable and ${serUnprofitableDaysRatio} of the time unprofitable.`;
@@ -1114,7 +1120,7 @@ export function init({
(lowestAnnual4YReturn) => { (lowestAnnual4YReturn) => {
const serLowestAnnual4YReturn = c( const serLowestAnnual4YReturn = c(
"cyan", "cyan",
`${fp(lowestAnnual4YReturn)}`, `${fp(lowestAnnual4YReturn)}`
); );
const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn; const lowestAnnual4YReturnPercentage = 1 + lowestAnnual4YReturn;
@@ -1130,22 +1136,22 @@ export function init({
const bitcoinValueAfter4y = bitcoinValueReturn(4); const bitcoinValueAfter4y = bitcoinValueReturn(4);
const serBitcoinValueAfter4y = c( const serBitcoinValueAfter4y = c(
"purple", "purple",
fd(bitcoinValueAfter4y), fd(bitcoinValueAfter4y)
); );
const bitcoinValueAfter10y = bitcoinValueReturn(10); const bitcoinValueAfter10y = bitcoinValueReturn(10);
const serBitcoinValueAfter10y = c( const serBitcoinValueAfter10y = c(
"fuchsia", "fuchsia",
fd(bitcoinValueAfter10y), fd(bitcoinValueAfter10y)
); );
const bitcoinValueAfter21y = bitcoinValueReturn(21); const bitcoinValueAfter21y = bitcoinValueReturn(21);
const serBitcoinValueAfter21y = c( const serBitcoinValueAfter21y = c(
"pink", "pink",
fd(bitcoinValueAfter21y), fd(bitcoinValueAfter21y)
); );
/** @param {number} v */ /** @param {number} v */
p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`; p4.innerHTML = `The lowest annual return after 4 years has historically been ${serLowestAnnual4YReturn}.<br/>Using it as the baseline, your Bitcoin would be worth ${serBitcoinValueAfter4y} after 4 years, ${serBitcoinValueAfter10y} after 10 years and ${serBitcoinValueAfter21y} after 21 years.`;
}, }
); );
totalInvestedAmountData.set((a) => a); totalInvestedAmountData.set((a) => a);
@@ -1163,7 +1169,7 @@ export function init({
daysCountData.set((a) => a); daysCountData.set((a) => a);
profitableDaysRatioData.set((a) => a); profitableDaysRatioData.set((a) => a);
unprofitableDaysRatioData.set((a) => a); unprofitableDaysRatioData.set((a) => a);
}, }
); );
}); });
}); });
+17 -14
View File
@@ -305,22 +305,25 @@ function createTable({
return l; return l;
}); });
signals.createEffect(vec.fetched[fetchedKey].vec, (vec) => { signals.createEffect(
if (!vec) return; () => vec.fetched().get(fetchedKey)?.vec(),
(vec) => {
if (!vec?.length) return;
const thIndex = colIndex() + 1; const thIndex = colIndex() + 1;
for (let i = 0; i < rowElements.length; i++) { for (let i = 0; i < rowElements.length; i++) {
const iRev = vec.length - 1 - i; const iRev = vec.length - 1 - i;
const value = vec[iRev]; const value = vec[iRev];
// @ts-ignore // @ts-ignore
rowElements[i].childNodes[thIndex].innerHTML = rowElements[i].childNodes[thIndex].innerHTML =
serializeValue({ serializeValue({
value, value,
unit, unit,
}); });
} }
}); },
);
return () => vecId; return () => vecId;
}, },
+3 -1
View File
@@ -2,7 +2,7 @@
// File auto-generated, any modifications will be overwritten // File auto-generated, any modifications will be overwritten
// //
export const VERSION = "v0.0.48"; export const VERSION = "v0.0.52";
/** @typedef {0} DateIndex */ /** @typedef {0} DateIndex */
/** @typedef {1} DecadeIndex */ /** @typedef {1} DecadeIndex */
@@ -1251,6 +1251,7 @@ export function createVecIdToIndexes() {
"fee-75p": [5], "fee-75p": [5],
"fee-90p": [5], "fee-90p": [5],
"fee-average": [0, 1, 2, 5, 7, 19, 22, 23], "fee-average": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-btc": [20],
"fee-in-btc-10p": [5], "fee-in-btc-10p": [5],
"fee-in-btc-25p": [5], "fee-in-btc-25p": [5],
"fee-in-btc-75p": [5], "fee-in-btc-75p": [5],
@@ -1260,6 +1261,7 @@ export function createVecIdToIndexes() {
"fee-in-btc-median": [5], "fee-in-btc-median": [5],
"fee-in-btc-min": [0, 1, 2, 5, 7, 19, 22, 23], "fee-in-btc-min": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-btc-sum": [0, 1, 2, 5, 7, 19, 22, 23], "fee-in-btc-sum": [0, 1, 2, 5, 7, 19, 22, 23],
"fee-in-usd": [20],
"fee-in-usd-10p": [5], "fee-in-usd-10p": [5],
"fee-in-usd-25p": [5], "fee-in-usd-25p": [5],
"fee-in-usd-75p": [5], "fee-in-usd-75p": [5],
+5 -5
View File
@@ -10,7 +10,7 @@ sw.addEventListener("install", (event) => {
sw.addEventListener("activate", (event) => { sw.addEventListener("activate", (event) => {
console.log("sw: active"); console.log("sw: active");
event.waitUntil(sw.clients.claim()); sw.clients.claim();
event.waitUntil( event.waitUntil(
caches caches
.keys() .keys()
@@ -42,6 +42,8 @@ sw.addEventListener("fetch", (event) => {
return; // let the browser handle it return; // let the browser handle it
} }
const cache = caches.open(CACHE_NAME);
// 2) NAVIGATION: networkfirst on your shell // 2) NAVIGATION: networkfirst on your shell
if (req.mode === "navigate") { if (req.mode === "navigate") {
event.respondWith( event.respondWith(
@@ -52,9 +54,7 @@ sw.addEventListener("fetch", (event) => {
if (response.ok || response.status === 304) { if (response.ok || response.status === 304) {
if (response.ok) { if (response.ok) {
const clone = response.clone(); const clone = response.clone();
caches cache.then((cache) => cache.put("/index.html", clone));
.open(CACHE_NAME)
.then((cache) => cache.put("/index.html", clone));
} }
return response; return response;
} }
@@ -72,7 +72,7 @@ sw.addEventListener("fetch", (event) => {
.then((response) => { .then((response) => {
if (response.ok) { if (response.ok) {
const clone = response.clone(); const clone = response.clone();
caches.open(CACHE_NAME).then((cache) => cache.put(req, clone)); cache.then((cache) => cache.put(req, clone));
} }
return response; return response;
}) })
+1
View File
@@ -1,5 +1,6 @@
{ {
"compilerOptions": { "compilerOptions": {
"allowJs": true,
"checkJs": true, "checkJs": true,
"strict": true, "strict": true,
"target": "ESNext", "target": "ESNext",