Compare commits

...

229 Commits

Author SHA1 Message Date
nym21 10b496e845 release: v0.0.100 2025-09-07 17:14:10 +02:00
nym21 bbe7bf390d crates: upgrade rest 2025-09-07 17:13:57 +02:00
nym21 4777b3400a crates: upgrade seqdb 2025-09-07 17:13:01 +02:00
nym21 acaa70e944 release: v0.0.99 2025-09-07 17:01:58 +02:00
nym21 4049d694f7 global: snapshot + pools + fixes 2025-09-07 17:01:34 +02:00
nym21 e155a3dacf bitview: fix localstorage error 2025-09-06 15:41:16 +02:00
nym21 a224e4c4d8 release: v0.0.98 2025-09-05 14:50:46 +02:00
nym21 edaeda5424 release: v0.0.97 2025-09-05 14:47:35 +02:00
nym21 09d974913d computer: pools part 1 + fetcher: fix url + interface: more ddos protection 2025-09-05 14:47:11 +02:00
nym21 f82edb290a global: add datasets and charts 2025-09-05 10:00:29 +02:00
nym21 3d8b33ae94 release: v0.0.96 2025-09-03 18:21:17 +02:00
nym21 565ecbd436 cargo: update 2025-09-03 18:20:58 +02:00
nym21 3359dfcc29 global: snapshot 2025-09-03 18:17:25 +02:00
nym21 1c2afd14dd global: fixes of Parser::new 2025-09-01 20:34:27 +02:00
nym21 fe5343c1d6 global: tiny snapshot 2025-09-01 20:21:51 +02:00
nym21 08cfefc02a zed: add project settings to improve search 2025-08-31 17:05:28 +02:00
nym21 f6d9332c48 bitview: fix screenshot in ios 2025-08-31 16:17:50 +02:00
nym21 cc6913c854 bitview: initial history support 2025-08-31 14:50:36 +02:00
nym21 8c75fbd0a4 server: fix urls in readme 2025-08-31 12:21:11 +02:00
nym21 0de6d62409 bitview: simplify options tree 2025-08-31 11:07:54 +02:00
nym21 5ba7ce5b7c bitview: small fixes 2025-08-30 12:11:15 +02:00
nym21 e106d30852 global: snapshot 2025-08-29 22:49:26 +02:00
nym21 30affc884b release: v0.0.95 2025-08-28 12:43:49 +02:00
nym21 745717ea49 global: added unrealized relative datasets 2025-08-28 12:43:28 +02:00
nym21 4efd98b758 release: v0.0.94 2025-08-28 00:31:36 +02:00
nym21 36640e3710 global: added datasets 2025-08-28 00:31:14 +02:00
nym21 311c4fd29d website: rename default to bitview 2025-08-27 11:52:22 +02:00
nym21 f50374f983 release: v0.0.93 2025-08-26 23:34:57 +02:00
nym21 82ceb7f021 cargo: update 2025-08-26 23:34:38 +02:00
nym21 0aba3bc1d8 release: v0.0.92 2025-08-26 22:27:16 +02:00
nym21 f6c984ff3c website: add screenshot feature 2025-08-26 22:26:55 +02:00
nym21 4091ab6b6c release: v0.0.91 2025-08-26 08:31:30 +02:00
nym21 fb9fd5b51a global: add datasets and charts + fixes 2025-08-26 08:31:08 +02:00
nym21 9389700a01 release: v0.0.90 2025-08-24 17:05:51 +02:00
nym21 016c1b2233 changelog: update 2025-08-24 17:05:35 +02:00
nym21 38b8a08297 release: v0.0.89 2025-08-24 16:46:54 +02:00
nym21 c9ffd3ad99 lock: update 2025-08-24 16:46:43 +02:00
nym21 61f960de28 global: snapshot 2025-08-24 16:45:20 +02:00
nym21 da1ff2cacc computer: stateful: maybe got rollback to work, tbd 2025-08-19 23:34:05 +02:00
nym21 05036c682f global: snapshot 2025-08-17 21:38:28 +02:00
nym21 7d47bc8042 changelog: add link to releases and to changes 2025-08-16 22:23:07 +02:00
nym21 98cfd160ef changelog: vibed 2025-08-16 19:16:09 +02:00
nym21 b5e3262b67 readmes: update 2025-08-16 18:21:44 +02:00
nym21 009fb35c4c computer: cleanup 2025-08-16 16:42:01 +02:00
nym21 8648d3131a computer: convert ComputedFrom to LazyFrom 2025-08-13 10:46:28 +02:00
nym21 00c316c35d readmes: vibed 2025-08-13 00:52:23 +02:00
nym21 5f8de8e756 computer: rollback part 1 2025-08-12 22:37:16 +02:00
nym21 ee5dc8fc41 computer: refactor 2025-08-10 16:00:44 +02:00
nym21 a61926988a release: v0.0.88 2025-08-10 14:16:31 +02:00
nym21 bd8c4dfb6b website: fix options 2025-08-10 14:16:13 +02:00
nym21 ce9b4bc4dd release: v0.0.87 2025-08-10 13:43:36 +02:00
nym21 8b12b00114 cargo: update deps 2025-08-10 13:43:17 +02:00
nym21 1775cc1d54 release: v0.0.86 2025-08-10 13:20:04 +02:00
nym21 e4bd09df24 lock: update crates 2025-08-10 13:19:52 +02:00
nym21 5e8c7da4df global: convert brk_vecs to its own crates and repo (seqdb/vecdb) + changes 2025-08-10 12:49:41 +02:00
nym21 c85592eefe release: v0.0.85 2025-08-07 22:35:09 +02:00
nym21 05861c9113 mcp: upgrade + made stateless 2025-08-07 22:34:46 +02:00
nym21 3508d1e315 release: v0.0.84 2025-08-07 21:23:19 +02:00
nym21 e3177b8054 global: per crate build.rs 2025-08-07 21:22:38 +02:00
nym21 03e3760152 global: snapshot + lock file + better errors 2025-08-07 17:29:30 +02:00
nym21 4740610923 global: compressed vecs work again 2025-08-05 23:38:43 +02:00
nym21 e28a0cde55 vecs: fix race condition 2025-08-04 23:48:20 +02:00
nym21 5b855fd835 global: snapshot 2025-08-04 11:38:46 +02:00
nym21 a2f5704581 global: snapshot 2025-08-03 23:38:58 +02:00
nym21 f7aa9424db global: one big snapshot 2025-08-02 16:59:22 +02:00
nym21 aa8b47a3dd global: cleanup 2025-07-29 17:46:30 +02:00
nym21 11911c1898 release: v0.0.83 2025-07-26 23:35:43 +02:00
nym21 4814c1971d vecs: add linux punch hole impl 2025-07-26 23:35:23 +02:00
nym21 be9569f3fb release: v0.0.82 2025-07-26 22:09:21 +02:00
nym21 900e72f95a cargo: cleanup deps 2025-07-26 14:28:26 +02:00
nym21 d2827f188b computer: temp remove rayon 2025-07-26 14:24:06 +02:00
nym21 cf9903b759 computer: init file with min length and regions 2025-07-26 08:57:13 +02:00
nym21 23f96461f4 computer: remove libc dep 2025-07-26 08:42:33 +02:00
nym21 9f2fd26e98 computer: fixes 2025-07-26 08:41:19 +02:00
nym21 78d837c080 computer: flush + punch 2025-07-26 01:04:36 +02:00
nym21 241b9312b7 cli: config changes 2025-07-26 00:46:35 +02:00
nym21 ed70ad7378 indexer: take readers before last export 2025-07-25 22:45:41 +02:00
nym21 00213176d8 indexer: small changes 2025-07-25 22:38:15 +02:00
nym21 406650a45a vec: removed 2025-07-25 20:38:57 +02:00
nym21 56750ccf3c vecs: part 11 2025-07-25 20:27:15 +02:00
nym21 dfc286b393 vecs: part 10 2025-07-25 20:22:54 +02:00
nym21 49a66f72fc crates: update rapidhash 2025-07-24 17:32:38 +02:00
nym21 3f237689da vecs: part 9 2025-07-24 17:19:05 +02:00
nym21 cf1fb483b3 vecs: part 8 2025-07-24 16:48:50 +02:00
nym21 b10f5e3f67 vecs: part 7 2025-07-23 23:55:13 +02:00
nym21 c4fc24c513 vecs: part 6 2025-07-23 09:17:26 +02:00
nym21 3ac9c2d95e vecs: part 5 2025-07-22 21:26:50 +02:00
nym21 e5ab4dafc0 vecs: part 4 2025-07-22 17:36:34 +02:00
nym21 10ae1911c3 vecs: part 3 2025-07-22 15:10:07 +02:00
nym21 73ebcdf0d6 vecs: part 2 2025-07-22 13:19:19 +02:00
nym21 5347523921 vecs: init 2025-07-21 11:02:25 +02:00
nym21 7ef70b953b vec: lazy: remove unneeded phantoms 2025-07-19 17:47:25 +02:00
nym21 ccaca524fe computer: libc sync 2025-07-19 10:10:01 +02:00
nym21 dd51f91cab computer: final fix for external disks crashing 2025-07-18 16:29:53 +02:00
nym21 537d98b41b release: v0.0.81 2025-07-17 23:45:01 +02:00
nym21 9c4cadfc04 vec: fix holes export 2025-07-17 17:29:53 +02:00
nym21 2001370441 mcp: use rust-rmcp instead of brk-rmcp 2025-07-17 16:34:29 +02:00
nym21 cc87b22757 computer: perf improvements 2025-07-17 16:17:21 +02:00
nym21 c0a65b30ad indexer: update example 2025-07-17 11:39:25 +02:00
nym21 c07e66c086 computer: fix stateful 2025-07-17 11:35:40 +02:00
nym21 a0cfc1be2b computer: convert stores to vecs part 2 2025-07-16 16:23:40 +02:00
nym21 1505454793 computer: convert stores to vecs part 1 2025-07-15 22:47:46 +02:00
nym21 e1dff66283 pr: merge #21 from deadmanoz/dockerize
Add Docker support
2025-07-15 15:51:32 +00:00
deadmanoz 5be801a086 Merge branch 'main' into dockerize 2025-07-15 08:50:22 -07:00
deadmanoz 94d4b05c29 Address review feedback 2025-07-15 08:48:39 -07:00
nym21 cebb889f7e cargo: update 2025-07-14 16:00:31 +02:00
nym21 c4ed6ed034 store: remove rotate_memtable as could be the root cause of the issue 2025-07-14 15:48:19 +02:00
nym21 ec960bfefa release: v0.0.80 2025-07-13 21:20:40 +02:00
nym21 79f689dde1 mcp: remove claude results examples due to dead links 2025-07-13 21:20:02 +02:00
nym21 3b3654df56 vec: add local and shared stored_len to raw variant 2025-07-13 19:30:50 +02:00
nym21 c66f008f07 release: v0.0.79 2025-07-13 17:18:14 +02:00
nym21 37d9498d90 crates: upgrade 2025-07-13 17:18:02 +02:00
nym21 1ff67093db website: apply datasets changes to charts 2025-07-13 17:14:34 +02:00
nym21 daed37ccb8 stores: forgot some files 2025-07-13 16:52:19 +02:00
nym21 d41d807b4f stores: bloom filters back to default due to slow reads, v3 will bring down the needed RAM 2025-07-13 16:49:45 +02:00
nym21 d6fa5c8a55 vec: fix header reading of existing file 2025-07-13 16:31:22 +02:00
nym21 2dd608dfed vec: don't store mmap in struct anymore 2025-07-13 11:50:34 +02:00
nym21 a98546f605 release: v0.0.78 2025-07-13 02:05:28 +02:00
nym21 3567559d4e release: v0.0.77 2025-07-13 01:54:51 +02:00
nym21 216476ee45 computer: reduce number of ratio datasets for some cohorts 2025-07-13 01:25:45 +02:00
nym21 3fc28c07fb computer: missed a file 2025-07-13 00:23:32 +02:00
nym21 85f6ef063d computer: perf again 2025-07-13 00:21:20 +02:00
nym21 1e71e2d68f computer: perf 2025-07-12 16:17:29 +02:00
nym21 b24a29895f global: perf + resource imprv 2025-07-12 15:07:02 +02:00
nym21 0167a2ae59 global: fixes 2025-07-12 11:18:51 +02:00
nym21 2c867103ca computer: remove dbg 2025-07-11 14:23:26 +02:00
nym21 8c289df336 computer: stateful: perf improvements 2025-07-11 11:43:53 +02:00
nym21 4489920cbf computer: fix coarse lazy indexes 2025-07-11 01:51:04 +02:00
nym21 029a85081b global: snapshot 2025-07-10 22:32:04 +02:00
nym21 1bc739d07f vec + comp: small changes 2025-07-10 18:35:54 +02:00
nym21 c229e218f6 global: adding semester + making coarser intervals computed instead of eager 2025-07-10 17:44:19 +02:00
nym21 a66f4ad4bd release: v0.0.76 2025-07-09 13:41:42 +02:00
nym21 1dd687dab7 bundler: upgrade rolldown dep 2025-07-09 13:41:31 +02:00
nym21 50ff6e2745 release: v0.0.75 2025-07-09 12:33:06 +02:00
nym21 811dec713b computer: reduce even more the number of par threads for compute_rest_part2 2025-07-09 12:32:50 +02:00
nym21 617d6f4bd7 release: v0.0.74 2025-07-09 11:51:24 +02:00
nym21 57cd2d6252 computer: fix par compute_rest_part2 crashing external drives 2025-07-09 11:48:41 +02:00
nym21 ec64f8d048 release: v0.0.73 2025-07-09 08:36:29 +02:00
nym21 ed288a9dba website: make panes the same size + remove saving their height 2025-07-09 00:06:38 +02:00
nym21 27da0a4102 packages: add lightweight-charts v5.0.8 2025-07-08 23:37:06 +02:00
nym21 3c01ba1a76 release: v0.0.72 2025-07-08 22:35:16 +02:00
nym21 252c8833ae global: upgrade deps 2025-07-08 22:34:51 +02:00
nym21 f45fb6efe6 global: renames and fixes 2025-07-08 21:33:18 +02:00
nym21 8cc1f8d691 computer: add more up to and from datasets 2025-07-07 23:53:59 +02:00
nym21 bff22b5182 websites: snapshot + todo: init 2025-07-07 13:16:43 +02:00
nym21 d31d47eb32 computer: store part 10 2025-07-05 17:44:51 +02:00
nym21 5fe984c39d indexer: rename some stores 2025-07-05 00:17:23 +02:00
nym21 7f07b0daa7 global: move addressindex_to_outputindex stores from computer to indexer 2025-07-04 17:30:43 +02:00
deadmanoz 5de9757d46 Remove services from docker 2025-07-04 16:37:39 +08:00
deadmanoz f89276d7b8 Remove redundant services 2025-07-04 15:51:28 +08:00
deadmanoz 30ba034206 Move docker artefacts into /docker directory 2025-07-04 13:00:12 +08:00
deadmanoz fa1e5aaa7f Make Parser::new the only entrypoint 2025-07-04 12:15:32 +08:00
deadmanoz 870c70180f Back to a single image/container setup 2025-07-04 11:40:37 +08:00
nym21 6d35c26b3f computer: store part 9 2025-07-03 19:15:02 +02:00
nym21 be4e693a27 computer: store part 8 2025-07-03 18:19:36 +02:00
nym21 5810276156 computer: store part 7 2025-07-02 23:49:24 +02:00
nym21 d10ac3f87b computer: store part 6 2025-07-02 16:02:18 +02:00
nym21 9810bc09e9 computer: store part 5 2025-07-01 20:03:00 +02:00
nym21 a0a13eb2a8 computer: store part 4 2025-07-01 13:57:48 +02:00
nym21 6e996797b8 computer: store part 3 2025-06-29 17:39:31 +02:00
nym21 663092b501 global: replace Value enum with Cow 2025-06-27 20:39:19 +02:00
nym21 8ea13544de computer: store part 2 2025-06-27 19:38:44 +02:00
nym21 e73daa6214 computer: init store 2025-06-27 10:52:36 +02:00
deadmanoz d83a833b4d Switch to multiple container setup 2025-06-27 12:56:25 +08:00
deadmanoz ec3a2f29f0 Docker functionality, change location of 'blk_index_to_blk_recap.json' 2025-06-27 12:56:03 +08:00
nym21 cf92c60a01 mcp: readme 2025-06-26 17:41:00 +02:00
nym21 b7f51b03bc global: snapshot 2025-06-26 16:40:29 +02:00
nym21 903e69ff77 release: v0.0.71 2025-06-25 17:59:19 +02:00
nym21 c4167ddaad mcp: small changes 2025-06-25 10:44:24 +02:00
nym21 50bfdb0d68 release: v0.0.70 2025-06-24 23:53:30 +02:00
nym21 a6cb09ff1c fetcher: fix brk url 2025-06-24 23:53:10 +02:00
nym21 e4c9f23476 release: v0.0.69 2025-06-24 13:23:14 +02:00
nym21 44e5415d43 vec: undo storing file in struct, can overwhelm system 2025-06-24 13:20:44 +02:00
nym21 1c653693ed release: v0.0.68 2025-06-24 12:19:07 +02:00
nym21 39c470ad7a core: increase max open files limit 2025-06-24 12:18:49 +02:00
nym21 1103e538a5 release: v0.0.67 2025-06-24 11:57:25 +02:00
nym21 c0cd4cba6f global: snapshot 2025-06-24 11:56:54 +02:00
nym21 b91120e8d4 readme: update 2025-06-24 08:42:40 +00:00
nym21 005774a4c2 mcp: update readme 2025-06-24 08:41:08 +00:00
nym21 16bbfebfba changelog: update titles 2025-06-24 08:26:01 +00:00
nym21 15505cd82d web: fix index type imports 2025-06-24 10:23:16 +02:00
nym21 016d80e002 server: use etag for vecs instead of date modified 2025-06-24 10:11:03 +02:00
nym21 0f3c267a48 global: rename some indexes 2025-06-23 22:00:09 +02:00
nym21 589bb02411 vec: single file with header 2025-06-23 20:48:00 +02:00
nym21 c0f4ece17b mcp: part 2 2025-06-21 20:34:14 +02:00
nym21 c3ae3cb768 server: mcp + global: refactor 2025-06-21 12:43:14 +02:00
nym21 c9e0f9d985 query: remove dup 'h' short index 2025-06-19 14:16:24 +02:00
nym21 e3431c2fa3 release: v0.0.66 2025-06-19 12:32:04 +02:00
nym21 5979b9771e global: cointime part 1 2025-06-19 12:29:34 +02:00
nym21 aa61832fb2 web: fix options cmumulative possible vecids type 2025-06-18 10:14:09 +02:00
nym21 2ac6e982b1 computer: cumulative destroyed coinblocks and cointime 2025-06-18 10:13:33 +02:00
nym21 3204ddcf07 pr: merge #18 from StevenBlack/typos
Minor refinements to the brk_cli readme
2025-06-17 23:06:32 +02:00
nym21 c87b1c133c release: v0.0.65 2025-06-17 22:30:32 +02:00
nym21 9b275ecdae web: fix hang when candles fetched are slightly diff than before 2025-06-17 22:30:11 +02:00
Steven Black d6fd7de361 Minor refinements to the brk_cli readme
* Fix typos
* Rephrasing some descriptions
* Links to run command parameters now link to the code as it is in blob  49d66a133e specifically.
* Lightly introduce some nice features of Github flavored markdown (IMPORTANT and TIP) to assess how this nuance is received by maintainers.
2025-06-17 14:07:03 -04:00
nym21 49d66a133e release: v0.0.64 2025-06-17 18:49:17 +02:00
nym21 c559f26d0e global: add a bunch of realized datasets + charts 2025-06-17 18:47:04 +02:00
nym21 bbe9f1bad2 global: 4y zscore + 200d sma + mayer's multiple 2025-06-17 10:01:49 +02:00
nym21 7e1fb6472d release: v0.0.63 2025-06-16 23:46:57 +02:00
nym21 0ff8d20573 web: fix the fix for the stutter + pwa assets 2025-06-16 23:46:39 +02:00
nym21 9c1f9448dc release: v0.0.62 2025-06-16 18:56:59 +02:00
nym21 43a6081dd6 web: fix stutter on update and save default chart settings to url params 2025-06-16 18:56:38 +02:00
nym21 985e961876 web: fix error in lockdown safari + charts: update instead of setData when possible 2025-06-16 18:20:56 +02:00
nym21 098f6de047 release: v0.0.61 2025-06-15 17:30:49 +02:00
nym21 1b0f90fd68 release: v0.0.60 2025-06-15 17:27:41 +02:00
nym21 12252f407b computer: fix open of ohlc if fetched from different API than prev ohlc 2025-06-15 17:27:16 +02:00
nym21 3b6e3f47ab release: v0.0.59 2025-06-15 12:40:46 +02:00
nym21 6a9ac9b025 brk: fix bundler use + bundler: remove minify html crate 2025-06-15 12:39:50 +02:00
nym21 ae6aa4088b release: v0.0.58 2025-06-15 01:50:22 +02:00
nym21 c08f431180 bundler: deploy brk_rolldown + fix edge case 2025-06-15 01:50:01 +02:00
nym21 123c1f56e9 release: v0.0.57 2025-06-14 22:47:57 +02:00
nym21 35ac65a864 server: update cache control for bundled websites 2025-06-14 22:47:26 +02:00
nym21 e9f362cc87 bundler: init working version 2025-06-14 20:17:49 +02:00
nym21 65685c23e1 release: v0.0.56 2025-06-13 18:03:28 +02:00
nym21 2f74748cea computer: stateful: reset when reorg detected 2025-06-13 18:03:09 +02:00
nym21 f477bd66f3 release: v0.0.55 2025-06-13 10:23:38 +02:00
nym21 d7d77ae8f0 global: multiple fixes 2025-06-13 10:22:03 +02:00
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
525 changed files with 52457 additions and 47528 deletions
+11 -6
View File
@@ -1,4 +1,4 @@
# This file was autogenerated by dist: https://opensource.axo.dev/cargo-dist/
# This file was autogenerated by dist: https://axodotdev.github.io/cargo-dist
#
# Copyright 2022-2024, axodotdev
# SPDX-License-Identifier: MIT or Apache-2.0
@@ -47,7 +47,7 @@ on:
jobs:
# Run 'dist plan' (or host) to determine what tasks we need to do
plan:
runs-on: "ubuntu-latest"
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.plan.outputs.manifest }}
tag: ${{ !github.event.pull_request && github.ref_name || '' }}
@@ -58,12 +58,13 @@ jobs:
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install dist
# we specify bash to get pipefail; it guards against the `curl` command
# failing. otherwise `sh` won't catch that `curl` returned non-0
shell: bash
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.28.0/cargo-dist-installer.sh | sh"
run: "curl --proto '=https' --tlsv1.2 -LsSf https://github.com/axodotdev/cargo-dist/releases/download/v0.29.0/cargo-dist-installer.sh | sh"
- name: Cache dist
uses: actions/upload-artifact@v4
with:
@@ -117,6 +118,7 @@ jobs:
git config --global core.longpaths true
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install Rust non-interactively if not already installed
if: ${{ matrix.container }}
@@ -168,13 +170,14 @@ jobs:
needs:
- plan
- build-local-artifacts
runs-on: "ubuntu-latest"
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
BUILD_MANIFEST_NAME: target/distrib/global-dist-manifest.json
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
@@ -218,12 +221,13 @@ jobs:
if: ${{ always() && needs.plan.outputs.publishing == 'true' && (needs.build-global-artifacts.result == 'skipped' || needs.build-global-artifacts.result == 'success') && (needs.build-local-artifacts.result == 'skipped' || needs.build-local-artifacts.result == 'success') }}
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
runs-on: "ubuntu-latest"
runs-on: "ubuntu-22.04"
outputs:
val: ${{ steps.host.outputs.manifest }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
- name: Install cached dist
uses: actions/download-artifact@v4
@@ -282,10 +286,11 @@ jobs:
# still allowing individual publish jobs to skip themselves (for prereleases).
# "host" however must run to completion, no skipping allowed!
if: ${{ always() && needs.host.result == 'success' }}
runs-on: "ubuntu-latest"
runs-on: "ubuntu-22.04"
env:
GH_TOKEN: ${{ secrets.GITHUB_TOKEN }}
steps:
- uses: actions/checkout@v4
with:
persist-credentials: false
submodules: recursive
+16 -4
View File
@@ -3,6 +3,9 @@
# Builds
target
websites/dist
bridge/
/ids.txt
# Copies
*\ copy*
@@ -10,9 +13,18 @@ target
# Ignored
_*
# Editors
.vscode
.zed
# Logs
.log
# Environment variables/configs
.env
# Profiling
profile.json.gz
flamegraph.svg
*.trace
# AI
.claude
CLAUDE.md
CLAUDE*.md
+20
View File
@@ -0,0 +1,20 @@
{
"file_scan_exclusions": [
// default
"**/.git",
"**/.svn",
"**/.hg",
"**/.jj",
"**/CVS",
"**/.DS_Store",
"**/Thumbs.db",
"**/.classpath",
"**/.settings",
// custom
"**/lean-qr/*/index.mjs",
"uFuzzy.mjs",
"lightweight-charts.standalone.production.mjs",
"**/modern-screenshot/*/index.mjs",
"**/solidjs-signals/*/dist/prod.js"
]
}
+909 -15
View File
@@ -1,15 +1,909 @@
# Changelog
*This changelog was generated by Claude Code.*
All notable changes to the Bitcoin Research Kit (BRK) project will be documented in this file.
## [v0.0.89](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.89) - 2025-08-24
### Documentation
- **Enhanced**: Comprehensive rewrite of all crate README files for improved clarity and developer experience across all workspace crates: [brk](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk/README.md), [brk_bundler](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_bundler/README.md), [brk_computer](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_computer/README.md), [brk_cli](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_cli/README.md), [brk_error](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_error/README.md)
- **Updated**: Main project [README](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/README.md) with better structure and comprehensive architecture documentation
- **Standardized**: README format across all workspace crates with consistent styling and improved developer experience
- **Added**: More detailed descriptions for each crate's purpose and functionality with examples
### Computer Module - Major Refactoring
- **Refactored**: Converted ComputedFrom pattern to [LazyFrom pattern](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_computer/src/grouped/builder_lazy.rs) for improved performance and memory efficiency
- **Added**: New [LazyVecBuilder implementation](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_computer/src/grouped/builder_lazy.rs) for on-demand computation with support for first, average, sum, max, min, last, and cumulative operations
- **Restructured**: Computer module [stateful operations](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_computer/src/stateful/mod.rs) with improved organization and rollback functionality
- **Enhanced**: [Address type organization](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_computer/src/stateful/addresstype/) into dedicated module structure
- **Improved**: Stateful rollback functionality for better error recovery and blockchain reorganization handling
### Data Structures
- **Added**: New [StoredI16](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_structs/src/structs/stored_i16.rs) data type for efficient 16-bit signed integer storage with compression support
- **Enhanced**: StoredF32 with additional [utility methods](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/crates/brk_structs/src/structs/stored_f32.rs) for mathematical operations
### Website Frontend
- **Updated**: Upgraded solid-signals from v0.3.2 to [solidjs-signals v0.4.1](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/websites/default/packages/solidjs-signals/) for improved reactivity and performance
- **Enhanced**: Frontend package management and dependency organization
- **Improved**: Chart rendering performance with updated signal library
### Build System
- **Updated**: All crate versions from 0.0.88 to 0.0.89 across the workspace
- **Enhanced**: [Cargo.toml](https://github.com/bitcoinresearchkit/brk/blob/v0.0.89/Cargo.toml) dependency management and version consistency
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.88...v0.0.89)
## [v0.0.88](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.88) - 2025-08-10
### Website
- **Fixed**: Conditional rendering of adjusted SOPR (Adjusted Spent Output Profit Ratio) charts in website options
- **Improved**: SOPR chart options now only display adjusted SOPR when the data is available in the vector indexes
- **Enhanced**: Better error handling for missing adjusted SOPR datasets
### Build System
- **Updated**: Added `rust-version.workspace = true` to all crate Cargo.toml files
- **Updated**: Bumped all crate versions from 0.0.87 to 0.0.88
- **Added**: Minimum supported Rust version constraint (1.89) to workspace configuration
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.87...v0.0.88)
## [v0.0.87](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.87) - 2025-08-10
### Major Architecture Change
- **BREAKING**: Extracted `brk_vecs` into separate external crate `vecdb`
- **Moved**: Vector storage engine to separate [seqdb](https://github.com/seqdb/seqdb) repository
- **Updated**: All crates now use `vecdb` instead of internal `brk_vecs`
- **Improved**: Better separation of concerns with vector storage as independent library
### Storage Engine
- **Renamed**: `brk_vecs``vecdb`
- **Renamed**: `brk_vecs_macros``vecdb_derive`
- **Updated**: All vector database operations now use `vecdb::Database` instead of `brk_vecs::File`
- **Enhanced**: Vector storage is now a standalone crate with its own versioning
### Dependencies
- **Updated**: `brk_rmcp` from 0.4.1 → 0.5.0
- **Updated**: `cc` from 1.2.31 → 1.2.32
- **Updated**: `hashbrown` from 0.15.4 → 0.15.5
- **Updated**: `rapidhash` from 3.0.0 → 3.1.0
- **Updated**: `rustversion` from 1.0.21 → 1.0.22
- **Updated**: `slab` from 0.4.10 → 0.4.11
### Configuration
- **Added**: `--exchanges` flag to control whether to fetch prices from exchange APIs
- **Enhanced**: Price fetching now has two levels - BRK API and exchanges
- **Improved**: Better RPC authentication error messages with troubleshooting guidance
### Build System
- **Updated**: All crate versions from 0.0.85 to 0.0.87
- **Added**: New dependency on external `vecdb` crate with derive features
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.85...v0.0.87)
## [v0.0.85](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.85) - 2025-08-07
### MCP (Model Context Protocol)
- **Updated**: Upgraded MCP implementation
- **Changed**: Made MCP stateless mode the default (was previously stateful to avoid breaking Claude)
- **Improved**: Better MCP server configuration with stateless operations
### Logging
- **Enhanced**: Updated logging utilities in `brk_logger`
### Build System
- **Updated**: Bumped version from 0.0.84 to 0.0.85
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.84...v0.0.85)
## [v0.0.84](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.84) - 2025-08-07
### Build System
- **Added**: Per-crate `build.rs` files for better build configuration
- **Improved**: Cargo.lock file management and error handling
- **Enhanced**: Build process optimization across all crates
### Vector Storage
- **Fixed**: Compressed vector functionality restored
- **Fixed**: Race condition in vector operations
- **Improved**: Vector storage stability and performance
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.83...v0.0.84)
## [v0.0.83](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.83) - 2025-07-26
### Vector Storage
- **Added**: Linux punch hole implementation for better disk space management
- **Enhanced**: Memory-mapped file handling improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.82...v0.0.83)
## [v0.0.82](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.82) - 2025-07-26
### Dependencies
- **Updated**: Cleaned up Cargo dependencies
- **Removed**: Temporarily removed rayon parallelization from computer module
- **Enhanced**: Dependency management and build optimization
### Computer Module
- **Fixed**: File initialization with minimum length and regions
- **Improved**: Memory management and performance optimizations
- **Added**: Flush and punch operations for better disk usage
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.81...v0.0.82)
## [v0.0.81](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.81) - 2025-07-17
### Vector Storage
- **Fixed**: Holes export functionality in vector storage
- **Enhanced**: Vector storage reliability
### MCP Integration
- **Updated**: Now uses `rust-rmcp` instead of `brk-rmcp` for better compatibility
### Computer Module
- **Improved**: Performance optimizations
- **Fixed**: Stateful operations
- **Enhanced**: Vector-to-store conversion process (parts 1 & 2)
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.80...v0.0.81)
## [v0.0.80](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.80) - 2025-07-13
### MCP (Model Context Protocol)
- **Removed**: Claude results examples due to dead links
- **Cleaned**: MCP example documentation
### Vector Storage
- **Added**: Local and shared `stored_len` tracking to raw vector variant
- **Enhanced**: Vector storage metadata management
### Dependencies
- **Updated**: Multiple crate upgrades for better compatibility
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.79...v0.0.80)
## [v0.0.79](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.79) - 2025-07-13
### Dependencies
- **Updated**: Upgraded multiple crates for better compatibility and performance
### Website
- **Applied**: Dataset changes to chart configurations
- **Enhanced**: Chart display with updated dataset handling
### Vector Storage
- **Fixed**: Header reading functionality for existing vector files
- **Improved**: Memory management by removing mmap storage from struct
- **Enhanced**: Vector file handling reliability
### Store Operations
- **Reverted**: Bloom filters back to default settings due to slow read performance
- **Optimized**: Memory usage for future v3 improvements
- **Fixed**: Missing store-related files
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.78...v0.0.79)
## [v0.0.78](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.78) - 2025-07-13
### Build System
- **Updated**: All crate versions bumped for version consistency across workspace
- **Enhanced**: Version consistency across workspace
### Computer Module
- **Optimized**: Reduced number of ratio datasets for some cohorts to improve performance
- **Enhanced**: Performance optimizations across computation modules
- **Improved**: Resource utilization and memory management
- **Fixed**: Coarse lazy indexes functionality
- **Removed**: Debug statements for cleaner production builds
### Global Improvements
- **Added**: Semester-based time intervals
- **Changed**: Coarser intervals now computed instead of eager evaluation
- **Fixed**: Various global module issues
- **Enhanced**: Performance and resource improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.76...v0.0.78)
## [v0.0.76](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.76) - 2025-07-09
### Bundler
- **Updated**: Upgraded Rolldown dependency for better bundling performance
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.75...v0.0.76)
## [v0.0.75](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.75) - 2025-07-09
### Computer Module
- **Fixed**: Reduced parallel thread count for `compute_rest_part2` to prevent external drive crashes
- **Improved**: Better handling of external storage devices during computation
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.74...v0.0.75)
## [v0.0.74](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.74) - 2025-07-09
### Computer Module
- **Fixed**: Parallel computation crashes on external drives
- **Enhanced**: Storage device compatibility improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.73...v0.0.74)
## [v0.0.73](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.73) - 2025-07-09
### Website
- **Fixed**: Chart panes now maintain equal sizes
- **Removed**: Automatic saving of pane heights preference
- **Updated**: Lightweight Charts to v5.0.8
- **Added**: TODO functionality initialization
- **Enhanced**: Website development snapshots
### Dependencies
- **Updated**: Global dependency upgrades across all crates
- **Enhanced**: System-wide dependency management
### Computer Module
- **Added**: More up-to and from datasets for enhanced analysis
- **Implemented**: Store functionality (parts 2-10)
- **Enhanced**: Data storage architecture with store-based approach
- **Replaced**: Value enum with Cow for better memory efficiency
### Global System
- **Moved**: `addressindex_to_outputindex` stores from computer to indexer module
- **Renamed**: Various stores for better organization
- **Enhanced**: Global module fixes and improvements
### Indexer
- **Renamed**: Store organization for better clarity
- **Enhanced**: Store management improvements
### Documentation
- **Added**: MCP (Model Context Protocol) README documentation
- **Enhanced**: Developer documentation
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.71...v0.0.73)
## [v0.0.71](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.71) - 2025-06-25
### MCP (Model Context Protocol)
- **Enhanced**: Small improvements to MCP functionality
- **Updated**: MCP integration refinements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.70...v0.0.71)
## [v0.0.70](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.70) - 2025-06-25
### Fetcher Module
- **Fixed**: BRK API URL endpoints
- **Enhanced**: Price fetching reliability
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.69...v0.0.70)
## [v0.0.69](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.69) - 2025-06-24
### Vector Storage
- **Reverted**: Storing file in struct (was overwhelming system resources)
- **Improved**: Memory management for vector operations
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.68...v0.0.69)
## [v0.0.68](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.68) - 2025-06-24
### Core System
- **Increased**: Maximum open files limit for better file handling
- **Enhanced**: System resource management
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.67...v0.0.68)
## [v0.0.67](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.67) - 2025-06-24
### Server Module
- **Changed**: Now uses ETag for vectors instead of date modified for better caching
- **Enhanced**: HTTP caching strategy improvements
### Vector Storage
- **Unified**: Single file with header approach for vector storage
- **Improved**: Vector file organization and management
### MCP Integration
- **Added**: MCP part 2 implementation
- **Refactored**: Server MCP integration
- **Enhanced**: MCP functionality expansion
### Global Improvements
- **Renamed**: Various indexes for better clarity
- **Updated**: Index naming conventions
- **Fixed**: Web index type imports
### CLI
- **Removed**: Duplicate 'h' short index in query functionality
### Documentation
- **Updated**: README improvements
- **Updated**: MCP documentation
- **Updated**: Changelog titles
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.66...v0.0.67)
## [v0.0.66](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.66) - 2025-06-19
### Cointime Implementation
- **Added**: Cointime functionality part 1
- **Implemented**: Cumulative destroyed coinblocks and cointime calculations
- **Enhanced**: Bitcoin time-based analytics
### Website
- **Fixed**: Options cumulative possible vecids type issue
- **Enhanced**: Chart options handling
### Documentation
- **Merged**: Pull request #18 with typo fixes from StevenBlack
- **Improved**: Code quality and documentation accuracy
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.65...v0.0.66)
## [v0.0.65](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.65) - 2025-06-17
### Website
- **Fixed**: Hang issue when candles fetched are slightly different than before
- **Enhanced**: Chart data fetching reliability
### Documentation
- **Improved**: Minor refinements to brk_cli README
- **Enhanced**: CLI documentation clarity
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.64...v0.0.65)
## [v0.0.64](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.64) - 2025-06-17
### Datasets
- **Added**: Multiple new realized datasets and charts
- **Added**: 4-year Z-score calculations
- **Added**: 200-day Simple Moving Average (SMA)
- **Added**: Mayer's Multiple indicator
- **Enhanced**: Financial analysis capabilities
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.63...v0.0.64)
## [v0.0.63](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.63) - 2025-06-16
### Website
- **Fixed**: Web interface stutter issue (follow-up fix)
- **Added**: PWA (Progressive Web App) assets
- **Enhanced**: User experience improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.62...v0.0.63)
## [v0.0.62](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.62) - 2025-06-16
### Website
- **Fixed**: Stutter on update and save default chart settings to URL parameters
- **Enhanced**: Chart settings persistence
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.61...v0.0.62)
## [v0.0.61](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.61) - 2025-06-15
### Website
- **Fixed**: Error in Safari lockdown mode
- **Improved**: Charts now update instead of setData when possible for better performance
### Computer Module
- **Fixed**: OHLC data opening when fetched from different API than previous OHLC (preparation fix)
- **Enhanced**: API data source consistency
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.59...v0.0.61)
## [v0.0.59](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.59) - 2025-06-15
### Computer Module
- **Fixed**: Open of OHLC data when fetched from different API than previous OHLC
- **Enhanced**: API data consistency handling
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.58...v0.0.59)
## [v0.0.58](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.58) - 2025-06-15
### Bundler
- **Fixed**: BRK bundler usage
- **Removed**: HTML minify crate for improved bundling
- **Deployed**: brk_rolldown bundler with edge case fixes
- **Initialized**: Working bundler version
- **Added**: Basic bundling functionality
### Server
- **Updated**: Cache control for bundled websites (continued improvements)
- **Enhanced**: Website serving performance
- **Enhanced**: Static file serving performance
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.56...v0.0.58)
## [v0.0.56](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.56) - 2025-06-13
### Computer Module
- **Enhanced**: Stateful operations now reset when blockchain reorganization detected
- **Improved**: Blockchain state management reliability
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.55...v0.0.56)
## [v0.0.55](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.55) - 2025-06-13
### Global
- **Fixed**: Multiple system-wide fixes and improvements
- **Enhanced**: Overall system stability
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.54...v0.0.55)
## [v0.0.54](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.54) - 2025-06-12
### Website
- **Enhanced**: Filter possible index choices in charts (initial implementation)
- **Enhanced**: Filter possible index choices in charts
- **Fixed**: CSS styling issues
- **Added**: Auto price series type for default website
- **Added**: Live price functionality
- **Removed**: ScrollToSelected functionality
### Vector Storage
- **Fixed**: Compressed vector functionality (ongoing improvements)
- **Fixed**: Compressed vector functionality (still has slow parallel read performance)
- **Changed**: CLI defaults to raw format for performance
- **Changed**: CLI now defaults to raw format for better performance
### Indexer
- **Enhanced**: Raw format only support
- **Limited**: Now only supports raw format
- **Fixed**: Global system fixes
- **Fixed**: Various global fixes
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.52...v0.0.54)
## [v0.0.52](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.52) - 2025-06-11
### Website
- **Fixed**: Service worker and related components
- **Enhanced**: Progressive Web App functionality
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.51...v0.0.52)
## [v0.0.51](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.51) - 2025-06-11
### Website
- **Enhanced**: Default website improvements and fixes
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.50...v0.0.51)
## [v0.0.50](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.50) - 2025-06-11
### Website
- **Fixed**: Minimum bar spacing for charts
- **Enhanced**: Chart display improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.49...v0.0.50)
## [v0.0.49](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.49) - 2025-06-11
### Build System
- **Updated**: Set full version of all crates for better versioning consistency
- **Enhanced**: Cargo workspace version management
### Website
- **Updated**: Multiple default website snapshots and improvements
### Documentation
- **Updated**: README improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.48...v0.0.49)
## [v0.0.48](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.48) - 2025-06-09
### Website
- **Enhanced**: Default website snapshots and continued development
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.47...v0.0.48)
## [v0.0.47](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.47) - 2025-06-08
### Server
- **Added**: Documentation for index-t-value functionality
- **Added**: Support for `/api/X-to-Y` endpoints
- **Fixed**: Query CLI functionality
- **Added**: Meta API endpoints for better API discoverability
### CLI
- **Added**: Count parameter to query functionality
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.46...v0.0.47)
## [v0.0.46](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.46) - 2025-06-08
### Server
- **Added**: DDoS protection for better server security
- **Enhanced**: Request rate limiting and protection
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.45...v0.0.46)
## [v0.0.45](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.45) - 2025-06-08
### Website
- **Moved**: Service worker to root directory
- **Updated**: Service worker functionality
- **Enhanced**: PWA capabilities
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.44...v0.0.45)
## [v0.0.44](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.44) - 2025-06-07
### Server
- **Fixed**: Existing folder endpoints functionality
- **Enhanced**: File serving reliability
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.43...v0.0.44)
## [v0.0.43](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.43) - 2025-06-07
### Website
- **Updated**: Dependencies and fixed CSS issues
- **Enhanced**: Styling and layout improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.42...v0.0.43)
## [v0.0.42](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.42) - 2025-06-07
### Global System
- **Fixed**: Multiple system fixes and improvements
- **Enhanced**: System-wide stability improvements
- **Resolved**: Data accuracy issues that were causing incorrect results
### Fetcher
- **Added**: Support for new API endpoints
- **Enhanced**: Data fetching capabilities
### Computer Module
- **Fixed**: Coinblocks overflow issue
- **Enhanced**: Calculation accuracy and stability
- **Increased**: Flush frequency for better data persistence
- **Fixed**: Eager mode functionality
- **Enhanced**: Performance and reliability
### Documentation
- **Reset**: Changelog for clean organization
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.40...v0.0.42)
## [v0.0.40](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.40) - 2025-05-25
### Global System
- **Fixed**: Multiple system-wide issues
- **Enhanced**: Overall stability and performance
- **Resolved**: Data accuracy issues in global computations
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.39...v0.0.40)
## [v0.0.39](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.39) - 2025-05-25
### UTXO System
- **Implemented**: UTXO dataset functionality (initial part 1)
- **Implemented**: UTXO dataset functionality (part 1)
- **Added**: Foundation for unspent transaction output analysis
- **Added**: Unspent transaction output tracking and analysis
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.37...v0.0.39)
## [v0.0.37](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.37) - 2025-05-14
### DCA Analysis
- **Added**: Dollar-Cost Averaging (DCA) classes and analysis
- **Enhanced**: Investment strategy analytics
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.36...v0.0.37)
## [v0.0.36](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.36) - 2025-05-13
### Website
- **Added**: Price line functionality to charts
- **Enhanced**: Chart visualization capabilities
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.35...v0.0.36)
## [v0.0.35](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.35) - 2025-05-12
### Financial Analytics
- **Added**: Investment return analysis (lump sum vs DCA)
- **Added**: Average and ratio datasets
- **Added**: Market Simple Moving Averages (SMAs)
- **Enhanced**: Financial analysis tools
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.34...v0.0.35)
## [v0.0.34](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.34) - 2025-05-09
### Website
- **Added**: Market charts functionality
- **Enhanced**: Chart display and interaction
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.33...v0.0.34)
## [v0.0.33](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.33) - 2025-05-08
### Vector Storage
- **Fixed**: Computed vector eager path functionality
- **Enhanced**: Path deletion for lazy vectors
- **Improved**: Vector storage reliability
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.32...v0.0.33)
## [v0.0.32](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.32) - 2025-05-08
### Vector Storage - Lazy Computation
- **Completed**: Lazy computation implementation
- **Enhanced**: On-demand vector calculation
- **Improved**: Memory efficiency for large datasets
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.31...v0.0.32)
## [v0.0.31](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.31) - 2025-04-30
### Global System
- **Fixed**: Multiple system fixes and improvements
- **Updated**: Fixed old X (Twitter) links that directed to impersonator accounts
- **Enhanced**: Link security and accuracy
### Computer Module
- **Removed**: Last indexes in favor of count-based approach
- **Enhanced**: Index management efficiency
### Vector Storage
- **Improved**: Vector iteration with `into_iter` for range collection
- **Enhanced**: Iterator performance and memory usage
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.30...v0.0.31)
## [v0.0.30](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.30) - 2025-04-23
### Website
- **Fixed**: Chart data fetching issues
- **Enhanced**: Data loading reliability
### Global System
- **Added**: Pay-to-Anchor (P2A) support
- **Enhanced**: Bitcoin script type support
- **Improved**: Transaction parsing capabilities
### Storage System
- **Unified**: Single keyspace for all stores using fjall
- **Enhanced**: Storage efficiency and organization
### Vector Storage
- **Implemented**: Lazy/eager computation model
- **Enhanced**: Flexible computation strategies
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.29...v0.0.30)
## [v0.0.29](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.29) - 2025-04-22
### Website (Kibo)
- **Fixed**: TypeScript ignore additions for better compilation
- **Enhanced**: Database functionality (part 2)
- **Improved**: Database integration and management
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.28...v0.0.29)
## [v0.0.28](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.28) - 2025-04-19
### Website (Kibo)
- **Implemented**: Database functionality (part 1)
- **Added**: Initial database integration for kibo website
### Computer Module
- **Enhanced**: Computer + kibo integration (part 14) with fixes
- **Improved**: Cross-module functionality
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.27...v0.0.28)
## [v0.0.27](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.27) - 2025-04-18
### Distribution
- **Fixed**: Tag version formatting issues
- **Enhanced**: Release packaging improvements
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.26...v0.0.27)
## [v0.0.26](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.26) - 2025-04-18
### Distribution
- **Fixed**: Ubuntu version compatibility
- **Enhanced**: Linux distribution support
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.25...v0.0.26)
## [v0.0.25](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.25) - 2025-04-18
### Distribution
- **Updated**: Ubuntu version for better compatibility
- **Added**: Manual trigger for GitHub Actions
- **Enhanced**: CI/CD pipeline flexibility
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.24...v0.0.25)
## [v0.0.24](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.24) - 2025-04-18
### Dependencies
- **Updated**: Upgraded multiple crates for better performance
- **Enhanced**: Dependency management
### Computer Module
- **Enhanced**: Computer + kibo integration (part 13)
- **Cleanup**: Kibo-related code organization
- **Continued**: Computer module development (part 12)
### Documentation
- **Updated**: Parser README documentation
- **Enhanced**: Documentation clarity and completeness
- **Updated**: Discord community links
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.23...v0.0.24)
## [v0.0.23](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.23) - 2025-04-14
### Dependencies
- **Updated**: Dependency upgrades across the workspace
- **Enhanced**: Compatibility and performance
- **Enhanced**: System compatibility
### Computer Module
- **Enhanced**: Computer + kibo integration (parts 10-11)
- **Improved**: Cross-component functionality
- **Improved**: Cross-module functionality
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.21...v0.0.23)
## [v0.0.21](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.21) - 2025-04-11
### Computer Module
- **Enhanced**: Computer + kibo integration (part 9)
- **Continued**: Ongoing computer module development
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.20...v0.0.21)
## [v0.0.20](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.20) - 2025-04-10
### Global Cleanup
- **Cleaned**: Old files and legacy code
- **Enhanced**: Codebase organization
### Vector Storage
- **Reworked**: Vector storage implementation (parts 1-4)
- **Optimized**: Performance improvements
- **Enhanced**: Memory efficiency
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.19...v0.0.20)
## [v0.0.19](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.19) - 2025-04-07
### Computer Module
- **Enhanced**: Computer functionality (parts 5-7)
- **Improved**: Data processing capabilities
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.18...v0.0.19)
## [v0.0.18](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.18) - 2025-04-05
### Computer Module
- **Enhanced**: Computer functionality (part 4)
- **Continued**: Core computer development
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.17...v0.0.18)
## [v0.0.17](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.17) - 2025-04-05
### Distribution
- **Moved**: Configuration to `config.toml` format
- **Enhanced**: Configuration management
### Storage
- **Initialized**: Disk-based storage functionality
- **Enhanced**: Data persistence capabilities
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.16...v0.0.17)
## [v0.0.16](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.16) - 2025-04-04
### Server
- **Fixed**: Repository version path issues
- **Enhanced**: Static file serving
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.15...v0.0.16)
## [v0.0.15](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.15) - 2025-04-04
### Server
- **Fixed**: Missing 'v' in version handling
- **Enhanced**: Version management
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.14...v0.0.15)
## [v0.0.14](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.14) - 2025-04-04
### Server
- **Fixed**: Downloaded repository version path
- **Enhanced**: File path resolution
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.13...v0.0.14)
## [v0.0.13](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.13) - 2025-04-04
### Global System
- **Enhanced**: System-wide improvements and snapshots
- **Fixed**: Various stability issues
### Documentation
- **Added**: Warning notices in README
- **Enhanced**: User guidance and safety information
### Website (Kibo)
- **Fixed**: Simulation functionality
- **Enhanced**: User interface improvements
- **Converted**: TypeScript types to JSDoc for better compatibility
- **Upgraded**: Signals library to tresshaked v0.2.4
### Server
- **Added**: API documentation
- **Enhanced**: Development experience
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0.12...v0.0.13)
## [v0.0.12](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0.12) - 2025-04-02
### Server
- **Enhanced**: API documentation and functionality
- **Improved**: Developer experience
### Website (Kibo)
- **Enhanced**: Various improvements (parts 1-5)
- **Changed**: Font from Satoshi to Geist
- **Improved**: Typography and design
### Documentation
- **Updated**: README files with badges and shields
- **Enhanced**: Project presentation
### Dependencies
- **Updated**: Crate updates across workspace
- **Enhanced**: Compatibility and performance
[View changes](https://github.com/bitcoinresearchkit/brk/compare/v0.0...v0.0.12)
## [v0.0](https://github.com/bitcoinresearchkit/brk/releases/tag/v0.0) - 2025-02-23
### Major Project Rewrite
- **BREAKING**: Complete rewrite from legacy "kibo" to modern "BRK" (Bitcoin Research Kit)
- **New**: Modular Rust workspace architecture with separate crates
- **Added**: Modern build system with Cargo workspace
- **Created**: New vector storage engine (`brk_vecs`, later became `vecdb`)
- **Established**: Clear separation between parser, indexer, computer, and server modules
- **Added**: MCP (Model Context Protocol) integration
- **Added**: RESTful API server with multiple output formats
- **Added**: Comprehensive CLI interface
- **Enhanced**: Performance and scalability improvements
---
## Previous Releases (Legacy from kibo project)
<!--
# v0.X.Y | WIP
![Image of the kibo Web App version 0.X.Y](https://github.com/kibo-money/kibo/blob/main/_assets/v0.X.Y.jpg)
![Image of the kibo Web App version 0.X.Y](https://github.com/bitcoinresearchkit/brk/blob/main/assets/v0.X.Y.jpg)
-->
# v0.X.0 | WIP | A new beginning
![Image of BRK's Web App version 0.1.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/brk-v0.1.0.png)
Full rewrite
# [kibo-v0.5.0](https://github.com/kibo-money/kibo/tree/eea56d394bf92c62c81da8b78b8c47ea730683f5) | [873199](https://mempool.space/block/0000000000000000000270925aa6a565be92e13164565a3f7994ca1966e48050) - 2024/12/04
# [kibo-v0.5.0](https://github.com/bitcoinresearchkit/brk/tree/eea56d394bf92c62c81da8b78b8c47ea730683f5) | [873199](https://mempool.space/block/0000000000000000000270925aa6a565be92e13164565a3f7994ca1966e48050) - 2024/12/04
![Image of the kibo Web App version 0.5.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.5.0.jpg)
![Image of the kibo Web App version 0.5.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/kibo-v0.5.0.jpg)
## Datasets
@@ -74,9 +968,9 @@ Full rewrite
- Moved back to this repo
# [kibo-v0.4.0](https://github.com/kibo-money/kibo/tree/a64c544815d9ef785e2fc1323582f774f16b9200) | [861950](https://mempool.space/block/00000000000000000000530d0e30ccf7deeace122dcc99f2668a06c6dad83629) - 2024/09/19
# [kibo-v0.4.0](https://github.com/bitcoinresearchkit/brk/tree/a64c544815d9ef785e2fc1323582f774f16b9200) | [861950](https://mempool.space/block/00000000000000000000530d0e30ccf7deeace122dcc99f2668a06c6dad83629) - 2024/09/19
![Image of the kibo Web App version 0.4.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.4.0.jpg)
![Image of the kibo Web App version 0.4.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/kibo-v0.4.0.jpg)
## Brand
@@ -111,9 +1005,9 @@ Full rewrite
- Added serving of the website
- Improved `Cache-Control` behavior
# [kibo-v0.3.0](https://github.com/kibo-money/kibo/tree/b68b016091c45b071218fba01bac5b76e8eaf18c) | [853930](https://mempool.space/block/00000000000000000002eb5e9a7950ca2d5d98bd1ed28fc9098aa630d417985d) - 2024/07/26
# [satonomics-v0.3.0](https://github.com/bitcoinresearchkit/brk/tree/b68b016091c45b071218fba01bac5b76e8eaf18c) | [853930](https://mempool.space/block/00000000000000000002eb5e9a7950ca2d5d98bd1ed28fc9098aa630d417985d) - 2024/07/26
![Image of the Satonomics Web App version 0.3.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.3.0.jpg)
![Image of the Satonomics Web App version 0.3.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/satonomics-v0.3.0.jpg)
## Parser
@@ -190,9 +1084,9 @@ Full rewrite
- Only run with a watcher if `cargo watch` is available
- Removed id_to_path file in favor for only `paths.d.ts` in `app/src/types`
# [kibo-v0.2.0](https://github.com/kibo-money/kibo/tree/248187889283597c5dbb806292297453c25e97b8) | [851286](https://mempool.space/block/0000000000000000000281ca7f1bf8c50702bfca168c7af1bdc67c977c1ac8ed) - 2024/07/08
# [satonomics-v0.2.0](https://github.com/bitcoinresearchkit/brk/tree/248187889283597c5dbb806292297453c25e97b8) | [851286](https://mempool.space/block/0000000000000000000281ca7f1bf8c50702bfca168c7af1bdc67c977c1ac8ed) - 2024/07/08
![Image of the Satonomics Web App version 0.2.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.2.0.jpg)
![Image of the Satonomics Web App version 0.2.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/satonomics-v0.2.0.jpg)
## App
@@ -226,9 +1120,9 @@ Full rewrite
- Fixed ulimit only being run in Mac OS instead of whenever the program is detected
# [kibo-v0.1.1](https://github.com/kibo-money/kibo/tree/e55b5195a9de9aea306903c94ed63cb1720fda5f) | [849240](https://mempool.space/block/000000000000000000002b8653988655071c07bb5f7181c038f9326bc86db741) - 2024/06/24
# [satonomics-v0.1.1](https://github.com/bitcoinresearchkit/brk/tree/e55b5195a9de9aea306903c94ed63cb1720fda5f) | [849240](https://mempool.space/block/000000000000000000002b8653988655071c07bb5f7181c038f9326bc86db741) - 2024/06/24
![Image of the Satonomics Web App version 0.1.1](https://github.com/kibo-money/kibo/blob/main/_assets/v0.1.1.jpg)
![Image of the Satonomics Web App version 0.1.1](https://github.com/bitcoinresearchkit/brk/blob/main/assets/satonomics-v0.1.1.jpg)
## Parser
@@ -276,10 +1170,10 @@ Full rewrite
- Deleted old price datasets and their backups
# [kibo-v0.1.0](https://github.com/kibo-money/kibo/tree/a1a576d088c8f83ed32d48753a7611f70a964574) | [848642](https://mempool.space/block/000000000000000000020be5761d70751252219a9557f55e91ecdfb86c4e026a) - 2024/06/19
# [satonomics-v0.1.0](https://github.com/bitcoinresearchkit/brk/tree/a1a576d088c8f83ed32d48753a7611f70a964574) | [848642](https://mempool.space/block/000000000000000000020be5761d70751252219a9557f55e91ecdfb86c4e026a) - 2024/06/19
![Image of the Satonomics Web App version 0.1.0](https://github.com/kibo-money/kibo/blob/main/_assets/v0.1.0.jpg)
![Image of the Satonomics Web App version 0.1.0](https://github.com/bitcoinresearchkit/brk/blob/main/assets/satonomics-v0.1.0.jpg)
# kibo-v0.0.1 | [835444](https://mempool.space/block/000000000000000000009f93907a0dd83c080d5585cc7ec82c076d45f6d7c872) - 2024/03/20
# satonomics-v0.0.1 | [835444](https://mempool.space/block/000000000000000000009f93907a0dd83c080d5585cc7ec82c076d45f6d7c872) - 2024/03/20
![Image of the Satonomics Web App version 0.0.X](https://github.com/kibo-money/kibo/blob/main/_assets/v0.0.X.jpg)
![Image of the Satonomics Web App version 0.0.X](https://github.com/bitcoinresearchkit/brk/blob/main/assets/satonomics-v0.0.1.jpg)
Generated
+2505 -559
View File
File diff suppressed because it is too large Load Diff
+41 -46
View File
@@ -4,55 +4,61 @@ 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.license = "MIT"
package.edition = "2024"
package.version = "0.0.50"
package.version = "0.0.100"
package.homepage = "https://bitcoinresearchkit.org"
package.repository = "https://github.com/bitcoinresearchkit/brk"
package.readme = "README.md"
package.rust-version = "1.89"
[profile.release]
lto = "fat"
codegen-units = 1
panic = "abort"
[profile.profiling]
inherits = "release"
debug = true
[profile.dist]
inherits = "release"
[workspace.dependencies]
arc-swap = "1.7.1"
allocative = { version = "0.3.4", features = ["parking_lot"] }
allocative_derive = "0.3.3"
axum = "0.8.4"
bincode = { version = "2.0.1", features = ["serde"] }
bitcoin = { version = "0.32.6", features = ["serde"] }
bitcoin = { version = "0.32.7", features = ["serde"] }
bitcoincore-rpc = "0.19.0"
brk_cli = { version = "0.0.50", path = "crates/brk_cli" }
brk_computer = { version = "0.0.50", path = "crates/brk_computer" }
brk_core = { version = "0.0.50", path = "crates/brk_core" }
brk_exit = { version = "0.0.50", path = "crates/brk_exit" }
brk_fetcher = { version = "0.0.50", path = "crates/brk_fetcher" }
brk_indexer = { version = "0.0.50", path = "crates/brk_indexer" }
brk_logger = { version = "0.0.50", path = "crates/brk_logger" }
brk_parser = { version = "0.0.50", path = "crates/brk_parser" }
brk_query = { version = "0.0.50", path = "crates/brk_query" }
brk_server = { version = "0.0.50", path = "crates/brk_server" }
brk_state = { version = "0.0.50", path = "crates/brk_state" }
brk_store = { version = "0.0.50", path = "crates/brk_store" }
brk_vec = { version = "0.0.50", path = "crates/brk_vec" }
brk_bundler = { version = "0.0.100", path = "crates/brk_bundler" }
brk_cli = { version = "0.0.100", path = "crates/brk_cli" }
brk_computer = { version = "0.0.100", path = "crates/brk_computer" }
brk_error = { version = "0.0.100", path = "crates/brk_error" }
brk_fetcher = { version = "0.0.100", path = "crates/brk_fetcher" }
brk_indexer = { version = "0.0.100", path = "crates/brk_indexer" }
brk_interface = { version = "0.0.100", path = "crates/brk_interface" }
brk_logger = { version = "0.0.100", path = "crates/brk_logger" }
brk_mcp = { version = "0.0.100", path = "crates/brk_mcp" }
brk_parser = { version = "0.0.100", path = "crates/brk_parser" }
brk_server = { version = "0.0.100", path = "crates/brk_server" }
brk_store = { version = "0.0.100", path = "crates/brk_store" }
brk_structs = { version = "0.0.100", path = "crates/brk_structs" }
byteview = "=0.6.1"
clap = { version = "4.5.40", features = ["string"] }
clap_derive = "4.5.40"
color-eyre = "0.6.5"
derive_deref = "1.1.1"
fjall = "2.11.0"
jiff = "0.2.14"
log = { version = "0.4.27" }
minreq = { version = "2.13.4", features = ["https", "serde_json"] }
rayon = "1.10.0"
serde = { version = "1.0.219" }
fjall = "2.11.2"
jiff = "0.2.15"
log = "0.4.28"
minreq = { version = "2.14.1", features = ["https", "serde_json"] }
parking_lot = "0.12.4"
quick_cache = "0.6.16"
rayon = "1.11.0"
serde = "1.0.219"
serde_bytes = "0.11.17"
serde_derive = "1.0.219"
serde_json = { version = "1.0.140", features = ["float_roundtrip"] }
tabled = "0.20.0"
tokio = { version = "1.45.1", features = ["rt-multi-thread"] }
zerocopy = { version = "0.8.25" }
zerocopy-derive = "0.8.25"
serde_json = { version = "1.0.143", features = ["float_roundtrip"] }
tokio = { version = "1.47.1", features = ["rt-multi-thread"] }
# vecdb = { path = "../seqdb/crates/vecdb", features = ["derive"]}
vecdb = { version = "0.2.11", features = ["derive"]}
zerocopy = "0.8.27"
zerocopy-derive = "0.8.27"
[workspace.metadata.release]
shared-version = true
@@ -61,20 +67,9 @@ pre-release-commit-message = "release: v{{version}}"
tag-message = "release: v{{version}}"
[workspace.metadata.dist]
cargo-dist-version = "0.28.0"
cargo-dist-version = "0.29.0"
ci = "github"
allow-dirty = ["ci"]
installers = []
targets = [
"aarch64-apple-darwin",
"aarch64-unknown-linux-gnu",
"x86_64-apple-darwin",
"x86_64-unknown-linux-gnu",
]
[workspace.metadata.dist.github-custom-runners]
global = "ubuntu-latest"
aarch64-apple-darwin.runner = "macos-14"
x86_64-unknown-linux-gnu.runner = "ubuntu-latest"
x86_64-unknown-linux-gnu.container = { image = "quay.io/pypa/manylinux_2_28_x86_64", host = "x86_64-unknown-linux-musl" }
aarch64-unknown-linux-gnu.runner = "ubuntu-latest"
aarch64-unknown-linux-gnu.container = { image = "quay.io/pypa/manylinux_2_28_x86_64", host = "x86_64-unknown-linux-musl" }
targets = ["aarch64-apple-darwin", "aarch64-unknown-linux-gnu", "x86_64-unknown-linux-gnu"]
rust-toolchain-version = "1.89"
+27 -35
View File
@@ -17,18 +17,12 @@
<a href="https://deps.rs/crate/brk">
<img src="https://deps.rs/crate/brk/latest/status.svg" alt="Dependency status">
</a>
<a href="https://discord.gg/HaR3wpH3nr">
<a href="https://discord.gg/WACpShCB7M">
<img src="https://img.shields.io/discord/1350431684562124850?label=discord" alt="Discord" />
</a>
<a href="https://primal.net/p/nprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6">
<img src="https://img.shields.io/badge/nostr-purple?link=https%3A%2F%2Fprimal.net%2Fp%2Fnprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6" alt="Nostr" />
</a>
<a href="https://bsky.app/profile/bitcoinresearchkit.org">
<img src="https://img.shields.io/badge/bluesky-blue?link=https%3A%2F%2Fbsky.app%2Fprofile%2Fbitcoinresearchkit.org" alt="Bluesky" />
</a>
<a href="https://x.com/brkdotorg">
<img src="https://img.shields.io/badge/x.com-black" alt="X" />
</a>
</p>
The Bitcoin Research Kit is a high-performance toolchain designed to parse, index, compute, serve and visualize data from a Bitcoin node, enabling users to gain deeper insights into the Bitcoin network.
@@ -37,13 +31,15 @@ In other words it's an alternative to [Glassnode](https://glassnode.com), [mempo
The toolkit can be used in various ways to accommodate as many needs as possible:
- **[Website](https://bitcoinresearchkit.org)** \
- **[Website](https://bitview.space)** \
Everyone is welcome to visit the official instance and showcase of the suite's capabilities. \
It has a wide range of functionalities including charts, tables and simulations which you can visit for free and without the need for an account. \
Also available at: [kibo.money](https://kibo.money) // [satonomics.xyz](https://satonomics.xyz)
- **[API](https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#endpoints)** \
Researchers and developers are free to use BRK's public API with ![Datasets variant count](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fbitcoinresearchkit.org%2Fapi%2Fvecs%2Fvariant-count&query=%24&style=flat&label=%20&color=white) dataset variants at your disposal. \
It has a wide range of functionalities including charts, tables and simulations which you can visit for free and without the need for an account.
- **[API](https://github.com/bitcoinresearchkit/brk/tree/main/crates/brk_server#brk-server)** \
Researchers and developers are free to use BRK's public API with ![Datasets variant count](https://img.shields.io/badge/dynamic/json?url=https%3A%2F%2Fbitcoinresearchkit.org%2Fapi%2Fvecs%2Fvec-count&query=%24&style=flat&label=%20&color=white) dataset variants at their disposal. \
Just like the website, it's entirely free, with no authentication or rate-limiting.
- **[AI](https://github.com/bitcoinresearchkit/brk/blob/main/crates/brk_mcp/README.md#brk-mcp)** \
LLMs have to possibility to connect to BRK's backend through a [MCP](https://modelcontextprotocol.io/introduction). \
It will give them access to the same tools as the API, with no restrictions, and allow you to have your very own data analysts.
- **[CLI](https://crates.io/crates/brk_cli)** \
Node runners are strongly encouraged to try out and self-host their own instance using BRK's command line interface. \
The CLI has multiple cogs available for users to tweak to adapt to all situations with even the possibility for web developers to create their own custom website which could later on be added as an alternative front-end.
@@ -55,23 +51,6 @@ The primary goal of this project is to be fully-featured and accessible for ever
In contrast, existing alternatives tend to be either [very costly](https://studio.glassnode.com/pricing) or missing essential features, with the vast majority being closed-source and unverifiable, which fundamentally undermines the principles of Bitcoin.
## Crates
- [`brk`](https://crates.io/crates/brk): Wrapper around all other `brk-*` crates
- [`brk_cli`](https://crates.io/crates/brk_cli): A standalone command line interface to interact with the Bitcoin Research Kit
- [`brk_computer`](https://crates.io/crates/brk_computer): A Bitcoin dataset computer, built on top of brk_indexer
- [`brk_core`](https://crates.io/crates/brk_core): The Core (Structs and Errors) of the Bitcoin Research Kit
- [`brk_exit`](https://crates.io/crates/brk_exit): An exit blocker built on top of ctrlc
- [`brk_fetcher`](https://crates.io/crates/brk_fetcher): A Bitcoin price fetcher
- [`brk_indexer`](https://crates.io/crates/brk_indexer): A Bitcoin Core indexer built on top of brk_parser
- [`brk_logger`](https://crates.io/crates/brk_logger): A clean logger used in the Bitcoin Research Kit.
- [`brk_parser`](https://crates.io/crates/brk_parser): A very fast Bitcoin Core block parser and iterator built on top of bitcoin-rust
- [`brk_query`](https://crates.io/crates/brk_query): A library that finds requested datasets.
- [`brk_server`](https://crates.io/crates/brk_server): A server that serves Bitcoin data and swappable front-ends, built on top of `brk_indexer`, `brk_fetcher` and `brk_computer`
- [`brk_state`](https://crates.io/crates/brk_state): Various states used mainly by the computer
- [`brk_store`](https://crates.io/crates/brk_store): A thin wrapper around [`fjall`](https://crates.io/crates/fjall)
- [`brk_vec`](https://crates.io/crates/brk_vec): A push-only, truncable, compressable, saveable Vec
## Hosting as a service
If you'd like to have your own instance hosted for you please contact [hosting@bitcoinresearchkit.org](mailto:hosting@bitcoinresearchkit.org).
@@ -82,7 +61,7 @@ If you'd like to have your own instance hosted for you please contact [hosting@b
- Updates delivered at your convenience
- Direct communication for feature requests and support
- Bitcoin Core or Knots with desired version
- Optional subdomains: `*.bitcoinresearchkit.org`, `*.kibo.money` and `*.satonomics.xyz`
- Optional subdomains
- Logo featured in the Readme if desired
Pricing: `0.01 BTC / month` *or* `0.1 BTC / year`
@@ -91,12 +70,25 @@ Pricing: `0.01 BTC / month` *or* `0.1 BTC / year`
Deepest gratitude to the [Open Sats](https://opensats.org/) public charity. Their grant — from December 2024 to the present — has been critical in sustaining this project.
Heartfelt thanks go out to every donor on [Nostr](https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44) and [Geyser.fund](https://geyser.fund/project/brk) whose support has ensured the availability of the [kibo.money](https://kibo.money) public instance.
Heartfelt thanks go out to every donor on [Nostr](https://primal.net/p/npub1jagmm3x39lmwfnrtvxcs9ac7g300y3dusv9lgzhk2e4x5frpxlrqa73v44) and [Geyser.fund](https://geyser.fund/project/brk) whose support has ensured the availability of the [bitcoinresearchkit.org](https://bitcoinresearchkit.org) public instance.
## Crates
- [`brk`](https://crates.io/crates/brk): A wrapper around all other `brk-*` crates
- [`brk_bundler`](https://crates.io/crates/brk_bundler): A thin wrapper around [`rolldown`](https://rolldown.rs/)
- [`brk_cli`](https://crates.io/crates/brk_cli): A command line interface to run a BRK instance
- [`brk_computer`](https://crates.io/crates/brk_computer): A Bitcoin dataset computer built on top of [`brk_indexer`](https://crates.io/crates/brk_indexer)
- [`brk_error`](https://crates.io/crates/brk_error): Errors used throughout BRK
- [`brk_fetcher`](https://crates.io/crates/brk_fetcher): A Bitcoin price fetcher
- [`brk_indexer`](https://crates.io/crates/brk_indexer): A Bitcoin indexer built on top of [`brk_parser`](https://crates.io/crates/brk_parser)
- [`brk_interface`](https://crates.io/crates/brk_interface): An interface to find and format data from BRK
- [`brk_logger`](https://crates.io/crates/brk_logger): A thin wrapper around [`env_logger`](https://crates.io/crates/env_logger)
- [`brk_mcp`](https://crates.io/crates/brk_mcp): A bridge for LLMs to access BRK
- [`brk_parser`](https://crates.io/crates/brk_parser): A very fast Bitcoin block parser and iterator built on top of [`bitcoin-rust`](https://crates.io/crates/bitcoin)
- [`brk_server`](https://crates.io/crates/brk_server): A server with an API for anything from BRK
- [`brk_store`](https://crates.io/crates/brk_store): A thin wrapper around [`fjall`](https://crates.io/crates/fjall)
- [`brk_structs`](https://crates.io/crates/brk_structs): Structs used throughout BRK
## Donate
[`bc1q09 8zsm89 m7kgyz e338vf ejhpdt 92ua9p 3peuve`](bitcoin:bc1q098zsm89m7kgyze338vfejhpdt92ua9p3peuve)
[`lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkxmmww3jkuar8d35kgetj8yuq363hv4`](lightning:lnurl1dp68gurn8ghj7ampd3kx2ar0veekzar0wd5xjtnrdakj7tnhv4kxctttdehhwm30d3h82unvwqhkxmmww3jkuar8d35kgetj8yuq363hv4)
[Geyser Fund](https://geyser.fund/project/brk)
+86
View File
@@ -0,0 +1,86 @@
# TODO
- __crates__
- _cli_
- check disk space on first launch
- add custom path support for config.toml
- maybe add bitcoind download and launch support
- via: https://github.com/rust-bitcoin/corepc/blob/master/node
- test read/write speed, add warning if too low (<2gb/s)
- pull latest version and notify is out of date
- _computer_
- **add rollback of states (in stateful)**
- add costs basis by percentile (percentile cost basis) back
- add support for per index computation
- fix min fee_rate which is always ZERO due to coinbase transaction
- before computing multiple sources check their length, panic if not equal
- add oracle price dataset (https://utxo.live/oracle/UTXOracle.py)
- add address counts relative to all datasets
- add revived/sent supply datasets
- add `in-sats` version of all price datasets (average and co)
- add `p2pk` group (sum of `p2pk33` and `p2pk65`)
- add chopiness datasets
- add utxo count, address count, supply data for by reused addresses in groups by address type
- add more date ranges (3-6 months and more)
- add pi cycle dataset
- add all possible charts from:
- https://mainnet.observer
- https://glassnode.com
- https://checkonchain.com
- https://researchbitcoin.net/exciting-update-coming-to-the-bitcoin-lab/
- https://mempool.space/research
- _indexer_
- parse only the needed block number
- maybe using https://developer.bitcoin.org/reference/rpc/getblockhash.html
- _interface_
- create pagination enum
- from to
- from option<count>
- to option<count>
- page + option<per page> default 1000 max 1000
- from/to/count params dont cap all combinations
- example: from -10,000 count 10, wont work if underlying vec isnt 10k or more long
- _parser_
- save `vec` file instead of `json`
- support lock file, process in read only if already opened in write mode
- if less than X (10 maybe ?) get block using rpc instead of parsing the block files
- _server_
- api
- add extensions support (.json .csv …)
- if format instead of extension then don't download file
- add support for https (rustls)
- __docs__
- _README_
- add a comparison table with alternatives
- add contribution section where help is needed
- documentation/mcp/datasets/different front ends
- add faq
- __websites__
- _default_
- explorer
- blocks
- transactions
- addresses
- miners
- maybe xpubs
- charts
- improve names and colors
- selected unit sometimes changes when going back end forth
- add support for custom charts
- price scale format depends on unit, hide digits for sats for example (if/when possible)
- table
- pagination
- exports (.json, .csv,…)
- search
- datasets add legend, and keywords ?
- height/address/txid
- api
- add api page with interactivity
- global
- **fix navigation/history**
- move share button to footer ?
- Use `ichart.createPane()` in wrapper
- improve behavior when local storage is unavailable
- by having a global state
- __global__
- check `TODO`s in codebase
Binary file not shown.

After

Width:  |  Height:  |  Size: 340 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 94 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 78 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 133 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 263 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 208 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 386 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 496 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 564 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 592 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 453 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 526 KiB

+17 -15
View File
@@ -7,49 +7,51 @@ homepage.workspace = true
repository.workspace = true
edition.workspace = true
version.workspace = true
rust-version.workspace = true
build = "build.rs"
[features]
full = [
"core",
"bundler",
"computer",
"exit",
"error",
"fetcher",
"indexer",
"interface",
"logger",
"mcp",
"parser",
"query",
"server",
"state",
"store",
"vec",
"structs",
]
core = ["brk_core"]
bundler = ["brk_bundler"]
computer = ["brk_computer"]
exit = ["brk_exit"]
error = ["brk_error"]
fetcher = ["brk_fetcher"]
indexer = ["brk_indexer"]
interface = ["brk_interface"]
logger = ["brk_logger"]
mcp = ["brk_mcp"]
parser = ["brk_parser"]
query = ["brk_query"]
server = ["brk_server"]
state = ["brk_state"]
store = ["brk_store"]
vec = ["brk_vec"]
structs = ["brk_structs"]
[dependencies]
brk_bundler = { workspace = true, optional = true }
brk_cli = { workspace = true }
brk_core = { workspace = true, optional = true }
brk_computer = { workspace = true, optional = true }
brk_exit = { workspace = true, optional = true }
brk_error = { workspace = true, optional = true }
brk_fetcher = { workspace = true, optional = true }
brk_indexer = { workspace = true, optional = true }
brk_interface = { workspace = true, optional = true }
brk_logger = { workspace = true, optional = true }
brk_mcp = { workspace = true, optional = true }
brk_parser = { workspace = true, optional = true }
brk_query = { workspace = true, optional = true }
brk_server = { workspace = true, optional = true }
brk_state = { workspace = true, optional = true }
brk_store = { workspace = true, optional = true }
brk_vec = { workspace = true, optional = true }
brk_structs = { workspace = true, optional = true }
[package.metadata.docs.rs]
all-features = true
+197
View File
@@ -0,0 +1,197 @@
# brk
**Main wrapper crate for the Bitcoin Research Kit (BRK)**
The `brk` crate serves as the primary entry point for the Bitcoin Research Kit, providing a unified interface to all BRK components through feature flags. It enables developers to selectively include only the components they need while maintaining a consistent API.
## What it provides
- **Unified Access**: Single crate providing access to the entire BRK ecosystem
- **Feature-based Selection**: Choose only the components you need
- **Consistent Versioning**: All components versioned together for compatibility
- **Simplified Dependencies**: Single dependency instead of multiple individual crates
## Available Components
### Core Data Pipeline
- **`parser`** ([brk_parser](../brk_parser/)) - High-performance Bitcoin block parser
- **`indexer`** ([brk_indexer](../brk_indexer/)) - Blockchain data indexer with dual storage
- **`computer`** ([brk_computer](../brk_computer/)) - Analytics engine for computed datasets
- **`interface`** ([brk_interface](../brk_interface/)) - Unified data query and formatting API
### Infrastructure
- **`structs`** ([brk_structs](../brk_structs/)) - Bitcoin-aware type system and data structures
- **`error`** ([brk_error](../brk_error/)) - Centralized error handling
- **`store`** ([brk_store](../brk_store/)) - Blockchain-aware key-value storage
### External Integration
- **`fetcher`** ([brk_fetcher](../brk_fetcher/)) - Bitcoin price data fetcher with multi-source fallback
- **`server`** ([brk_server](../brk_server/)) - HTTP server with REST API
- **`mcp`** ([brk_mcp](../brk_mcp/)) - Model Context Protocol for LLM integration
### Utilities
- **`cli`** ([brk_cli](../brk_cli/)) - Command line interface (always enabled)
- **`logger`** ([brk_logger](../brk_logger/)) - Logging utilities
- **`bundler`** ([brk_bundler](../brk_bundler/)) - Asset bundling for web interfaces
## Usage
### Full Installation
```toml
[dependencies]
brk = { version = "0.0.88", features = ["full"] }
```
```rust
use brk::*;
// Access all components
let config = cli::Config::load()?;
let parser = parser::Parser::new(/* ... */);
let indexer = indexer::Indexer::forced_import("./data")?;
let computer = computer::Computer::forced_import("./data", &indexer, None)?;
```
### Selective Components
```toml
[dependencies]
brk = { version = "0.0.88", features = ["parser", "indexer", "computer"] }
```
```rust
use brk::{parser, indexer, computer};
// Core data pipeline only
let parser = parser::Parser::new(blocks_dir, Some(output_dir), rpc);
let mut indexer = indexer::Indexer::forced_import(output_dir)?;
let mut computer = computer::Computer::forced_import(output_dir, &indexer, None)?;
```
### Minimal Setup
```toml
[dependencies]
brk = { version = "0.0.88", features = ["structs", "parser"] }
```
```rust
use brk::{structs, parser};
// Just parsing and types
let height = structs::Height::new(800_000);
let parser = parser::Parser::new(blocks_dir, Some(output_dir), rpc);
```
## Feature Flags
| Feature | Description | Dependencies |
|---------|-------------|--------------|
| `full` | Enable all components | All crates |
| `cli` | Command line interface | Always enabled |
| `structs` | Core type system | Foundation for other crates |
| `error` | Error handling | Used by most crates |
| `parser` | Block parsing | `structs`, `error` |
| `store` | Key-value storage | `structs`, `error` |
| `indexer` | Blockchain indexing | `parser`, `store` |
| `computer` | Analytics computation | `indexer`, `fetcher` (optional) |
| `fetcher` | Price data fetching | `structs`, `error` |
| `interface` | Data query API | `indexer`, `computer` |
| `server` | HTTP server | `interface`, `mcp` |
| `mcp` | LLM integration | `interface` |
| `logger` | Logging utilities | Standalone |
| `bundler` | Asset bundling | Standalone |
## Common Usage Patterns
### Complete BRK Instance
```rust
use brk::*;
// Full data pipeline setup
let config = cli::Config::load()?;
let rpc = /* Bitcoin Core RPC client */;
let parser = parser::Parser::new(config.blocks_dir, Some(config.output_dir), rpc);
let mut indexer = indexer::Indexer::forced_import(&config.output_dir)?;
let mut computer = computer::Computer::forced_import(&config.output_dir, &indexer, None)?;
let interface = interface::Interface::build(&indexer, &computer);
let server = server::Server::new(interface, config.website_path);
// Start server
server.serve(true).await?;
```
### Data Analysis
```rust
use brk::{indexer, computer, interface};
// Analysis-focused setup
let indexer = indexer::Indexer::forced_import("./brk_data")?;
let computer = computer::Computer::forced_import("./brk_data", &indexer, None)?;
let interface = interface::Interface::build(&indexer, &computer);
// Query data
let params = interface::Params {
index: interface::Index::Height,
ids: vec!["price_usd", "difficulty"].into(),
rest: interface::ParamsOpt::default()
.set_from(-100)
.set_format(interface::Format::CSV),
};
let csv_data = interface.search_and_format(params)?;
```
### Custom Integration
```rust
use brk::{structs, parser, error};
// Custom application with BRK components
fn analyze_blocks() -> error::Result<()> {
let parser = parser::Parser::new(blocks_dir, Some(output_dir), rpc);
parser.parse(None, None)
.iter()
.take(1000) // First 1000 blocks
.for_each(|(height, block, hash)| {
println!("Block {}: {} transactions", height, block.txdata.len());
});
Ok(())
}
```
## Version Compatibility
All BRK crates are released together with synchronized versions. When using the `brk` wrapper crate, you're guaranteed compatibility between all components.
- **Current version**: 0.0.88
- **Rust MSRV**: 1.89+
- **Bitcoin Core**: v25.0 - v29.0
## Performance Characteristics
The `brk` crate itself adds no runtime overhead - it simply re-exports the underlying crates. Performance characteristics depend on which components you use:
- **Full pipeline**: ~13-15 hours initial sync, ~40GB storage overhead
- **Parser only**: ~4 minutes to parse entire blockchain
- **Indexer only**: ~7-8 hours to index blockchain, ~5-6GB RAM usage
- **Server**: Low latency API responses with caching and compression
## Dependencies
The `brk` crate's dependencies are determined by enabled features. Core dependencies include:
- `brk_cli` - Always included for configuration and CLI support
- Individual `brk_*` crates based on enabled features
- Transitive dependencies from enabled components
For specific dependency information, see individual crate READMEs.
---
*This README was generated by Claude Code*
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap_or_default();
if profile == "release" {
println!("cargo:rustc-flag=-C");
println!("cargo:rustc-flag=target-cpu=native");
}
}
+1
View File
@@ -0,0 +1 @@
sudo cargo flamegraph --profile profiling --root
+2
View File
@@ -0,0 +1,2 @@
cargo build --profile profiling
samply record ../../target/profiling/brk
+18 -15
View File
@@ -1,16 +1,23 @@
#![doc = include_str!(concat!("../", env!("CARGO_PKG_README")))]
#![doc = include_str!("../README.md")]
#[cfg(feature = "core")]
#[cfg(feature = "bundler")]
#[doc(inline)]
pub use brk_core as core;
pub use brk_bundler as bundler;
#[doc(inline)]
pub use brk_cli as cli;
#[cfg(feature = "structs")]
#[doc(inline)]
pub use brk_structs as structs;
#[cfg(feature = "computer")]
#[doc(inline)]
pub use brk_computer as computer;
#[cfg(feature = "exit")]
#[cfg(feature = "error")]
#[doc(inline)]
pub use brk_exit as exit;
pub use brk_error as error;
#[cfg(feature = "fetcher")]
#[doc(inline)]
@@ -24,26 +31,22 @@ pub use brk_indexer as indexer;
#[doc(inline)]
pub use brk_logger as logger;
#[cfg(feature = "mcp")]
#[doc(inline)]
pub use brk_mcp as mcp;
#[cfg(feature = "parser")]
#[doc(inline)]
pub use brk_parser as parser;
#[cfg(feature = "query")]
#[cfg(feature = "interface")]
#[doc(inline)]
pub use brk_query as query;
pub use brk_interface as interface;
#[cfg(feature = "server")]
#[doc(inline)]
pub use brk_server as server;
#[cfg(feature = "state")]
#[doc(inline)]
pub use brk_state as state;
#[cfg(feature = "store")]
#[doc(inline)]
pub use brk_store as store;
#[cfg(feature = "vec")]
#[doc(inline)]
pub use brk_vec as vec;
+18
View File
@@ -0,0 +1,18 @@
[package]
name = "brk_bundler"
description = "A thin wrapper around rolldown"
version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
build = "build.rs"
[dependencies]
log = { workspace = true }
notify = "8.2.0"
brk_rolldown = "0.1.4"
# brk_rolldown = { path = "../../../rolldown/crates/rolldown"}
sugar_path = "1.2.0"
tokio = { workspace = true }
+186
View File
@@ -0,0 +1,186 @@
# brk_bundler
**Asset bundling for BRK web interfaces using Rolldown**
`brk_bundler` provides JavaScript/TypeScript bundling capabilities for BRK's web interfaces. It's a thin wrapper around Rolldown (Rust-based Rollup alternative) with BRK-specific configuration for building optimized web assets with file watching and automatic rebuilding.
## What it provides
- **JavaScript Bundling**: Modern ES modules bundling with minification
- **File Watching**: Automatic rebuilding on source file changes
- **Asset Processing**: Copies and processes static assets
- **Version Injection**: Automatic version string replacement in service workers
- **Development Mode**: Live rebuilding for rapid development
## Key Features
### Bundling Capabilities
- **ES Module Support**: Modern JavaScript bundling with tree-shaking
- **Minification**: Automatic code minification for production builds
- **Source Maps**: Generated source maps for debugging
- **Entry Point Processing**: Configurable entry points with hashed output names
### File System Operations
- **Directory Copying**: Copies entire source directories to distribution
- **Selective Processing**: Special handling for specific file types
- **Path Resolution**: Automatic path resolution and asset linking
### Development Features
- **Hot Rebuilding**: Automatic rebuilds on file changes
- **Watch Mode**: Monitors source files and triggers rebuilds
- **Version Replacement**: Injects build version into service workers
## Usage
### Basic Bundling
```rust
use brk_bundler::bundle;
use std::path::Path;
// Bundle without watching (production)
let websites_path = Path::new("./websites");
let source_folder = "default";
let dist_path = bundle(websites_path, source_folder, false).await?;
println!("Bundled to: {:?}", dist_path);
```
### Development Mode with Watching
```rust
// Bundle with file watching (development)
let dist_path = bundle(websites_path, "default", true).await?;
// Bundler now watches for changes and rebuilds automatically
// This will run in the background until the process exits
```
### Integration with BRK CLI
```rust
// Typically called from brk_cli when serving websites
async fn setup_website(config: &Config) -> Result<PathBuf> {
let websites_path = config.websites_path();
let source_folder = match config.website_mode {
WebsiteMode::Default => "default",
WebsiteMode::Custom => "custom",
WebsiteMode::None => return Ok(PathBuf::new()),
};
// Bundle the website assets
let dist_path = bundle(websites_path, source_folder, config.dev_mode).await?;
Ok(dist_path)
}
```
## File Structure
The bundler expects this directory structure:
```
websites/
├── default/ # Default website source
│ ├── index.html # Main HTML file
│ ├── service-worker.js # Service worker (version injected)
│ ├── scripts/ # JavaScript/TypeScript source
│ │ ├── entry.js # Main entry point
│ │ ├── main.js # Application logic
│ │ └── ... # Other JS modules
│ └── assets/ # Static assets
└── dist/ # Generated output directory
├── index.html # Processed HTML with updated script references
├── service-worker.js # Service worker with version injected
├── scripts/ # Bundled and minified JavaScript
│ └── main-[hash].js # Hashed output file
└── assets/ # Copied static assets
```
## Bundling Process
1. **Clean**: Removes existing `dist/` directory
2. **Copy**: Copies all source files to `dist/`
3. **Bundle JavaScript**:
- Processes `scripts/entry.js` as entry point
- Generates minified bundle with source maps
- Creates hashed filename for cache busting
4. **Process HTML**: Updates script references to hashed filenames
5. **Process Service Worker**: Injects current version string
6. **Watch** (if enabled): Monitors for file changes and rebuilds
## Configuration
The bundler uses Rolldown with these optimized settings:
```rust
BundlerOptions {
input: Some(vec![source_entry.into()]), // scripts/entry.js
dir: Some("./dist/scripts".to_string()), // Output directory
cwd: Some(websites_path), // Working directory
minify: Some(RawMinifyOptions::Bool(true)), // Enable minification
sourcemap: Some(SourceMapType::File), // Generate source maps
..Default::default()
}
```
## File Watching
In watch mode, the bundler monitors:
- **Source files**: Non-script files are copied on change
- **JavaScript files**: Trigger full rebuild via Rolldown watcher
- **HTML files**: Processed to update script references
- **Service worker**: Version injection on changes
### Watch Events Handled
- `Create` - New files added
- `Modify` - Existing files changed
- Ignores `Delete` and other events
## Version Injection
Service workers get automatic version injection:
```javascript
// In source service-worker.js
const VERSION = '__VERSION__';
// After bundling
const VERSION = 'v0.0.88';
```
This enables proper cache invalidation across releases.
## Performance Features
- **Async Operations**: All bundling operations are async
- **Incremental Builds**: Only rebuilds changed files in watch mode
- **Parallel Processing**: Uses Tokio for concurrent file operations
- **Efficient Copying**: Direct file system operations
## Error Handling
- **Graceful Failures**: Logs errors but continues watching
- **Path Resolution**: Automatic path absolutization and validation
- **File System Errors**: Proper error propagation with context
## Dependencies
- `brk_rolldown` - Rust-based Rollup bundler
- `notify` - File system watching
- `tokio` - Async runtime for file operations
- `sugar_path` - Path manipulation utilities
- `log` - Error logging
## Integration Points
The bundler integrates with:
- **brk_cli**: Called during website setup
- **brk_server**: Serves bundled assets
- **Development workflow**: Provides live rebuilding
---
*This README was generated by Claude Code*
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap_or_default();
if profile == "release" {
println!("cargo:rustc-flag=-C");
println!("cargo:rustc-flag=target-cpu=native");
}
}
+151
View File
@@ -0,0 +1,151 @@
#![doc = include_str!("../README.md")]
use std::{
fs, io,
path::{Path, PathBuf},
sync::Arc,
};
use brk_rolldown::{Bundler, BundlerOptions, RawMinifyOptions, SourceMapType};
use log::error;
use notify::{EventKind, RecursiveMode, Watcher};
use sugar_path::SugarPath;
use tokio::sync::Mutex;
const VERSION: &str = env!("CARGO_PKG_VERSION");
pub async fn bundle(websites_path: &Path, source_folder: &str, watch: bool) -> io::Result<PathBuf> {
let source_path = websites_path.join(source_folder);
let dist_path = websites_path.join("dist");
let _ = fs::remove_dir_all(&dist_path);
copy_dir_all(&source_path, &dist_path)?;
let source_scripts = format!("./{source_folder}/scripts");
let source_entry = format!("{source_scripts}/entry.js");
let absolute_websites_path = websites_path.absolutize();
let mut bundler = Bundler::new(BundlerOptions {
input: Some(vec![source_entry.into()]),
dir: Some("./dist/scripts".to_string()),
cwd: Some(absolute_websites_path),
minify: Some(RawMinifyOptions::Bool(true)),
sourcemap: Some(SourceMapType::File),
..Default::default()
});
if let Err(error) = bundler.write().await {
error!("{error:?}");
}
let absolute_source_index_path = source_path.join("index.html").absolutize();
let absolute_source_index_path_clone = absolute_source_index_path.clone();
let absolute_source_path = source_path.absolutize();
let absolute_source_path_clone = absolute_source_path.clone();
let absolute_source_scripts_path = websites_path.join(source_scripts).absolutize();
let absolute_source_sw_path = source_path.join("service-worker.js").absolutize();
let absolute_source_sw_path_clone = absolute_source_sw_path.clone();
let absolute_dist_entry_path = dist_path.join("scripts/entry.js").absolutize();
let absolute_dist_index_path = dist_path.join("index.html").absolutize();
let absolute_dist_path = dist_path.absolutize();
let absolute_dist_path_clone = absolute_dist_path.clone();
let absolute_dist_sw_path = dist_path.join("service-worker.js").absolutize();
let write_index = move || {
let mut contents = fs::read_to_string(&absolute_source_index_path).unwrap();
if let Ok(entry) = fs::read_to_string(absolute_dist_path_clone.join("scripts/entry.js"))
&& let Some(start) = entry.find("main")
&& let Some(end) = entry.find(".js")
{
let main_hashed = &entry[start..end];
contents = contents.replace("/scripts/main.js", &format!("/scripts/{main_hashed}.js"));
}
let _ = fs::write(&absolute_dist_index_path, contents);
};
let write_sw = move || {
let contents = fs::read_to_string(&absolute_source_sw_path)
.unwrap()
.replace("__VERSION__", &format!("v{VERSION}"));
let _ = fs::write(&absolute_dist_sw_path, contents);
};
write_index();
write_sw();
if !watch {
return Ok(dist_path);
}
tokio::spawn(async move {
let write_index_clone = write_index.clone();
let mut entry_watcher = notify::recommended_watcher(
move |res: Result<notify::Event, notify::Error>| match res {
Ok(_) => write_index_clone(),
Err(e) => error!("watch error: {e:?}"),
},
)
.unwrap();
entry_watcher
.watch(&absolute_dist_entry_path, RecursiveMode::Recursive)
.unwrap();
let mut source_watcher = notify::recommended_watcher(
move |res: Result<notify::Event, notify::Error>| match res {
Ok(event) => match event.kind {
EventKind::Create(_) => event.paths,
EventKind::Modify(_) => event.paths,
_ => vec![],
}
.into_iter()
.filter(|path| path.starts_with(&absolute_source_path))
.filter(|path| !path.starts_with(&absolute_source_scripts_path))
.for_each(|source_path| {
let suffix = source_path.strip_prefix(&absolute_source_path).unwrap();
let dist_path = absolute_dist_path.join(suffix);
if source_path == absolute_source_index_path_clone {
write_index();
} else if source_path == absolute_source_sw_path_clone {
write_sw();
} else {
let _ = fs::copy(&source_path, &dist_path);
}
}),
Err(e) => error!("watch error: {e:?}"),
},
)
.unwrap();
source_watcher
.watch(&absolute_source_path_clone, RecursiveMode::Recursive)
.unwrap();
let watcher =
brk_rolldown::Watcher::new(vec![Arc::new(Mutex::new(bundler))], None).unwrap();
watcher.start().await;
});
Ok(dist_path)
}
fn copy_dir_all(src: impl AsRef<Path>, dst: impl AsRef<Path>) -> io::Result<()> {
fs::create_dir_all(&dst)?;
for entry in fs::read_dir(src)? {
let entry = entry?;
let ty = entry.file_type()?;
if ty.is_dir() {
copy_dir_all(entry.path(), dst.as_ref().join(entry.file_name()))?;
} else {
fs::copy(entry.path(), dst.as_ref().join(entry.file_name()))?;
}
}
Ok(())
}
+12 -10
View File
@@ -1,32 +1,34 @@
[package]
name = "brk_cli"
description = "A command line interface to interact with the full Bitcoin Research Kit"
description = "A command line interface to run a BRK instance"
version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
build = "build.rs"
[dependencies]
bitcoincore-rpc = { workspace = true }
brk_bundler = { workspace = true }
brk_computer = { workspace = true }
brk_core = { workspace = true }
brk_exit = { workspace = true }
brk_fetcher = { workspace = true }
brk_indexer = { workspace = true }
brk_interface = { workspace = true }
brk_logger = { workspace = true }
brk_parser = { workspace = true }
brk_query = { workspace = true }
brk_server = { workspace = true }
brk_vec = { workspace = true }
clap = { workspace = true }
clap_derive = { workspace = true }
color-eyre = { workspace = true }
vecdb = { workspace = true }
clap = { version = "4.5.47", features = ["string"] }
clap_derive = "4.5.47"
color-eyre = "0.6.5"
log = { workspace = true }
minreq = { workspace = true }
serde = { workspace = true }
tabled = { workspace = true }
tokio = { workspace = true }
toml = "0.8.23"
toml = "0.9.5"
zip = { version = "5.0.0", default-features = false, features = ["deflate"] }
[[bin]]
name = "brk"
+166 -68
View File
@@ -1,91 +1,189 @@
# BRK Cli
# brk_cli
<p align="left">
<a href="https://github.com/bitcoinresearchkit/brk">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/bitcoinresearchkit/brk?style=social">
</a>
<a href="https://github.com/bitcoinresearchkit/brk/blob/main/LICENSE.md">
<img src="https://img.shields.io/crates/l/brk" alt="License" />
</a>
<a href="https://crates.io/crates/brk_cli">
<img src="https://img.shields.io/crates/v/brk_cli" alt="Version" />
</a>
<a href="https://docs.rs/brk_cli">
<img src="https://img.shields.io/docsrs/brk_cli" alt="Documentation" />
</a>
<img src="https://img.shields.io/crates/size/brk_cli" alt="Size" />
<a href="https://deps.rs/crate/brk_cli">
<img src="https://deps.rs/crate/brk_cli/latest/status.svg" alt="Dependency status">
</a>
<a href="https://discord.gg/HaR3wpH3nr">
<img src="https://img.shields.io/discord/1350431684562124850?label=discord" alt="Discord" />
</a>
<a href="https://primal.net/p/nprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6">
<img src="https://img.shields.io/badge/nostr-purple?link=https%3A%2F%2Fprimal.net%2Fp%2Fnprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6" alt="Nostr" />
</a>
<a href="https://bsky.app/profile/bitcoinresearchkit.org">
<img src="https://img.shields.io/badge/bluesky-blue?link=https%3A%2F%2Fbsky.app%2Fprofile%2Fbitcoinresearchkit.org" alt="Bluesky" />
</a>
<a href="https://x.com/brkdotorg">
<img src="https://img.shields.io/badge/x.com-black" alt="X" />
</a>
</p>
**Command line interface for running complete BRK instances**
A command line interface to interact with the full Bitcoin Research Kit. It's built on top of every other create and gives the possility to use BRK using the terminal instead of Rust.
`brk_cli` provides the main command-line interface for operating the Bitcoin Research Kit. It orchestrates the complete data pipeline from Bitcoin Core block parsing through analytics computation to HTTP API serving, with automatic configuration management and graceful operation.
It has 2 commandes for now (other than `help` and `version`) which are `run` and `query`. The former is used to run the processing (indexer + computer) and/or the server. The latter uses `brk_query` as its backend just like to server to be able to get datasets via the terminal instead of the API. Both commands are very costumizable by having all the parameters of their Rust counterparts ([`run`](https://github.com/bitcoinresearchkit/brk/blob/main/crates/brk_cli/src/run.rs#L91-L147), [`query`](https://github.com/bitcoinresearchkit/brk/blob/main/crates/brk_query/src/params.rs)).
## What it provides
## Requirements
- **Complete Pipeline Orchestration**: Coordinates parser, indexer, computer, and server components
- **Automatic Configuration**: Saves settings to `~/.brk/config.toml` for consistent operation
- **Continuous Operation**: Handles blockchain updates and incremental processing
- **Web Interface Options**: Configurable website serving (none, default, custom)
- **Graceful Shutdown**: Ctrl+C handling with proper cleanup
### Hardware
## Key Features
#### Recommended
### Pipeline Management
- **Automatic dependency handling**: Ensures Bitcoin Core sync before processing
- **Incremental updates**: Only processes new blocks since last run
- **Error recovery**: Automatic retry logic and graceful error handling
- **Resource management**: Optimized memory usage and disk I/O
- [Latest base model Mac mini](https://www.apple.com/mac-mini/)
- [Thunderbolt 4 SSD enclosure](https://satechi.net/products/usb4-nvme-ssd-pro-enclosure/Z2lkOi8vc2hvcGlmeS9Qcm9kdWN0VmFyaWFudC80MDE4ODQ3MDA2NzI4OA==?queryID=7961465089021ee203a60db7e62e90d2)
- [2 TB NVMe SSD](https://shop.sandisk.com/products/ssd/internal-ssd/wd-black-sn850x-nvme-ssd?sku=WDS200T2X0E-00BCA0)
### Configuration System
- **Auto-save configuration**: All CLI options saved to persistent config
- **Flexible paths**: Configurable Bitcoin directory, blocks directory, and output directory
- **RPC authentication**: Cookie file or username/password authentication
- **Data source options**: Configurable price fetching and exchange APIs
#### Minimum
### Operation Modes
- **Initial sync**: Full blockchain processing from genesis
- **Continuous operation**: Real-time processing of new blocks
- **Update mode**: Resume from last processed block
- **Server mode**: HTTP API with optional web interface
To be determined
### Software
- [Bitcoin](https://bitcoin.org/en/full-node)
- [Rust](https://www.rust-lang.org/tools/install)
- Unix based operating system (Mac OS or Linux)
- Ubuntu users need to install `open-ssl` via `sudo apt install libssl-dev pkg-config`
## Download
### Binaries
You can find a pre-built binary for your operating system on the releases page ([link](https://github.com/bitcoinresearchkit/brk/releases/latest)).
### Cargo
## Installation
### Binary Release
```bash
# Install
cargo install brk # or `cargo install brk_cli`, the result is the same
# Update
cargo install brk # or `cargo install-update -a` if you have `cargo-update` installed
# Download from GitHub releases
# https://github.com/bitcoinresearchkit/brk/releases/latest
```
### Source
### Via Cargo
```bash
cargo install brk --locked
```
### From Source
```bash
git clone https://github.com/bitcoinresearchkit/brk.git
cd brk/crates/brk
cargo run -r
cd brk && cargo build --release
```
## Usage
Run `brk -h` to view each available command and their respective description.
### First Run (Configuration Setup)
`-h` works also for commands, which mean that `brk run -h` will explain all the parameters of `brk run` for example.
```bash
# Basic setup with default options
brk --brkdir ./my_brk_data
Every parameter set for `brk run` will be saved at `~/.brk/config.toml`, which will allow you to simply run `brk run` next time.
# Full configuration
brk --bitcoindir ~/.bitcoin \
--brkdir ./brk_data \
--fetch true \
--exchanges true \
--website default
```
Then the easiest to let others access your server is to use `cloudflared` which will also cache requests. For more information go to: https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/
### Subsequent Runs
```bash
# Uses saved configuration from ~/.brk/config.toml
brk
# Override specific options
brk --website none --fetch false
```
### Command Line Options
```bash
brk --help
```
## Configuration Reference
All options are automatically saved to `~/.brk/config.toml`:
### Core Paths
- `--bitcoindir <PATH>` - Bitcoin Core directory (default: `~/.bitcoin`)
- `--blocksdir <PATH>` - Block files directory (default: `bitcoindir/blocks`)
- `--brkdir <PATH>` - BRK output directory (default: `~/.brk`)
### Data Sources
- `--fetch <BOOL>` - Enable price data fetching (default: `true`)
- `--exchanges <BOOL>` - Use exchange APIs for prices (default: `true`)
### Web Interface
- `--website <OPTION>` - Web interface mode:
- `none` - API only, no web interface
- `default` - Built-in web interface from `websites/default/`
- `custom` - Serve custom website from `websites/custom/`
### Bitcoin Core RPC
- `--rpcconnect <IP>` - RPC host (default: `localhost`)
- `--rpcport <PORT>` - RPC port (default: `8332`)
- `--rpccookiefile <PATH>` - Cookie authentication file
- `--rpcuser <USERNAME>` - Username authentication
- `--rpcpassword <PASSWORD>` - Password authentication
## Operation Flow
1. **Configuration Loading**: Loads saved config from `~/.brk/config.toml`
2. **Bitcoin Core Connection**: Establishes RPC connection and waits for sync
3. **Data Pipeline Initialization**: Sets up parser, indexer, computer, and interface
4. **Processing Loop**:
- Index new blocks from Bitcoin Core
- Compute analytics on new data
- Update cached data
5. **Server Startup**: Launches HTTP API with optional web interface
6. **Continuous Operation**: Monitors for new blocks and processes incrementally
## System Requirements
- **Bitcoin Core**: Fully synced node with RPC enabled
- **Storage**: ~32% of blockchain size (~233GB as of 2025)
- **Memory**:
- Peak: ~7-8GB during initial indexing
- Steady state: ~4-5GB during operation
- **OS**: macOS or Linux
- Ubuntu: `sudo apt install libssl-dev pkg-config`
## Performance Characteristics
### Initial Sync
- **Full blockchain processing**: ~13-15 hours total
- **Parser phase**: ~4 minutes for block parsing
- **Indexer phase**: ~7-8 hours for data indexing
- **Computer phase**: ~6-7 hours for analytics computation
### Continuous Operation
- **New block processing**: 3-5 seconds per block
- **API response times**: Typically <100ms with caching
- **Memory usage**: Stable ~4-5GB during normal operation
## Configuration File
Example `~/.brk/config.toml`:
```toml
bitcoindir = "/Users/username/.bitcoin"
blocksdir = "/Users/username/.bitcoin/blocks"
brkdir = "/Users/username/brk_data"
fetch = true
exchanges = true
website = "default"
rpcconnect = "localhost"
rpcport = 8332
rpccookiefile = "/Users/username/.bitcoin/.cookie"
```
## Error Handling
- **Bitcoin Core sync**: Waits for node sync before processing
- **RPC connection**: Automatic retry logic for connection issues
- **Processing errors**: Graceful error handling with detailed logging
- **Graceful shutdown**: Ctrl+C handling with proper cleanup and state saving
## Logging
Logs are written to `~/.brk/brk.log` with colored console output:
- Request/response logging with timing
- Processing progress indicators
- Error reporting and debugging information
## Dependencies
- `brk_parser` - Bitcoin block parsing
- `brk_indexer` - Blockchain data indexing
- `brk_computer` - Analytics computation
- `brk_interface` - Data query interface
- `brk_server` - HTTP API server
- `brk_logger` - Logging utilities
- `bitcoincore_rpc` - Bitcoin Core RPC client
- `color_eyre` - Enhanced error reporting
---
*This README was generated by Claude Code*
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap_or_default();
if profile == "release" {
println!("cargo:rustc-flag=-C");
println!("cargo:rustc-flag=target-cpu=native");
}
}
+154
View File
@@ -0,0 +1,154 @@
use std::{fs, io, path::Path};
use brk_computer::pools;
use brk_interface::{Index, Interface};
use brk_server::VERSION;
use crate::website::Website;
const BRIDGE_PATH: &str = "scripts/bridge";
#[allow(clippy::upper_case_acronyms)]
pub trait Bridge {
fn generate_bridge_files(&self, website: Website, websites_path: &Path) -> io::Result<()>;
}
impl Bridge for Interface<'static> {
fn generate_bridge_files(&self, website: Website, websites_path: &Path) -> io::Result<()> {
if website.is_none() {
return Ok(());
}
let path = websites_path.join(website.to_folder_name());
if !fs::exists(&path)? {
return Ok(());
}
let path = path.join(BRIDGE_PATH);
fs::create_dir_all(&path)?;
generate_vecs_file(self, &path)?;
generate_pools_file(&path)
}
}
fn generate_pools_file(parent: &Path) -> io::Result<()> {
let path = parent.join(Path::new("pools.js"));
let pools = pools();
let mut contents = "//
// File auto-generated, any modifications will be overwritten
//
"
.to_string();
contents += "
/** @typedef {ReturnType<typeof createPools>} Pools */
/** @typedef {keyof Pools} Pool */
export function createPools() {
return /** @type {const} */ ({
";
let mut sorted_pools: Vec<_> = pools.iter().collect();
sorted_pools.sort_by(|a, b| a.name.to_lowercase().cmp(&b.name.to_lowercase()));
contents += &sorted_pools
.iter()
.map(|pool| {
let id = pool.serialized_id();
format!(" {id}: \"{}\",", pool.name)
})
.collect::<Vec<_>>()
.join("\n");
contents += "\n });\n}\n";
fs::write(path, contents)
}
fn generate_vecs_file(interface: &Interface<'static>, parent: &Path) -> io::Result<()> {
let path = parent.join(Path::new("vecs.js"));
let indexes = Index::all();
let mut contents = format!(
"//
// File auto-generated, any modifications will be overwritten
//
export const VERSION = \"v{VERSION}\";
"
);
contents += &indexes
.iter()
.enumerate()
.map(|(i_of_i, i)| {
// let lowered = i.to_string().to_lowercase();
format!("/** @typedef {{{i_of_i}}} {i} */",)
})
.collect::<Vec<_>>()
.join("\n");
contents += &format!(
"\n\n/** @typedef {{{}}} Index */\n",
indexes
.iter()
.map(|i| i.to_string())
.collect::<Vec<_>>()
.join(" | ")
);
contents += "
/** @typedef {ReturnType<typeof createIndexes>} Indexes */
export function createIndexes() {
return {
";
contents += &indexes
.iter()
.enumerate()
.map(|(i_of_i, i)| {
let lowered = i.to_string().to_lowercase();
format!(" {lowered}: /** @satisfies {{{i}}} */ ({i_of_i}),",)
})
.collect::<Vec<_>>()
.join("\n");
contents += " };\n}\n";
contents += "
/** @typedef {ReturnType<typeof createVecIdToIndexes>} VecIdToIndexes
/** @typedef {keyof VecIdToIndexes} VecId */
/**
* @returns {Record<any, number[]>}
*/
export function createVecIdToIndexes() {
return {
";
interface
.id_to_index_to_vec()
.iter()
.for_each(|(id, index_to_vec)| {
let indexes = index_to_vec
.keys()
.map(|i| (*i as u8).to_string())
// .map(|i| i.to_string())
.collect::<Vec<_>>()
.join(", ");
contents += &format!(" \"{id}\": [{indexes}],\n");
});
contents += " };\n}\n";
fs::write(path, contents)
}
@@ -1,176 +1,86 @@
use std::{
fs,
path::{Path, PathBuf},
thread::sleep,
time::Duration,
};
use bitcoincore_rpc::{self, Auth, Client, RpcApi};
use brk_computer::Computer;
use brk_core::{default_bitcoin_path, default_brk_path, dot_brk_path};
use brk_exit::Exit;
use bitcoincore_rpc::{self, Auth, Client};
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_server::{Server, Website};
use brk_vec::{Computation, Format};
use clap_derive::{Parser, ValueEnum};
use clap::Parser;
use color_eyre::eyre::eyre;
use log::info;
use serde::{Deserialize, Serialize};
use serde::{Deserialize, Deserializer, Serialize};
pub fn run(config: RunConfig) -> color_eyre::Result<()> {
let config = RunConfig::import(Some(config))?;
use crate::{default_bitcoin_path, default_brk_path, dot_brk_path, website::Website};
let rpc = config.rpc()?;
let exit = Exit::new();
let parser = brk_parser::Parser::new(config.blocksdir(), rpc);
let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?;
indexer.import_stores()?;
indexer.import_vecs()?;
let wait_for_synced_node = || -> color_eyre::Result<()> {
let is_synced = || -> color_eyre::Result<bool> {
let info = rpc.get_blockchain_info()?;
Ok(info.headers == info.blocks)
};
if !is_synced()? {
info!("Waiting for node to be synced...");
while !is_synced()? {
sleep(Duration::from_secs(1))
}
}
Ok(())
};
let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format);
computer.import_stores(&indexer)?;
computer.import_vecs(&indexer, config.computation())?;
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(async {
let server = if config.serve() {
let served_indexer = indexer.clone();
let served_computer = computer.clone();
let server = Server::new(served_indexer, served_computer, config.website())?;
let opt = Some(tokio::spawn(async move {
server.serve().await.unwrap();
}));
sleep(Duration::from_secs(1));
opt
} else {
None
};
if config.process() {
loop {
wait_for_synced_node()?;
let block_count = rpc.get_block_count()?;
info!("{} blocks found.", block_count + 1);
let starting_indexes = indexer.index(&parser, rpc, &exit)?;
computer.compute(&mut indexer, starting_indexes, &exit)?;
if let Some(delay) = config.delay() {
sleep(Duration::from_secs(delay))
}
info!("Waiting for new blocks...");
while block_count == rpc.get_block_count()? {
sleep(Duration::from_secs(1))
}
}
}
if let Some(handle) = server {
handle.await.unwrap();
}
Ok(())
})
}
const DOWNLOADS: &str = "downloads";
#[derive(Parser, Debug, Default, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize)]
pub struct RunConfig {
#[command(version, about)]
pub struct Config {
/// Bitcoin main directory path, defaults: ~/.bitcoin, ~/Library/Application\ Support/Bitcoin, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")]
bitcoindir: Option<String>,
/// Bitcoin blocks directory path, default: --bitcoindir/blocks, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")]
blocksdir: Option<String>,
/// Bitcoin Research Kit outputs directory path, default: ~/.brk, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")]
brkdir: Option<String>,
/// Executed by the runner, default: all, saved
#[arg(short, long)]
mode: Option<Mode>,
/// 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
#[arg(short, long)]
computation: Option<Computation>,
/// Activate compression of datasets, set to true to save disk space or false if prioritize speed, default: compressed, saved
#[arg(short, long, value_name = "FORMAT")]
format: Option<Format>,
/// Activate fetching prices from exchanges APIs and the computation of all related datasets, default: true, saved
/// Activate fetching prices from BRK's API and the computation of all price related datasets, default: true, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short = 'F', long, value_name = "BOOL")]
fetch: Option<bool>,
/// Website served by the server (if active), default: default, saved
/// Activate fetching prices from exchanges APIs if `fetch` is also set to `true`, default: true, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "BOOL")]
exchanges: Option<bool>,
/// Website served by the server, default: default, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(short, long)]
website: Option<Website>,
/// Bitcoin RPC ip, default: localhost, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "IP")]
rpcconnect: Option<String>,
/// Bitcoin RPC port, default: 8332, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PORT")]
rpcport: Option<u16>,
/// Bitcoin RPC cookie file, default: --bitcoindir/.cookie, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PATH")]
rpccookiefile: Option<String>,
/// Bitcoin RPC username, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "USERNAME")]
rpcuser: Option<String>,
/// Bitcoin RPC password, saved
#[serde(default, deserialize_with = "default_on_error")]
#[arg(long, value_name = "PASSWORD")]
rpcpassword: Option<String>,
/// Delay between runs, default: 0, saved
#[arg(long, value_name = "SECONDS")]
delay: Option<u64>,
/// DEV: Activate checking address hashes for collisions when indexing, default: false, saved
#[arg(long, value_name = "BOOL")]
#[serde(default, deserialize_with = "default_on_error")]
#[arg(skip)]
check_collisions: Option<bool>,
}
impl RunConfig {
pub fn import(config_args: Option<RunConfig>) -> color_eyre::Result<Self> {
impl Config {
pub fn import() -> color_eyre::Result<Self> {
let config_args = Some(Config::parse());
let path = dot_brk_path();
let _ = fs::create_dir_all(&path);
@@ -192,20 +102,12 @@ impl RunConfig {
config_saved.brkdir = Some(brkdir);
}
if let Some(mode) = config_args.mode.take() {
config_saved.mode = Some(mode);
}
if let Some(computation) = config_args.computation.take() {
config_saved.computation = Some(computation);
}
if let Some(fetch) = config_args.fetch.take() {
config_saved.fetch = Some(fetch);
}
if let Some(format) = config_args.format.take() {
config_saved.format = Some(format);
if let Some(exchanges) = config_args.exchanges.take() {
config_saved.exchanges = Some(exchanges);
}
if let Some(website) = config_args.website.take() {
@@ -232,15 +134,11 @@ impl RunConfig {
config_saved.rpcpassword = Some(rpcpassword);
}
if let Some(delay) = config_args.delay.take() {
config_saved.delay = Some(delay);
}
if let Some(check_collisions) = config_args.check_collisions.take() {
config_saved.check_collisions = Some(check_collisions);
}
if config_args != RunConfig::default() {
if config_args != Config::default() {
dbg!(config_args);
panic!("Didn't consume the full config")
}
@@ -252,19 +150,6 @@ impl RunConfig {
config.write(&path)?;
// info!("Configuration {{");
// info!(" bitcoindir: {:?}", config.bitcoindir);
// info!(" brkdir: {:?}", config.brkdir);
// info!(" mode: {:?}", config.mode);
// info!(" website: {:?}", config.website);
// info!(" rpcconnect: {:?}", config.rpcconnect);
// info!(" rpcport: {:?}", config.rpcport);
// info!(" rpccookiefile: {:?}", config.rpccookiefile);
// info!(" rpcuser: {:?}", config.rpcuser);
// info!(" rpcpassword: {:?}", config.rpcpassword);
// info!(" delay: {:?}", config.delay);
// info!("}}");
Ok(config)
}
@@ -292,7 +177,9 @@ impl RunConfig {
if self.rpc_auth().is_err() {
println!(
"No way found to authenticate the RPC client, please either set --rpccookiefile or --rpcuser and --rpcpassword.\nRun the program with '-h' for help."
"Unsuccessful authentication with the RPC client.
First make sure that `bitcoind` is running. If it is then please either set --rpccookiefile or --rpcuser and --rpcpassword as the default values seemed to have failed.
Finally, you can run the program with '-h' for help."
);
std::process::exit(1);
}
@@ -300,7 +187,7 @@ impl RunConfig {
fn read(path: &Path) -> Self {
fs::read_to_string(path).map_or_else(
|_| RunConfig::default(),
|_| Config::default(),
|contents| toml::from_str(&contents).unwrap_or_default(),
)
}
@@ -343,10 +230,6 @@ impl RunConfig {
self.rpcport
}
pub fn delay(&self) -> Option<u64> {
self.delay
}
pub fn bitcoindir(&self) -> PathBuf {
self.bitcoindir
.as_ref()
@@ -366,22 +249,12 @@ impl RunConfig {
.map_or_else(default_brk_path, |s| Self::fix_user_path(s.as_ref()))
}
pub fn outputsdir(&self) -> PathBuf {
self.brkdir().join("outputs")
}
pub fn harsdir(&self) -> PathBuf {
self.outputsdir().join("hars")
self.brkdir().join("hars")
}
pub fn process(&self) -> bool {
self.mode
.is_none_or(|m| m == Mode::All || m == Mode::Processor)
}
pub fn serve(&self) -> bool {
self.mode
.is_none_or(|m| m == Mode::All || m == Mode::Server)
pub fn downloads_dir(&self) -> PathBuf {
dot_brk_path().join(DOWNLOADS)
}
fn path_cookiefile(&self) -> PathBuf {
@@ -410,24 +283,20 @@ impl RunConfig {
}
pub fn website(&self) -> Website {
self.website.unwrap_or(Website::Default)
self.website.unwrap_or(Website::Bitview)
}
pub fn fetch(&self) -> bool {
self.fetch.is_none_or(|b| b)
}
pub fn exchanges(&self) -> bool {
self.exchanges.is_none_or(|b| b)
}
pub fn fetcher(&self) -> Option<Fetcher> {
self.fetch()
.then(|| Fetcher::import(Some(self.harsdir().as_path())).unwrap())
}
pub fn computation(&self) -> Computation {
self.computation.unwrap_or_default()
}
pub fn format(&self) -> Format {
self.format.unwrap_or_default()
.then(|| Fetcher::import(self.exchanges(), Some(self.harsdir().as_path())).unwrap())
}
pub fn check_collisions(&self) -> bool {
@@ -435,23 +304,13 @@ impl RunConfig {
}
}
#[derive(
Default,
Debug,
Clone,
Copy,
Parser,
ValueEnum,
Serialize,
Deserialize,
PartialEq,
Eq,
PartialOrd,
Ord,
)]
pub enum Mode {
#[default]
All,
Processor,
Server,
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()),
}
}
+134 -31
View File
@@ -1,46 +1,149 @@
use std::{fs, thread};
#![doc = include_str!("../README.md")]
use brk_core::{dot_brk_log_path, dot_brk_path};
use brk_query::Params as QueryArgs;
use clap::Parser;
use clap_derive::{Parser, Subcommand};
use query::query;
use run::{RunConfig, run};
use std::{
fs,
io::Cursor,
path::Path,
thread::{self, sleep},
time::Duration,
};
mod query;
mod run;
use bitcoincore_rpc::{self, RpcApi};
use brk_bundler::bundle;
use brk_computer::Computer;
use brk_indexer::Indexer;
use brk_interface::Interface;
use brk_parser::Parser;
use brk_server::{Server, VERSION};
use log::info;
use vecdb::Exit;
#[derive(Parser)]
#[command(version, about)]
#[command(propagate_version = true)]
struct Cli {
#[command(subcommand)]
command: Commands,
}
mod bridge;
mod config;
mod paths;
mod website;
#[derive(Subcommand, Debug)]
enum Commands {
/// Run the indexer, computer and server
Run(RunConfig),
/// Query generated datasets via the `run` command in a similar fashion as the server's API
Query(QueryArgs),
}
use crate::{bridge::Bridge, config::Config, paths::*};
pub fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
fs::create_dir_all(dot_brk_path())?;
brk_logger::init(Some(&dot_brk_log_path()));
let cli = Cli::parse();
brk_logger::init(Some(&dot_brk_log_path()))?;
thread::Builder::new()
.stack_size(128 * 1024 * 1024)
.spawn(|| match cli.command {
Commands::Run(args) => run(args),
Commands::Query(args) => query(args),
})?
.stack_size(256 * 1024 * 1024)
.spawn(run)?
.join()
.unwrap()
}
pub fn run() -> color_eyre::Result<()> {
let config = Config::import()?;
let rpc = config.rpc()?;
let exit = Exit::new();
exit.set_ctrlc_handler();
let parser = Parser::new(config.blocksdir(), Some(config.brkdir()), rpc);
let mut indexer = Indexer::forced_import(&config.brkdir())?;
let wait_for_synced_node = |rpc_client: &bitcoincore_rpc::Client| -> color_eyre::Result<()> {
let is_synced = || -> color_eyre::Result<bool> {
let info = rpc_client.get_blockchain_info()?;
Ok(info.headers == info.blocks)
};
if !is_synced()? {
info!("Waiting for node to be synced...");
while !is_synced()? {
sleep(Duration::from_secs(1))
}
}
Ok(())
};
let mut computer = Computer::forced_import(&config.brkdir(), &indexer, config.fetcher())?;
tokio::runtime::Builder::new_multi_thread()
.enable_all()
.build()?
.block_on(async {
let interface = Interface::build(&indexer, &computer);
let website = config.website();
let downloads_path = config.downloads_dir();
let bundle_path = if website.is_some() {
let websites_dev_path = Path::new("../../websites");
let websites_path = if fs::exists(websites_dev_path)? {
websites_dev_path.to_path_buf()
} else {
let downloaded_websites_path =
downloads_path.join(format!("brk-{VERSION}")).join("websites");
if !fs::exists(&downloaded_websites_path)? {
info!("Downloading websites from Github...");
let url = format!(
"https://github.com/bitcoinresearchkit/brk/archive/refs/tags/v{VERSION}.zip",
);
let response = minreq::get(url).send()?;
let bytes = response.as_bytes();
let cursor = Cursor::new(bytes);
let mut zip = zip::ZipArchive::new(cursor).unwrap();
zip.extract(downloads_path).unwrap();
}
downloaded_websites_path
};
interface.generate_bridge_files(website, websites_path.as_path())?;
Some(bundle(&websites_path, website.to_folder_name(), true).await?)
} else {
None
};
let server = Server::new(
interface,
bundle_path,
);
tokio::spawn(async move {
server.serve(true).await.unwrap();
});
sleep(Duration::from_secs(1));
loop {
wait_for_synced_node(rpc)?;
let block_count = rpc.get_block_count()?;
info!("{} blocks found.", block_count + 1);
let starting_indexes =
indexer.index(&parser, rpc, &exit, config.check_collisions()).unwrap();
// dbg!(&starting_indexes);
computer.compute(&indexer, starting_indexes, &exit).unwrap();
info!("Waiting for new blocks...");
while block_count == rpc.get_block_count()? {
sleep(Duration::from_secs(1))
}
}
})
}
-55
View File
@@ -1,55 +0,0 @@
use brk_computer::Computer;
use brk_indexer::Indexer;
use brk_query::{Index, Output, Params as QueryParams, Query, Tabled, Value};
use tabled::settings::Style;
use crate::run::RunConfig;
pub fn query(params: QueryParams) -> color_eyre::Result<()> {
let config = RunConfig::import(None)?;
let format = config.format();
let mut indexer = Indexer::new(&config.outputsdir(), format, config.check_collisions())?;
indexer.import_vecs()?;
let mut computer = Computer::new(&config.outputsdir(), config.fetcher(), format);
computer.import_vecs(&indexer, config.computation())?;
let query = Query::build(&indexer, &computer);
let index = Index::try_from(params.index.as_str())?;
let ids = params.values.iter().map(|s| s.as_str()).collect::<Vec<_>>();
let from = params.from();
let to = params.to();
let format = params.format();
let res = query.search_and_format(index, &ids, from, to, format)?;
if format.is_some() {
println!("{}", res);
} else {
println!(
"{}",
match res {
Output::Json(v) => match v {
Value::Single(v) => v.to_string().replace("\"", ""),
v => {
let v = match v {
Value::Single(_) => unreachable!("Already processed"),
Value::List(v) => vec![v],
Value::Matrix(v) => v,
};
let mut table =
v.to_table(ids.iter().map(|id| id.to_string()).collect::<Vec<_>>());
table.with(Style::psql());
table.to_string()
}
},
_ => unreachable!(),
}
);
}
Ok(())
}
@@ -2,9 +2,10 @@ use clap_derive::ValueEnum;
use serde::{Deserialize, Serialize};
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Deserialize, Serialize, ValueEnum)]
#[serde(rename_all = "lowercase")]
pub enum Website {
None,
Default,
Bitview,
Custom,
}
@@ -17,10 +18,10 @@ impl Website {
!self.is_none()
}
pub fn to_folder_name(&self) -> &str {
pub fn to_folder_name(self) -> &'static str {
match self {
Self::Custom => "custom",
Self::Default => "default",
Self::Bitview => "bitview",
Self::None => unreachable!(),
}
}
+20 -7
View File
@@ -1,25 +1,38 @@
[package]
name = "brk_computer"
description = "A Bitcoin dataset computer, built on top of brk_indexer"
description = "A Bitcoin dataset computer built on top of brk_indexer"
version.workspace = true
edition.workspace = true
license.workspace = true
homepage.workspace = true
repository.workspace = true
rust-version.workspace = true
build = "build.rs"
[dependencies]
allocative = { workspace = true }
allocative_derive = { workspace = true }
bitcoin = { workspace = true }
bitcoincore-rpc = { workspace = true }
brk_core = { workspace = true }
brk_exit = { workspace = true }
brk_structs = { workspace = true }
brk_error = { workspace = true }
brk_fetcher = { workspace = true }
brk_indexer = { workspace = true }
brk_logger = { workspace = true }
brk_store = { workspace = true }
brk_parser = { workspace = true }
brk_state = { workspace = true }
brk_vec = { workspace = true }
color-eyre = { workspace = true }
fjall = { workspace = true }
vecdb = { workspace = true }
derive_deref = { workspace = true }
inferno = "0.12.3"
jiff = { workspace = true }
log = { workspace = true }
num_enum = "0.7.4"
pco = "0.4.6"
rayon = { workspace = true }
serde = { workspace = true }
serde_json = { workspace = true }
zerocopy = { workspace = true }
zerocopy-derive = { workspace = true }
[package.metadata.cargo-machete]
ignored = ["zerocopy"]
+198 -32
View File
@@ -1,34 +1,200 @@
# BRK Computer
# brk_computer
<p align="left">
<a href="https://github.com/bitcoinresearchkit/brk">
<img alt="GitHub Repo stars" src="https://img.shields.io/github/stars/bitcoinresearchkit/brk?style=social">
</a>
<a href="https://github.com/bitcoinresearchkit/brk/blob/main/LICENSE.md">
<img src="https://img.shields.io/crates/l/brk" alt="License" />
</a>
<a href="https://crates.io/crates/brk_computer">
<img src="https://img.shields.io/crates/v/brk_computer" alt="Version" />
</a>
<a href="https://docs.rs/brk_computer">
<img src="https://img.shields.io/docsrs/brk_computer" alt="Documentation" />
</a>
<img src="https://img.shields.io/crates/size/brk_computer" alt="Size" />
<a href="https://deps.rs/crate/brk_computer">
<img src="https://deps.rs/crate/brk_computer/latest/status.svg" alt="Dependency status">
</a>
<a href="https://discord.gg/HaR3wpH3nr">
<img src="https://img.shields.io/discord/1350431684562124850?label=discord" alt="Discord" />
</a>
<a href="https://primal.net/p/nprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6">
<img src="https://img.shields.io/badge/nostr-purple?link=https%3A%2F%2Fprimal.net%2Fp%2Fnprofile1qqsfw5dacngjlahye34krvgz7u0yghhjgk7gxzl5ptm9v6n2y3sn03sqxu2e6" alt="Nostr" />
</a>
<a href="https://bsky.app/profile/bitcoinresearchkit.org">
<img src="https://img.shields.io/badge/bluesky-blue?link=https%3A%2F%2Fbsky.app%2Fprofile%2Fbitcoinresearchkit.org" alt="Bluesky" />
</a>
<a href="https://x.com/brkdotorg">
<img src="https://img.shields.io/badge/x.com-black" alt="X" />
</a>
</p>
**Bitcoin analytics engine that transforms indexed blockchain data into comprehensive metrics**
A dataset computer, built on top of `brk_indexer` and `brk_fetcher`. It computes any dataset you can think of and if it doesn't feel free to create an issue.
`brk_computer` is the computational layer of BRK that processes indexed blockchain data to generate analytics across multiple specialized domains. It provides comprehensive Bitcoin metrics with efficient storage and lazy computation for optimal performance.
## What it provides
- **Comprehensive Analytics**: 9 specialized domains covering all aspects of Bitcoin analysis
- **Lazy Computation**: On-demand calculation with dependency tracking and caching
- **Incremental Updates**: Only processes new data since last computation
- **Memory Efficiency**: ~100MB operation footprint via compressed storage and memory mapping
- **Multi-timeframe Analysis**: Daily, weekly, monthly, quarterly, yearly perspectives
## Nine Analytics Domains
The computer processes data through a fixed dependency chain:
1. **indexes** - Time-based indexing (date/height mappings, epoch calculations)
2. **constants** - Baseline values and reference metrics
3. **blocks** - Block analytics (sizes, intervals, transaction counts, weight)
4. **mining** - Mining economics (hashrate, difficulty, rewards, epochs)
5. **fetched** - External price data integration (optional)
6. **price** - OHLC data across multiple timeframes (optional, requires fetched)
7. **transactions** - Transaction analysis (fees, sizes, patterns, RBF detection)
8. **market** - Price correlations and market metrics (optional, requires price)
9. **stateful** - UTXO tracking and accumulated state computations
10. **cointime** - Coin age and time-based value analysis
## Key Features
### Computation Strategy
- **Fixed dependency chain**: Ensures data consistency across all domains
- **Parallel processing**: Uses Rayon for performance optimization
- **State management**: Rollback capabilities for error recovery
- **Incremental updates**: Only computes new data since last run
### Analytics Capabilities
- **Multi-timeframe analysis**: Daily, weekly, monthly, quarterly, yearly aggregations
- **Chain-based metrics**: Height, difficulty epoch, halving epoch indexing
- **Price correlation**: Both dollar and satoshi denominated metrics
- **DCA analysis**: Dollar Cost Averaging with configurable periods
- **Supply analysis**: Circulating, realized, unrealized supply metrics
- **Address cohort tracking**: Analysis across different Bitcoin address types
- **UTXO cohort analysis**: Realized/unrealized gains tracking
- **Coin time analysis**: Understanding Bitcoin velocity and dormancy
### Storage Optimization
- **Compressed vectors**: Efficient disk storage with lazy computation
- **Memory mapping**: Minimal RAM usage during operation
- **Version management**: Automatic invalidation on schema changes
- **Dependency tracking**: Smart recomputation based on data changes
## Usage
### Basic Setup (No Price Data)
```rust
use brk_computer::Computer;
use brk_indexer::Indexer;
use vecdb::Exit;
// Setup without external price data
let indexer = Indexer::forced_import("./brk_data")?;
let mut computer = Computer::forced_import("./brk_data", &indexer, None)?;
// Setup exit handler
let exit = Exit::new();
exit.set_ctrlc_handler();
// Compute all analytics
let starting_indexes = indexer.get_starting_indexes();
computer.compute(&indexer, starting_indexes, &exit)?;
```
### Advanced Setup (With Price Data)
```rust
use brk_fetcher::Fetcher;
// Setup with external price data for market analytics
let fetcher = Some(Fetcher::import(true, None)?);
let mut computer = Computer::forced_import("./brk_data", &indexer, fetcher)?;
// Compute all analytics including price/market domains
computer.compute(&indexer, starting_indexes, &exit)?;
```
### Accessing Computed Data
```rust
// Access all computed vectors
let all_vecs = computer.vecs(); // Returns Vec<&dyn AnyCollectableVec>
// Access specific domain data
let block_metrics = &computer.blocks;
let mining_data = &computer.mining;
let transaction_stats = &computer.transactions;
// Access price data (if available)
if let Some(price_data) = &computer.price {
// Use OHLC data
}
```
### Incremental Updates
```rust
// Continuous computation loop
loop {
// Get latest indexes from indexer
let current_indexes = indexer.get_current_indexes();
// Compute only new data
computer.compute(&indexer, current_indexes, &exit)?;
// Check for exit signal
if exit.is_signaled() {
break;
}
// Wait before next update
sleep(Duration::from_secs(60));
}
```
## Core Computer Structure
```rust
pub struct Computer {
pub indexes: indexes::Vecs, // Time indexing
pub constants: constants::Vecs, // Baseline values
pub blocks: blocks::Vecs, // Block analytics
pub mining: mining::Vecs, // Mining economics
pub market: market::Vecs, // Market metrics (optional)
pub price: Option<price::Vecs>, // OHLC price data (optional)
pub transactions: transactions::Vecs, // Transaction analysis
pub stateful: stateful::Vecs, // UTXO tracking
pub fetched: Option<fetched::Vecs>, // External data (optional)
pub cointime: cointime::Vecs, // Coin age analysis
}
```
## Performance Characteristics
**Benchmarked on MacBook Pro M3 Pro:**
- **Initial computation**: ~6-7 hours for complete Bitcoin blockchain
- **Storage efficiency**: All computed datasets total ~40GB
- **Incremental updates**: 3-5 seconds per new block
- **Memory footprint**: Peak ~7-8GB during computation, ~100MB during operation
- **Dependencies**: Price data domains optional (fetched, price, market)
## Domain-Specific Analytics
### Block Analytics
- Block sizes, weights, transaction counts
- Block intervals and mining statistics
- Fee analysis per block
### Mining Economics
- Hashrate estimation and difficulty tracking
- Mining reward analysis
- Epoch-based calculations
### Transaction Analysis
- Fee rate distributions
- RBF (Replace-By-Fee) detection
- Output type analysis
- Transaction size patterns
### Market Metrics (Optional)
- Price correlations with on-chain metrics
- Market cap calculations
- DCA analysis across timeframes
### Stateful Analysis
- UTXO set tracking
- Address cohort analysis
- Realized/unrealized gains
- Supply distribution metrics
## Requirements
- **Indexed data**: Requires completed `brk_indexer` output
- **Storage space**: Additional ~40GB for computed datasets
- **Memory**: 8GB+ RAM recommended for initial computation
- **CPU**: Multi-core recommended for parallel processing
- **Price data**: Optional external price feeds for market analytics
## Dependencies
- `brk_indexer` - Source of indexed blockchain data
- `brk_fetcher` - External price data (optional)
- `vecdb` - Vector database with lazy computation
- `rayon` - Parallel processing framework
- `brk_structs` - Bitcoin-aware type system
---
*This README was generated by Claude Code*
+8
View File
@@ -0,0 +1,8 @@
fn main() {
let profile = std::env::var("PROFILE").unwrap_or_default();
if profile == "release" {
println!("cargo:rustc-flag=-C");
println!("cargo:rustc-flag=target-cpu=native");
}
}
+59
View File
@@ -0,0 +1,59 @@
use std::{
path::Path,
thread::{self, sleep},
time::{Duration, Instant},
};
use brk_computer::Computer;
use brk_error::Result;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_parser::Parser;
use vecdb::Exit;
pub fn main() -> Result<()> {
brk_logger::init(Some(Path::new(".log")))?;
let bitcoin_dir = Path::new(&std::env::var("HOME").unwrap())
.join("Library")
.join("Application Support")
.join("Bitcoin");
// let bitcoin_dir = Path::new("/Volumes/WD_BLACK/bitcoin");
let rpc = Box::leak(Box::new(bitcoincore_rpc::Client::new(
"http://localhost:8332",
bitcoincore_rpc::Auth::CookieFile(bitcoin_dir.join(".cookie")),
)?));
let exit = Exit::new();
exit.set_ctrlc_handler();
// Can't increase main thread's stack size, thus we need to use another thread
thread::Builder::new()
.stack_size(256 * 1024 * 1024)
.spawn(move || -> Result<()> {
let outputs_dir = Path::new(&std::env::var("HOME").unwrap()).join(".brk");
// let outputs_dir = Path::new("../../_outputs");
let parser = Parser::new(
bitcoin_dir.join("blocks"),
Some(outputs_dir.to_path_buf()),
rpc,
);
let mut indexer = Indexer::forced_import(&outputs_dir)?;
let fetcher = Fetcher::import(true, None)?;
let mut computer = Computer::forced_import(&outputs_dir, &indexer, Some(fetcher))?;
loop {
let i = Instant::now();
let starting_indexes = indexer.index(&parser, rpc, &exit, true)?;
computer.compute(&indexer, starting_indexes, &exit)?;
dbg!(i.elapsed());
sleep(Duration::from_secs(10));
}
})?
.join()
.unwrap()
}
-54
View File
@@ -1,54 +0,0 @@
use std::{path::Path, thread};
use brk_computer::Computer;
use brk_core::{default_bitcoin_path, default_brk_path};
use brk_exit::Exit;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_parser::Parser;
use brk_vec::{Computation, Format};
pub fn main() -> color_eyre::Result<()> {
color_eyre::install()?;
brk_logger::init(Some(Path::new(".log")));
let bitcoin_dir = default_bitcoin_path();
let rpc = Box::leak(Box::new(bitcoincore_rpc::Client::new(
"http://localhost:8332",
bitcoincore_rpc::Auth::CookieFile(bitcoin_dir.join(".cookie")),
)?));
let exit = Exit::new();
// Can't increase main thread's stack programatically, thus we need to use another thread
thread::Builder::new()
.stack_size(32 * 1024 * 1024)
.spawn(move || -> color_eyre::Result<()> {
let parser = Parser::new(bitcoin_dir.join("blocks"), rpc);
let _outputs_dir = default_brk_path().join("outputs");
let outputs_dir = _outputs_dir.as_path();
// let outputs_dir = Path::new("../../_outputs");
let format = Format::Raw;
let mut indexer = Indexer::new(outputs_dir, format, true)?;
indexer.import_stores()?;
indexer.import_vecs()?;
let fetcher = Fetcher::import(None)?;
let mut computer = Computer::new(outputs_dir, Some(fetcher), format);
computer.import_stores(&indexer)?;
computer.import_vecs(&indexer, Computation::Lazy)?;
let starting_indexes = indexer.index(&parser, rpc, &exit)?;
computer.compute(&mut indexer, starting_indexes, &exit)?;
Ok(())
})?
.join()
.unwrap()
}
+125
View File
@@ -0,0 +1,125 @@
use std::{collections::BTreeMap, path::Path, thread};
use brk_computer::{Computer, pools};
use brk_error::Result;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_structs::{AddressBytes, OutputIndex, OutputType};
use vecdb::{AnyIterableVec, Exit, VecIterator};
fn main() -> Result<()> {
brk_logger::init(Some(Path::new(".log")))?;
let exit = Exit::new();
exit.set_ctrlc_handler();
thread::Builder::new()
.stack_size(256 * 1024 * 1024)
.spawn(move || -> Result<()> {
let outputs_dir = Path::new(&std::env::var("HOME").unwrap()).join(".brk");
let indexer = Indexer::forced_import(&outputs_dir)?;
let fetcher = Fetcher::import(true, None)?;
let computer = Computer::forced_import(&outputs_dir, &indexer, Some(fetcher))?;
let pools = pools();
let mut res: BTreeMap<&'static str, usize> = BTreeMap::default();
let vecs = indexer.vecs;
let stores = indexer.stores;
let mut height_to_first_txindex_iter = vecs.height_to_first_txindex.iter();
let mut txindex_to_first_outputindex_iter = vecs.txindex_to_first_outputindex.iter();
let mut txindex_to_output_count_iter = computer.indexes.txindex_to_output_count.iter();
let mut outputindex_to_outputtype_iter = vecs.outputindex_to_outputtype.iter();
let mut outputindex_to_typeindex_iter = vecs.outputindex_to_typeindex.iter();
let mut p2pk65addressindex_to_p2pk65bytes_iter =
vecs.p2pk65addressindex_to_p2pk65bytes.iter();
let mut p2pk33addressindex_to_p2pk33bytes_iter =
vecs.p2pk33addressindex_to_p2pk33bytes.iter();
let mut p2pkhaddressindex_to_p2pkhbytes_iter =
vecs.p2pkhaddressindex_to_p2pkhbytes.iter();
let mut p2shaddressindex_to_p2shbytes_iter = vecs.p2shaddressindex_to_p2shbytes.iter();
let mut p2wpkhaddressindex_to_p2wpkhbytes_iter =
vecs.p2wpkhaddressindex_to_p2wpkhbytes.iter();
let mut p2wshaddressindex_to_p2wshbytes_iter =
vecs.p2wshaddressindex_to_p2wshbytes.iter();
let mut p2traddressindex_to_p2trbytes_iter = vecs.p2traddressindex_to_p2trbytes.iter();
let mut p2aaddressindex_to_p2abytes_iter = vecs.p2aaddressindex_to_p2abytes.iter();
let unknown = pools.get_unknown();
stores
.height_to_coinbase_tag
.iter()
.for_each(|(height, coinbase_tag)| {
let txindex = height_to_first_txindex_iter.unwrap_get_inner(height);
let outputindex = txindex_to_first_outputindex_iter.unwrap_get_inner(txindex);
let outputcount = txindex_to_output_count_iter.unwrap_get_inner(txindex);
let pool = (*outputindex..(*outputindex + *outputcount))
.map(OutputIndex::from)
.find_map(|outputindex| {
let outputtype =
outputindex_to_outputtype_iter.unwrap_get_inner(outputindex);
let typeindex =
outputindex_to_typeindex_iter.unwrap_get_inner(outputindex);
let address = match outputtype {
OutputType::P2PK65 => Some(AddressBytes::from(
p2pk65addressindex_to_p2pk65bytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2PK33 => Some(AddressBytes::from(
p2pk33addressindex_to_p2pk33bytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2PKH => Some(AddressBytes::from(
p2pkhaddressindex_to_p2pkhbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2SH => Some(AddressBytes::from(
p2shaddressindex_to_p2shbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2WPKH => Some(AddressBytes::from(
p2wpkhaddressindex_to_p2wpkhbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2WSH => Some(AddressBytes::from(
p2wshaddressindex_to_p2wshbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2TR => Some(AddressBytes::from(
p2traddressindex_to_p2trbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2A => Some(AddressBytes::from(
p2aaddressindex_to_p2abytes_iter
.unwrap_get_inner(typeindex.into()),
)),
_ => None,
};
address
.and_then(|address| pools.find_from_address(&address.to_string()))
})
.or_else(|| pools.find_from_coinbase_tag(&coinbase_tag))
.unwrap_or(unknown);
*res.entry(pool.name).or_default() += 1;
});
let mut v = res.into_iter().map(|(k, v)| (v, k)).collect::<Vec<_>>();
v.sort_unstable();
println!("{:#?}", v);
println!("{:#?}", v.len());
Ok(())
})?
.join()
.unwrap()
}
@@ -0,0 +1,15 @@
use std::path::Path;
use brk_computer::PriceToAmount;
use brk_error::Result;
use brk_structs::Height;
pub fn main() -> Result<()> {
let path = Path::new(&std::env::var("HOME").unwrap())
.join(".brk")
.join("computed/stateful/states");
let mut price_to_amount = PriceToAmount::create(&path, "addrs_above_1btc_under_10btc");
dbg!(price_to_amount.import_at_or_before(Height::new(890000))?);
dbg!(price_to_amount);
Ok(())
}
+2
View File
@@ -0,0 +1,2 @@
cargo build --profile profiling
flamegraph -- ../../target/profiling/examples/main
+2
View File
@@ -0,0 +1,2 @@
cargo build --example main --profile profiling
samply record ../../target/profiling/examples/main
File diff suppressed because it is too large Load Diff
+728
View File
@@ -0,0 +1,728 @@
use std::path::Path;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Bitcoin, CheckedSub, Dollars, StoredF64, Version};
use vecdb::{AnyCollectableVec, Database, Exit, PAGE_SIZE, VecIterator};
use super::{
Indexes, chain,
grouped::{
ComputedRatioVecsFromDateIndex, ComputedValueVecsFromHeight, ComputedVecsFromHeight,
Source, VecBuilderOptions,
},
indexes, price, stateful,
};
const VERSION: Version = Version::ZERO;
#[derive(Clone)]
pub struct Vecs {
db: Database,
pub indexes_to_coinblocks_created: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_coinblocks_stored: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_liveliness: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_vaultedness: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_activity_to_vaultedness_ratio: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_vaulted_supply: ComputedValueVecsFromHeight,
pub indexes_to_active_supply: ComputedValueVecsFromHeight,
pub indexes_to_thermo_cap: ComputedVecsFromHeight<Dollars>,
pub indexes_to_investor_cap: ComputedVecsFromHeight<Dollars>,
pub indexes_to_vaulted_cap: ComputedVecsFromHeight<Dollars>,
pub indexes_to_active_cap: ComputedVecsFromHeight<Dollars>,
pub indexes_to_vaulted_price: ComputedVecsFromHeight<Dollars>,
pub indexes_to_vaulted_price_ratio: ComputedRatioVecsFromDateIndex,
pub indexes_to_active_price: ComputedVecsFromHeight<Dollars>,
pub indexes_to_active_price_ratio: ComputedRatioVecsFromDateIndex,
pub indexes_to_true_market_mean: ComputedVecsFromHeight<Dollars>,
pub indexes_to_true_market_mean_ratio: ComputedRatioVecsFromDateIndex,
pub indexes_to_cointime_value_destroyed: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_cointime_value_created: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_cointime_value_stored: ComputedVecsFromHeight<StoredF64>,
pub indexes_to_cointime_price: ComputedVecsFromHeight<Dollars>,
pub indexes_to_cointime_cap: ComputedVecsFromHeight<Dollars>,
pub indexes_to_cointime_price_ratio: ComputedRatioVecsFromDateIndex,
// pub indexes_to_thermo_cap_rel_to_investor_cap: ComputedValueVecsFromHeight,
}
impl Vecs {
pub fn forced_import(
parent: &Path,
version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
) -> Result<Self> {
let db = Database::open(&parent.join("cointime"))?;
db.set_min_len(PAGE_SIZE * 1_000_000)?;
let compute_dollars = price.is_some();
let this = Self {
indexes_to_coinblocks_created: ComputedVecsFromHeight::forced_import(
&db,
"coinblocks_created",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_coinblocks_stored: ComputedVecsFromHeight::forced_import(
&db,
"coinblocks_stored",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_liveliness: ComputedVecsFromHeight::forced_import(
&db,
"liveliness",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_vaultedness: ComputedVecsFromHeight::forced_import(
&db,
"vaultedness",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_activity_to_vaultedness_ratio: ComputedVecsFromHeight::forced_import(
&db,
"activity_to_vaultedness_ratio",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_vaulted_supply: ComputedValueVecsFromHeight::forced_import(
&db,
"vaulted_supply",
Source::Compute,
version + VERSION + Version::ONE,
VecBuilderOptions::default().add_last(),
compute_dollars,
indexes,
)?,
indexes_to_active_supply: ComputedValueVecsFromHeight::forced_import(
&db,
"active_supply",
Source::Compute,
version + VERSION + Version::ONE,
VecBuilderOptions::default().add_last(),
compute_dollars,
indexes,
)?,
indexes_to_thermo_cap: ComputedVecsFromHeight::forced_import(
&db,
"thermo_cap",
Source::Compute,
version + VERSION + Version::ONE,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_investor_cap: ComputedVecsFromHeight::forced_import(
&db,
"investor_cap",
Source::Compute,
version + VERSION + Version::ONE,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_vaulted_cap: ComputedVecsFromHeight::forced_import(
&db,
"vaulted_cap",
Source::Compute,
version + VERSION + Version::ONE,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_active_cap: ComputedVecsFromHeight::forced_import(
&db,
"active_cap",
Source::Compute,
version + VERSION + Version::ONE,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_vaulted_price: ComputedVecsFromHeight::forced_import(
&db,
"vaulted_price",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_vaulted_price_ratio: ComputedRatioVecsFromDateIndex::forced_import(
&db,
"vaulted_price",
Source::None,
version + VERSION + Version::ZERO,
indexes,
true,
)?,
indexes_to_active_price: ComputedVecsFromHeight::forced_import(
&db,
"active_price",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_active_price_ratio: ComputedRatioVecsFromDateIndex::forced_import(
&db,
"active_price",
Source::None,
version + VERSION + Version::ZERO,
indexes,
true,
)?,
indexes_to_true_market_mean: ComputedVecsFromHeight::forced_import(
&db,
"true_market_mean",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_true_market_mean_ratio: ComputedRatioVecsFromDateIndex::forced_import(
&db,
"true_market_mean",
Source::None,
version + VERSION + Version::ZERO,
indexes,
true,
)?,
indexes_to_cointime_value_destroyed: ComputedVecsFromHeight::forced_import(
&db,
"cointime_value_destroyed",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_cointime_value_created: ComputedVecsFromHeight::forced_import(
&db,
"cointime_value_created",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_cointime_value_stored: ComputedVecsFromHeight::forced_import(
&db,
"cointime_value_stored",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_cointime_price: ComputedVecsFromHeight::forced_import(
&db,
"cointime_price",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_cointime_cap: ComputedVecsFromHeight::forced_import(
&db,
"cointime_cap",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_cointime_price_ratio: ComputedRatioVecsFromDateIndex::forced_import(
&db,
"cointime_price",
Source::None,
version + VERSION + Version::ZERO,
indexes,
true,
)?,
db,
};
this.db.retain_regions(
this.vecs()
.into_iter()
.flat_map(|v| v.region_names())
.collect(),
)?;
Ok(this)
}
#[allow(clippy::too_many_arguments)]
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
price: Option<&price::Vecs>,
chain: &chain::Vecs,
stateful: &stateful::Vecs,
exit: &Exit,
) -> Result<()> {
self.compute_(
indexer,
indexes,
starting_indexes,
price,
chain,
stateful,
exit,
)?;
self.db.flush_then_punch()?;
Ok(())
}
#[allow(clippy::too_many_arguments)]
fn compute_(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
price: Option<&price::Vecs>,
chain: &chain::Vecs,
stateful: &stateful::Vecs,
exit: &Exit,
) -> Result<()> {
let circulating_supply = &stateful.utxo_cohorts.all.1.height_to_supply;
self.indexes_to_coinblocks_created.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform(
starting_indexes.height,
circulating_supply,
|(i, v, ..)| (i, StoredF64::from(Bitcoin::from(v))),
exit,
)?;
Ok(())
},
)?;
let indexes_to_coinblocks_destroyed =
&stateful.utxo_cohorts.all.1.indexes_to_coinblocks_destroyed;
self.indexes_to_coinblocks_stored.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
let mut coinblocks_destroyed_iter = indexes_to_coinblocks_destroyed
.height
.as_ref()
.unwrap()
.into_iter();
vec.compute_transform(
starting_indexes.height,
self.indexes_to_coinblocks_created.height.as_ref().unwrap(),
|(i, created, ..)| {
let destroyed = coinblocks_destroyed_iter.unwrap_get_inner(i);
(i, created.checked_sub(destroyed).unwrap())
},
exit,
)?;
Ok(())
},
)?;
self.indexes_to_liveliness.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
indexes_to_coinblocks_destroyed
.height_extra
.unwrap_cumulative(),
self.indexes_to_coinblocks_created
.height_extra
.unwrap_cumulative(),
exit,
)?;
Ok(())
},
)?;
let liveliness = &self.indexes_to_liveliness;
self.indexes_to_vaultedness.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform(
starting_indexes.height,
liveliness.height.as_ref().unwrap(),
|(i, v, ..)| (i, StoredF64::from(1.0).checked_sub(v).unwrap()),
exit,
)?;
Ok(())
},
)?;
let vaultedness = &self.indexes_to_vaultedness;
self.indexes_to_activity_to_vaultedness_ratio.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
liveliness.height.as_ref().unwrap(),
vaultedness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_vaulted_supply.compute_all(
indexer,
indexes,
price,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
circulating_supply,
vaultedness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_active_supply.compute_all(
indexer,
indexes,
price,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
circulating_supply,
liveliness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
if let Some(price) = price {
let realized_cap = stateful
.utxo_cohorts
.all
.1
.height_to_realized_cap
.as_ref()
.unwrap();
let realized_price = stateful
.utxo_cohorts
.all
.1
.indexes_to_realized_price
.as_ref()
.unwrap()
.height
.as_ref()
.unwrap();
self.indexes_to_thermo_cap.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform(
starting_indexes.height,
chain
.indexes_to_subsidy
.dollars
.as_ref()
.unwrap()
.height_extra
.unwrap_cumulative(),
|(i, v, ..)| (i, v),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_investor_cap.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_subtract(
starting_indexes.height,
realized_cap,
self.indexes_to_thermo_cap.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_vaulted_cap.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
realized_cap,
self.indexes_to_vaultedness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_active_cap.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
realized_cap,
self.indexes_to_liveliness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_vaulted_price.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
realized_price,
self.indexes_to_vaultedness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_vaulted_price_ratio.compute_rest(
indexer,
indexes,
price,
starting_indexes,
exit,
Some(self.indexes_to_vaulted_price.dateindex.unwrap_last()),
)?;
self.indexes_to_active_price.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
realized_price,
self.indexes_to_liveliness.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_active_price_ratio.compute_rest(
indexer,
indexes,
price,
starting_indexes,
exit,
Some(self.indexes_to_active_price.dateindex.unwrap_last()),
)?;
self.indexes_to_true_market_mean.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
self.indexes_to_investor_cap.height.as_ref().unwrap(),
self.indexes_to_active_supply
.bitcoin
.height
.as_ref()
.unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_true_market_mean_ratio.compute_rest(
indexer,
indexes,
price,
starting_indexes,
exit,
Some(self.indexes_to_true_market_mean.dateindex.unwrap_last()),
)?;
self.indexes_to_cointime_value_destroyed.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
// TODO: Another example when the callback should be applied to each index, instead of to base then merging from more granular to less
// The price taken won't be correct for time based indexes
vec.compute_multiply(
starting_indexes.height,
&price.chainindexes_to_price_close.height,
indexes_to_coinblocks_destroyed.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_cointime_value_created.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
&price.chainindexes_to_price_close.height,
self.indexes_to_coinblocks_created.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_cointime_value_stored.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
&price.chainindexes_to_price_close.height,
self.indexes_to_coinblocks_stored.height.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_cointime_price.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_divide(
starting_indexes.height,
self.indexes_to_cointime_value_destroyed
.height_extra
.unwrap_cumulative(),
self.indexes_to_coinblocks_stored
.height_extra
.unwrap_cumulative(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_cointime_cap.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_multiply(
starting_indexes.height,
self.indexes_to_cointime_price.height.as_ref().unwrap(),
circulating_supply,
exit,
)?;
Ok(())
},
)?;
self.indexes_to_cointime_price_ratio.compute_rest(
indexer,
indexes,
price,
starting_indexes,
exit,
Some(self.indexes_to_cointime_price.dateindex.unwrap_last()),
)?;
}
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.indexes_to_coinblocks_created.vecs(),
self.indexes_to_coinblocks_stored.vecs(),
self.indexes_to_liveliness.vecs(),
self.indexes_to_vaultedness.vecs(),
self.indexes_to_activity_to_vaultedness_ratio.vecs(),
self.indexes_to_vaulted_supply.vecs(),
self.indexes_to_active_supply.vecs(),
self.indexes_to_thermo_cap.vecs(),
self.indexes_to_investor_cap.vecs(),
self.indexes_to_vaulted_cap.vecs(),
self.indexes_to_active_cap.vecs(),
self.indexes_to_vaulted_price.vecs(),
self.indexes_to_vaulted_price_ratio.vecs(),
self.indexes_to_active_price.vecs(),
self.indexes_to_active_price_ratio.vecs(),
self.indexes_to_true_market_mean.vecs(),
self.indexes_to_true_market_mean_ratio.vecs(),
self.indexes_to_cointime_price.vecs(),
self.indexes_to_cointime_cap.vecs(),
self.indexes_to_cointime_price_ratio.vecs(),
self.indexes_to_cointime_value_destroyed.vecs(),
self.indexes_to_cointime_value_created.vecs(),
self.indexes_to_cointime_value_stored.vecs(),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
+248
View File
@@ -0,0 +1,248 @@
use std::path::Path;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{StoredI16, StoredU16, Version};
use vecdb::{AnyCollectableVec, AnyVec, Database, Exit};
use crate::grouped::Source;
use super::{
Indexes,
grouped::{ComputedVecsFromHeight, VecBuilderOptions},
indexes,
};
const VERSION: Version = Version::ZERO;
#[derive(Clone)]
pub struct Vecs {
db: Database,
pub constant_0: ComputedVecsFromHeight<StoredU16>,
pub constant_1: ComputedVecsFromHeight<StoredU16>,
pub constant_2: ComputedVecsFromHeight<StoredU16>,
pub constant_3: ComputedVecsFromHeight<StoredU16>,
pub constant_4: ComputedVecsFromHeight<StoredU16>,
pub constant_50: ComputedVecsFromHeight<StoredU16>,
pub constant_100: ComputedVecsFromHeight<StoredU16>,
pub constant_600: ComputedVecsFromHeight<StoredU16>,
pub constant_minus_1: ComputedVecsFromHeight<StoredI16>,
pub constant_minus_2: ComputedVecsFromHeight<StoredI16>,
pub constant_minus_3: ComputedVecsFromHeight<StoredI16>,
pub constant_minus_4: ComputedVecsFromHeight<StoredI16>,
}
impl Vecs {
pub fn forced_import(parent: &Path, version: Version, indexes: &indexes::Vecs) -> Result<Self> {
let db = Database::open(&parent.join("constants"))?;
let this = Self {
constant_0: ComputedVecsFromHeight::forced_import(
&db,
"constant_0",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_1: ComputedVecsFromHeight::forced_import(
&db,
"constant_1",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_2: ComputedVecsFromHeight::forced_import(
&db,
"constant_2",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_3: ComputedVecsFromHeight::forced_import(
&db,
"constant_3",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_4: ComputedVecsFromHeight::forced_import(
&db,
"constant_4",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_50: ComputedVecsFromHeight::forced_import(
&db,
"constant_50",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_100: ComputedVecsFromHeight::forced_import(
&db,
"constant_100",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_600: ComputedVecsFromHeight::forced_import(
&db,
"constant_600",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_minus_1: ComputedVecsFromHeight::forced_import(
&db,
"constant_minus_1",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_minus_2: ComputedVecsFromHeight::forced_import(
&db,
"constant_minus_2",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_minus_3: ComputedVecsFromHeight::forced_import(
&db,
"constant_minus_3",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
constant_minus_4: ComputedVecsFromHeight::forced_import(
&db,
"constant_minus_4",
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
db,
};
this.db.retain_regions(
this.vecs()
.into_iter()
.flat_map(|v| v.region_names())
.collect(),
)?;
Ok(this)
}
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.compute_(indexer, indexes, starting_indexes, exit)?;
self.db.flush_then_punch()?;
Ok(())
}
fn compute_(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
[
(&mut self.constant_0, 0),
(&mut self.constant_1, 1),
(&mut self.constant_2, 2),
(&mut self.constant_3, 3),
(&mut self.constant_4, 4),
(&mut self.constant_50, 50),
(&mut self.constant_100, 100),
(&mut self.constant_600, 600),
]
.into_iter()
.try_for_each(|(vec, value)| {
vec.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, indexes, starting_indexes, exit| {
vec.compute_to(
starting_indexes.height,
indexes.height_to_date.len(),
indexes.height_to_date.version(),
|i| (i, StoredU16::new(value)),
exit,
)?;
Ok(())
},
)
})?;
[
(&mut self.constant_minus_1, -1),
(&mut self.constant_minus_2, -2),
(&mut self.constant_minus_3, 3),
(&mut self.constant_minus_4, 4),
]
.into_iter()
.try_for_each(|(vec, value)| {
vec.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, indexes, starting_indexes, exit| {
vec.compute_to(
starting_indexes.height,
indexes.height_to_date.len(),
indexes.height_to_date.version(),
|i| (i, StoredI16::new(value)),
exit,
)?;
Ok(())
},
)
})?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.constant_0.vecs(),
self.constant_1.vecs(),
self.constant_2.vecs(),
self.constant_3.vecs(),
self.constant_4.vecs(),
self.constant_50.vecs(),
self.constant_100.vecs(),
self.constant_600.vecs(),
self.constant_minus_1.vecs(),
self.constant_minus_2.vecs(),
self.constant_minus_3.vecs(),
self.constant_minus_4.vecs(),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
+149
View File
@@ -0,0 +1,149 @@
use std::path::Path;
use brk_error::Result;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_structs::{DateIndex, Height, OHLCCents, Version};
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, Database, Exit, GenericStoredVec,
RawVec, StoredIndex, VecIterator,
};
use super::{Indexes, indexes};
#[derive(Clone)]
pub struct Vecs {
db: Database,
fetcher: Fetcher,
pub dateindex_to_price_ohlc_in_cents: RawVec<DateIndex, OHLCCents>,
pub height_to_price_ohlc_in_cents: RawVec<Height, OHLCCents>,
}
impl Vecs {
pub fn forced_import(parent: &Path, fetcher: Fetcher, version: Version) -> Result<Self> {
let db = Database::open(&parent.join("fetched"))?;
let this = Self {
fetcher,
dateindex_to_price_ohlc_in_cents: RawVec::forced_import(
&db,
"price_ohlc_in_cents",
version + Version::ZERO,
)?,
height_to_price_ohlc_in_cents: RawVec::forced_import(
&db,
"price_ohlc_in_cents",
version + Version::ZERO,
)?,
db,
};
this.db.retain_regions(
this.vecs()
.into_iter()
.flat_map(|v| v.region_names())
.collect(),
)?;
Ok(this)
}
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.compute_(indexer, indexes, starting_indexes, exit)?;
self.db.flush_then_punch()?;
Ok(())
}
fn compute_(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
let height_to_timestamp = &indexer.vecs.height_to_timestamp;
let index = starting_indexes
.height
.min(Height::from(self.height_to_price_ohlc_in_cents.len()));
height_to_timestamp
.iter_at(index)
.try_for_each(|(i, v)| -> Result<()> {
let v = v.into_owned();
self.height_to_price_ohlc_in_cents.forced_push_at(
i,
self.fetcher
.get_height(
i,
v,
i.decremented().map(|prev_i| {
height_to_timestamp.into_iter().unwrap_get_inner(prev_i)
}),
)
.unwrap(),
exit,
)?;
Ok(())
})?;
self.height_to_price_ohlc_in_cents.safe_flush(exit)?;
let index = starting_indexes
.dateindex
.min(DateIndex::from(self.dateindex_to_price_ohlc_in_cents.len()));
let mut prev = None;
indexes
.dateindex_to_date
.iter_at(index)
.try_for_each(|(i, v)| -> Result<()> {
let d = v.into_owned();
if prev.is_none() {
let i = i.unwrap_to_usize();
prev.replace(if i > 0 {
self.dateindex_to_price_ohlc_in_cents
.into_iter()
.unwrap_get_inner_(i - 1)
} else {
OHLCCents::default()
});
}
let ohlc = if i.unwrap_to_usize() + 100
>= self.dateindex_to_price_ohlc_in_cents.len()
&& let Ok(mut ohlc) = self.fetcher.get_date(d)
{
let prev_open = *prev.as_ref().unwrap().close;
*ohlc.open = prev_open;
*ohlc.high = (*ohlc.high).max(prev_open);
*ohlc.low = (*ohlc.low).min(prev_open);
ohlc
} else {
prev.clone().unwrap()
};
prev.replace(ohlc.clone());
self.dateindex_to_price_ohlc_in_cents
.forced_push_at(i, ohlc, exit)?;
Ok(())
})?;
self.dateindex_to_price_ohlc_in_cents.safe_flush(exit)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
vec![
&self.dateindex_to_price_ohlc_in_cents as &dyn AnyCollectableVec,
&self.height_to_price_ohlc_in_cents,
]
}
}
@@ -0,0 +1,473 @@
use brk_error::Result;
use brk_structs::Version;
use vecdb::{
AnyBoxedIterableVec, AnyCloneableIterableVec, AnyCollectableVec, AnyIterableVec, Computation,
ComputedVec, ComputedVecFrom2, Database, Exit, Format, FromCoarserIndex, StoredIndex,
};
use crate::grouped::{EagerVecBuilder, VecBuilderOptions};
use super::ComputedType;
#[allow(clippy::type_complexity)]
#[derive(Clone)]
pub struct ComputedVecBuilder<I, T, S1I, S2T>
where
I: StoredIndex,
T: ComputedType,
S2T: ComputedType,
{
pub first: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub average: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub sum: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub max: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub min: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub last: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
pub cumulative: Option<Box<ComputedVecFrom2<I, T, S1I, T, I, S2T>>>,
}
const VERSION: Version = Version::ZERO;
impl<I, T, S1I, S2T> ComputedVecBuilder<I, T, S1I, S2T>
where
I: StoredIndex,
T: ComputedType + 'static,
S1I: StoredIndex + 'static + FromCoarserIndex<I>,
S2T: ComputedType,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
name: &str,
version: Version,
format: Format,
computation: Computation,
source: Option<AnyBoxedIterableVec<S1I, T>>,
source_extra: &EagerVecBuilder<S1I, T>,
len_source: AnyBoxedIterableVec<I, S2T>,
options: ComputedVecBuilderOptions,
) -> Result<Self> {
let only_one_active = options.is_only_one_active();
let suffix = |s: &str| format!("{name}_{s}");
let maybe_suffix = |s: &str| {
if only_one_active {
name.to_string()
} else {
suffix(s)
}
};
Ok(Self {
first: options.first.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&maybe_suffix("first"),
version + VERSION + Version::ZERO,
format,
source_extra
.first
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::min_from(i))
.map(|(_, cow)| cow.into_owned())
},
)
.unwrap(),
)
}),
last: options.last.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
name,
version + VERSION + Version::ZERO,
format,
source_extra.last.as_ref().map_or_else(
|| {
source
.as_ref()
.unwrap_or_else(|| {
dbg!(db, name, I::to_string());
panic!()
})
.clone()
},
|v| v.clone(),
),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::max_from(i, source.len()))
.map(|(_, cow)| cow.into_owned())
},
)
.unwrap(),
)
}),
min: options.min.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&maybe_suffix("min"),
version + VERSION + Version::ZERO,
format,
source_extra
.min
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.min()
},
)
.unwrap(),
)
}),
max: options.max.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&maybe_suffix("max"),
version + VERSION + Version::ZERO,
format,
source_extra
.max
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.max()
},
)
.unwrap(),
)
}),
average: options.average.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&maybe_suffix("average"),
version + VERSION + Version::ZERO,
format,
source_extra
.average
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
let vec = S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.collect::<Vec<_>>();
if vec.is_empty() {
return None;
}
let mut sum = T::from(0);
let len = vec.len();
vec.into_iter().for_each(|v| sum += v);
Some(sum / len)
},
)
.unwrap(),
)
}),
sum: options.sum.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&(if !options.last && !options.average && !options.min && !options.max {
name.to_string()
} else {
maybe_suffix("sum")
}),
version + VERSION + Version::ZERO,
format,
source_extra
.sum
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
let vec = S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.collect::<Vec<_>>();
if vec.is_empty() {
return None;
}
let mut sum = T::from(0);
vec.into_iter().for_each(|v| sum += v);
Some(sum)
},
)
.unwrap(),
)
}),
cumulative: options.cumulative.then(|| {
Box::new(
ComputedVec::forced_import_or_init_from_2(
computation,
db,
&suffix("cumulative"),
version + VERSION + Version::ZERO,
format,
source_extra.cumulative.as_ref().unwrap().boxed_clone(),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::max_from(i, source.len()))
.map(|(_, cow)| cow.into_owned())
},
)
.unwrap(),
)
}),
})
}
pub fn compute_if_necessary<T2>(
&mut self,
max_from: I,
len_source: &impl AnyIterableVec<I, T2>,
exit: &Exit,
) -> Result<()> {
if let Some(first) = self.first.as_mut() {
first.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(last) = self.last.as_mut() {
last.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(min) = self.min.as_mut() {
min.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(max) = self.max.as_mut() {
max.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(average) = self.average.as_mut() {
average.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(sum) = self.sum.as_mut() {
sum.compute_if_necessary(max_from, len_source, exit)?;
}
if let Some(cumulative) = self.cumulative.as_mut() {
cumulative.compute_if_necessary(max_from, len_source, exit)?;
}
Ok(())
}
pub fn starting_index(&self, max_from: I) -> I {
max_from.min(I::from(
self.vecs().into_iter().map(|v| v.len()).min().unwrap(),
))
}
pub fn unwrap_first(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.first.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_average(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.average.as_ref().unwrap()
}
pub fn unwrap_sum(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.sum.as_ref().unwrap()
}
pub fn unwrap_max(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.max.as_ref().unwrap()
}
pub fn unwrap_min(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.min.as_ref().unwrap()
}
pub fn unwrap_last(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.last.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_cumulative(&self) -> &ComputedVecFrom2<I, T, S1I, T, I, S2T> {
self.cumulative.as_ref().unwrap()
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
let mut v: Vec<&dyn AnyCollectableVec> = vec![];
if let Some(first) = self.first.as_ref() {
v.push(first.as_ref());
}
if let Some(last) = self.last.as_ref() {
v.push(last.as_ref());
}
if let Some(min) = self.min.as_ref() {
v.push(min.as_ref());
}
if let Some(max) = self.max.as_ref() {
v.push(max.as_ref());
}
if let Some(average) = self.average.as_ref() {
v.push(average.as_ref());
}
if let Some(sum) = self.sum.as_ref() {
v.push(sum.as_ref());
}
if let Some(cumulative) = self.cumulative.as_ref() {
v.push(cumulative.as_ref());
}
v
}
}
#[derive(Default, Clone, Copy)]
pub struct ComputedVecBuilderOptions {
average: bool,
sum: bool,
max: bool,
min: bool,
first: bool,
last: bool,
cumulative: bool,
}
impl From<VecBuilderOptions> for ComputedVecBuilderOptions {
fn from(value: VecBuilderOptions) -> Self {
Self {
average: value.average(),
sum: value.sum(),
max: value.max(),
min: value.min(),
first: value.first(),
last: value.last(),
cumulative: value.cumulative(),
}
}
}
impl ComputedVecBuilderOptions {
pub fn add_first(mut self) -> Self {
self.first = true;
self
}
pub fn add_last(mut self) -> Self {
self.last = true;
self
}
pub fn add_min(mut self) -> Self {
self.min = true;
self
}
pub fn add_max(mut self) -> Self {
self.max = true;
self
}
pub fn add_average(mut self) -> Self {
self.average = true;
self
}
pub fn add_sum(mut self) -> Self {
self.sum = true;
self
}
pub fn add_cumulative(mut self) -> Self {
self.cumulative = true;
self
}
#[allow(unused)]
pub fn rm_min(mut self) -> Self {
self.min = false;
self
}
#[allow(unused)]
pub fn rm_max(mut self) -> Self {
self.max = false;
self
}
#[allow(unused)]
pub fn rm_average(mut self) -> Self {
self.average = false;
self
}
#[allow(unused)]
pub fn rm_sum(mut self) -> Self {
self.sum = false;
self
}
#[allow(unused)]
pub fn rm_cumulative(mut self) -> Self {
self.cumulative = false;
self
}
pub fn add_minmax(mut self) -> Self {
self.min = true;
self.max = true;
self
}
pub fn is_only_one_active(&self) -> bool {
[
self.average,
self.sum,
self.max,
self.min,
self.first,
self.last,
self.cumulative,
]
.iter()
.filter(|b| **b)
.count()
== 1
}
pub fn copy_self_extra(&self) -> Self {
Self {
cumulative: self.cumulative,
..Self::default()
}
}
}
@@ -1,16 +1,17 @@
use std::path::Path;
use brk_core::{CheckedSub, Result, StoredUsize, Version};
use brk_exit::Exit;
use brk_vec::{AnyCollectableVec, AnyIterableVec, EagerVec, Format, StoredIndex, StoredType};
use color_eyre::eyre::ContextCompat;
use allocative::Allocative;
use brk_error::{Error, Result};
use brk_structs::{CheckedSub, StoredU64, Version};
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, Database, EagerVec, Exit, Format,
GenericStoredVec, StoredIndex, StoredRaw,
};
use crate::utils::get_percentile;
use super::ComputedType;
#[derive(Clone, Debug)]
pub struct ComputedVecBuilder<I, T>
#[derive(Clone, Debug, Allocative)]
pub struct EagerVecBuilder<I, T>
where
I: StoredIndex,
T: ComputedType,
@@ -19,11 +20,11 @@ where
pub average: Option<Box<EagerVec<I, T>>>,
pub sum: Option<Box<EagerVec<I, T>>>,
pub max: Option<Box<EagerVec<I, T>>>,
pub _90p: Option<Box<EagerVec<I, T>>>,
pub _75p: Option<Box<EagerVec<I, T>>>,
pub p90: Option<Box<EagerVec<I, T>>>,
pub p75: Option<Box<EagerVec<I, T>>>,
pub median: Option<Box<EagerVec<I, T>>>,
pub _25p: Option<Box<EagerVec<I, T>>>,
pub _10p: Option<Box<EagerVec<I, T>>>,
pub p25: Option<Box<EagerVec<I, T>>>,
pub p10: Option<Box<EagerVec<I, T>>>,
pub min: Option<Box<EagerVec<I, T>>>,
pub last: Option<Box<EagerVec<I, T>>>,
pub cumulative: Option<Box<EagerVec<I, T>>>,
@@ -31,30 +32,29 @@ where
const VERSION: Version = Version::ZERO;
impl<I, T> ComputedVecBuilder<I, T>
impl<I, T> EagerVecBuilder<I, T>
where
I: StoredIndex,
T: ComputedType,
{
pub fn forced_import_compressed(
db: &Database,
name: &str,
version: Version,
options: VecBuilderOptions,
) -> Result<Self> {
Self::forced_import(db, name, version, Format::Compressed, options)
}
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
options: VecBuilderOptions,
) -> Result<Self> {
let only_one_active = options.is_only_one_active();
let prefix = |s: &str| format!("{s}_{name}");
let maybe_prefix = |s: &str| {
if only_one_active {
name.to_string()
} else {
prefix(s)
}
};
let suffix = |s: &str| format!("{name}_{s}");
let maybe_suffix = |s: &str| {
@@ -69,8 +69,8 @@ where
first: options.first.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_prefix("first"),
db,
&maybe_suffix("first"),
version + VERSION + Version::ZERO,
format,
)
@@ -79,13 +79,13 @@ where
}),
last: options.last.then(|| {
Box::new(
EagerVec::forced_import(path, name, version + Version::ZERO, format).unwrap(),
EagerVec::forced_import(db, name, version + Version::ZERO, format).unwrap(),
)
}),
min: options.min.then(|| {
Box::new(
EagerVec::forced_import(
path,
db,
&maybe_suffix("min"),
version + VERSION + Version::ZERO,
format,
@@ -96,7 +96,7 @@ where
max: options.max.then(|| {
Box::new(
EagerVec::forced_import(
path,
db,
&maybe_suffix("max"),
version + VERSION + Version::ZERO,
format,
@@ -107,7 +107,7 @@ where
median: options.median.then(|| {
Box::new(
EagerVec::forced_import(
path,
db,
&maybe_suffix("median"),
version + VERSION + Version::ZERO,
format,
@@ -118,8 +118,8 @@ where
average: options.average.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("average"),
db,
&maybe_suffix("avg"),
version + VERSION + Version::ZERO,
format,
)
@@ -129,8 +129,12 @@ where
sum: options.sum.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("sum"),
db,
&(if !options.last && !options.average && !options.min && !options.max {
name.to_string()
} else {
maybe_suffix("sum")
}),
version + VERSION + Version::ZERO,
format,
)
@@ -140,52 +144,52 @@ where
cumulative: options.cumulative.then(|| {
Box::new(
EagerVec::forced_import(
path,
&prefix("cumulative"),
db,
&suffix("cumulative"),
version + VERSION + Version::ZERO,
format,
)
.unwrap(),
)
}),
_90p: options._90p.then(|| {
p90: options.p90.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("90p"),
db,
&maybe_suffix("p90"),
version + VERSION + Version::ZERO,
format,
)
.unwrap(),
)
}),
_75p: options._75p.then(|| {
p75: options.p75.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("75p"),
db,
&maybe_suffix("p75"),
version + VERSION + Version::ZERO,
format,
)
.unwrap(),
)
}),
_25p: options._25p.then(|| {
p25: options.p25.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("25p"),
db,
&maybe_suffix("p25"),
version + VERSION + Version::ZERO,
format,
)
.unwrap(),
)
}),
_10p: options._10p.then(|| {
p10: options.p10.then(|| {
Box::new(
EagerVec::forced_import(
path,
&maybe_suffix("10p"),
db,
&maybe_suffix("p10"),
version + VERSION + Version::ZERO,
format,
)
@@ -207,7 +211,7 @@ where
return Ok(());
};
self.validate_computed_version_or_reset_file(source.version())?;
self.validate_computed_version_or_reset(source.version())?;
let index = self.starting_index(max_from);
@@ -217,8 +221,9 @@ where
cumulative_vec.iter().unwrap_get_inner(index)
});
source.iter_at(index).try_for_each(|(i, v)| -> Result<()> {
cumulative = cumulative.clone() + v.into_inner();
cumulative_vec.forced_push_at(i, cumulative.clone(), exit)
cumulative += v.into_owned();
cumulative_vec.forced_push_at(i, cumulative, exit)?;
Ok(())
})?;
self.safe_flush(exit)?;
@@ -231,13 +236,13 @@ where
max_from: I,
source: &impl AnyIterableVec<I2, T>,
first_indexes: &impl AnyIterableVec<I, I2>,
count_indexes: &impl AnyIterableVec<I, StoredUsize>,
count_indexes: &impl AnyIterableVec<I, StoredU64>,
exit: &Exit,
) -> Result<()>
where
I2: StoredIndex + StoredType + CheckedSub<I2>,
I2: StoredIndex + StoredRaw + CheckedSub<I2>,
{
self.validate_computed_version_or_reset_file(
self.validate_computed_version_or_reset(
source.version() + first_indexes.version() + count_indexes.version(),
)?;
@@ -256,10 +261,10 @@ where
first_indexes
.iter_at(index)
.try_for_each(|(i, first_index)| -> Result<()> {
let first_index = first_index.into_inner();
.try_for_each(|(index, first_index)| -> Result<()> {
let first_index = first_index.into_owned();
let count_index = count_indexes_iter.unwrap_get_inner(i);
let count_index = count_indexes_iter.unwrap_get_inner(index);
if let Some(first) = self.first.as_mut() {
let f = source_iter
@@ -269,7 +274,7 @@ where
}
if let Some(last) = self.last.as_mut() {
let count_index = *count_index;
let count_index = *count_index as usize;
if count_index == 0 {
panic!("should compute last if count can be 0")
}
@@ -280,7 +285,7 @@ where
// dbg!(first_index, count_index, last_index);
// })
// .unwrap()
// .into_inner();
// .into_owned();
last.forced_push_at(index, v, exit)?;
}
@@ -288,19 +293,19 @@ where
let needs_average_sum_or_cumulative =
needs_sum_or_cumulative || self.average.is_some();
let needs_sorted = self.max.is_some()
|| self._90p.is_some()
|| self._75p.is_some()
|| self.p90.is_some()
|| self.p75.is_some()
|| self.median.is_some()
|| self._25p.is_some()
|| self._10p.is_some()
|| self.p25.is_some()
|| self.p10.is_some()
|| self.min.is_some();
let needs_values = needs_sorted || needs_average_sum_or_cumulative;
if needs_values {
source_iter.set(first_index);
let mut values = (&mut source_iter)
.take(*count_index)
.map(|(_, v)| v.into_inner())
.take(*count_index as usize)
.map(|(_, v)| v.into_owned())
.collect::<Vec<_>>();
if needs_sorted {
@@ -308,14 +313,14 @@ where
if let Some(max) = self.max.as_mut() {
max.forced_push_at(
i,
values
index,
*values
.last()
.context("expect some")
.ok_or(Error::Str("expect some"))
.inspect_err(|_| {
dbg!(
&values,
max.path(),
max.name(),
first_indexes.name(),
first_index,
count_indexes.name(),
@@ -324,34 +329,33 @@ where
source.name()
);
})
.unwrap()
.clone(),
.unwrap(),
exit,
)?;
}
if let Some(_90p) = self._90p.as_mut() {
_90p.forced_push_at(i, get_percentile(&values, 0.90), exit)?;
if let Some(p90) = self.p90.as_mut() {
p90.forced_push_at(index, get_percentile(&values, 0.90), exit)?;
}
if let Some(_75p) = self._75p.as_mut() {
_75p.forced_push_at(i, get_percentile(&values, 0.75), exit)?;
if let Some(p75) = self.p75.as_mut() {
p75.forced_push_at(index, get_percentile(&values, 0.75), exit)?;
}
if let Some(median) = self.median.as_mut() {
median.forced_push_at(i, get_percentile(&values, 0.50), exit)?;
median.forced_push_at(index, get_percentile(&values, 0.50), exit)?;
}
if let Some(_25p) = self._25p.as_mut() {
_25p.forced_push_at(i, get_percentile(&values, 0.25), exit)?;
if let Some(p25) = self.p25.as_mut() {
p25.forced_push_at(index, get_percentile(&values, 0.25), exit)?;
}
if let Some(_10p) = self._10p.as_mut() {
_10p.forced_push_at(i, get_percentile(&values, 0.10), exit)?;
if let Some(p10) = self.p10.as_mut() {
p10.forced_push_at(index, get_percentile(&values, 0.10), exit)?;
}
if let Some(min) = self.min.as_mut() {
min.forced_push_at(i, values.first().unwrap().clone(), exit)?;
min.forced_push_at(index, *values.first().unwrap(), exit)?;
}
}
@@ -360,19 +364,19 @@ where
let sum = values.into_iter().fold(T::from(0), |a, b| a + b);
if let Some(average) = self.average.as_mut() {
let avg = sum.clone() / len;
average.forced_push_at(i, avg, exit)?;
let avg = sum / len;
average.forced_push_at(index, avg, exit)?;
}
if needs_sum_or_cumulative {
if let Some(sum_vec) = self.sum.as_mut() {
sum_vec.forced_push_at(i, sum.clone(), exit)?;
sum_vec.forced_push_at(index, sum, exit)?;
}
if let Some(cumulative_vec) = self.cumulative.as_mut() {
let t = cumulative.as_ref().unwrap().clone() + sum;
cumulative.replace(t.clone());
cumulative_vec.forced_push_at(i, t, exit)?;
let t = cumulative.unwrap() + sum;
cumulative.replace(t);
cumulative_vec.forced_push_at(index, t, exit)?;
}
}
}
@@ -390,24 +394,24 @@ where
pub fn from_aligned<I2>(
&mut self,
max_from: I,
source: &ComputedVecBuilder<I2, T>,
source: &EagerVecBuilder<I2, T>,
first_indexes: &impl AnyIterableVec<I, I2>,
count_indexes: &impl AnyIterableVec<I, StoredUsize>,
count_indexes: &impl AnyIterableVec<I, StoredU64>,
exit: &Exit,
) -> Result<()>
where
I2: StoredIndex + StoredType + CheckedSub<I2>,
I2: StoredIndex + StoredRaw + CheckedSub<I2>,
{
if self._90p.is_some()
|| self._75p.is_some()
if self.p90.is_some()
|| self.p75.is_some()
|| self.median.is_some()
|| self._25p.is_some()
|| self._10p.is_some()
|| self.p25.is_some()
|| self.p10.is_some()
{
panic!("unsupported");
}
self.validate_computed_version_or_reset_file(
self.validate_computed_version_or_reset(
VERSION + first_indexes.version() + count_indexes.version(),
)?;
@@ -430,10 +434,10 @@ where
first_indexes
.iter_at(index)
.try_for_each(|(i, first_index, ..)| -> Result<()> {
let first_index = first_index.into_inner();
.try_for_each(|(index, first_index, ..)| -> Result<()> {
let first_index = first_index.into_owned();
let count_index = count_indexes_iter.unwrap_get_inner(i);
let count_index = count_indexes_iter.unwrap_get_inner(index);
if let Some(first) = self.first.as_mut() {
let v = source_first_iter
@@ -444,7 +448,7 @@ where
}
if let Some(last) = self.last.as_mut() {
let count_index = *count_index;
let count_index = *count_index as usize;
if count_index == 0 {
panic!("should compute last if count can be 0")
}
@@ -468,22 +472,22 @@ where
let source_max_iter = source_max_iter.as_mut().unwrap();
source_max_iter.set(first_index);
let mut values = source_max_iter
.take(*count_index)
.map(|(_, v)| v.into_inner())
.take(*count_index as usize)
.map(|(_, v)| v.into_owned())
.collect::<Vec<_>>();
values.sort_unstable();
max.forced_push_at(i, values.last().unwrap().clone(), exit)?;
max.forced_push_at(index, *values.last().unwrap(), exit)?;
}
if let Some(min) = self.min.as_mut() {
let source_min_iter = source_min_iter.as_mut().unwrap();
source_min_iter.set(first_index);
let mut values = source_min_iter
.take(*count_index)
.map(|(_, v)| v.into_inner())
.take(*count_index as usize)
.map(|(_, v)| v.into_owned())
.collect::<Vec<_>>();
values.sort_unstable();
min.forced_push_at(i, values.first().unwrap().clone(), exit)?;
min.forced_push_at(index, *values.first().unwrap(), exit)?;
}
}
@@ -492,8 +496,8 @@ where
let source_average_iter = source_average_iter.as_mut().unwrap();
source_average_iter.set(first_index);
let values = source_average_iter
.take(*count_index)
.map(|(_, v)| v.into_inner())
.take(*count_index as usize)
.map(|(_, v)| v.into_owned())
.collect::<Vec<_>>();
let len = values.len();
@@ -501,27 +505,27 @@ where
// TODO: Multiply by count then divide by cumulative
// Right now it's not 100% accurate as there could be more or less elements in the lower timeframe (28 days vs 31 days in a month for example)
let avg = cumulative / len;
average.forced_push_at(i, avg, exit)?;
average.forced_push_at(index, avg, exit)?;
}
if needs_sum_or_cumulative {
let source_sum_iter = source_sum_iter.as_mut().unwrap();
source_sum_iter.set(first_index);
let values = source_sum_iter
.take(*count_index)
.map(|(_, v)| v.into_inner())
.take(*count_index as usize)
.map(|(_, v)| v.into_owned())
.collect::<Vec<_>>();
let sum = values.into_iter().fold(T::from(0), |a, b| a + b);
if let Some(sum_vec) = self.sum.as_mut() {
sum_vec.forced_push_at(i, sum.clone(), exit)?;
sum_vec.forced_push_at(index, sum, exit)?;
}
if let Some(cumulative_vec) = self.cumulative.as_mut() {
let t = cumulative.as_ref().unwrap().clone() + sum;
cumulative.replace(t.clone());
cumulative_vec.forced_push_at(i, t, exit)?;
let t = cumulative.unwrap() + sum;
cumulative.replace(t);
cumulative_vec.forced_push_at(index, t, exit)?;
}
}
}
@@ -555,24 +559,24 @@ where
self.max.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_90p(&self) -> &EagerVec<I, T> {
self._90p.as_ref().unwrap()
pub fn unwrap_p90(&self) -> &EagerVec<I, T> {
self.p90.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_75p(&self) -> &EagerVec<I, T> {
self._75p.as_ref().unwrap()
pub fn unwrap_p75(&self) -> &EagerVec<I, T> {
self.p75.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_median(&self) -> &EagerVec<I, T> {
self.median.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_25p(&self) -> &EagerVec<I, T> {
self._25p.as_ref().unwrap()
pub fn unwrap_p25(&self) -> &EagerVec<I, T> {
self.p25.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_10p(&self) -> &EagerVec<I, T> {
self._10p.as_ref().unwrap()
pub fn unwrap_p10(&self) -> &EagerVec<I, T> {
self.p10.as_ref().unwrap()
}
pub fn unwrap_min(&self) -> &EagerVec<I, T> {
self.min.as_ref().unwrap()
@@ -612,17 +616,17 @@ where
if let Some(cumulative) = self.cumulative.as_ref() {
v.push(cumulative.as_ref());
}
if let Some(_90p) = self._90p.as_ref() {
v.push(_90p.as_ref());
if let Some(p90) = self.p90.as_ref() {
v.push(p90.as_ref());
}
if let Some(_75p) = self._75p.as_ref() {
v.push(_75p.as_ref());
if let Some(p75) = self.p75.as_ref() {
v.push(p75.as_ref());
}
if let Some(_25p) = self._25p.as_ref() {
v.push(_25p.as_ref());
if let Some(p25) = self.p25.as_ref() {
v.push(p25.as_ref());
}
if let Some(_10p) = self._10p.as_ref() {
v.push(_10p.as_ref());
if let Some(p10) = self.p10.as_ref() {
v.push(p10.as_ref());
}
v
@@ -653,58 +657,58 @@ where
if let Some(cumulative) = self.cumulative.as_mut() {
cumulative.safe_flush(exit)?;
}
if let Some(_90p) = self._90p.as_mut() {
_90p.safe_flush(exit)?;
if let Some(p90) = self.p90.as_mut() {
p90.safe_flush(exit)?;
}
if let Some(_75p) = self._75p.as_mut() {
_75p.safe_flush(exit)?;
if let Some(p75) = self.p75.as_mut() {
p75.safe_flush(exit)?;
}
if let Some(_25p) = self._25p.as_mut() {
_25p.safe_flush(exit)?;
if let Some(p25) = self.p25.as_mut() {
p25.safe_flush(exit)?;
}
if let Some(_10p) = self._10p.as_mut() {
_10p.safe_flush(exit)?;
if let Some(p10) = self.p10.as_mut() {
p10.safe_flush(exit)?;
}
Ok(())
}
pub fn validate_computed_version_or_reset_file(&mut self, version: Version) -> Result<()> {
pub fn validate_computed_version_or_reset(&mut self, version: Version) -> Result<()> {
if let Some(first) = self.first.as_mut() {
first.validate_computed_version_or_reset_file(Version::ZERO + version)?;
first.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(last) = self.last.as_mut() {
last.validate_computed_version_or_reset_file(Version::ZERO + version)?;
last.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(min) = self.min.as_mut() {
min.validate_computed_version_or_reset_file(Version::ZERO + version)?;
min.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(max) = self.max.as_mut() {
max.validate_computed_version_or_reset_file(Version::ZERO + version)?;
max.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(median) = self.median.as_mut() {
median.validate_computed_version_or_reset_file(Version::ZERO + version)?;
median.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(average) = self.average.as_mut() {
average.validate_computed_version_or_reset_file(Version::ZERO + version)?;
average.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(sum) = self.sum.as_mut() {
sum.validate_computed_version_or_reset_file(Version::ZERO + version)?;
sum.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(cumulative) = self.cumulative.as_mut() {
cumulative.validate_computed_version_or_reset_file(Version::ZERO + version)?;
cumulative.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(_90p) = self._90p.as_mut() {
_90p.validate_computed_version_or_reset_file(Version::ZERO + version)?;
if let Some(p90) = self.p90.as_mut() {
p90.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(_75p) = self._75p.as_mut() {
_75p.validate_computed_version_or_reset_file(Version::ZERO + version)?;
if let Some(p75) = self.p75.as_mut() {
p75.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(_25p) = self._25p.as_mut() {
_25p.validate_computed_version_or_reset_file(Version::ZERO + version)?;
if let Some(p25) = self.p25.as_mut() {
p25.validate_computed_version_or_reset(Version::ZERO + version)?;
}
if let Some(_10p) = self._10p.as_mut() {
_10p.validate_computed_version_or_reset_file(Version::ZERO + version)?;
if let Some(p10) = self.p10.as_mut() {
p10.validate_computed_version_or_reset(Version::ZERO + version)?;
}
Ok(())
@@ -712,22 +716,70 @@ where
}
#[derive(Default, Clone, Copy)]
pub struct StorableVecGeneatorOptions {
pub struct VecBuilderOptions {
average: bool,
sum: bool,
max: bool,
_90p: bool,
_75p: bool,
p90: bool,
p75: bool,
median: bool,
_25p: bool,
_10p: bool,
p25: bool,
p10: bool,
min: bool,
first: bool,
last: bool,
cumulative: bool,
}
impl StorableVecGeneatorOptions {
impl VecBuilderOptions {
pub fn average(&self) -> bool {
self.average
}
pub fn sum(&self) -> bool {
self.sum
}
pub fn max(&self) -> bool {
self.max
}
pub fn p90(&self) -> bool {
self.p90
}
pub fn p75(&self) -> bool {
self.p75
}
pub fn median(&self) -> bool {
self.median
}
pub fn p25(&self) -> bool {
self.p25
}
pub fn p10(&self) -> bool {
self.p10
}
pub fn min(&self) -> bool {
self.min
}
pub fn first(&self) -> bool {
self.first
}
pub fn last(&self) -> bool {
self.last
}
pub fn cumulative(&self) -> bool {
self.cumulative
}
pub fn add_first(mut self) -> Self {
self.first = true;
self
@@ -765,26 +817,26 @@ impl StorableVecGeneatorOptions {
}
#[allow(unused)]
pub fn add_90p(mut self) -> Self {
self._90p = true;
pub fn add_p90(mut self) -> Self {
self.p90 = true;
self
}
#[allow(unused)]
pub fn add_75p(mut self) -> Self {
self._75p = true;
pub fn add_p75(mut self) -> Self {
self.p75 = true;
self
}
#[allow(unused)]
pub fn add_25p(mut self) -> Self {
self._25p = true;
pub fn add_p25(mut self) -> Self {
self.p25 = true;
self
}
#[allow(unused)]
pub fn add_10p(mut self) -> Self {
self._10p = true;
pub fn add_p10(mut self) -> Self {
self.p10 = true;
self
}
@@ -824,26 +876,26 @@ impl StorableVecGeneatorOptions {
}
#[allow(unused)]
pub fn rm_90p(mut self) -> Self {
self._90p = false;
pub fn rm_p90(mut self) -> Self {
self.p90 = false;
self
}
#[allow(unused)]
pub fn rm_75p(mut self) -> Self {
self._75p = false;
pub fn rm_p75(mut self) -> Self {
self.p75 = false;
self
}
#[allow(unused)]
pub fn rm_25p(mut self) -> Self {
self._25p = false;
pub fn rm_p25(mut self) -> Self {
self.p25 = false;
self
}
#[allow(unused)]
pub fn rm_10p(mut self) -> Self {
self._10p = false;
pub fn rm_p10(mut self) -> Self {
self.p10 = false;
self
}
@@ -860,20 +912,20 @@ impl StorableVecGeneatorOptions {
}
pub fn add_percentiles(mut self) -> Self {
self._90p = true;
self._75p = true;
self.p90 = true;
self.p75 = true;
self.median = true;
self._25p = true;
self._10p = true;
self.p25 = true;
self.p10 = true;
self
}
pub fn remove_percentiles(mut self) -> Self {
self._90p = false;
self._75p = false;
self.p90 = false;
self.p75 = false;
self.median = false;
self._25p = false;
self._10p = false;
self.p25 = false;
self.p10 = false;
self
}
@@ -882,11 +934,11 @@ impl StorableVecGeneatorOptions {
self.average,
self.sum,
self.max,
self._90p,
self._75p,
self.p90,
self.p75,
self.median,
self._25p,
self._10p,
self.p25,
self.p10,
self.min,
self.first,
self.last,
@@ -0,0 +1,396 @@
use allocative::Allocative;
use brk_structs::Version;
use vecdb::{
AnyBoxedIterableVec, AnyCloneableIterableVec, AnyCollectableVec, FromCoarserIndex,
LazyVecFrom2, StoredIndex,
};
use crate::grouped::{EagerVecBuilder, VecBuilderOptions};
use super::ComputedType;
#[allow(clippy::type_complexity)]
#[derive(Clone, Allocative)]
pub struct LazyVecBuilder<I, T, S1I, S2T>
where
I: StoredIndex,
T: ComputedType,
S2T: ComputedType,
{
pub first: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub average: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub sum: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub max: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub min: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub last: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
pub cumulative: Option<Box<LazyVecFrom2<I, T, S1I, T, I, S2T>>>,
}
const VERSION: Version = Version::ZERO;
impl<I, T, S1I, S2T> LazyVecBuilder<I, T, S1I, S2T>
where
I: StoredIndex,
T: ComputedType + 'static,
S1I: StoredIndex + 'static + FromCoarserIndex<I>,
S2T: ComputedType,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
name: &str,
version: Version,
source: Option<AnyBoxedIterableVec<S1I, T>>,
source_extra: &EagerVecBuilder<S1I, T>,
len_source: AnyBoxedIterableVec<I, S2T>,
options: LazyVecBuilderOptions,
) -> Self {
let only_one_active = options.is_only_one_active();
let suffix = |s: &str| format!("{name}_{s}");
let maybe_suffix = |s: &str| {
if only_one_active {
name.to_string()
} else {
suffix(s)
}
};
Self {
first: options.first.then(|| {
Box::new(LazyVecFrom2::init(
&maybe_suffix("first"),
version + VERSION + Version::ZERO,
source_extra
.first
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::min_from(i))
.map(|(_, cow)| cow.into_owned())
},
))
}),
last: options.last.then(|| {
Box::new(LazyVecFrom2::init(
name,
version + VERSION + Version::ZERO,
source_extra.last.as_ref().map_or_else(
|| {
source
.as_ref()
.unwrap_or_else(|| {
dbg!(name, I::to_string());
panic!()
})
.clone()
},
|v| v.clone(),
),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::max_from(i, source.len()))
.map(|(_, cow)| cow.into_owned())
},
))
}),
min: options.min.then(|| {
Box::new(LazyVecFrom2::init(
&maybe_suffix("min"),
version + VERSION + Version::ZERO,
source_extra
.min
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.min()
},
))
}),
max: options.max.then(|| {
Box::new(LazyVecFrom2::init(
&maybe_suffix("max"),
version + VERSION + Version::ZERO,
source_extra
.max
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.max()
},
))
}),
average: options.average.then(|| {
Box::new(LazyVecFrom2::init(
&maybe_suffix("avg"),
version + VERSION + Version::ZERO,
source_extra
.average
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
let vec = S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.collect::<Vec<_>>();
if vec.is_empty() {
return None;
}
let mut sum = T::from(0);
let len = vec.len();
vec.into_iter().for_each(|v| sum += v);
Some(sum / len)
},
))
}),
sum: options.sum.then(|| {
Box::new(LazyVecFrom2::init(
&(if !options.last && !options.average && !options.min && !options.max {
name.to_string()
} else {
maybe_suffix("sum")
}),
version + VERSION + Version::ZERO,
source_extra
.sum
.as_ref()
.map_or_else(|| source.as_ref().unwrap().clone(), |v| v.clone()),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
let vec = S1I::inclusive_range_from(i, source.len())
.flat_map(|i| source.next_at(i).map(|(_, cow)| cow.into_owned()))
.collect::<Vec<_>>();
if vec.is_empty() {
return None;
}
let mut sum = T::from(0);
vec.into_iter().for_each(|v| sum += v);
Some(sum)
},
))
}),
cumulative: options.cumulative.then(|| {
Box::new(LazyVecFrom2::init(
&suffix("cumulative"),
version + VERSION + Version::ZERO,
source_extra.cumulative.as_ref().unwrap().boxed_clone(),
len_source.clone(),
|i: I, source, len_source| {
if i.unwrap_to_usize() >= len_source.len() {
return None;
}
source
.next_at(S1I::max_from(i, source.len()))
.map(|(_, cow)| cow.into_owned())
},
))
}),
}
}
pub fn starting_index(&self, max_from: I) -> I {
max_from.min(I::from(
self.vecs().into_iter().map(|v| v.len()).min().unwrap(),
))
}
pub fn unwrap_first(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.first.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_average(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.average.as_ref().unwrap()
}
pub fn unwrap_sum(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.sum.as_ref().unwrap()
}
pub fn unwrap_max(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.max.as_ref().unwrap()
}
pub fn unwrap_min(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.min.as_ref().unwrap()
}
pub fn unwrap_last(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.last.as_ref().unwrap()
}
#[allow(unused)]
pub fn unwrap_cumulative(&self) -> &LazyVecFrom2<I, T, S1I, T, I, S2T> {
self.cumulative.as_ref().unwrap()
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
let mut v: Vec<&dyn AnyCollectableVec> = vec![];
if let Some(first) = self.first.as_ref() {
v.push(first.as_ref());
}
if let Some(last) = self.last.as_ref() {
v.push(last.as_ref());
}
if let Some(min) = self.min.as_ref() {
v.push(min.as_ref());
}
if let Some(max) = self.max.as_ref() {
v.push(max.as_ref());
}
if let Some(average) = self.average.as_ref() {
v.push(average.as_ref());
}
if let Some(sum) = self.sum.as_ref() {
v.push(sum.as_ref());
}
if let Some(cumulative) = self.cumulative.as_ref() {
v.push(cumulative.as_ref());
}
v
}
}
#[derive(Default, Clone, Copy)]
pub struct LazyVecBuilderOptions {
average: bool,
sum: bool,
max: bool,
min: bool,
first: bool,
last: bool,
cumulative: bool,
}
impl From<VecBuilderOptions> for LazyVecBuilderOptions {
fn from(value: VecBuilderOptions) -> Self {
Self {
average: value.average(),
sum: value.sum(),
max: value.max(),
min: value.min(),
first: value.first(),
last: value.last(),
cumulative: value.cumulative(),
}
}
}
impl LazyVecBuilderOptions {
pub fn add_first(mut self) -> Self {
self.first = true;
self
}
pub fn add_last(mut self) -> Self {
self.last = true;
self
}
pub fn add_min(mut self) -> Self {
self.min = true;
self
}
pub fn add_max(mut self) -> Self {
self.max = true;
self
}
pub fn add_average(mut self) -> Self {
self.average = true;
self
}
pub fn add_sum(mut self) -> Self {
self.sum = true;
self
}
pub fn add_cumulative(mut self) -> Self {
self.cumulative = true;
self
}
#[allow(unused)]
pub fn rm_min(mut self) -> Self {
self.min = false;
self
}
#[allow(unused)]
pub fn rm_max(mut self) -> Self {
self.max = false;
self
}
#[allow(unused)]
pub fn rm_average(mut self) -> Self {
self.average = false;
self
}
#[allow(unused)]
pub fn rm_sum(mut self) -> Self {
self.sum = false;
self
}
#[allow(unused)]
pub fn rm_cumulative(mut self) -> Self {
self.cumulative = false;
self
}
pub fn add_minmax(mut self) -> Self {
self.min = true;
self.max = true;
self
}
pub fn is_only_one_active(&self) -> bool {
[
self.average,
self.sum,
self.max,
self.min,
self.first,
self.last,
self.cumulative,
]
.iter()
.filter(|b| **b)
.count()
== 1
}
pub fn copy_self_extra(&self) -> Self {
Self {
cumulative: self.cumulative,
..Self::default()
}
}
}
@@ -0,0 +1,23 @@
use std::ops::{Add, AddAssign, Div};
use vecdb::StoredCompressed;
pub trait ComputedType
where
Self: StoredCompressed
+ From<usize>
+ Div<usize, Output = Self>
+ Add<Output = Self>
+ AddAssign
+ Ord,
{
}
impl<T> ComputedType for T where
T: StoredCompressed
+ From<usize>
+ Div<usize, Output = Self>
+ Add<Output = Self>
+ AddAssign
+ Ord
{
}
@@ -0,0 +1,178 @@
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{
DateIndex, DecadeIndex, MonthIndex, QuarterIndex, SemesterIndex, Version, WeekIndex, YearIndex,
};
use vecdb::{AnyCloneableIterableVec, AnyCollectableVec, AnyIterableVec, Database, EagerVec, Exit};
use crate::{Indexes, grouped::LazyVecBuilder, indexes};
use super::{ComputedType, EagerVecBuilder, Source, VecBuilderOptions};
#[derive(Clone, Allocative)]
pub struct ComputedVecsFromDateIndex<T>
where
T: ComputedType + PartialOrd,
{
pub dateindex: Option<EagerVec<DateIndex, T>>,
pub dateindex_extra: EagerVecBuilder<DateIndex, T>,
pub weekindex: LazyVecBuilder<WeekIndex, T, DateIndex, WeekIndex>,
pub monthindex: LazyVecBuilder<MonthIndex, T, DateIndex, MonthIndex>,
pub quarterindex: LazyVecBuilder<QuarterIndex, T, DateIndex, QuarterIndex>,
pub semesterindex: LazyVecBuilder<SemesterIndex, T, DateIndex, SemesterIndex>,
pub yearindex: LazyVecBuilder<YearIndex, T, DateIndex, YearIndex>,
pub decadeindex: LazyVecBuilder<DecadeIndex, T, DateIndex, DecadeIndex>,
}
const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromDateIndex<T>
where
T: ComputedType + 'static,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
name: &str,
source: Source<DateIndex, T>,
version: Version,
indexes: &indexes::Vecs,
options: VecBuilderOptions,
) -> Result<Self> {
let dateindex = source.is_compute().then(|| {
EagerVec::forced_import_compressed(db, name, version + VERSION + Version::ZERO).unwrap()
});
let dateindex_extra = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
options.copy_self_extra(),
)?;
let options = options.remove_percentiles();
let dateindex_source = source.vec().or(dateindex.as_ref().map(|v| v.boxed_clone()));
Ok(Self {
weekindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.weekindex_to_weekindex.boxed_clone(),
options.into(),
),
monthindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.monthindex_to_monthindex.boxed_clone(),
options.into(),
),
quarterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.quarterindex_to_quarterindex.boxed_clone(),
options.into(),
),
semesterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.semesterindex_to_semesterindex.boxed_clone(),
options.into(),
),
yearindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.yearindex_to_yearindex.boxed_clone(),
options.into(),
),
decadeindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
dateindex_source.clone(),
&dateindex_extra,
indexes.decadeindex_to_decadeindex.boxed_clone(),
options.into(),
),
dateindex,
dateindex_extra,
})
}
pub fn compute_all<F>(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> Result<()>
where
F: FnMut(
&mut EagerVec<DateIndex, T>,
&Indexer,
&indexes::Vecs,
&Indexes,
&Exit,
) -> Result<()>,
{
compute(
self.dateindex.as_mut().unwrap(),
indexer,
indexes,
starting_indexes,
exit,
)?;
let dateindex: Option<&EagerVec<DateIndex, T>> = None;
self.compute_rest(starting_indexes, exit, dateindex)
}
pub fn compute_rest(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
dateindex: Option<&impl AnyIterableVec<DateIndex, T>>,
) -> Result<()> {
if let Some(dateindex) = dateindex {
self.dateindex_extra
.extend(starting_indexes.dateindex, dateindex, exit)?;
} else {
let dateindex = self.dateindex.as_ref().unwrap();
self.dateindex_extra
.extend(starting_indexes.dateindex, dateindex, exit)?;
}
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.dateindex
.as_ref()
.map_or(vec![], |v| vec![v as &dyn AnyCollectableVec]),
self.dateindex_extra.vecs(),
self.weekindex.vecs(),
self.monthindex.vecs(),
self.quarterindex.vecs(),
self.semesterindex.vecs(),
self.yearindex.vecs(),
self.decadeindex.vecs(),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
@@ -1,116 +1,132 @@
use std::path::Path;
use allocative::Allocative;
use brk_error::Result;
use brk_core::{
DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, Result, Version,
WeekIndex, YearIndex,
};
use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, AnyIterableVec, EagerVec, Format};
use brk_structs::{
DateIndex, DecadeIndex, DifficultyEpoch, Height, MonthIndex, QuarterIndex, SemesterIndex,
Version, WeekIndex, YearIndex,
};
use vecdb::{AnyCloneableIterableVec, AnyCollectableVec, AnyIterableVec, Database, EagerVec, Exit};
use crate::vecs::{Indexes, indexes};
use crate::{
Indexes,
grouped::{LazyVecBuilder, Source},
indexes,
};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
use super::{ComputedType, EagerVecBuilder, VecBuilderOptions};
#[derive(Clone)]
#[derive(Clone, Allocative)]
pub struct ComputedVecsFromHeight<T>
where
T: ComputedType + PartialOrd,
{
pub height: Option<EagerVec<Height, T>>,
pub height_extra: ComputedVecBuilder<Height, T>,
pub dateindex: ComputedVecBuilder<DateIndex, T>,
pub weekindex: ComputedVecBuilder<WeekIndex, T>,
pub difficultyepoch: ComputedVecBuilder<DifficultyEpoch, T>,
pub monthindex: ComputedVecBuilder<MonthIndex, T>,
pub quarterindex: ComputedVecBuilder<QuarterIndex, T>,
pub yearindex: ComputedVecBuilder<YearIndex, T>,
pub height_extra: EagerVecBuilder<Height, T>,
pub dateindex: EagerVecBuilder<DateIndex, T>,
pub weekindex: LazyVecBuilder<WeekIndex, T, DateIndex, WeekIndex>,
pub difficultyepoch: EagerVecBuilder<DifficultyEpoch, T>,
pub monthindex: LazyVecBuilder<MonthIndex, T, DateIndex, MonthIndex>,
pub quarterindex: LazyVecBuilder<QuarterIndex, T, DateIndex, QuarterIndex>,
pub semesterindex: LazyVecBuilder<SemesterIndex, T, DateIndex, SemesterIndex>,
pub yearindex: LazyVecBuilder<YearIndex, T, DateIndex, YearIndex>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
pub decadeindex: ComputedVecBuilder<DecadeIndex, T>,
pub decadeindex: LazyVecBuilder<DecadeIndex, T, DateIndex, DecadeIndex>,
}
const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromHeight<T>
where
T: ComputedType + Ord + From<f64>,
T: ComputedType + Ord + From<f64> + 'static,
f64: From<T>,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
compute_source: bool,
source: Source<Height, T>,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let height = compute_source.then(|| {
EagerVec::forced_import(path, name, version + VERSION + Version::ZERO, format).unwrap()
indexes: &indexes::Vecs,
options: VecBuilderOptions,
) -> Result<Self> {
let height = source.is_compute().then(|| {
EagerVec::forced_import_compressed(db, name, version + VERSION + Version::ZERO).unwrap()
});
let height_extra = ComputedVecBuilder::forced_import(
path,
let height_extra = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options.copy_self_extra(),
)?;
let dateindex = ComputedVecBuilder::forced_import(
path,
let dateindex = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options,
)?;
let options = options.remove_percentiles();
Ok(Self {
weekindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.weekindex_to_weekindex.boxed_clone(),
options.into(),
),
monthindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.monthindex_to_monthindex.boxed_clone(),
options.into(),
),
quarterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.quarterindex_to_quarterindex.boxed_clone(),
options.into(),
),
semesterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.semesterindex_to_semesterindex.boxed_clone(),
options.into(),
),
yearindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.yearindex_to_yearindex.boxed_clone(),
options.into(),
),
decadeindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.decadeindex_to_decadeindex.boxed_clone(),
options.into(),
),
// halvingepoch: StorableVecGeneator::forced_import(db, name, version + VERSION + Version::ZERO, format, options)?,
height,
height_extra,
dateindex,
weekindex: ComputedVecBuilder::forced_import(
path,
difficultyepoch: EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
difficultyepoch: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
monthindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
quarterindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
yearindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, version + VERSION + Version::ZERO, format, options)?,
decadeindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
})
@@ -123,7 +139,7 @@ where
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> color_eyre::Result<()>
) -> Result<()>
where
F: FnMut(&mut EagerVec<Height, T>, &Indexer, &indexes::Vecs, &Indexes, &Exit) -> Result<()>,
{
@@ -144,9 +160,9 @@ where
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
height: Option<&impl AnyIterableVec<Height, T>>,
) -> color_eyre::Result<()> {
if let Some(height) = height {
height_vec: Option<&impl AnyIterableVec<Height, T>>,
) -> Result<()> {
if let Some(height) = height_vec {
self.height_extra
.extend(starting_indexes.height, height, exit)?;
@@ -188,46 +204,6 @@ where
)?;
}
self.weekindex.from_aligned(
starting_indexes.weekindex,
&self.dateindex,
&indexes.weekindex_to_first_dateindex,
&indexes.weekindex_to_dateindex_count,
exit,
)?;
self.monthindex.from_aligned(
starting_indexes.monthindex,
&self.dateindex,
&indexes.monthindex_to_first_dateindex,
&indexes.monthindex_to_dateindex_count,
exit,
)?;
self.quarterindex.from_aligned(
starting_indexes.quarterindex,
&self.monthindex,
&indexes.quarterindex_to_first_monthindex,
&indexes.quarterindex_to_monthindex_count,
exit,
)?;
self.yearindex.from_aligned(
starting_indexes.yearindex,
&self.monthindex,
&indexes.yearindex_to_first_monthindex,
&indexes.yearindex_to_monthindex_count,
exit,
)?;
self.decadeindex.from_aligned(
starting_indexes.decadeindex,
&self.yearindex,
&indexes.decadeindex_to_first_yearindex,
&indexes.decadeindex_to_yearindex_count,
exit,
)?;
Ok(())
}
@@ -242,6 +218,7 @@ where
self.difficultyepoch.vecs(),
self.monthindex.vecs(),
self.quarterindex.vecs(),
self.semesterindex.vecs(),
self.yearindex.vecs(),
// self.halvingepoch.vecs(),
self.decadeindex.vecs(),
@@ -1,13 +1,12 @@
use std::path::Path;
use brk_error::Result;
use brk_core::{DifficultyEpoch, Height, Result, Version};
use brk_exit::Exit;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, EagerVec, Format};
use brk_structs::{DifficultyEpoch, Height, Version};
use vecdb::{AnyCollectableVec, Database, EagerVec, Exit};
use crate::vecs::{Indexes, indexes};
use crate::{Indexes, indexes};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
use super::{ComputedType, EagerVecBuilder, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedVecsFromHeightStrict<T>
@@ -15,8 +14,8 @@ where
T: ComputedType + PartialOrd,
{
pub height: EagerVec<Height, T>,
pub height_extra: ComputedVecBuilder<Height, T>,
pub difficultyepoch: ComputedVecBuilder<DifficultyEpoch, T>,
pub height_extra: EagerVecBuilder<Height, T>,
pub difficultyepoch: EagerVecBuilder<DifficultyEpoch, T>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
}
@@ -28,20 +27,18 @@ where
f64: From<T>,
{
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
options: VecBuilderOptions,
) -> Result<Self> {
let height =
EagerVec::forced_import(path, name, version + VERSION + Version::ZERO, format)?;
EagerVec::forced_import_compressed(db, name, version + VERSION + Version::ZERO)?;
let height_extra = ComputedVecBuilder::forced_import(
path,
let height_extra = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options.copy_self_extra(),
)?;
@@ -50,14 +47,13 @@ where
Ok(Self {
height,
height_extra,
difficultyepoch: ComputedVecBuilder::forced_import(
path,
difficultyepoch: EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, version + VERSION + Version::ZERO, format, options)?,
// halvingepoch: StorableVecGeneator::forced_import(db, name, version + VERSION + Version::ZERO, format, options)?,
})
}
@@ -68,7 +64,7 @@ where
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> color_eyre::Result<()>
) -> Result<()>
where
F: FnMut(&mut EagerVec<Height, T>, &Indexer, &indexes::Vecs, &Indexes, &Exit) -> Result<()>,
{
@@ -1,121 +1,140 @@
use std::path::Path;
use brk_core::{
Bitcoin, DateIndex, DecadeIndex, DifficultyEpoch, Dollars, Height, MonthIndex, QuarterIndex,
Result, Sats, TxIndex, Version, WeekIndex, YearIndex,
};
use brk_exit::Exit;
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_vec::{
AnyCollectableVec, AnyVec, CollectableVec, EagerVec, Format, StoredIndex, VecIterator,
use brk_structs::{
Bitcoin, DateIndex, DecadeIndex, DifficultyEpoch, Dollars, Height, MonthIndex, QuarterIndex,
Sats, SemesterIndex, TxIndex, Version, WeekIndex, YearIndex,
};
use vecdb::{
AnyCloneableIterableVec, AnyCollectableVec, AnyVec, CollectableVec, Database, EagerVec, Exit,
GenericStoredVec, StoredIndex, VecIterator,
};
use crate::vecs::{Indexes, fetched, indexes};
use crate::{
Indexes,
grouped::{LazyVecBuilder, Source},
indexes, price,
};
use super::{ComputedType, ComputedVecBuilder, StorableVecGeneatorOptions};
use super::{ComputedType, EagerVecBuilder, VecBuilderOptions};
#[derive(Clone)]
#[derive(Clone, Allocative)]
pub struct ComputedVecsFromTxindex<T>
where
T: ComputedType + PartialOrd,
{
pub txindex: Option<Box<EagerVec<TxIndex, T>>>,
pub height: ComputedVecBuilder<Height, T>,
pub dateindex: ComputedVecBuilder<DateIndex, T>,
pub weekindex: ComputedVecBuilder<WeekIndex, T>,
pub difficultyepoch: ComputedVecBuilder<DifficultyEpoch, T>,
pub monthindex: ComputedVecBuilder<MonthIndex, T>,
pub quarterindex: ComputedVecBuilder<QuarterIndex, T>,
pub yearindex: ComputedVecBuilder<YearIndex, T>,
pub height: EagerVecBuilder<Height, T>,
pub dateindex: EagerVecBuilder<DateIndex, T>,
pub weekindex: LazyVecBuilder<WeekIndex, T, DateIndex, WeekIndex>,
pub difficultyepoch: EagerVecBuilder<DifficultyEpoch, T>,
pub monthindex: LazyVecBuilder<MonthIndex, T, DateIndex, MonthIndex>,
pub quarterindex: LazyVecBuilder<QuarterIndex, T, DateIndex, QuarterIndex>,
pub semesterindex: LazyVecBuilder<SemesterIndex, T, DateIndex, SemesterIndex>,
pub yearindex: LazyVecBuilder<YearIndex, T, DateIndex, YearIndex>,
// TODO: pub halvingepoch: StorableVecGeneator<Halvingepoch, T>,
pub decadeindex: ComputedVecBuilder<DecadeIndex, T>,
pub decadeindex: LazyVecBuilder<DecadeIndex, T, DateIndex, DecadeIndex>,
}
const VERSION: Version = Version::ZERO;
impl<T> ComputedVecsFromTxindex<T>
where
T: ComputedType + Ord + From<f64>,
T: ComputedType + Ord + From<f64> + 'static,
f64: From<T>,
{
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
compute_source: bool,
source: Source<TxIndex, T>,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let txindex = compute_source.then(|| {
indexes: &indexes::Vecs,
options: VecBuilderOptions,
) -> Result<Self> {
let txindex = source.is_compute().then(|| {
Box::new(
EagerVec::forced_import(path, name, version + VERSION + Version::ZERO, format)
EagerVec::forced_import_compressed(db, name, version + VERSION + Version::ZERO)
.unwrap(),
)
});
let height = ComputedVecBuilder::forced_import(
path,
let height = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options,
)?;
let options = options.remove_percentiles();
let dateindex = EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
options,
)?;
Ok(Self {
weekindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.weekindex_to_weekindex.boxed_clone(),
options.into(),
),
monthindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.monthindex_to_monthindex.boxed_clone(),
options.into(),
),
quarterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.quarterindex_to_quarterindex.boxed_clone(),
options.into(),
),
semesterindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.semesterindex_to_semesterindex.boxed_clone(),
options.into(),
),
yearindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.yearindex_to_yearindex.boxed_clone(),
options.into(),
),
decadeindex: LazyVecBuilder::forced_import(
name,
version + VERSION + Version::ZERO,
None,
&dateindex,
indexes.decadeindex_to_decadeindex.boxed_clone(),
options.into(),
),
txindex,
height,
dateindex: ComputedVecBuilder::forced_import(
path,
dateindex,
difficultyepoch: EagerVecBuilder::forced_import_compressed(
db,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
weekindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
difficultyepoch: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
monthindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
quarterindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
yearindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
// halvingepoch: StorableVecGeneator::forced_import(path, name, version + VERSION + Version::ZERO, format, options)?,
decadeindex: ComputedVecBuilder::forced_import(
path,
name,
version + VERSION + Version::ZERO,
format,
options,
)?,
// halvingepoch: StorableVecGeneator::forced_import(db, name, version + VERSION + Version::ZERO, format, options)?,
})
}
@@ -127,7 +146,7 @@ where
// starting_indexes: &Indexes,
// exit: &Exit,
// mut compute: F,
// ) -> color_eyre::Result<()>
// ) -> Result<()>
// where
// F: FnMut(
// &mut EagerVec<TxIndex, T>,
@@ -163,7 +182,7 @@ where
self.height.compute(
starting_indexes.height,
txindex,
&indexer.vecs().height_to_first_txindex,
&indexer.vecs.height_to_first_txindex,
&indexes.height_to_txindex_count,
exit,
)?;
@@ -173,7 +192,7 @@ where
self.height.compute(
starting_indexes.height,
txindex,
&indexer.vecs().height_to_first_txindex,
&indexer.vecs.height_to_first_txindex,
&indexes.height_to_txindex_count,
exit,
)?;
@@ -196,46 +215,6 @@ where
exit,
)?;
self.weekindex.from_aligned(
starting_indexes.weekindex,
&self.dateindex,
&indexes.weekindex_to_first_dateindex,
&indexes.weekindex_to_dateindex_count,
exit,
)?;
self.monthindex.from_aligned(
starting_indexes.monthindex,
&self.dateindex,
&indexes.monthindex_to_first_dateindex,
&indexes.monthindex_to_dateindex_count,
exit,
)?;
self.quarterindex.from_aligned(
starting_indexes.quarterindex,
&self.monthindex,
&indexes.quarterindex_to_first_monthindex,
&indexes.quarterindex_to_monthindex_count,
exit,
)?;
self.yearindex.from_aligned(
starting_indexes.yearindex,
&self.monthindex,
&indexes.yearindex_to_first_monthindex,
&indexes.yearindex_to_monthindex_count,
exit,
)?;
self.decadeindex.from_aligned(
starting_indexes.decadeindex,
&self.yearindex,
&indexes.decadeindex_to_first_yearindex,
&indexes.decadeindex_to_yearindex_count,
exit,
)?;
self.difficultyepoch.from_aligned(
starting_indexes.difficultyepoch,
&self.height,
@@ -258,6 +237,7 @@ where
self.difficultyepoch.vecs(),
self.monthindex.vecs(),
self.quarterindex.vecs(),
self.semesterindex.vecs(),
self.yearindex.vecs(),
// self.halvingepoch.vecs(),
self.decadeindex.vecs(),
@@ -285,11 +265,11 @@ impl ComputedVecsFromTxindex<Bitcoin> {
};
self.height
.validate_computed_version_or_reset_file(txindex_version)?;
.validate_computed_version_or_reset(txindex_version)?;
let starting_index = self.height.starting_index(starting_indexes.height);
(starting_index.unwrap_to_usize()..indexer.vecs().height_to_weight.len())
(starting_index.unwrap_to_usize()..indexer.vecs.height_to_weight.len())
.map(Height::from)
.try_for_each(|height| -> Result<()> {
if let Some(first) = self.height.first.as_mut() {
@@ -340,24 +320,24 @@ impl ComputedVecsFromTxindex<Bitcoin> {
exit,
)?;
}
if let Some(_90p) = self.height._90p.as_mut() {
if let Some(_90p) = self.height.p90.as_mut() {
_90p.forced_push_at(
height,
Bitcoin::from(
sats.height
.unwrap_90p()
.unwrap_p90()
.into_iter()
.unwrap_get_inner(height),
),
exit,
)?;
}
if let Some(_75p) = self.height._75p.as_mut() {
if let Some(_75p) = self.height.p75.as_mut() {
_75p.forced_push_at(
height,
Bitcoin::from(
sats.height
.unwrap_75p()
.unwrap_p75()
.into_iter()
.unwrap_get_inner(height),
),
@@ -376,24 +356,24 @@ impl ComputedVecsFromTxindex<Bitcoin> {
exit,
)?;
}
if let Some(_25p) = self.height._25p.as_mut() {
if let Some(_25p) = self.height.p25.as_mut() {
_25p.forced_push_at(
height,
Bitcoin::from(
sats.height
.unwrap_25p()
.unwrap_p25()
.into_iter()
.unwrap_get_inner(height),
),
exit,
)?;
}
if let Some(_10p) = self.height._10p.as_mut() {
if let Some(_10p) = self.height.p10.as_mut() {
_10p.forced_push_at(
height,
Bitcoin::from(
sats.height
.unwrap_10p()
.unwrap_p10()
.into_iter()
.unwrap_get_inner(height),
),
@@ -455,7 +435,7 @@ impl ComputedVecsFromTxindex<Dollars> {
exit: &Exit,
bitcoin: &ComputedVecsFromTxindex<Bitcoin>,
txindex: Option<&impl CollectableVec<TxIndex, Dollars>>,
fetched: &fetched::Vecs,
price: &price::Vecs,
) -> Result<()> {
let txindex_version = if let Some(txindex) = txindex {
txindex.version()
@@ -464,13 +444,13 @@ impl ComputedVecsFromTxindex<Dollars> {
};
self.height
.validate_computed_version_or_reset_file(txindex_version)?;
.validate_computed_version_or_reset(txindex_version)?;
let starting_index = self.height.starting_index(starting_indexes.height);
let mut close_iter = fetched.chainindexes_to_close.height.into_iter();
let mut close_iter = price.chainindexes_to_price_close.height.into_iter();
(starting_index.unwrap_to_usize()..indexer.vecs().height_to_weight.len())
(starting_index.unwrap_to_usize()..indexer.vecs.height_to_weight.len())
.map(Height::from)
.try_for_each(|height| -> Result<()> {
let price = *close_iter.unwrap_get_inner(height);
@@ -523,25 +503,25 @@ impl ComputedVecsFromTxindex<Dollars> {
exit,
)?;
}
if let Some(_90p) = self.height._90p.as_mut() {
if let Some(_90p) = self.height.p90.as_mut() {
_90p.forced_push_at(
height,
price
* bitcoin
.height
.unwrap_90p()
.unwrap_p90()
.into_iter()
.unwrap_get_inner(height),
exit,
)?;
}
if let Some(_75p) = self.height._75p.as_mut() {
if let Some(_75p) = self.height.p75.as_mut() {
_75p.forced_push_at(
height,
price
* bitcoin
.height
.unwrap_75p()
.unwrap_p75()
.into_iter()
.unwrap_get_inner(height),
exit,
@@ -559,25 +539,25 @@ impl ComputedVecsFromTxindex<Dollars> {
exit,
)?;
}
if let Some(_25p) = self.height._25p.as_mut() {
if let Some(_25p) = self.height.p25.as_mut() {
_25p.forced_push_at(
height,
price
* bitcoin
.height
.unwrap_25p()
.unwrap_p25()
.into_iter()
.unwrap_get_inner(height),
exit,
)?;
}
if let Some(_10p) = self.height._10p.as_mut() {
if let Some(_10p) = self.height.p10.as_mut() {
_10p.forced_push_at(
height,
price
* bitcoin
.height
.unwrap_10p()
.unwrap_p10()
.into_iter()
.unwrap_get_inner(height),
exit,
@@ -1,22 +1,28 @@
mod builder;
mod builder_eager;
mod builder_lazy;
mod computed;
mod from_dateindex;
mod from_height;
mod from_height_strict;
mod from_txindex;
mod ratio_from_dateindex;
mod r#type;
mod sd_from_dateindex;
mod source;
mod value_from_dateindex;
mod value_from_height;
mod value_from_txindex;
mod value_height;
pub use builder::*;
pub use builder_eager::*;
pub use builder_lazy::*;
use computed::*;
pub use from_dateindex::*;
pub use from_height::*;
pub use from_height_strict::*;
pub use from_txindex::*;
pub use ratio_from_dateindex::*;
use r#type::*;
pub use sd_from_dateindex::*;
pub use source::*;
pub use value_from_dateindex::*;
pub use value_from_height::*;
pub use value_from_txindex::*;
@@ -0,0 +1,712 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Date, DateIndex, Dollars, StoredF32, Version};
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, CollectableVec, Database, EagerVec,
Exit, GenericStoredVec, StoredIndex, VecIterator,
};
use crate::{
Indexes,
grouped::{ComputedStandardDeviationVecsFromDateIndex, source::Source},
indexes, price,
utils::get_percentile,
};
use super::{ComputedVecsFromDateIndex, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedRatioVecsFromDateIndex {
pub price: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio: ComputedVecsFromDateIndex<StoredF32>,
pub ratio_1w_sma: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_1m_sma: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct99: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct98: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct95: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct5: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct2: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct1: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub ratio_pct99_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_pct98_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_pct95_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_pct5_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_pct2_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_pct1_in_usd: Option<ComputedVecsFromDateIndex<Dollars>>,
pub ratio_sd: Option<ComputedStandardDeviationVecsFromDateIndex>,
pub ratio_4y_sd: Option<ComputedStandardDeviationVecsFromDateIndex>,
pub ratio_2y_sd: Option<ComputedStandardDeviationVecsFromDateIndex>,
pub ratio_1y_sd: Option<ComputedStandardDeviationVecsFromDateIndex>,
}
const VERSION: Version = Version::TWO;
impl ComputedRatioVecsFromDateIndex {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
name: &str,
source: Source<DateIndex, Dollars>,
version: Version,
indexes: &indexes::Vecs,
extended: bool,
) -> Result<Self> {
let options = VecBuilderOptions::default().add_last();
Ok(Self {
price: source.is_compute().then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
name,
Source::Compute,
version + VERSION,
indexes,
options,
)
.unwrap()
}),
ratio: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
ratio_1w_sma: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_1w_sma"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_1m_sma: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_1m_sma"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_sd: extended.then(|| {
ComputedStandardDeviationVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio"),
usize::MAX,
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
)
.unwrap()
}),
ratio_1y_sd: extended.then(|| {
ComputedStandardDeviationVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_1y"),
365,
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
)
.unwrap()
}),
ratio_2y_sd: extended.then(|| {
ComputedStandardDeviationVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_2y"),
2 * 365,
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
)
.unwrap()
}),
ratio_4y_sd: extended.then(|| {
ComputedStandardDeviationVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_4y"),
4 * 365,
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
)
.unwrap()
}),
ratio_pct99: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct99"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct98: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct98"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct95: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct95"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct5: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct5"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct2: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct2"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct1: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct1"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct99_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct99_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct98_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct98_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct95_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct95_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct5_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct5_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct2_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct2_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
ratio_pct1_in_usd: extended.then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_ratio_pct1_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
})
}
pub fn compute_all<F>(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: &price::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
compute: F,
) -> Result<()>
where
F: FnMut(
&mut EagerVec<DateIndex, Dollars>,
&Indexer,
&indexes::Vecs,
&Indexes,
&Exit,
) -> Result<()>,
{
self.price.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
compute,
)?;
let date_to_price_opt: Option<&EagerVec<DateIndex, Dollars>> = None;
self.compute_rest(
indexer,
indexes,
price,
starting_indexes,
exit,
date_to_price_opt,
)
}
pub fn compute_rest(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: &price::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
price_opt: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
) -> Result<()> {
let closes = price.timeindexes_to_price_close.dateindex.as_ref().unwrap();
let price = price_opt.unwrap_or_else(|| unsafe {
std::mem::transmute(&self.price.as_ref().unwrap().dateindex)
});
self.ratio.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_transform2(
starting_indexes.dateindex,
closes,
price,
|(i, close, price, ..)| {
if price == Dollars::ZERO {
(i, StoredF32::from(1.0))
} else {
(i, StoredF32::from(*close / price))
}
},
exit,
)?;
Ok(())
},
)?;
if self.ratio_1w_sma.is_none() {
return Ok(());
}
let min_ratio_date = DateIndex::try_from(Date::MIN_RATIO).unwrap();
self.ratio_1w_sma.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sma_(
starting_indexes.dateindex,
self.ratio.dateindex.as_ref().unwrap(),
7,
exit,
Some(min_ratio_date),
)?;
Ok(())
},
)?;
self.ratio_1m_sma.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sma_(
starting_indexes.dateindex,
self.ratio.dateindex.as_ref().unwrap(),
30,
exit,
Some(min_ratio_date),
)?;
Ok(())
},
)?;
let ratio_version = self.ratio.dateindex.as_ref().unwrap().version();
self.mut_ratio_vecs()
.iter_mut()
.try_for_each(|v| -> Result<()> {
v.validate_computed_version_or_reset(
Version::ZERO + v.inner_version() + ratio_version,
)?;
Ok(())
})?;
let starting_dateindex = self
.mut_ratio_vecs()
.iter()
.map(|v| DateIndex::from(v.len()))
.min()
.unwrap()
.min(starting_indexes.dateindex);
let mut sorted = self.ratio.dateindex.as_ref().unwrap().collect_range(
Some(min_ratio_date.unwrap_to_usize()),
Some(starting_dateindex.unwrap_to_usize()),
)?;
sorted.sort_unstable();
self.ratio
.dateindex
.as_ref()
.unwrap()
.iter_at(starting_dateindex)
.try_for_each(|(index, ratio)| -> Result<()> {
if index < min_ratio_date {
self.ratio_pct5
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
self.ratio_pct2
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
self.ratio_pct1
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
self.ratio_pct95
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
self.ratio_pct98
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
self.ratio_pct99
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, StoredF32::NAN, exit)?;
} else {
let ratio = ratio.into_owned();
let pos = sorted.binary_search(&ratio).unwrap_or_else(|pos| pos);
sorted.insert(pos, ratio);
self.ratio_pct1
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.01), exit)?;
self.ratio_pct2
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.02), exit)?;
self.ratio_pct5
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.05), exit)?;
self.ratio_pct95
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.95), exit)?;
self.ratio_pct98
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.98), exit)?;
self.ratio_pct99
.as_mut()
.unwrap()
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, get_percentile(&sorted, 0.99), exit)?;
}
Ok(())
})?;
self.mut_ratio_vecs()
.into_iter()
.try_for_each(|v| v.safe_flush(exit))?;
self.ratio_pct1.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_pct2.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_pct5.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_pct95.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_pct98.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
self.ratio_pct99.as_mut().unwrap().compute_rest(
starting_indexes,
exit,
None as Option<&EagerVec<_, _>>,
)?;
let date_to_price = price_opt.unwrap_or_else(|| unsafe {
std::mem::transmute(&self.price.as_ref().unwrap().dateindex)
});
self.ratio_pct99_in_usd.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
let mut iter = self
.ratio_pct99
.as_ref()
.unwrap()
.dateindex
.as_ref()
.unwrap()
.into_iter();
vec.compute_transform(
starting_indexes.dateindex,
date_to_price,
|(i, price, ..)| {
let multiplier = iter.unwrap_get_inner(i);
(i, price * multiplier)
},
exit,
)?;
Ok(())
},
)?;
let compute_in_usd =
|in_usd: Option<&mut ComputedVecsFromDateIndex<Dollars>>,
source: Option<&ComputedVecsFromDateIndex<StoredF32>>| {
in_usd.unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
let mut iter = source.unwrap().dateindex.as_ref().unwrap().into_iter();
vec.compute_transform(
starting_indexes.dateindex,
date_to_price,
|(i, price, ..)| {
let multiplier = iter.unwrap_get_inner(i);
(i, price * multiplier)
},
exit,
)?;
Ok(())
},
)
};
compute_in_usd(self.ratio_pct1_in_usd.as_mut(), self.ratio_pct1.as_ref())?;
compute_in_usd(self.ratio_pct2_in_usd.as_mut(), self.ratio_pct2.as_ref())?;
compute_in_usd(self.ratio_pct5_in_usd.as_mut(), self.ratio_pct5.as_ref())?;
compute_in_usd(self.ratio_pct95_in_usd.as_mut(), self.ratio_pct95.as_ref())?;
compute_in_usd(self.ratio_pct98_in_usd.as_mut(), self.ratio_pct98.as_ref())?;
compute_in_usd(self.ratio_pct99_in_usd.as_mut(), self.ratio_pct99.as_ref())?;
self.ratio_sd.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
self.ratio.dateindex.as_ref().unwrap(),
Some(date_to_price),
)?;
self.ratio_4y_sd.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
self.ratio.dateindex.as_ref().unwrap(),
Some(date_to_price),
)?;
self.ratio_2y_sd.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
self.ratio.dateindex.as_ref().unwrap(),
Some(date_to_price),
)?;
self.ratio_1y_sd.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
self.ratio.dateindex.as_ref().unwrap(),
Some(date_to_price),
)?;
Ok(())
}
fn mut_ratio_vecs(&mut self) -> Vec<&mut EagerVec<DateIndex, StoredF32>> {
[
self.ratio_pct1
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
self.ratio_pct2
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
self.ratio_pct5
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
self.ratio_pct95
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
self.ratio_pct98
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
self.ratio_pct99
.as_mut()
.map_or(vec![], |v| vec![v.dateindex.as_mut().unwrap()]),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.price.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio.vecs(),
self.ratio_1w_sma.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_1m_sma.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_sd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_1y_sd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_2y_sd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_4y_sd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct1.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct2.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct5.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct95.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct98.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct99.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct1_in_usd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct2_in_usd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct5_in_usd.as_ref().map_or(vec![], |v| v.vecs()),
self.ratio_pct95_in_usd
.as_ref()
.map_or(vec![], |v| v.vecs()),
self.ratio_pct98_in_usd
.as_ref()
.map_or(vec![], |v| v.vecs()),
self.ratio_pct99_in_usd
.as_ref()
.map_or(vec![], |v| v.vecs()),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
@@ -0,0 +1,707 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{CheckedSub, Date, DateIndex, Dollars, StoredF32, Version};
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, BoxedVecIterator, CollectableVec,
Database, EagerVec, Exit, GenericStoredVec, StoredIndex,
};
use crate::{Indexes, grouped::source::Source, indexes};
use super::{ComputedVecsFromDateIndex, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedStandardDeviationVecsFromDateIndex {
days: usize,
pub sma: Option<ComputedVecsFromDateIndex<StoredF32>>,
pub sd: ComputedVecsFromDateIndex<StoredF32>,
pub _0sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p0_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub p1sd: ComputedVecsFromDateIndex<StoredF32>,
pub p1_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub p2sd: ComputedVecsFromDateIndex<StoredF32>,
pub p2_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub p3sd: ComputedVecsFromDateIndex<StoredF32>,
pub m0_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub m1sd: ComputedVecsFromDateIndex<StoredF32>,
pub m1_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub m2sd: ComputedVecsFromDateIndex<StoredF32>,
pub m2_5sd: ComputedVecsFromDateIndex<StoredF32>,
pub m3sd: ComputedVecsFromDateIndex<StoredF32>,
pub p0_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p1sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p1_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p2sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p2_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub p3sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m0_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m1sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m1_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m2sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m2_5sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub m3sd_in_usd: ComputedVecsFromDateIndex<Dollars>,
pub zscore: ComputedVecsFromDateIndex<StoredF32>,
}
const VERSION: Version = Version::ONE;
impl ComputedStandardDeviationVecsFromDateIndex {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
name: &str,
days: usize,
source: Source<DateIndex, StoredF32>,
version: Version,
indexes: &indexes::Vecs,
) -> Result<Self> {
let options = VecBuilderOptions::default().add_last();
Ok(Self {
days,
sma: source.is_compute().then(|| {
ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_sma"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)
.unwrap()
}),
sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p0_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p0_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p1sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p1sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p1_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p1_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p2sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p2sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p2_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p2_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p3sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p3sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m0_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m0_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m1sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m1sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m1_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m1_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m2sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m2sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m2_5sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m2_5sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m3sd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m3sd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
_0sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_0sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p0_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p0_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p1sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p1sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p1_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p1_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p2sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p2sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p2_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p2_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
p3sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_p3sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m0_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m0_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m1sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m1sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m1_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m1_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m2sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m2sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m2_5sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m2_5sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
m3sd_in_usd: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_m3sd_in_usd"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
zscore: ComputedVecsFromDateIndex::forced_import(
db,
&format!("{name}_zscore"),
Source::Compute,
version + VERSION + Version::ZERO,
indexes,
options,
)?,
})
}
pub fn compute_all(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
source: &impl CollectableVec<DateIndex, StoredF32>,
source_in_usd: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
) -> Result<()> {
let min_date = DateIndex::try_from(Date::MIN_RATIO).unwrap();
self.sma.as_mut().unwrap().compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sma_(
starting_indexes.dateindex,
source,
self.days,
exit,
Some(min_date),
)?;
Ok(())
},
)?;
let sma_opt: Option<&EagerVec<DateIndex, StoredF32>> = None;
self.compute_rest(
indexer,
indexes,
starting_indexes,
exit,
sma_opt,
source,
source_in_usd,
)
}
#[allow(clippy::too_many_arguments)]
pub fn compute_rest(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
sma_opt: Option<&impl AnyIterableVec<DateIndex, StoredF32>>,
source: &impl CollectableVec<DateIndex, StoredF32>,
source_in_usd: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
) -> Result<()> {
let sma = sma_opt.unwrap_or_else(|| unsafe {
std::mem::transmute(&self.sma.as_ref().unwrap().dateindex)
});
let min_date = DateIndex::try_from(Date::MIN_RATIO).unwrap();
let source_version = source.version();
self.mut_vecs().iter_mut().try_for_each(|v| -> Result<()> {
v.validate_computed_version_or_reset(
Version::ZERO + v.inner_version() + source_version,
)?;
Ok(())
})?;
let starting_dateindex = self
.mut_vecs()
.iter()
.map(|v| DateIndex::from(v.len()))
.min()
.unwrap()
.min(starting_indexes.dateindex);
let mut sorted = source.collect_range(
Some(min_date.unwrap_to_usize()),
Some(starting_dateindex.unwrap_to_usize()),
)?;
sorted.sort_unstable();
let mut sma_iter = sma.iter();
source
.iter_at(starting_dateindex)
.try_for_each(|(index, ratio)| -> Result<()> {
if index < min_date {
self.sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p0_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p1sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p1_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p2sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p2_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.p3sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m0_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m1sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m1_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m2sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m2_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
self.m3sd.dateindex.as_mut().unwrap().forced_push_at(
index,
StoredF32::NAN,
exit,
)?;
} else {
let ratio = ratio.into_owned();
let pos = sorted.binary_search(&ratio).unwrap_or_else(|pos| pos);
sorted.insert(pos, ratio);
let avg = sma_iter.unwrap_get_inner(index);
let population =
index.checked_sub(min_date).unwrap().unwrap_to_usize() as f32 + 1.0;
let sd = StoredF32::from(
(sorted.iter().map(|v| (**v - *avg).powi(2)).sum::<f32>() / population)
.sqrt(),
);
self.sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, sd, exit)?;
self.p0_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + StoredF32::from(0.5 * *sd),
exit,
)?;
self.p1sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, avg + sd, exit)?;
self.p1_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + StoredF32::from(1.5 * *sd),
exit,
)?;
self.p2sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + 2 * sd,
exit,
)?;
self.p2_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + StoredF32::from(2.5 * *sd),
exit,
)?;
self.p3sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg + 3 * sd,
exit,
)?;
self.m0_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg - StoredF32::from(0.5 * *sd),
exit,
)?;
self.m1sd
.dateindex
.as_mut()
.unwrap()
.forced_push_at(index, avg - sd, exit)?;
self.m1_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg - StoredF32::from(1.5 * *sd),
exit,
)?;
self.m2sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg - 2 * sd,
exit,
)?;
self.m2_5sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg - StoredF32::from(2.5 * *sd),
exit,
)?;
self.m3sd.dateindex.as_mut().unwrap().forced_push_at(
index,
avg - 3 * sd,
exit,
)?;
}
Ok(())
})?;
drop(sma_iter);
self.mut_vecs()
.into_iter()
.try_for_each(|v| v.safe_flush(exit))?;
[
&mut self.sd,
&mut self.p0_5sd,
&mut self.p1sd,
&mut self.p1_5sd,
&mut self.p2sd,
&mut self.p2_5sd,
&mut self.p3sd,
&mut self.m0_5sd,
&mut self.m1sd,
&mut self.m1_5sd,
&mut self.m2sd,
&mut self.m2_5sd,
&mut self.m3sd,
]
.into_iter()
.try_for_each(|v| {
v.compute_rest(starting_indexes, exit, None as Option<&EagerVec<_, _>>)
})?;
self.zscore.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_zscore(
starting_indexes.dateindex,
source,
sma,
self.sd.dateindex.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
let Some(price) = source_in_usd else {
return Ok(());
};
let compute_in_usd =
|in_usd: &mut ComputedVecsFromDateIndex<Dollars>,
mut iter: BoxedVecIterator<DateIndex, StoredF32>| {
in_usd.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform(
starting_indexes.dateindex,
price,
|(i, price, ..)| {
let multiplier = iter.unwrap_get_inner(i);
(i, price * multiplier)
},
exit,
)?;
Ok(())
},
)
};
compute_in_usd(&mut self._0sd_in_usd, sma.iter())?;
compute_in_usd(
&mut self.p0_5sd_in_usd,
self.p0_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.p1sd_in_usd,
self.p1sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.p1_5sd_in_usd,
self.p1_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.p2sd_in_usd,
self.p2sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.p2_5sd_in_usd,
self.p2_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.p3sd_in_usd,
self.p3sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m0_5sd_in_usd,
self.m0_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m1sd_in_usd,
self.m1sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m1_5sd_in_usd,
self.m1_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m2sd_in_usd,
self.m2sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m2_5sd_in_usd,
self.m2_5sd.dateindex.as_ref().unwrap().iter(),
)?;
compute_in_usd(
&mut self.m3sd_in_usd,
self.m3sd.dateindex.as_ref().unwrap().iter(),
)?;
Ok(())
}
fn mut_vecs(&mut self) -> [&mut EagerVec<DateIndex, StoredF32>; 13] {
[
self.sd.dateindex.as_mut().unwrap(),
self.p0_5sd.dateindex.as_mut().unwrap(),
self.p1sd.dateindex.as_mut().unwrap(),
self.p1_5sd.dateindex.as_mut().unwrap(),
self.p2sd.dateindex.as_mut().unwrap(),
self.p2_5sd.dateindex.as_mut().unwrap(),
self.p3sd.dateindex.as_mut().unwrap(),
self.m0_5sd.dateindex.as_mut().unwrap(),
self.m1sd.dateindex.as_mut().unwrap(),
self.m1_5sd.dateindex.as_mut().unwrap(),
self.m2sd.dateindex.as_mut().unwrap(),
self.m2_5sd.dateindex.as_mut().unwrap(),
self.m3sd.dateindex.as_mut().unwrap(),
]
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.sma.as_ref().map_or(vec![], |v| v.vecs()),
self.sd.vecs(),
self.p0_5sd.vecs(),
self.p1sd.vecs(),
self.p1_5sd.vecs(),
self.p2sd.vecs(),
self.p2_5sd.vecs(),
self.p3sd.vecs(),
self.m0_5sd.vecs(),
self.m1sd.vecs(),
self.m1_5sd.vecs(),
self.m2sd.vecs(),
self.m2_5sd.vecs(),
self.m3sd.vecs(),
self._0sd_in_usd.vecs(),
self.p0_5sd_in_usd.vecs(),
self.p1sd_in_usd.vecs(),
self.p1_5sd_in_usd.vecs(),
self.p2sd_in_usd.vecs(),
self.p2_5sd_in_usd.vecs(),
self.p3sd_in_usd.vecs(),
self.m0_5sd_in_usd.vecs(),
self.m1sd_in_usd.vecs(),
self.m1_5sd_in_usd.vecs(),
self.m2sd_in_usd.vecs(),
self.m2_5sd_in_usd.vecs(),
self.m3sd_in_usd.vecs(),
self.zscore.vecs(),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
+51
View File
@@ -0,0 +1,51 @@
use vecdb::AnyBoxedIterableVec;
#[derive(Clone)]
pub enum Source<I, T> {
Compute,
None,
Vec(AnyBoxedIterableVec<I, T>),
}
impl<I, T> Source<I, T> {
pub fn is_compute(&self) -> bool {
matches!(self, Self::Compute)
}
pub fn is_none(&self) -> bool {
matches!(self, Self::None)
}
pub fn is_vec(&self) -> bool {
matches!(self, Self::Vec(_))
}
pub fn vec(self) -> Option<AnyBoxedIterableVec<I, T>> {
match self {
Self::Vec(v) => Some(v),
_ => None,
}
}
}
impl<I, T> From<bool> for Source<I, T> {
fn from(value: bool) -> Self {
if value { Self::Compute } else { Self::None }
}
}
impl<I, T> From<AnyBoxedIterableVec<I, T>> for Source<I, T> {
fn from(value: AnyBoxedIterableVec<I, T>) -> Self {
Self::Vec(value)
}
}
impl<I, T> From<Option<AnyBoxedIterableVec<I, T>>> for Source<I, T> {
fn from(value: Option<AnyBoxedIterableVec<I, T>>) -> Self {
if let Some(v) = value {
Self::Vec(v)
} else {
Self::None
}
}
}
@@ -1,13 +1,16 @@
use std::path::Path;
use brk_core::{Bitcoin, DateIndex, Dollars, Result, Sats, Version};
use brk_exit::Exit;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, CollectableVec, EagerVec, Format, StoredVec};
use brk_structs::{Bitcoin, DateIndex, Dollars, Sats, Version};
use vecdb::{AnyCollectableVec, CollectableVec, Database, EagerVec, Exit, StoredVec};
use crate::vecs::{Indexes, fetched, grouped::ComputedVecsFromDateIndex, indexes};
use crate::{
Indexes,
grouped::ComputedVecsFromDateIndex,
indexes, price,
traits::{ComputeFromBitcoin, ComputeFromSats},
};
use super::StorableVecGeneatorOptions;
use super::{Source, VecBuilderOptions};
#[derive(Clone)]
pub struct ComputedValueVecsFromDateIndex {
@@ -19,39 +22,40 @@ pub struct ComputedValueVecsFromDateIndex {
const VERSION: Version = Version::ZERO;
impl ComputedValueVecsFromDateIndex {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
compute_source: bool,
source: Source<DateIndex, Sats>,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
options: VecBuilderOptions,
compute_dollars: bool,
) -> color_eyre::Result<Self> {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
sats: ComputedVecsFromDateIndex::forced_import(
path,
db,
name,
compute_source,
source,
version + VERSION,
format,
indexes,
options,
)?,
bitcoin: ComputedVecsFromDateIndex::forced_import(
path,
db,
&format!("{name}_in_btc"),
true,
Source::Compute,
version + VERSION,
format,
indexes,
options,
)?,
dollars: compute_dollars.then(|| {
ComputedVecsFromDateIndex::forced_import(
path,
db,
&format!("{name}_in_usd"),
true,
Source::Compute,
version + VERSION,
format,
indexes,
options,
)
.unwrap()
@@ -63,11 +67,11 @@ impl ComputedValueVecsFromDateIndex {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> color_eyre::Result<()>
) -> Result<()>
where
F: FnMut(
&mut EagerVec<DateIndex, Sats>,
@@ -86,7 +90,7 @@ impl ComputedValueVecsFromDateIndex {
)?;
let dateindex: Option<&StoredVec<DateIndex, Sats>> = None;
self.compute_rest(indexer, indexes, fetched, starting_indexes, exit, dateindex)?;
self.compute_rest(indexer, indexes, price, starting_indexes, exit, dateindex)?;
Ok(())
}
@@ -95,14 +99,14 @@ impl ComputedValueVecsFromDateIndex {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
dateindex: Option<&impl CollectableVec<DateIndex, Sats>>,
) -> color_eyre::Result<()> {
) -> Result<()> {
if let Some(dateindex) = dateindex {
self.sats
.compute_rest(indexes, starting_indexes, exit, Some(dateindex))?;
.compute_rest(starting_indexes, exit, Some(dateindex))?;
self.bitcoin.compute_all(
indexer,
@@ -116,8 +120,7 @@ impl ComputedValueVecsFromDateIndex {
} else {
let dateindex: Option<&StoredVec<DateIndex, Sats>> = None;
self.sats
.compute_rest(indexes, starting_indexes, exit, dateindex)?;
self.sats.compute_rest(starting_indexes, exit, dateindex)?;
self.bitcoin.compute_all(
indexer,
@@ -135,10 +138,10 @@ impl ComputedValueVecsFromDateIndex {
}
let dateindex_to_bitcoin = self.bitcoin.dateindex.as_ref().unwrap();
let dateindex_to_close = fetched
let dateindex_to_price_close = price
.as_ref()
.unwrap()
.timeindexes_to_close
.timeindexes_to_price_close
.dateindex
.as_ref()
.unwrap();
@@ -153,7 +156,7 @@ impl ComputedValueVecsFromDateIndex {
v.compute_from_bitcoin(
starting_indexes.dateindex,
dateindex_to_bitcoin,
dateindex_to_close,
dateindex_to_price_close,
exit,
)
},
@@ -1,15 +1,19 @@
use std::path::Path;
use brk_core::{Bitcoin, Dollars, Height, Result, Sats, Version};
use brk_exit::Exit;
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, CollectableVec, EagerVec, Format, StoredVec};
use brk_structs::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{AnyCollectableVec, CollectableVec, Database, EagerVec, Exit, StoredVec};
use crate::vecs::{Indexes, fetched, indexes};
use crate::{
Indexes,
grouped::Source,
indexes, price,
traits::{ComputeFromBitcoin, ComputeFromSats},
};
use super::{ComputedVecsFromHeight, StorableVecGeneatorOptions};
use super::{ComputedVecsFromHeight, VecBuilderOptions};
#[derive(Clone)]
#[derive(Clone, Allocative)]
pub struct ComputedValueVecsFromHeight {
pub sats: ComputedVecsFromHeight<Sats>,
pub bitcoin: ComputedVecsFromHeight<Bitcoin>,
@@ -19,39 +23,40 @@ pub struct ComputedValueVecsFromHeight {
const VERSION: Version = Version::ZERO;
impl ComputedValueVecsFromHeight {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
compute_source: bool,
source: Source<Height, Sats>,
version: Version,
format: Format,
options: StorableVecGeneatorOptions,
options: VecBuilderOptions,
compute_dollars: bool,
) -> color_eyre::Result<Self> {
indexes: &indexes::Vecs,
) -> Result<Self> {
Ok(Self {
sats: ComputedVecsFromHeight::forced_import(
path,
db,
name,
compute_source,
source,
version + VERSION,
format,
indexes,
options,
)?,
bitcoin: ComputedVecsFromHeight::forced_import(
path,
db,
&format!("{name}_in_btc"),
true,
Source::Compute,
version + VERSION,
format,
indexes,
options,
)?,
dollars: compute_dollars.then(|| {
ComputedVecsFromHeight::forced_import(
path,
db,
&format!("{name}_in_usd"),
true,
Source::Compute,
version + VERSION,
format,
indexes,
options,
)
.unwrap()
@@ -63,11 +68,11 @@ impl ComputedValueVecsFromHeight {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> color_eyre::Result<()>
) -> Result<()>
where
F: FnMut(
&mut EagerVec<Height, Sats>,
@@ -86,7 +91,7 @@ impl ComputedValueVecsFromHeight {
)?;
let height: Option<&StoredVec<Height, Sats>> = None;
self.compute_rest(indexer, indexes, fetched, starting_indexes, exit, height)?;
self.compute_rest(indexer, indexes, price, starting_indexes, exit, height)?;
Ok(())
}
@@ -95,11 +100,11 @@ impl ComputedValueVecsFromHeight {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
height: Option<&impl CollectableVec<Height, Sats>>,
) -> color_eyre::Result<()> {
) -> Result<()> {
if let Some(height) = height {
self.sats
.compute_rest(indexes, starting_indexes, exit, Some(height))?;
@@ -135,7 +140,7 @@ impl ComputedValueVecsFromHeight {
}
let height_to_bitcoin = self.bitcoin.height.as_ref().unwrap();
let height_to_close = &fetched.as_ref().unwrap().chainindexes_to_close.height;
let height_to_price_close = &price.as_ref().unwrap().chainindexes_to_price_close.height;
if let Some(dollars) = self.dollars.as_mut() {
dollars.compute_all(
@@ -147,7 +152,7 @@ impl ComputedValueVecsFromHeight {
v.compute_from_bitcoin(
starting_indexes.height,
height_to_bitcoin,
height_to_close,
height_to_price_close,
exit,
)
},
@@ -1,34 +1,24 @@
use std::path::Path;
use brk_core::{Bitcoin, Close, Dollars, Height, Sats, TxIndex, Version};
use brk_exit::Exit;
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_vec::{
AnyCollectableVec, BoxedAnyIterableVec, CloneableAnyIterableVec, CollectableVec, Computation,
ComputedVecFrom3, Format, LazyVecFrom1, StoredIndex, StoredVec,
use brk_structs::{Bitcoin, Close, Dollars, Height, Sats, TxIndex, Version};
use vecdb::{
AnyCloneableIterableVec, AnyCollectableVec, CollectableVec, Database, Exit, LazyVecFrom1,
LazyVecFrom3, StoredIndex, StoredVec,
};
use crate::vecs::{Indexes, fetched, indexes};
use crate::{Indexes, grouped::Source, indexes, price};
use super::{ComputedVecsFromTxindex, StorableVecGeneatorOptions};
use super::{ComputedVecsFromTxindex, VecBuilderOptions};
#[derive(Clone)]
#[derive(Clone, Allocative)]
pub struct ComputedValueVecsFromTxindex {
pub sats: ComputedVecsFromTxindex<Sats>,
pub bitcoin_txindex: LazyVecFrom1<TxIndex, Bitcoin, TxIndex, Sats>,
pub bitcoin: ComputedVecsFromTxindex<Bitcoin>,
#[allow(clippy::type_complexity)]
pub dollars_txindex: Option<
ComputedVecFrom3<
TxIndex,
Dollars,
TxIndex,
Bitcoin,
TxIndex,
Height,
Height,
Close<Dollars>,
>,
LazyVecFrom3<TxIndex, Dollars, TxIndex, Bitcoin, TxIndex, Height, Height, Close<Dollars>>,
>,
pub dollars: Option<ComputedVecsFromTxindex<Dollars>>,
}
@@ -38,81 +28,78 @@ const VERSION: Version = Version::ZERO;
impl ComputedValueVecsFromTxindex {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
indexes: &indexes::Vecs,
source: Option<BoxedAnyIterableVec<TxIndex, Sats>>,
source: Source<TxIndex, Sats>,
version: Version,
computation: Computation,
format: Format,
fetched: Option<&fetched::Vecs>,
options: StorableVecGeneatorOptions,
) -> color_eyre::Result<Self> {
let compute_source = source.is_none();
let compute_dollars = fetched.is_some();
price: Option<&price::Vecs>,
options: VecBuilderOptions,
) -> Result<Self> {
let compute_dollars = price.is_some();
let name_in_btc = format!("{name}_in_btc");
let name_in_usd = format!("{name}_in_usd");
let sats = ComputedVecsFromTxindex::forced_import(
path,
db,
name,
compute_source,
source.clone(),
version + VERSION,
format,
indexes,
options,
)?;
let source_vec = source.vec();
let bitcoin_txindex = LazyVecFrom1::init(
&name_in_btc,
version + VERSION,
source.map_or_else(|| sats.txindex.as_ref().unwrap().boxed_clone(), |s| s),
source_vec.map_or_else(|| sats.txindex.as_ref().unwrap().boxed_clone(), |s| s),
|txindex: TxIndex, iter| {
iter.next_at(txindex.unwrap_to_usize()).map(|(_, value)| {
let sats = value.into_inner();
let sats = value.into_owned();
Bitcoin::from(sats)
})
},
);
let bitcoin = ComputedVecsFromTxindex::forced_import(
path,
db,
&name_in_btc,
false,
Source::None,
version + VERSION,
format,
indexes,
options,
)?;
let dollars_txindex = fetched.map(|fetched| {
ComputedVecFrom3::forced_import_or_init_from_3(
computation,
path,
let dollars_txindex = price.map(|price| {
LazyVecFrom3::init(
&name_in_usd,
version + VERSION,
format,
bitcoin_txindex.boxed_clone(),
indexes.txindex_to_height.boxed_clone(),
fetched.chainindexes_to_close.height.boxed_clone(),
price.chainindexes_to_price_close.height.boxed_clone(),
|txindex: TxIndex,
txindex_to_btc_iter,
txindex_to_in_btc_iter,
txindex_to_height_iter,
height_to_close_iter| {
height_to_price_close_iter| {
let txindex = txindex.unwrap_to_usize();
txindex_to_btc_iter.next_at(txindex).and_then(|(_, value)| {
let btc = value.into_inner();
txindex_to_height_iter
.next_at(txindex)
.and_then(|(_, value)| {
let height = value.into_inner();
height_to_close_iter
.next_at(height.unwrap_to_usize())
.map(|(_, close)| *close.into_inner() * btc)
})
})
txindex_to_in_btc_iter
.next_at(txindex)
.and_then(|(_, value)| {
let btc = value.into_owned();
txindex_to_height_iter
.next_at(txindex)
.and_then(|(_, value)| {
let height = value.into_owned();
height_to_price_close_iter
.next_at(height.unwrap_to_usize())
.map(|(_, close)| *close.into_owned() * btc)
})
})
},
)
.unwrap()
});
Ok(Self {
@@ -122,11 +109,11 @@ impl ComputedValueVecsFromTxindex {
dollars_txindex,
dollars: compute_dollars.then(|| {
ComputedVecsFromTxindex::forced_import(
path,
db,
&name_in_usd,
false,
Source::None,
version + VERSION,
format,
indexes,
options,
)
.unwrap()
@@ -138,11 +125,11 @@ impl ComputedValueVecsFromTxindex {
// &mut self,
// indexer: &Indexer,
// indexes: &indexes::Vecs,
// fetched: Option<&marketprice::Vecs>,
// price: Option<&marketprice::Vecs>,
// starting_indexes: &Indexes,
// exit: &Exit,
// mut compute: F,
// ) -> color_eyre::Result<()>
// ) -> Result<()>
// where
// F: FnMut(
// &mut EagerVec<TxIndex, Sats>,
@@ -180,8 +167,8 @@ impl ComputedValueVecsFromTxindex {
starting_indexes: &Indexes,
exit: &Exit,
txindex: Option<&impl CollectableVec<TxIndex, Sats>>,
fetched: Option<&fetched::Vecs>,
) -> color_eyre::Result<()> {
price: Option<&price::Vecs>,
) -> Result<()> {
if let Some(txindex) = txindex {
self.sats
.compute_rest(indexer, indexes, starting_indexes, exit, Some(txindex))?;
@@ -203,12 +190,6 @@ impl ComputedValueVecsFromTxindex {
if let Some(dollars) = self.dollars.as_mut() {
let dollars_txindex = self.dollars_txindex.as_mut().unwrap();
dollars_txindex.compute_if_necessary(
starting_indexes.txindex,
&indexer.vecs().txindex_to_txid,
exit,
)?;
dollars.compute_rest_from_bitcoin(
indexer,
indexes,
@@ -216,7 +197,7 @@ impl ComputedValueVecsFromTxindex {
exit,
&self.bitcoin,
Some(dollars_txindex),
fetched.as_ref().unwrap(),
price.as_ref().unwrap(),
)?;
}
@@ -226,7 +207,11 @@ impl ComputedValueVecsFromTxindex {
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.sats.vecs(),
vec![&self.bitcoin_txindex as &dyn AnyCollectableVec],
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()),
]
.into_iter()
@@ -1,11 +1,14 @@
use std::path::Path;
use brk_core::{Bitcoin, Dollars, Height, Result, Sats, Version};
use brk_exit::Exit;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, CollectableVec, EagerVec, Format, StoredVec};
use brk_structs::{Bitcoin, Dollars, Height, Sats, Version};
use vecdb::{AnyCollectableVec, CollectableVec, Database, EagerVec, Exit, Format, StoredVec};
use crate::vecs::{Indexes, fetched, indexes};
use crate::{
Indexes,
grouped::Source,
indexes, price,
traits::{ComputeFromBitcoin, ComputeFromSats},
};
#[derive(Clone)]
pub struct ComputedHeightValueVecs {
@@ -18,27 +21,27 @@ const VERSION: Version = Version::ZERO;
impl ComputedHeightValueVecs {
pub fn forced_import(
path: &Path,
db: &Database,
name: &str,
compute_source: bool,
source: Source<Height, Sats>,
version: Version,
format: Format,
compute_dollars: bool,
) -> color_eyre::Result<Self> {
) -> Result<Self> {
Ok(Self {
sats: compute_source.then(|| {
EagerVec::forced_import(path, name, version + VERSION + Version::ZERO, format)
sats: source.is_compute().then(|| {
EagerVec::forced_import(db, name, version + VERSION + Version::ZERO, format)
.unwrap()
}),
bitcoin: EagerVec::forced_import(
path,
db,
&format!("{name}_in_btc"),
version + VERSION + Version::ZERO,
format,
)?,
dollars: compute_dollars.then(|| {
EagerVec::forced_import(
path,
db,
&format!("{name}_in_usd"),
version + VERSION + Version::ZERO,
format,
@@ -52,11 +55,11 @@ impl ComputedHeightValueVecs {
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
mut compute: F,
) -> color_eyre::Result<()>
) -> Result<()>
where
F: FnMut(
&mut EagerVec<Height, Sats>,
@@ -75,18 +78,18 @@ impl ComputedHeightValueVecs {
)?;
let height: Option<&StoredVec<Height, Sats>> = None;
self.compute_rest(fetched, starting_indexes, exit, height)?;
self.compute_rest(price, starting_indexes, exit, height)?;
Ok(())
}
pub fn compute_rest(
&mut self,
fetched: Option<&fetched::Vecs>,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
height: Option<&impl CollectableVec<Height, Sats>>,
) -> color_eyre::Result<()> {
) -> Result<()> {
if let Some(height) = height {
self.bitcoin
.compute_from_sats(starting_indexes.height, height, exit)?;
@@ -99,13 +102,13 @@ impl ComputedHeightValueVecs {
}
let height_to_bitcoin = &self.bitcoin;
let height_to_close = &fetched.as_ref().unwrap().chainindexes_to_close.height;
let height_to_price_close = &price.as_ref().unwrap().chainindexes_to_price_close.height;
if let Some(dollars) = self.dollars.as_mut() {
dollars.compute_from_bitcoin(
starting_indexes.height,
height_to_bitcoin,
height_to_close,
height_to_price_close,
exit,
)?;
}
File diff suppressed because it is too large Load Diff
+222 -78
View File
@@ -1,104 +1,248 @@
#![doc = include_str!("../README.md")]
#![doc = "\n## Example\n\n```rust"]
#![doc = include_str!("../examples/main.rs")]
#![doc = "```"]
use std::path::{Path, PathBuf};
use std::path::Path;
use brk_core::Version;
use brk_exit::Exit;
use brk_error::Result;
use brk_fetcher::Fetcher;
use brk_indexer::Indexer;
use brk_vec::{AnyCollectableVec, Computation, Format};
mod stores;
mod utils;
mod vecs;
use brk_structs::Version;
use log::info;
use stores::Stores;
use vecs::Vecs;
use vecdb::{AnyCollectableVec, Exit, Format};
mod chain;
mod cointime;
mod constants;
mod fetched;
mod grouped;
mod indexes;
mod market;
mod pools;
mod price;
mod stateful;
mod states;
mod traits;
mod utils;
use indexes::Indexes;
pub use pools::*;
pub use states::PriceToAmount;
use states::*;
#[derive(Clone)]
pub struct Computer {
path: PathBuf,
fetcher: Option<Fetcher>,
vecs: Option<Vecs>,
stores: Option<Stores>,
format: Format,
pub indexes: indexes::Vecs,
pub constants: constants::Vecs,
pub market: market::Vecs,
pub pools: pools::Vecs,
pub price: Option<price::Vecs>,
pub chain: chain::Vecs,
pub stateful: stateful::Vecs,
pub fetched: Option<fetched::Vecs>,
pub cointime: cointime::Vecs,
}
const VERSION: Version = Version::ONE;
const VERSION: Version = Version::new(4);
impl Computer {
pub fn new(outputs_dir: &Path, fetcher: Option<Fetcher>, format: Format) -> Self {
Self {
path: outputs_dir.to_owned(),
fetcher,
vecs: None,
stores: None,
format,
}
}
pub fn import_vecs(
&mut self,
indexer: &Indexer,
computation: Computation,
) -> color_eyre::Result<()> {
self.vecs = Some(Vecs::import(
// TODO: Give self.path, join inside import
&self.path.join("vecs/computed"),
VERSION + Version::ZERO,
indexer,
self.fetcher.is_some(),
computation,
self.format,
)?);
Ok(())
}
/// Do NOT import multiple times or things will break !!!
/// Clone struct instead
pub fn import_stores(&mut self, indexer: &Indexer) -> color_eyre::Result<()> {
self.stores = Some(Stores::import(
// TODO: Give self.path, join inside import
&self.path.join("stores"),
VERSION + Version::ZERO,
indexer.keyspace(),
)?);
Ok(())
}
}
pub fn forced_import(
outputs_path: &Path,
indexer: &Indexer,
fetcher: Option<Fetcher>,
) -> Result<Self> {
info!("Importing computer...");
let computed_path = outputs_path.join("computed");
let indexes =
indexes::Vecs::forced_import(&computed_path, VERSION + Version::ZERO, indexer)?;
let fetched = fetcher.map(|fetcher| {
fetched::Vecs::forced_import(outputs_path, fetcher, VERSION + Version::ZERO).unwrap()
});
let price = fetched.is_some().then(|| {
price::Vecs::forced_import(&computed_path, VERSION + Version::ZERO, &indexes).unwrap()
});
Ok(Self {
constants: constants::Vecs::forced_import(
&computed_path,
VERSION + Version::ZERO,
&indexes,
)?,
market: market::Vecs::forced_import(&computed_path, VERSION + Version::ZERO, &indexes)?,
stateful: stateful::Vecs::forced_import(
&computed_path,
VERSION + Version::ZERO,
Format::Compressed,
&indexes,
price.as_ref(),
)?,
chain: chain::Vecs::forced_import(
&computed_path,
VERSION + Version::ZERO,
indexer,
&indexes,
price.as_ref(),
)?,
pools: pools::Vecs::forced_import(
&computed_path,
VERSION + Version::ZERO,
&indexes,
price.as_ref(),
)?,
cointime: cointime::Vecs::forced_import(
&computed_path,
VERSION + Version::ZERO,
&indexes,
price.as_ref(),
)?,
indexes,
fetched,
price,
})
}
impl Computer {
pub fn compute(
&mut self,
indexer: &mut Indexer,
indexer: &Indexer,
starting_indexes: brk_indexer::Indexes,
exit: &Exit,
) -> color_eyre::Result<()> {
info!("Computing...");
self.vecs
.as_mut()
.unwrap()
.compute(indexer, starting_indexes, self.fetcher.as_mut(), exit)
) -> Result<()> {
info!("Computing indexes...");
let mut starting_indexes = self.indexes.compute(indexer, starting_indexes, exit)?;
info!("Computing constants...");
self.constants
.compute(indexer, &self.indexes, &starting_indexes, exit)?;
if let Some(fetched) = self.fetched.as_mut() {
info!("Computing fetched...");
fetched.compute(indexer, &self.indexes, &starting_indexes, exit)?;
info!("Computing prices...");
self.price.as_mut().unwrap().compute(
indexer,
&self.indexes,
&starting_indexes,
fetched,
exit,
)?;
}
std::thread::scope(|scope| -> Result<()> {
let chain = scope.spawn(|| -> Result<()> {
info!("Computing chain...");
self.chain.compute(
indexer,
&self.indexes,
&starting_indexes,
self.price.as_ref(),
exit,
)?;
Ok(())
});
if let Some(price) = self.price.as_ref() {
info!("Computing market...");
self.market
.compute(indexer, &self.indexes, price, &starting_indexes, exit)?;
}
// let _ = generate_allocation_files(&self.pools);
chain.join().unwrap()?;
Ok(())
})?;
self.pools.compute(
indexer,
&self.indexes,
&starting_indexes,
&self.chain,
self.price.as_ref(),
exit,
)?;
// return Ok(());
info!("Computing stateful...");
self.stateful.compute(
indexer,
&self.indexes,
&self.chain,
self.price.as_ref(),
&mut starting_indexes,
exit,
)?;
info!("Computing cointime...");
self.cointime.compute(
indexer,
&self.indexes,
&starting_indexes,
self.price.as_ref(),
&self.chain,
&self.stateful,
exit,
)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
// pub fn vecs(&self) -> &Vecs {
self.vecs.as_ref().unwrap().vecs()
[
self.constants.vecs(),
self.indexes.vecs(),
self.market.vecs(),
self.chain.vecs(),
self.stateful.vecs(),
self.cointime.vecs(),
self.pools.vecs(),
self.fetched.as_ref().map_or(vec![], |v| v.vecs()),
self.price.as_ref().map_or(vec![], |v| v.vecs()),
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
// pub fn mut_vecs(&mut self) -> &mut Vecs {
// self.vecs.as_mut().unwrap()
// }
pub fn stores(&self) -> &Stores {
self.stores.as_ref().unwrap()
}
pub fn mut_stores(&mut self) -> &mut Stores {
self.stores.as_mut().unwrap()
pub fn static_clone(&self) -> &'static Self {
Box::leak(Box::new(self.clone()))
}
}
// pub fn generate_allocation_files(monitored: &pools::Vecs) -> Result<()> {
// info!("Generating Allocative files...");
// let mut flamegraph = allocative::FlameGraphBuilder::default();
// flamegraph.visit_root(monitored);
// let output = flamegraph.finish();
// let folder = format!(
// "at-{}",
// jiff::Timestamp::now().strftime("%Y-%m-%d_%Hh%Mm%Ss"),
// );
// let path = std::path::PathBuf::from(&format!("./target/flamegraph/{folder}"));
// std::fs::create_dir_all(&path)?;
// // fs::write(path.join("flamegraph.src"), &output.flamegraph())?;
// let mut fg_svg = Vec::new();
// inferno::flamegraph::from_reader(
// &mut inferno::flamegraph::Options::default(),
// output.flamegraph().write().as_bytes(),
// &mut fg_svg,
// )?;
// std::fs::write(path.join("flamegraph.svg"), &fg_svg)?;
// std::fs::write(path.join("warnings.txt"), output.warnings())?;
// info!("Successfully generated Allocative files");
// Ok(())
// }
File diff suppressed because it is too large Load Diff
+287
View File
@@ -0,0 +1,287 @@
use allocative::Allocative;
use num_enum::{FromPrimitive, IntoPrimitive};
use serde::{Deserialize, Serialize};
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
// Created from the list in `pools.rs`
// Can be used as index for said list
#[allow(clippy::upper_case_acronyms)]
#[derive(
Debug,
Copy,
Clone,
PartialEq,
Eq,
PartialOrd,
Ord,
Serialize,
Deserialize,
FromPrimitive,
IntoPrimitive,
FromBytes,
IntoBytes,
Immutable,
KnownLayout,
Allocative,
)]
#[serde(rename_all = "lowercase")]
#[repr(u8)]
pub enum PoolId {
#[default]
Unknown,
BlockFills,
UltimusPool,
TerraPool,
Luxor,
OneThash,
BtcCom,
Bitfarms,
HuobiPool,
WayiCn,
CanoePool,
BtcTop,
BitcoinCom,
Pool175btc,
GbMiners,
AXbt,
AsicMiner,
BitMinter,
BitcoinRussia,
BtcServ,
SimplecoinUs,
BtcGuild,
Eligius,
OzCoin,
EclipseMc,
MaxBtc,
TripleMining,
CoinLab,
Pool50btc,
GhashIo,
StMiningCorp,
Bitparking,
Mmpool,
Polmine,
KncMiner,
Bitalo,
F2Pool,
Hhtt,
MegaBigPower,
MtRed,
NmcBit,
YourbtcNet,
GiveMeCoins,
BraiinsPool,
AntPool,
MultiCoinCo,
BcpoolIo,
Cointerra,
KanoPool,
SoloCk,
CkPool,
NiceHash,
BitClub,
BitcoinAffiliateNetwork,
Btcc,
BwPool,
ExxBw,
Bitsolo,
BitFury,
TwentyOneInc,
DigitalBtc,
EightBaochi,
MyBtcCoinPool,
TbDice,
HashPool,
Nexious,
BravoMining,
HotPool,
OkExPool,
BcMonster,
OneHash,
Bixin,
TatmasPool,
ViaBtc,
ConnectBtc,
BatPool,
Waterhole,
DcExploration,
Dcex,
BtPool,
FiftyEightCoin,
BitcoinIndia,
ShawnP0wers,
PHashIo,
RigPool,
HaoZhuZhu,
SevenPool,
MiningKings,
HashBx,
DPool,
Rawpool,
Haominer,
Helix,
BitcoinUkraine,
Poolin,
SecretSuperstar,
TigerpoolNet,
SigmapoolCom,
OkpoolTop,
Hummerpool,
Tangpool,
BytePool,
SpiderPool,
NovaBlock,
MiningCity,
BinancePool,
Minerium,
LubianCom,
Okkong,
AaoPool,
EmcdPool,
FoundryUsa,
SbiCrypto,
ArkPool,
PureBtcCom,
MaraPool,
KuCoinPool,
EntrustCharityPool,
OkMiner,
Titan,
PegaPool,
BtcNuggets,
CloudHashing,
DigitalXMintsy,
Telco214,
BtcPoolParty,
Multipool,
TransactionCoinMining,
BtcDig,
TrickysBtcPool,
BtcMp,
Eobot,
Unomp,
Patels,
GoGreenLight,
EkanemBtc,
Canoe,
Tiger,
OneM1x,
Zulupool,
SecPool,
Ocean,
WhitePool,
Wk057,
FutureBitApolloSolo,
CarbonNegative,
PortlandHodl,
Phoenix,
Neopool,
MaxiPool,
BitFuFuPool,
LuckyPool,
MiningDutch,
PublicPool,
MiningSquared,
InnopolisTech,
BtcLab,
Parasite,
Dummy158,
Dummy159,
Dummy160,
Dummy161,
Dummy162,
Dummy163,
Dummy164,
Dummy165,
Dummy166,
Dummy167,
Dummy168,
Dummy169,
Dummy170,
Dummy171,
Dummy172,
Dummy173,
Dummy174,
Dummy175,
Dummy176,
Dummy177,
Dummy178,
Dummy179,
Dummy180,
Dummy181,
Dummy182,
Dummy183,
Dummy184,
Dummy185,
Dummy186,
Dummy187,
Dummy188,
Dummy189,
Dummy190,
Dummy191,
Dummy192,
Dummy193,
Dummy194,
Dummy195,
Dummy196,
Dummy197,
Dummy198,
Dummy199,
Dummy200,
Dummy201,
Dummy202,
Dummy203,
Dummy204,
Dummy205,
Dummy206,
Dummy207,
Dummy208,
Dummy209,
Dummy210,
Dummy211,
Dummy212,
Dummy213,
Dummy214,
Dummy215,
Dummy216,
Dummy217,
Dummy218,
Dummy219,
Dummy220,
Dummy221,
Dummy222,
Dummy223,
Dummy224,
Dummy225,
Dummy226,
Dummy227,
Dummy228,
Dummy229,
Dummy230,
Dummy231,
Dummy232,
Dummy233,
Dummy234,
Dummy235,
Dummy236,
Dummy237,
Dummy238,
Dummy239,
Dummy240,
Dummy241,
Dummy242,
Dummy243,
Dummy244,
Dummy245,
Dummy246,
Dummy247,
Dummy248,
Dummy249,
Dummy250,
Dummy251,
Dummy252,
Dummy253,
Dummy254,
Dummy255,
}
+240
View File
@@ -0,0 +1,240 @@
use std::{collections::BTreeMap, path::Path};
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_store::AnyStore;
use brk_structs::{AddressBytes, Height, OutputIndex, OutputType};
use rayon::prelude::*;
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, Database, Exit, GenericStoredVec,
PAGE_SIZE, RawVec, StoredIndex, VecIterator, Version,
};
mod id;
mod pool;
#[allow(clippy::module_inception)]
mod pools;
mod vecs;
pub use id::*;
pub use pool::*;
pub use pools::*;
use crate::{
chain,
indexes::{self, Indexes},
price,
};
#[derive(Clone, Allocative)]
pub struct Vecs {
db: Database,
pools: &'static Pools,
height_to_pool: RawVec<Height, PoolId>,
vecs: BTreeMap<PoolId, vecs::Vecs>,
}
impl Vecs {
pub fn forced_import(
parent_path: &Path,
parent_version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
) -> Result<Self> {
let db = Database::open(&parent_path.join("pools"))?;
db.set_min_len(PAGE_SIZE * 1_000_000)?;
let pools = pools();
let version = parent_version + Version::new(3) + Version::new(pools.len() as u64);
let this = Self {
height_to_pool: RawVec::forced_import(&db, "pool", version + Version::ZERO)?,
vecs: pools
.iter()
.map(|pool| {
vecs::Vecs::forced_import(
&db,
pool.id,
pools,
version + Version::ZERO,
indexes,
price,
)
.map(|vecs| (pool.id, vecs))
})
.collect::<Result<BTreeMap<_, _>>>()?,
pools,
db,
};
this.db.retain_regions(
this.vecs()
.into_iter()
.flat_map(|v| v.region_names())
.collect(),
)?;
Ok(this)
}
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
chain: &chain::Vecs,
price: Option<&price::Vecs>,
exit: &Exit,
) -> Result<()> {
self.compute_(indexer, indexes, starting_indexes, chain, price, exit)?;
self.db.flush_then_punch()?;
Ok(())
}
fn compute_(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
chain: &chain::Vecs,
price: Option<&price::Vecs>,
exit: &Exit,
) -> Result<()> {
self.compute_height_to_pool(indexer, indexes, starting_indexes, exit)?;
self.vecs.par_iter_mut().try_for_each(|(_, vecs)| {
vecs.compute(
indexer,
indexes,
starting_indexes,
&self.height_to_pool,
chain,
price,
exit,
)
})?;
Ok(())
}
fn compute_height_to_pool(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.height_to_pool.validate_computed_version_or_reset(
self.height_to_pool.version() + indexer.stores.height_to_coinbase_tag.version(),
)?;
let mut height_to_first_txindex_iter = indexer.vecs.height_to_first_txindex.iter();
let mut txindex_to_first_outputindex_iter =
indexer.vecs.txindex_to_first_outputindex.iter();
let mut txindex_to_output_count_iter = indexes.txindex_to_output_count.iter();
let mut outputindex_to_outputtype_iter = indexer.vecs.outputindex_to_outputtype.iter();
let mut outputindex_to_typeindex_iter = indexer.vecs.outputindex_to_typeindex.iter();
let mut p2pk65addressindex_to_p2pk65bytes_iter =
indexer.vecs.p2pk65addressindex_to_p2pk65bytes.iter();
let mut p2pk33addressindex_to_p2pk33bytes_iter =
indexer.vecs.p2pk33addressindex_to_p2pk33bytes.iter();
let mut p2pkhaddressindex_to_p2pkhbytes_iter =
indexer.vecs.p2pkhaddressindex_to_p2pkhbytes.iter();
let mut p2shaddressindex_to_p2shbytes_iter =
indexer.vecs.p2shaddressindex_to_p2shbytes.iter();
let mut p2wpkhaddressindex_to_p2wpkhbytes_iter =
indexer.vecs.p2wpkhaddressindex_to_p2wpkhbytes.iter();
let mut p2wshaddressindex_to_p2wshbytes_iter =
indexer.vecs.p2wshaddressindex_to_p2wshbytes.iter();
let mut p2traddressindex_to_p2trbytes_iter =
indexer.vecs.p2traddressindex_to_p2trbytes.iter();
let mut p2aaddressindex_to_p2abytes_iter = indexer.vecs.p2aaddressindex_to_p2abytes.iter();
let unknown = self.pools.get_unknown();
let min = starting_indexes
.height
.unwrap_to_usize()
.min(self.height_to_pool.len());
indexer
.stores
.height_to_coinbase_tag
.iter()
.skip(min)
.try_for_each(|(height, coinbase_tag)| -> Result<()> {
let txindex = height_to_first_txindex_iter.unwrap_get_inner(height);
let outputindex = txindex_to_first_outputindex_iter.unwrap_get_inner(txindex);
let outputcount = txindex_to_output_count_iter.unwrap_get_inner(txindex);
let pool = (*outputindex..(*outputindex + *outputcount))
.map(OutputIndex::from)
.find_map(|outputindex| {
let outputtype =
outputindex_to_outputtype_iter.unwrap_get_inner(outputindex);
let typeindex = outputindex_to_typeindex_iter.unwrap_get_inner(outputindex);
let address = match outputtype {
OutputType::P2PK65 => Some(AddressBytes::from(
p2pk65addressindex_to_p2pk65bytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2PK33 => Some(AddressBytes::from(
p2pk33addressindex_to_p2pk33bytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2PKH => Some(AddressBytes::from(
p2pkhaddressindex_to_p2pkhbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2SH => Some(AddressBytes::from(
p2shaddressindex_to_p2shbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2WPKH => Some(AddressBytes::from(
p2wpkhaddressindex_to_p2wpkhbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2WSH => Some(AddressBytes::from(
p2wshaddressindex_to_p2wshbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2TR => Some(AddressBytes::from(
p2traddressindex_to_p2trbytes_iter
.unwrap_get_inner(typeindex.into()),
)),
OutputType::P2A => Some(AddressBytes::from(
p2aaddressindex_to_p2abytes_iter.unwrap_get_inner(typeindex.into()),
)),
_ => None,
};
address
.and_then(|address| self.pools.find_from_address(&address.to_string()))
})
.or_else(|| self.pools.find_from_coinbase_tag(&coinbase_tag))
.unwrap_or(unknown);
self.height_to_pool.push_if_needed(height, pool.id)?;
Ok(())
})?;
self.height_to_pool.safe_flush(exit)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.vecs
.iter()
.flat_map(|(_, vecs)| vecs.vecs())
.collect::<Vec<_>>(),
vec![&self.height_to_pool],
]
.into_iter()
.flatten()
.collect::<Vec<_>>()
}
}
+46
View File
@@ -0,0 +1,46 @@
use allocative::Allocative;
use crate::pools::PoolId;
#[derive(Debug, Allocative)]
pub struct Pool {
pub id: PoolId,
pub name: &'static str,
pub addresses: Box<[&'static str]>,
pub tags: Box<[&'static str]>,
pub tags_lowercase: Box<[String]>,
pub link: &'static str,
}
impl Pool {
pub fn serialized_id(&self) -> String {
let value = serde_json::to_value(self.id).unwrap();
value.as_str().unwrap().to_string()
}
}
impl From<(usize, JSONPool)> for Pool {
fn from((index, pool): (usize, JSONPool)) -> Self {
Self {
id: (index as u8).into(),
name: pool.name,
addresses: pool.addresses,
tags_lowercase: pool
.tags
.iter()
.map(|t| t.to_lowercase())
.collect::<Vec<_>>()
.into_boxed_slice(),
tags: pool.tags,
link: pool.link,
}
}
}
#[derive(Debug)]
pub struct JSONPool {
pub name: &'static str,
pub addresses: Box<[&'static str]>,
pub tags: Box<[&'static str]>,
pub link: &'static str,
}
File diff suppressed because it is too large Load Diff
+467
View File
@@ -0,0 +1,467 @@
use allocative::Allocative;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Height, Sats, StoredF32, StoredU16, StoredU32};
use vecdb::{AnyCollectableVec, AnyIterableVec, Database, Exit, StoredIndex, VecIterator, Version};
use crate::{
PoolId, Pools, chain,
grouped::{
ComputedValueVecsFromHeight, ComputedVecsFromDateIndex, ComputedVecsFromHeight, Source,
VecBuilderOptions,
},
indexes::{self, Indexes},
price,
};
#[derive(Clone, Allocative)]
pub struct Vecs {
id: PoolId,
indexes_to_blocks_mined: ComputedVecsFromHeight<StoredU32>,
indexes_to_1w_blocks_mined: ComputedVecsFromDateIndex<StoredU32>,
indexes_to_1m_blocks_mined: ComputedVecsFromDateIndex<StoredU32>,
indexes_to_1y_blocks_mined: ComputedVecsFromDateIndex<StoredU32>,
indexes_to_subsidy: ComputedValueVecsFromHeight,
indexes_to_fee: ComputedValueVecsFromHeight,
indexes_to_coinbase: ComputedValueVecsFromHeight,
indexes_to_dominance: ComputedVecsFromDateIndex<StoredF32>,
indexes_to_1d_dominance: ComputedVecsFromDateIndex<StoredF32>,
indexes_to_1w_dominance: ComputedVecsFromDateIndex<StoredF32>,
indexes_to_1m_dominance: ComputedVecsFromDateIndex<StoredF32>,
indexes_to_1y_dominance: ComputedVecsFromDateIndex<StoredF32>,
indexes_to_days_since_block: ComputedVecsFromDateIndex<StoredU16>,
}
impl Vecs {
pub fn forced_import(
db: &Database,
id: PoolId,
pools: &Pools,
parent_version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
) -> Result<Self> {
let pool = pools.get(id);
let name = pool.serialized_id();
let suffix = |s: &str| format!("{name}_{s}");
let compute_dollars = price.is_some();
let version = parent_version + Version::ZERO;
Ok(Self {
id,
indexes_to_blocks_mined: ComputedVecsFromHeight::forced_import(
db,
&suffix("blocks_mined"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_sum().add_cumulative(),
)?,
indexes_to_1w_blocks_mined: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1w_blocks_mined"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1m_blocks_mined: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1m_blocks_mined"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1y_blocks_mined: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1y_blocks_mined"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_subsidy: ComputedValueVecsFromHeight::forced_import(
db,
&suffix("subsidy"),
Source::Compute,
version + Version::ZERO,
VecBuilderOptions::default().add_sum().add_cumulative(),
compute_dollars,
indexes,
)?,
indexes_to_fee: ComputedValueVecsFromHeight::forced_import(
db,
&suffix("fee"),
Source::Compute,
version + Version::ZERO,
VecBuilderOptions::default().add_sum().add_cumulative(),
compute_dollars,
indexes,
)?,
indexes_to_coinbase: ComputedValueVecsFromHeight::forced_import(
db,
&suffix("coinbase"),
Source::Compute,
version + Version::ZERO,
VecBuilderOptions::default().add_sum().add_cumulative(),
compute_dollars,
indexes,
)?,
indexes_to_dominance: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("dominance"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1d_dominance: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1d_dominance"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1w_dominance: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1w_dominance"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1m_dominance: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1m_dominance"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_1y_dominance: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("1y_dominance"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
indexes_to_days_since_block: ComputedVecsFromDateIndex::forced_import(
db,
&suffix("days_since_block"),
Source::Compute,
version + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
})
}
#[allow(clippy::too_many_arguments)]
pub fn compute(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
height_to_pool: &impl AnyIterableVec<Height, PoolId>,
chain: &chain::Vecs,
price: Option<&price::Vecs>,
exit: &Exit,
) -> Result<()> {
self.indexes_to_blocks_mined.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform(
starting_indexes.height,
height_to_pool,
|(h, id, ..)| {
(
h,
if id == self.id {
StoredU32::ONE
} else {
StoredU32::ZERO
},
)
},
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1w_blocks_mined.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sum(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_sum(),
7,
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1m_blocks_mined.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sum(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_sum(),
30,
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1y_blocks_mined.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
v.compute_sum(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_sum(),
365,
exit,
)?;
Ok(())
},
)?;
let height_to_blocks_mined = self.indexes_to_blocks_mined.height.as_ref().unwrap();
self.indexes_to_subsidy.compute_all(
indexer,
indexes,
price,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform2(
starting_indexes.height,
height_to_blocks_mined,
chain.indexes_to_subsidy.sats.height.as_ref().unwrap(),
|(h, mined, sats, ..)| {
(
h,
if mined == StoredU32::ONE {
sats
} else {
Sats::ZERO
},
)
},
exit,
)?;
Ok(())
},
)?;
self.indexes_to_fee.compute_all(
indexer,
indexes,
price,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform2(
starting_indexes.height,
height_to_blocks_mined,
chain.indexes_to_fee.sats.height.unwrap_sum(),
|(h, mined, sats, ..)| {
(
h,
if mined == StoredU32::ONE {
sats
} else {
Sats::ZERO
},
)
},
exit,
)?;
Ok(())
},
)?;
self.indexes_to_coinbase.compute_all(
indexer,
indexes,
price,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_transform2(
starting_indexes.height,
height_to_blocks_mined,
chain.indexes_to_coinbase.sats.height.as_ref().unwrap(),
|(h, mined, sats, ..)| {
(
h,
if mined == StoredU32::ONE {
sats
} else {
Sats::ZERO
},
)
},
exit,
)?;
Ok(())
},
)?;
self.indexes_to_dominance.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_percentage(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_cumulative(),
chain.indexes_to_block_count.dateindex.unwrap_cumulative(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1d_dominance.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_percentage(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_sum(),
chain.indexes_to_block_count.dateindex.unwrap_sum(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1w_dominance.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_percentage(
starting_indexes.dateindex,
self.indexes_to_1w_blocks_mined.dateindex.as_ref().unwrap(),
chain.indexes_to_1w_block_count.dateindex.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1m_dominance.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_percentage(
starting_indexes.dateindex,
self.indexes_to_1m_blocks_mined.dateindex.as_ref().unwrap(),
chain.indexes_to_1m_block_count.dateindex.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_1y_dominance.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|vec, _, _, starting_indexes, exit| {
vec.compute_percentage(
starting_indexes.dateindex,
self.indexes_to_1y_blocks_mined.dateindex.as_ref().unwrap(),
chain.indexes_to_1y_block_count.dateindex.as_ref().unwrap(),
exit,
)?;
Ok(())
},
)?;
self.indexes_to_days_since_block.compute_all(
indexer,
indexes,
starting_indexes,
exit,
|v, _, _, starting_indexes, exit| {
let mut prev = None;
v.compute_transform2(
starting_indexes.dateindex,
self.indexes_to_blocks_mined.dateindex.unwrap_sum(),
self.indexes_to_blocks_mined.dateindex.unwrap_cumulative(),
|(i, sum, cumulative, slf)| {
if prev.is_none() {
let i = i.unwrap_to_usize();
prev.replace(if i > 0 {
slf.into_iter().unwrap_get_inner_(i - 1)
} else {
StoredU16::ZERO
});
}
let days = if !cumulative.is_zero() && sum.is_zero() {
prev.unwrap() + StoredU16::ONE
} else {
StoredU16::ZERO
};
prev.replace(days);
(i, days)
},
exit,
)?;
Ok(())
},
)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.indexes_to_blocks_mined.vecs(),
self.indexes_to_1w_blocks_mined.vecs(),
self.indexes_to_1m_blocks_mined.vecs(),
self.indexes_to_1y_blocks_mined.vecs(),
self.indexes_to_subsidy.vecs(),
self.indexes_to_fee.vecs(),
self.indexes_to_coinbase.vecs(),
self.indexes_to_dominance.vecs(),
self.indexes_to_1d_dominance.vecs(),
self.indexes_to_1w_dominance.vecs(),
self.indexes_to_1m_dominance.vecs(),
self.indexes_to_1y_dominance.vecs(),
self.indexes_to_days_since_block.vecs(),
]
.into_iter()
.flatten()
.collect()
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,260 @@
use std::{ops::Deref, path::Path};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Bitcoin, DateIndex, Dollars, Height, StoredU64, Version};
use vecdb::{
AnyCollectableVec, AnyIterableVec, AnyStoredVec, AnyVec, Database, EagerVec, Exit, Format,
GenericStoredVec, VecIterator,
};
use crate::{
Indexes,
grouped::{ComputedVecsFromHeight, Source, VecBuilderOptions},
indexes, price,
stateful::{
common,
r#trait::{CohortVecs, DynCohortVecs},
},
states::AddressCohortState,
};
const VERSION: Version = Version::ZERO;
#[derive(Clone)]
pub struct Vecs {
starting_height: Option<Height>,
pub state: Option<AddressCohortState>,
pub inner: common::Vecs,
pub height_to_addr_count: EagerVec<Height, StoredU64>,
pub indexes_to_addr_count: ComputedVecsFromHeight<StoredU64>,
}
impl Vecs {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
cohort_name: Option<&str>,
format: Format,
version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
states_path: Option<&Path>,
compute_rel_to_all: bool,
) -> Result<Self> {
let compute_dollars = price.is_some();
let suffix = |s: &str| cohort_name.map_or(s.to_string(), |name| format!("{name}_{s}"));
Ok(Self {
starting_height: None,
state: states_path.map(|states_path| {
AddressCohortState::new(
states_path,
cohort_name.unwrap_or_default(),
compute_dollars,
)
}),
height_to_addr_count: EagerVec::forced_import(
db,
&suffix("addr_count"),
version + VERSION + Version::ZERO,
format,
)?,
indexes_to_addr_count: ComputedVecsFromHeight::forced_import(
db,
&suffix("addr_count"),
Source::None,
version + VERSION + Version::ZERO,
indexes,
VecBuilderOptions::default().add_last(),
)?,
inner: common::Vecs::forced_import(
db,
cohort_name,
format,
version,
indexes,
price,
false,
compute_rel_to_all,
false,
)?,
})
}
}
impl DynCohortVecs for Vecs {
fn min_height_vecs_len(&self) -> usize {
[
self.height_to_addr_count.len(),
self.inner.min_height_vecs_len(),
]
.into_iter()
.min()
.unwrap()
}
fn reset_state_starting_height(&mut self) {
self.starting_height = Some(Height::ZERO);
}
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
let starting_height = self
.inner
.import_state(starting_height, &mut self.state.as_mut().unwrap().inner)?;
self.starting_height = Some(starting_height);
if let Some(prev_height) = starting_height.decremented() {
self.state.as_mut().unwrap().addr_count = *self
.height_to_addr_count
.into_iter()
.unwrap_get_inner(prev_height);
}
Ok(starting_height)
}
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.height_to_addr_count
.validate_computed_version_or_reset(
base_version + self.height_to_addr_count.inner_version(),
)?;
self.inner.validate_computed_versions(base_version)
}
fn forced_pushed_at(&mut self, height: Height, exit: &Exit) -> Result<()> {
if self.starting_height.unwrap() > height {
return Ok(());
}
self.height_to_addr_count.forced_push_at(
height,
self.state.as_ref().unwrap().addr_count.into(),
exit,
)?;
self.inner
.forced_pushed_at(height, exit, &self.state.as_ref().unwrap().inner)
}
fn compute_then_force_push_unrealized_states(
&mut self,
height: Height,
height_price: Option<Dollars>,
dateindex: Option<DateIndex>,
date_price: Option<Option<Dollars>>,
exit: &Exit,
) -> Result<()> {
self.inner.compute_then_force_push_unrealized_states(
height,
height_price,
dateindex,
date_price,
exit,
&self.state.as_ref().unwrap().inner,
)
}
fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> {
self.height_to_addr_count.safe_flush(exit)?;
self.inner
.safe_flush_stateful_vecs(height, exit, &mut self.state.as_mut().unwrap().inner)
}
#[allow(clippy::too_many_arguments)]
fn compute_rest_part1(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.indexes_to_addr_count.compute_rest(
indexes,
starting_indexes,
exit,
Some(&self.height_to_addr_count),
)?;
self.inner
.compute_rest_part1(indexer, indexes, price, starting_indexes, exit)
}
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
[
self.inner.vecs(),
self.indexes_to_addr_count.vecs(),
vec![&self.height_to_addr_count],
]
.concat()
}
}
impl CohortVecs for Vecs {
fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.height_to_addr_count.compute_sum_of_others(
starting_indexes.height,
others
.iter()
.map(|v| &v.height_to_addr_count)
.collect::<Vec<_>>()
.as_slice(),
exit,
)?;
self.inner.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.inner).collect::<Vec<_>>(),
exit,
)
}
#[allow(clippy::too_many_arguments)]
fn compute_rest_part2(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl AnyIterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl AnyIterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
height_to_realized_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_realized_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
exit: &Exit,
) -> Result<()> {
self.inner.compute_rest_part2(
indexer,
indexes,
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
height_to_realized_cap,
dateindex_to_realized_cap,
exit,
)
}
}
impl Deref for Vecs {
type Target = common::Vecs;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
@@ -0,0 +1,555 @@
use std::path::Path;
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{
AddressGroups, Bitcoin, ByAmountRange, ByGreatEqualAmount, ByLowerThanAmount, DateIndex,
Dollars, GroupFilter, Height, Version,
};
use derive_deref::{Deref, DerefMut};
use vecdb::{AnyIterableVec, Database, Exit, Format};
use crate::{
Indexes, indexes, price,
stateful::{
address_cohort,
r#trait::{CohortVecs, DynCohortVecs},
},
};
const VERSION: Version = Version::new(0);
#[derive(Clone, Deref, DerefMut)]
pub struct Vecs(AddressGroups<(GroupFilter, address_cohort::Vecs)>);
impl Vecs {
pub fn forced_import(
db: &Database,
version: Version,
format: Format,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
states_path: &Path,
) -> Result<Self> {
Ok(Self(
AddressGroups {
amount_range: ByAmountRange {
_0sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_with_0sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_1sat_to_10sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1sat_under_10sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_10sats_to_100sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10sats_under_100sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_100sats_to_1k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100sats_under_1k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_1k_sats_to_10k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1k_sats_under_10k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_10k_sats_to_100k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10k_sats_under_100k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_100k_sats_to_1m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100k_sats_under_1m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_1m_sats_to_10m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1m_sats_under_10m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_10m_sats_to_1btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10m_sats_under_1btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_1btc_to_10btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1btc_under_10btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_10btc_to_100btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10btc_under_100btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_100btc_to_1k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100btc_under_1k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_1k_btc_to_10k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1k_btc_under_10k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_10k_btc_to_100k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10k_btc_under_100k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
_100k_btc_or_more: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
Some(states_path),
true,
)?,
},
lt_amount: ByLowerThanAmount {
_10sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_10sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_100sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_1k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_10k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_100k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_1m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_10m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_1btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_10btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_100btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_1k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_10k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_under_100k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
},
ge_amount: ByGreatEqualAmount {
_1sat: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1sat"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100k_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100k_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10m_sats: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10m_sats"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_100btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_100btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_1k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_1k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
_10k_btc: address_cohort::Vecs::forced_import(
db,
Some("addrs_above_10k_btc"),
format,
version + VERSION + Version::ZERO,
indexes,
price,
None,
true,
)?,
},
}
.into(),
))
}
pub fn compute_overlapping_vecs(
&mut self,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
let by_size_range = self.0.amount_range.as_vec();
[
self.0
.ge_amount
.as_mut_vec()
.into_iter()
.map(|(filter, vecs)| {
(
vecs,
by_size_range
.into_iter()
.filter(|(other, _)| filter.includes(other))
.map(|(_, v)| v)
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>(),
self.0
.lt_amount
.as_mut_vec()
.into_iter()
.map(|(filter, vecs)| {
(
vecs,
by_size_range
.into_iter()
.filter(|(other, _)| filter.includes(other))
.map(|(_, v)| v)
.collect::<Vec<_>>(),
)
})
.collect::<Vec<_>>(),
]
.into_iter()
.flatten()
.try_for_each(|(vecs, stateful)| {
vecs.compute_from_stateful(starting_indexes, &stateful, exit)
})
}
pub fn compute_rest_part1(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.as_mut_vecs().into_iter().try_for_each(|(_, v)| {
v.compute_rest_part1(indexer, indexes, price, starting_indexes, exit)
})
}
#[allow(clippy::too_many_arguments)]
pub fn compute_rest_part2(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl AnyIterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl AnyIterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
height_to_realized_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_realized_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
exit: &Exit,
) -> Result<()> {
self.0.as_boxed_mut_vecs().into_iter().try_for_each(|v| {
v.into_iter().try_for_each(|(_, v)| {
v.compute_rest_part2(
indexer,
indexes,
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
height_to_realized_cap,
dateindex_to_realized_cap,
exit,
)
})
})
}
pub fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> {
self.as_mut_separate_vecs()
.into_iter()
.try_for_each(|(_, v)| v.safe_flush_stateful_vecs(height, exit))
}
}
@@ -0,0 +1,47 @@
use brk_structs::{ByAddressType, Height};
use derive_deref::{Deref, DerefMut};
use vecdb::VecIterator;
use super::AddressTypeToHeightToAddressCount;
#[derive(Debug, Default, Deref, DerefMut)]
pub struct AddressTypeToAddressCount(ByAddressType<u64>);
impl From<(&AddressTypeToHeightToAddressCount, Height)> for AddressTypeToAddressCount {
fn from((groups, starting_height): (&AddressTypeToHeightToAddressCount, Height)) -> Self {
if let Some(prev_height) = starting_height.decremented() {
Self(ByAddressType {
p2pk65: groups
.p2pk65
.into_iter()
.unwrap_get_inner(prev_height)
.into(),
p2pk33: groups
.p2pk33
.into_iter()
.unwrap_get_inner(prev_height)
.into(),
p2pkh: groups
.p2pkh
.into_iter()
.unwrap_get_inner(prev_height)
.into(),
p2sh: groups.p2sh.into_iter().unwrap_get_inner(prev_height).into(),
p2wpkh: groups
.p2wpkh
.into_iter()
.unwrap_get_inner(prev_height)
.into(),
p2wsh: groups
.p2wsh
.into_iter()
.unwrap_get_inner(prev_height)
.into(),
p2tr: groups.p2tr.into_iter().unwrap_get_inner(prev_height).into(),
p2a: groups.p2a.into_iter().unwrap_get_inner(prev_height).into(),
})
} else {
Default::default()
}
}
}
@@ -0,0 +1,43 @@
use brk_error::Result;
use brk_structs::{ByAddressType, Height, StoredU64};
use derive_deref::{Deref, DerefMut};
use vecdb::{EagerVec, Exit, GenericStoredVec};
use super::AddressTypeToAddressCount;
#[derive(Debug, Clone, Deref, DerefMut)]
pub struct AddressTypeToHeightToAddressCount(ByAddressType<EagerVec<Height, StoredU64>>);
impl From<ByAddressType<EagerVec<Height, StoredU64>>> for AddressTypeToHeightToAddressCount {
fn from(value: ByAddressType<EagerVec<Height, StoredU64>>) -> Self {
Self(value)
}
}
impl AddressTypeToHeightToAddressCount {
pub fn forced_push_at(
&mut self,
height: Height,
addresstype_to_usize: &AddressTypeToAddressCount,
exit: &Exit,
) -> Result<()> {
self.p2pk65
.forced_push_at(height, addresstype_to_usize.p2pk65.into(), exit)?;
self.p2pk33
.forced_push_at(height, addresstype_to_usize.p2pk33.into(), exit)?;
self.p2pkh
.forced_push_at(height, addresstype_to_usize.p2pkh.into(), exit)?;
self.p2sh
.forced_push_at(height, addresstype_to_usize.p2sh.into(), exit)?;
self.p2wpkh
.forced_push_at(height, addresstype_to_usize.p2wpkh.into(), exit)?;
self.p2wsh
.forced_push_at(height, addresstype_to_usize.p2wsh.into(), exit)?;
self.p2tr
.forced_push_at(height, addresstype_to_usize.p2tr.into(), exit)?;
self.p2a
.forced_push_at(height, addresstype_to_usize.p2a.into(), exit)?;
Ok(())
}
}
@@ -0,0 +1,9 @@
use std::collections::BTreeMap;
use brk_structs::Height;
use derive_deref::{Deref, DerefMut};
use crate::stateful::AddressTypeToVec;
#[derive(Debug, Default, Deref, DerefMut)]
pub struct HeightToAddressTypeToVec<T>(pub BTreeMap<Height, AddressTypeToVec<T>>);
@@ -0,0 +1,85 @@
use brk_error::Result;
use brk_structs::{ByAddressType, StoredU64};
use derive_deref::{Deref, DerefMut};
use vecdb::{AnyCollectableVec, Exit};
use crate::{Indexes, grouped::ComputedVecsFromHeight, indexes};
use super::AddressTypeToHeightToAddressCount;
#[derive(Clone, Deref, DerefMut)]
pub struct AddressTypeToIndexesToAddressCount(ByAddressType<ComputedVecsFromHeight<StoredU64>>);
impl From<ByAddressType<ComputedVecsFromHeight<StoredU64>>> for AddressTypeToIndexesToAddressCount {
fn from(value: ByAddressType<ComputedVecsFromHeight<StoredU64>>) -> Self {
Self(value)
}
}
impl AddressTypeToIndexesToAddressCount {
pub fn compute(
&mut self,
indexes: &indexes::Vecs,
starting_indexes: &Indexes,
exit: &Exit,
addresstype_to_height_to_addresscount: &AddressTypeToHeightToAddressCount,
) -> Result<()> {
self.p2pk65.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2pk65),
)?;
self.p2pk33.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2pk33),
)?;
self.p2pkh.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2pkh),
)?;
self.p2sh.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2sh),
)?;
self.p2wpkh.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2wpkh),
)?;
self.p2wsh.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2wsh),
)?;
self.p2tr.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2tr),
)?;
self.p2a.compute_rest(
indexes,
starting_indexes,
exit,
Some(&addresstype_to_height_to_addresscount.p2a),
)?;
Ok(())
}
pub fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
self.0
.as_typed_vec()
.into_iter()
.flat_map(|(_, v)| v.vecs())
.collect::<Vec<_>>()
}
}
@@ -0,0 +1,13 @@
mod addresscount;
mod height_to_addresscount;
mod height_to_vec;
mod indexes_to_addresscount;
mod typeindex_tree;
mod vec;
pub use addresscount::*;
pub use height_to_addresscount::*;
pub use height_to_vec::*;
pub use indexes_to_addresscount::*;
pub use typeindex_tree::*;
pub use vec::*;
@@ -0,0 +1,45 @@
use std::{collections::BTreeMap, mem};
use brk_structs::{ByAddressType, TypeIndex};
use derive_deref::{Deref, DerefMut};
#[derive(Debug, Deref, DerefMut)]
pub struct AddressTypeToTypeIndexTree<T>(ByAddressType<BTreeMap<TypeIndex, T>>);
impl<T> AddressTypeToTypeIndexTree<T> {
pub fn merge(mut self, mut other: Self) -> Self {
Self::merge_(&mut self.p2pk65, &mut other.p2pk65);
Self::merge_(&mut self.p2pk33, &mut other.p2pk33);
Self::merge_(&mut self.p2pkh, &mut other.p2pkh);
Self::merge_(&mut self.p2sh, &mut other.p2sh);
Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh);
Self::merge_(&mut self.p2wsh, &mut other.p2wsh);
Self::merge_(&mut self.p2tr, &mut other.p2tr);
Self::merge_(&mut self.p2a, &mut other.p2a);
self
}
fn merge_(own: &mut BTreeMap<TypeIndex, T>, other: &mut BTreeMap<TypeIndex, T>) {
if own.len() >= other.len() {
own.append(other);
} else {
other.append(own);
mem::swap(own, other);
}
}
}
impl<T> Default for AddressTypeToTypeIndexTree<T> {
fn default() -> Self {
Self(ByAddressType {
p2pk65: BTreeMap::default(),
p2pk33: BTreeMap::default(),
p2pkh: BTreeMap::default(),
p2sh: BTreeMap::default(),
p2wpkh: BTreeMap::default(),
p2wsh: BTreeMap::default(),
p2tr: BTreeMap::default(),
p2a: BTreeMap::default(),
})
}
}
@@ -0,0 +1,56 @@
use std::mem;
use brk_structs::ByAddressType;
use derive_deref::{Deref, DerefMut};
#[derive(Debug, Deref, DerefMut)]
pub struct AddressTypeToVec<T>(ByAddressType<Vec<T>>);
impl<T> AddressTypeToVec<T> {
pub fn merge(mut self, mut other: Self) -> Self {
Self::merge_(&mut self.p2pk65, &mut other.p2pk65);
Self::merge_(&mut self.p2pk33, &mut other.p2pk33);
Self::merge_(&mut self.p2pkh, &mut other.p2pkh);
Self::merge_(&mut self.p2sh, &mut other.p2sh);
Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh);
Self::merge_(&mut self.p2wsh, &mut other.p2wsh);
Self::merge_(&mut self.p2tr, &mut other.p2tr);
Self::merge_(&mut self.p2a, &mut other.p2a);
self
}
pub fn merge_mut(&mut self, mut other: Self) {
Self::merge_(&mut self.p2pk65, &mut other.p2pk65);
Self::merge_(&mut self.p2pk33, &mut other.p2pk33);
Self::merge_(&mut self.p2pkh, &mut other.p2pkh);
Self::merge_(&mut self.p2sh, &mut other.p2sh);
Self::merge_(&mut self.p2wpkh, &mut other.p2wpkh);
Self::merge_(&mut self.p2wsh, &mut other.p2wsh);
Self::merge_(&mut self.p2tr, &mut other.p2tr);
Self::merge_(&mut self.p2a, &mut other.p2a);
}
fn merge_(own: &mut Vec<T>, other: &mut Vec<T>) {
if own.len() >= other.len() {
own.append(other);
} else {
other.append(own);
mem::swap(own, other);
}
}
}
impl<T> Default for AddressTypeToVec<T> {
fn default() -> Self {
Self(ByAddressType {
p2pk65: vec![],
p2pk33: vec![],
p2pkh: vec![],
p2sh: vec![],
p2wpkh: vec![],
p2wsh: vec![],
p2tr: vec![],
p2a: vec![],
})
}
}
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,49 @@
use std::collections::BTreeMap;
use vecdb::{CompressedVec, RawVec, StoredCompressed, StoredIndex, StoredRaw};
#[derive(Debug)]
pub struct RangeMap<I, T>(BTreeMap<I, T>);
impl<I, T> RangeMap<I, T>
where
I: StoredIndex,
T: StoredIndex,
{
pub fn get(&self, key: I) -> Option<&T> {
self.0.range(..=key).next_back().map(|(&min, value)| {
if min > key {
unreachable!()
}
value
})
}
}
impl<I, T> From<&RawVec<I, T>> for RangeMap<T, I>
where
I: StoredIndex,
T: StoredIndex + StoredRaw,
{
fn from(vec: &RawVec<I, T>) -> Self {
Self(
vec.into_iter()
.map(|(i, v)| (v.into_owned(), i))
.collect::<BTreeMap<_, _>>(),
)
}
}
impl<I, T> From<&CompressedVec<I, T>> for RangeMap<T, I>
where
I: StoredIndex,
T: StoredIndex + StoredCompressed,
{
fn from(vec: &CompressedVec<I, T>) -> Self {
Self(
vec.into_iter()
.map(|(i, v)| (v.into_owned(), i))
.collect::<BTreeMap<_, _>>(),
)
}
}
+65
View File
@@ -0,0 +1,65 @@
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Bitcoin, DateIndex, Dollars, Height, Version};
use vecdb::{AnyCollectableVec, AnyIterableVec, Exit};
use crate::{Indexes, indexes, price};
pub trait DynCohortVecs: Send + Sync {
fn min_height_vecs_len(&self) -> usize;
fn reset_state_starting_height(&mut self);
fn import_state(&mut self, starting_height: Height) -> Result<Height>;
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()>;
fn forced_pushed_at(&mut self, height: Height, exit: &Exit) -> Result<()>;
fn compute_then_force_push_unrealized_states(
&mut self,
height: Height,
height_price: Option<Dollars>,
dateindex: Option<DateIndex>,
date_price: Option<Option<Dollars>>,
exit: &Exit,
) -> Result<()>;
fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()>;
#[allow(clippy::too_many_arguments)]
fn compute_rest_part1(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()>;
fn vecs(&self) -> Vec<&dyn AnyCollectableVec>;
}
pub trait CohortVecs: DynCohortVecs {
fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()>;
#[allow(clippy::too_many_arguments)]
fn compute_rest_part2(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl AnyIterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl AnyIterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
height_to_realized_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_realized_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
exit: &Exit,
) -> Result<()>;
}
@@ -0,0 +1,190 @@
use std::{ops::Deref, path::Path};
use brk_error::Result;
use brk_indexer::Indexer;
use brk_structs::{Bitcoin, DateIndex, Dollars, Height, Version};
use vecdb::{AnyCollectableVec, AnyIterableVec, Database, Exit, Format};
use crate::{
Indexes, UTXOCohortState, indexes, price,
stateful::{
common,
r#trait::{CohortVecs, DynCohortVecs},
},
};
#[derive(Clone)]
pub struct Vecs {
state_starting_height: Option<Height>,
pub state: Option<UTXOCohortState>,
inner: common::Vecs,
}
impl Vecs {
#[allow(clippy::too_many_arguments)]
pub fn forced_import(
db: &Database,
cohort_name: Option<&str>,
format: Format,
version: Version,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
states_path: Option<&Path>,
extended: bool,
compute_rel_to_all: bool,
compute_adjusted: bool,
) -> Result<Self> {
let compute_dollars = price.is_some();
Ok(Self {
state_starting_height: None,
state: states_path.map(|states_path| {
UTXOCohortState::new(
states_path,
cohort_name.unwrap_or_default(),
compute_dollars,
)
}),
inner: common::Vecs::forced_import(
db,
cohort_name,
format,
version,
indexes,
price,
extended,
compute_rel_to_all,
compute_adjusted,
)?,
})
}
}
impl DynCohortVecs for Vecs {
fn min_height_vecs_len(&self) -> usize {
self.inner.min_height_vecs_len()
}
fn reset_state_starting_height(&mut self) {
self.state_starting_height = Some(Height::ZERO);
}
fn import_state(&mut self, starting_height: Height) -> Result<Height> {
let starting_height = self
.inner
.import_state(starting_height, self.state.as_mut().unwrap())?;
self.state_starting_height = Some(starting_height);
Ok(starting_height)
}
fn validate_computed_versions(&mut self, base_version: Version) -> Result<()> {
self.inner.validate_computed_versions(base_version)
}
fn forced_pushed_at(&mut self, height: Height, exit: &Exit) -> Result<()> {
if self.state_starting_height.unwrap() > height {
return Ok(());
}
self.inner
.forced_pushed_at(height, exit, self.state.as_ref().unwrap())
}
fn compute_then_force_push_unrealized_states(
&mut self,
height: Height,
height_price: Option<Dollars>,
dateindex: Option<DateIndex>,
date_price: Option<Option<Dollars>>,
exit: &Exit,
) -> Result<()> {
self.inner.compute_then_force_push_unrealized_states(
height,
height_price,
dateindex,
date_price,
exit,
self.state.as_mut().unwrap(),
)
}
fn safe_flush_stateful_vecs(&mut self, height: Height, exit: &Exit) -> Result<()> {
self.inner
.safe_flush_stateful_vecs(height, exit, self.state.as_mut().unwrap())
}
#[allow(clippy::too_many_arguments)]
fn compute_rest_part1(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
exit: &Exit,
) -> Result<()> {
self.inner
.compute_rest_part1(indexer, indexes, price, starting_indexes, exit)
}
fn vecs(&self) -> Vec<&dyn AnyCollectableVec> {
self.inner.vecs()
}
}
impl CohortVecs for Vecs {
fn compute_from_stateful(
&mut self,
starting_indexes: &Indexes,
others: &[&Self],
exit: &Exit,
) -> Result<()> {
self.inner.compute_from_stateful(
starting_indexes,
&others.iter().map(|v| &v.inner).collect::<Vec<_>>(),
exit,
)
}
#[allow(clippy::too_many_arguments)]
fn compute_rest_part2(
&mut self,
indexer: &Indexer,
indexes: &indexes::Vecs,
price: Option<&price::Vecs>,
starting_indexes: &Indexes,
height_to_supply: &impl AnyIterableVec<Height, Bitcoin>,
dateindex_to_supply: &impl AnyIterableVec<DateIndex, Bitcoin>,
height_to_market_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_market_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
height_to_realized_cap: Option<&impl AnyIterableVec<Height, Dollars>>,
dateindex_to_realized_cap: Option<&impl AnyIterableVec<DateIndex, Dollars>>,
exit: &Exit,
) -> Result<()> {
self.inner.compute_rest_part2(
indexer,
indexes,
price,
starting_indexes,
height_to_supply,
dateindex_to_supply,
height_to_market_cap,
dateindex_to_market_cap,
height_to_realized_cap,
dateindex_to_realized_cap,
exit,
)
}
}
impl Deref for Vecs {
type Target = common::Vecs;
fn deref(&self) -> &Self::Target {
&self.inner
}
}
File diff suppressed because it is too large Load Diff
@@ -0,0 +1,54 @@
use brk_structs::{EmptyAddressData, EmptyAddressIndex, LoadedAddressData, LoadedAddressIndex};
#[derive(Debug)]
pub enum WithAddressDataSource<T> {
New(T),
FromLoadedAddressDataVec((LoadedAddressIndex, T)),
FromEmptyAddressDataVec((EmptyAddressIndex, T)),
}
impl<T> WithAddressDataSource<T> {
pub fn is_new(&self) -> bool {
matches!(self, Self::New(_))
}
pub fn is_from_emptyaddressdata(&self) -> bool {
matches!(self, Self::FromEmptyAddressDataVec(_))
}
pub fn deref_mut(&mut self) -> &mut T {
match self {
Self::New(v) => v,
Self::FromLoadedAddressDataVec((_, v)) => v,
Self::FromEmptyAddressDataVec((_, v)) => v,
}
}
}
impl From<WithAddressDataSource<EmptyAddressData>> for WithAddressDataSource<LoadedAddressData> {
fn from(value: WithAddressDataSource<EmptyAddressData>) -> Self {
match value {
WithAddressDataSource::New(v) => Self::New(v.into()),
WithAddressDataSource::FromLoadedAddressDataVec((i, v)) => {
Self::FromLoadedAddressDataVec((i, v.into()))
}
WithAddressDataSource::FromEmptyAddressDataVec((i, v)) => {
Self::FromEmptyAddressDataVec((i, v.into()))
}
}
}
}
impl From<WithAddressDataSource<LoadedAddressData>> for WithAddressDataSource<EmptyAddressData> {
fn from(value: WithAddressDataSource<LoadedAddressData>) -> Self {
match value {
WithAddressDataSource::New(v) => Self::New(v.into()),
WithAddressDataSource::FromLoadedAddressDataVec((i, v)) => {
Self::FromLoadedAddressDataVec((i, v.into()))
}
WithAddressDataSource::FromEmptyAddressDataVec((i, v)) => {
Self::FromEmptyAddressDataVec((i, v.into()))
}
}
}
}
@@ -1,13 +1,17 @@
use std::ops::{Add, AddAssign, SubAssign};
use brk_core::{Dollars, Timestamp};
use brk_structs::{Dollars, Timestamp};
use serde::Serialize;
use super::SupplyState;
#[derive(Debug, Clone)]
#[derive(Debug, Clone, Serialize)]
pub struct BlockState {
#[serde(flatten)]
pub supply: SupplyState,
#[serde(skip)]
pub price: Option<Dollars>,
#[serde(skip)]
pub timestamp: Timestamp,
}
@@ -0,0 +1,122 @@
use std::path::Path;
use brk_error::Result;
use brk_structs::{Dollars, Height, LoadedAddressData, Sats};
use crate::SupplyState;
use super::CohortState;
#[derive(Clone)]
pub struct AddressCohortState {
pub addr_count: u64,
pub inner: CohortState,
}
impl AddressCohortState {
pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self {
Self {
addr_count: 0,
inner: CohortState::new(path, name, compute_dollars),
}
}
pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> {
self.inner.reset_price_to_amount_if_needed()
}
pub fn reset_single_iteration_values(&mut self) {
self.inner.reset_single_iteration_values();
}
#[allow(clippy::too_many_arguments)]
pub fn send(
&mut self,
addressdata: &mut LoadedAddressData,
value: Sats,
current_price: Option<Dollars>,
prev_price: Option<Dollars>,
blocks_old: usize,
days_old: f64,
older_than_hour: bool,
) -> Result<()> {
let compute_price = current_price.is_some();
let prev_realized_price = compute_price.then(|| addressdata.realized_price());
let prev_supply_state = SupplyState {
utxos: addressdata.utxos as u64,
value: addressdata.amount(),
};
addressdata.send(value, prev_price)?;
let supply_state = SupplyState {
utxos: addressdata.utxos as u64,
value: addressdata.amount(),
};
self.inner.send_(
&SupplyState { utxos: 1, value },
current_price,
prev_price,
blocks_old,
days_old,
older_than_hour,
compute_price.then(|| (addressdata.realized_price(), &supply_state)),
prev_realized_price.map(|prev_price| (prev_price, &prev_supply_state)),
);
Ok(())
}
pub fn receive(
&mut self,
address_data: &mut LoadedAddressData,
value: Sats,
price: Option<Dollars>,
) {
let compute_price = price.is_some();
let prev_realized_price = compute_price.then(|| address_data.realized_price());
let prev_supply_state = SupplyState {
utxos: address_data.utxos as u64,
value: address_data.amount(),
};
address_data.receive(value, price);
let supply_state = SupplyState {
utxos: address_data.utxos as u64,
value: address_data.amount(),
};
self.inner.receive_(
&SupplyState { utxos: 1, value },
price,
compute_price.then(|| (address_data.realized_price(), &supply_state)),
prev_realized_price.map(|prev_price| (prev_price, &prev_supply_state)),
);
}
pub fn add(&mut self, addressdata: &LoadedAddressData) {
self.addr_count += 1;
self.inner.increment_(
&addressdata.into(),
addressdata.realized_cap,
addressdata.realized_price(),
);
}
pub fn subtract(&mut self, addressdata: &LoadedAddressData) {
self.addr_count = self.addr_count.checked_sub(1).unwrap();
self.inner.decrement_(
&addressdata.into(),
addressdata.realized_cap,
addressdata.realized_price(),
);
}
pub fn commit(&mut self, height: Height) -> Result<()> {
self.inner.commit(height)
}
}
@@ -0,0 +1,317 @@
use std::{cmp::Ordering, path::Path};
use brk_error::Result;
use brk_structs::{CheckedSub, Dollars, Height, Sats};
use crate::{PriceToAmount, RealizedState, SupplyState, UnrealizedState};
#[derive(Clone)]
pub struct CohortState {
pub supply: SupplyState,
pub realized: Option<RealizedState>,
pub satblocks_destroyed: Sats,
pub satdays_destroyed: Sats,
price_to_amount: Option<PriceToAmount>,
}
impl CohortState {
pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self {
Self {
supply: SupplyState::default(),
realized: compute_dollars.then_some(RealizedState::NAN),
satblocks_destroyed: Sats::ZERO,
satdays_destroyed: Sats::ZERO,
price_to_amount: compute_dollars.then_some(PriceToAmount::create(path, name)),
}
}
pub fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
if let Some(price_to_amount) = self.price_to_amount.as_mut() {
price_to_amount.import_at_or_before(height)
} else {
Ok(height)
}
}
pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> {
if let Some(price_to_amount) = self.price_to_amount.as_mut() {
price_to_amount.clean()?;
price_to_amount.init();
}
Ok(())
}
pub fn price_to_amount_first_key_value(&self) -> Option<(&Dollars, &Sats)> {
self.price_to_amount.as_ref().unwrap().first_key_value()
}
pub fn price_to_amount_last_key_value(&self) -> Option<(&Dollars, &Sats)> {
self.price_to_amount.as_ref().unwrap().last_key_value()
}
pub fn reset_single_iteration_values(&mut self) {
self.satdays_destroyed = Sats::ZERO;
self.satblocks_destroyed = Sats::ZERO;
if let Some(realized) = self.realized.as_mut() {
realized.reset_single_iteration_values();
}
}
pub fn increment(&mut self, supply_state: &SupplyState, price: Option<Dollars>) {
self.supply += supply_state;
if supply_state.value > Sats::ZERO
&& let Some(realized) = self.realized.as_mut()
{
let price = price.unwrap();
realized.increment(supply_state, price);
self.price_to_amount
.as_mut()
.unwrap()
.increment(price, supply_state);
}
}
pub fn increment_(
&mut self,
supply_state: &SupplyState,
realized_cap: Dollars,
realized_price: Dollars,
) {
self.supply += supply_state;
if supply_state.value > Sats::ZERO
&& let Some(realized) = self.realized.as_mut()
{
realized.increment_(realized_cap);
self.price_to_amount
.as_mut()
.unwrap()
.increment(realized_price, supply_state);
}
}
pub fn decrement(&mut self, supply_state: &SupplyState, price: Option<Dollars>) {
self.supply -= supply_state;
if supply_state.value > Sats::ZERO
&& let Some(realized) = self.realized.as_mut()
{
let price = price.unwrap();
realized.decrement(supply_state, price);
self.price_to_amount
.as_mut()
.unwrap()
.decrement(price, supply_state);
}
}
pub fn decrement_(
&mut self,
supply_state: &SupplyState,
realized_cap: Dollars,
realized_price: Dollars,
) {
self.supply -= supply_state;
if supply_state.value > Sats::ZERO
&& let Some(realized) = self.realized.as_mut()
{
realized.decrement_(realized_cap);
self.price_to_amount
.as_mut()
.unwrap()
.decrement(realized_price, supply_state);
}
}
pub fn receive(&mut self, supply_state: &SupplyState, price: Option<Dollars>) {
self.receive_(
supply_state,
price,
price.map(|price| (price, supply_state)),
None,
);
}
pub fn receive_(
&mut self,
supply_state: &SupplyState,
price: Option<Dollars>,
price_to_amount_increment: Option<(Dollars, &SupplyState)>,
price_to_amount_decrement: Option<(Dollars, &SupplyState)>,
) {
self.supply += supply_state;
if supply_state.value > Sats::ZERO
&& let Some(realized) = self.realized.as_mut()
{
let price = price.unwrap();
realized.receive(supply_state, price);
if let Some((price, supply)) = price_to_amount_increment
&& supply.value.is_not_zero()
{
self.price_to_amount
.as_mut()
.unwrap()
.increment(price, supply);
}
if let Some((price, supply)) = price_to_amount_decrement
&& supply.value.is_not_zero()
{
self.price_to_amount
.as_mut()
.unwrap()
.decrement(price, supply);
}
}
}
pub fn send(
&mut self,
supply_state: &SupplyState,
current_price: Option<Dollars>,
prev_price: Option<Dollars>,
blocks_old: usize,
days_old: f64,
older_than_hour: bool,
) {
self.send_(
supply_state,
current_price,
prev_price,
blocks_old,
days_old,
older_than_hour,
None,
prev_price.map(|prev_price| (prev_price, supply_state)),
);
}
#[allow(clippy::too_many_arguments)]
pub fn send_(
&mut self,
supply_state: &SupplyState,
current_price: Option<Dollars>,
prev_price: Option<Dollars>,
blocks_old: usize,
days_old: f64,
older_than_hour: bool,
price_to_amount_increment: Option<(Dollars, &SupplyState)>,
price_to_amount_decrement: Option<(Dollars, &SupplyState)>,
) {
if supply_state.utxos == 0 {
return;
}
self.supply -= supply_state;
if supply_state.value > Sats::ZERO {
self.satblocks_destroyed += supply_state.value * blocks_old;
self.satdays_destroyed +=
Sats::from((u64::from(supply_state.value) as f64 * days_old).floor() as u64);
if let Some(realized) = self.realized.as_mut() {
let current_price = current_price.unwrap();
let prev_price = prev_price.unwrap();
realized.send(supply_state, current_price, prev_price, older_than_hour);
if let Some((price, supply)) = price_to_amount_increment
&& supply.value.is_not_zero()
{
self.price_to_amount
.as_mut()
.unwrap()
.increment(price, supply);
}
if let Some((price, supply)) = price_to_amount_decrement
&& supply.value.is_not_zero()
{
self.price_to_amount
.as_mut()
.unwrap()
.decrement(price, supply);
}
}
}
}
pub fn compute_unrealized_states(
&self,
height_price: Dollars,
date_price: Option<Dollars>,
) -> (UnrealizedState, Option<UnrealizedState>) {
if self.price_to_amount.as_ref().unwrap().is_empty() {
return (
UnrealizedState::NAN,
date_price.map(|_| UnrealizedState::NAN),
);
}
let mut height_unrealized_state = UnrealizedState::ZERO;
let mut date_unrealized_state = date_price.map(|_| UnrealizedState::ZERO);
let update_state =
|price: Dollars, current_price: Dollars, sats: Sats, state: &mut UnrealizedState| {
match price.cmp(&current_price) {
Ordering::Less => {
state.supply_in_profit += sats;
if price > Dollars::ZERO && current_price > Dollars::ZERO {
let diff = current_price.checked_sub(price).unwrap();
// Add back once in a while to verify, but generally not needed
// if diff <= Dollars::ZERO {
// dbg!(price, current_price, diff, sats);
// panic!();
// }
state.unrealized_profit += diff * sats;
}
}
Ordering::Greater => {
state.supply_in_loss += sats;
if price > Dollars::ZERO && current_price > Dollars::ZERO {
let diff = price.checked_sub(current_price).unwrap();
// Add back once in a while to verify, but generally not needed
// if diff <= Dollars::ZERO {
// dbg!(price, current_price, diff, sats);
// panic!();
// }
state.unrealized_loss += diff * sats;
}
}
Ordering::Equal => {
state.supply_breakeven += sats;
}
}
};
self.price_to_amount
.as_ref()
.unwrap()
.iter()
.for_each(|(&price, &sats)| {
update_state(price, height_price, sats, &mut height_unrealized_state);
if let Some(date_price) = date_price {
update_state(
price,
date_price,
sats,
date_unrealized_state.as_mut().unwrap(),
)
}
});
(height_unrealized_state, date_unrealized_state)
}
pub fn commit(&mut self, height: Height) -> Result<()> {
if let Some(price_to_amount) = self.price_to_amount.as_mut() {
price_to_amount.flush(height)?;
}
Ok(())
}
}
@@ -0,0 +1,7 @@
mod address;
mod common;
mod utxo;
pub use address::*;
pub use common::*;
pub use utxo::*;
@@ -0,0 +1,19 @@
use std::path::Path;
use brk_error::Result;
use derive_deref::{Deref, DerefMut};
use super::CohortState;
#[derive(Clone, Deref, DerefMut)]
pub struct UTXOCohortState(CohortState);
impl UTXOCohortState {
pub fn new(path: &Path, name: &str, compute_dollars: bool) -> Self {
Self(CohortState::new(path, name, compute_dollars))
}
pub fn reset_price_to_amount_if_needed(&mut self) -> Result<()> {
self.0.reset_price_to_amount_if_needed()
}
}
@@ -1,19 +1,15 @@
mod block;
mod cohort;
mod outputs;
mod realized;
// mod hot;
mod cohorts;
mod price_to_amount;
mod realized;
mod supply;
mod transacted;
mod unrealized;
pub use block::*;
pub use cohort::*;
pub use outputs::*;
pub use realized::*;
pub use unrealized::*;
// pub use hot::*;
pub use cohorts::*;
pub use price_to_amount::*;
pub use realized::*;
pub use supply::*;
pub use transacted::*;
pub use unrealized::*;
@@ -0,0 +1,171 @@
use std::{
collections::BTreeMap,
fs,
path::{Path, PathBuf},
};
use brk_error::{Error, Result};
use brk_structs::{Dollars, Height, Sats};
use derive_deref::{Deref, DerefMut};
use pco::standalone::{simple_decompress, simpler_compress};
use serde::{Deserialize, Serialize};
use zerocopy::{FromBytes, IntoBytes};
use crate::states::SupplyState;
#[derive(Clone, Debug)]
pub struct PriceToAmount {
pathbuf: PathBuf,
state: Option<State>,
}
const STATE_AT_: &str = "state_at_";
const STATE_TO_KEEP: usize = 10;
impl PriceToAmount {
pub fn create(path: &Path, name: &str) -> Self {
Self {
pathbuf: path.join(format!("{name}_price_to_amount")),
state: None,
}
}
pub fn import_at_or_before(&mut self, height: Height) -> Result<Height> {
let files = self.read_dir(None)?;
let (&height, path) = files
.range(..=height)
.next_back()
.ok_or(Error::Str("Not found"))?;
self.state = Some(State::deserialize(&fs::read(path)?)?);
Ok(height)
}
pub fn iter(&self) -> impl Iterator<Item = (&Dollars, &Sats)> {
self.state.as_ref().unwrap().iter()
}
pub fn is_empty(&self) -> bool {
self.state.as_ref().unwrap().is_empty()
}
pub fn first_key_value(&self) -> Option<(&Dollars, &Sats)> {
self.state.as_ref().unwrap().first_key_value()
}
pub fn last_key_value(&self) -> Option<(&Dollars, &Sats)> {
self.state.as_ref().unwrap().last_key_value()
}
pub fn increment(&mut self, price: Dollars, supply_state: &SupplyState) {
*self.state.as_mut().unwrap().entry(price).or_default() += supply_state.value;
}
pub fn decrement(&mut self, price: Dollars, supply_state: &SupplyState) {
if let Some(amount) = self.state.as_mut().unwrap().get_mut(&price) {
*amount -= supply_state.value;
if *amount == Sats::ZERO {
self.state.as_mut().unwrap().remove(&price);
}
} else {
dbg!(price, &self.pathbuf);
unreachable!();
}
}
pub fn init(&mut self) {
self.state.replace(State::default());
}
pub fn clean(&mut self) -> Result<()> {
let _ = fs::remove_dir_all(&self.pathbuf);
fs::create_dir_all(&self.pathbuf)?;
Ok(())
}
fn read_dir(&self, keep_only_before: Option<Height>) -> Result<BTreeMap<Height, PathBuf>> {
Ok(fs::read_dir(&self.pathbuf)?
.filter_map(|entry| {
let path = entry.ok()?.path();
let name = path.file_name()?.to_str()?;
let height_str = name.strip_prefix(STATE_AT_).unwrap_or(name);
if let Ok(h) = height_str.parse::<u32>().map(Height::from) {
if keep_only_before.is_none_or(|height| h < height) {
Some((h, path))
} else {
let _ = fs::remove_file(path);
None
}
} else {
None
}
})
.collect::<BTreeMap<Height, PathBuf>>())
}
pub fn flush(&mut self, height: Height) -> Result<()> {
let files = self.read_dir(Some(height))?;
for (_, path) in files
.iter()
.take(files.len().saturating_sub(STATE_TO_KEEP - 1))
{
fs::remove_file(path)?;
}
fs::write(
self.path_state(height),
self.state.as_ref().unwrap().serialize()?,
)?;
Ok(())
}
fn path_state(&self, height: Height) -> PathBuf {
Self::path_state_(&self.pathbuf, height)
}
fn path_state_(path: &Path, height: Height) -> PathBuf {
path.join(u32::from(height).to_string())
}
}
#[derive(Clone, Default, Debug, Deref, DerefMut, Serialize, Deserialize)]
struct State(BTreeMap<Dollars, Sats>);
const COMPRESSION_LEVEL: usize = 4;
impl State {
fn serialize(&self) -> vecdb::Result<Vec<u8>> {
let keys: Vec<f64> = self.keys().cloned().map(f64::from).collect();
let values: Vec<u64> = self.values().cloned().map(u64::from).collect();
let compressed_keys = simpler_compress(&keys, COMPRESSION_LEVEL)?;
let compressed_values = simpler_compress(&values, COMPRESSION_LEVEL)?;
let mut buffer = Vec::new();
buffer.extend(keys.len().as_bytes());
buffer.extend(compressed_keys.len().as_bytes());
buffer.extend(compressed_keys);
buffer.extend(compressed_values);
Ok(buffer)
}
fn deserialize(data: &[u8]) -> vecdb::Result<Self> {
let entry_count = usize::read_from_bytes(&data[0..8])?;
let keys_len = usize::read_from_bytes(&data[8..16])?;
let keys: Vec<f64> = simple_decompress(&data[16..16 + keys_len])?;
let values: Vec<u64> = simple_decompress(&data[16 + keys_len..])?;
let map: BTreeMap<Dollars, Sats> = keys
.into_iter()
.zip(values)
.map(|(k, v)| (Dollars::from(k), Sats::from(v)))
.collect();
assert_eq!(map.len(), entry_count);
Ok(Self(map))
}
}

Some files were not shown because too many files have changed in this diff Show More