mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-20 15:38:11 -07:00
global: MASSIVE snapshot
This commit is contained in:
@@ -0,0 +1,334 @@
|
||||
use brk_error::Result;
|
||||
use brk_types::{Bitcoin, CheckedSub, Close, Date, DateIndex, Dollars, Sats, StoredF32};
|
||||
use vecdb::{
|
||||
AnyStoredVec, AnyVec, EagerVec, Exit, GenericStoredVec, IterableVec, PcoVec, VecIndex, Version,
|
||||
};
|
||||
|
||||
mod pricing;
|
||||
|
||||
// TODO: Re-export when Phase 3 (Pricing migration) is complete
|
||||
// pub use pricing::{Priced, Pricing, Unpriced};
|
||||
|
||||
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
|
||||
|
||||
pub trait ComputeDCAStackViaLen {
|
||||
fn compute_dca_stack_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
fn compute_dca_stack_via_from(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
from: DateIndex,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl ComputeDCAStackViaLen for EagerVec<PcoVec<DateIndex, Sats>> {
|
||||
fn compute_dca_stack_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset(closes.version())?;
|
||||
|
||||
let index = max_from.to_usize().min(self.len());
|
||||
|
||||
// Initialize prev before the loop to avoid checking on every iteration
|
||||
let mut prev = if index == 0 {
|
||||
Sats::ZERO
|
||||
} else {
|
||||
self.read_at_unwrap_once(index - 1)
|
||||
};
|
||||
|
||||
let mut lookback = closes.create_lookback(index, len, 0);
|
||||
|
||||
closes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index)
|
||||
.try_for_each(|(i, closes)| {
|
||||
let price = *closes;
|
||||
let i_usize = i.to_usize();
|
||||
|
||||
let mut stack = Sats::ZERO;
|
||||
|
||||
if price != Dollars::ZERO {
|
||||
stack = prev + Sats::from(Bitcoin::from(DCA_AMOUNT / price));
|
||||
|
||||
let prev_price =
|
||||
*lookback.get_and_push(i_usize, Close::new(price), Close::default());
|
||||
if prev_price != Dollars::ZERO {
|
||||
stack = stack
|
||||
.checked_sub(Sats::from(Bitcoin::from(DCA_AMOUNT / prev_price)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
|
||||
prev = stack;
|
||||
|
||||
self.truncate_push_at(i, stack)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_dca_stack_via_from(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
from: DateIndex,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset(closes.version())?;
|
||||
|
||||
let from = from.to_usize();
|
||||
let index = max_from.min(DateIndex::from(self.len()));
|
||||
|
||||
// Initialize prev before the loop to avoid checking on every iteration
|
||||
let mut prev = if index.to_usize() == 0 {
|
||||
Sats::ZERO
|
||||
} else {
|
||||
self.read_at_unwrap_once(index.to_usize() - 1)
|
||||
};
|
||||
|
||||
closes
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index.to_usize())
|
||||
.try_for_each(|(i, closes)| {
|
||||
let price = *closes;
|
||||
|
||||
let mut stack = Sats::ZERO;
|
||||
|
||||
if price != Dollars::ZERO && i >= from {
|
||||
stack = prev + Sats::from(Bitcoin::from(DCA_AMOUNT / price));
|
||||
}
|
||||
|
||||
prev = stack;
|
||||
|
||||
self.truncate_push_at(i, stack)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComputeDCAAveragePriceViaLen {
|
||||
fn compute_dca_average_price_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
stacks: &impl IterableVec<DateIndex, Sats>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
|
||||
fn compute_dca_average_price_via_from(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
stacks: &impl IterableVec<DateIndex, Sats>,
|
||||
from: DateIndex,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl ComputeDCAAveragePriceViaLen for EagerVec<PcoVec<DateIndex, Dollars>> {
|
||||
fn compute_dca_average_price_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
stacks: &impl IterableVec<DateIndex, Sats>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset(Version::ONE + stacks.version())?;
|
||||
|
||||
let index = max_from.min(DateIndex::from(self.len()));
|
||||
|
||||
let first_price_date = DateIndex::try_from(Date::new(2010, 7, 12))
|
||||
.unwrap()
|
||||
.to_usize();
|
||||
|
||||
stacks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index.to_usize())
|
||||
.try_for_each(|(i, stack)| {
|
||||
let mut average_price = Dollars::from(f64::NAN);
|
||||
if i > first_price_date {
|
||||
average_price = DCA_AMOUNT
|
||||
* len
|
||||
.min(i.to_usize() + 1)
|
||||
.min(i.checked_sub(first_price_date).unwrap().to_usize() + 1)
|
||||
/ Bitcoin::from(stack);
|
||||
}
|
||||
self.truncate_push_at(i, average_price)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn compute_dca_average_price_via_from(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
stacks: &impl IterableVec<DateIndex, Sats>,
|
||||
from: DateIndex,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset(stacks.version())?;
|
||||
|
||||
let index = max_from.min(DateIndex::from(self.len()));
|
||||
|
||||
let from = from.to_usize();
|
||||
|
||||
stacks
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index.to_usize())
|
||||
.try_for_each(|(i, stack)| {
|
||||
let mut average_price = Dollars::from(f64::NAN);
|
||||
if i >= from {
|
||||
average_price = DCA_AMOUNT * (i.to_usize() + 1 - from) / Bitcoin::from(stack);
|
||||
}
|
||||
self.truncate_push_at(i, average_price)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComputeLumpSumStackViaLen {
|
||||
fn compute_lump_sum_stack_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
lookback_prices: &impl IterableVec<DateIndex, Dollars>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl ComputeLumpSumStackViaLen for EagerVec<PcoVec<DateIndex, Sats>> {
|
||||
/// Compute lump sum stack: sats you would have if you invested (len * DCA_AMOUNT) at the lookback price
|
||||
fn compute_lump_sum_stack_via_len(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl IterableVec<DateIndex, Close<Dollars>>,
|
||||
lookback_prices: &impl IterableVec<DateIndex, Dollars>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset(closes.version())?;
|
||||
|
||||
let index = max_from.to_usize().min(self.len());
|
||||
let total_invested = DCA_AMOUNT * len;
|
||||
|
||||
lookback_prices
|
||||
.iter()
|
||||
.enumerate()
|
||||
.skip(index)
|
||||
.try_for_each(|(i, lookback_price)| {
|
||||
let stack = if lookback_price == Dollars::ZERO {
|
||||
Sats::ZERO
|
||||
} else {
|
||||
Sats::from(Bitcoin::from(total_invested / lookback_price))
|
||||
};
|
||||
|
||||
self.truncate_push_at(i, stack)
|
||||
})?;
|
||||
|
||||
let _lock = exit.lock();
|
||||
self.write()?;
|
||||
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComputeFromBitcoin<I> {
|
||||
fn compute_from_bitcoin(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
bitcoin: &impl IterableVec<I, Bitcoin>,
|
||||
price: &impl IterableVec<I, Close<Dollars>>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<I> ComputeFromBitcoin<I> for EagerVec<PcoVec<I, Dollars>>
|
||||
where
|
||||
I: VecIndex,
|
||||
{
|
||||
fn compute_from_bitcoin(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
bitcoin: &impl IterableVec<I, Bitcoin>,
|
||||
price: &impl IterableVec<I, Close<Dollars>>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.compute_transform2(
|
||||
max_from,
|
||||
bitcoin,
|
||||
price,
|
||||
|(i, bitcoin, price, _)| (i, *price * bitcoin),
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
pub trait ComputeDrawdown<I> {
|
||||
fn compute_drawdown(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
close: &impl IterableVec<I, Close<Dollars>>,
|
||||
ath: &impl IterableVec<I, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>;
|
||||
}
|
||||
|
||||
impl<I> ComputeDrawdown<I> for EagerVec<PcoVec<I, StoredF32>>
|
||||
where
|
||||
I: VecIndex,
|
||||
{
|
||||
fn compute_drawdown(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
close: &impl IterableVec<I, Close<Dollars>>,
|
||||
ath: &impl IterableVec<I, Dollars>,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.compute_transform2(
|
||||
max_from,
|
||||
ath,
|
||||
close,
|
||||
|(i, ath, close, _)| {
|
||||
if ath == Dollars::ZERO {
|
||||
(i, StoredF32::default())
|
||||
} else {
|
||||
let drawdown = StoredF32::from((*ath - **close) / *ath * -100.0);
|
||||
(i, drawdown)
|
||||
}
|
||||
},
|
||||
exit,
|
||||
)?;
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,123 @@
|
||||
//! Compile-time type-state for price-dependent data.
|
||||
//!
|
||||
// TODO: Remove this once Phase 3 (Pricing migration) is complete
|
||||
#![allow(dead_code)]
|
||||
//!
|
||||
//! This module provides the `Pricing` trait which enables compile-time
|
||||
//! differentiation between priced and unpriced data variants. Instead of
|
||||
//! using `Option<T>` for price-dependent fields, structs use `P: Pricing`
|
||||
//! with associated types that are either concrete types (for `Priced`) or
|
||||
//! `()` (for `Unpriced`).
|
||||
//!
|
||||
//! Benefits:
|
||||
//! - LSP/autocomplete visibility: no Options cluttering suggestions
|
||||
//! - Compile-time guarantees: cannot access price data on `Unpriced` variants
|
||||
//! - Zero runtime overhead: `()` is a ZST (zero-sized type)
|
||||
|
||||
use brk_traversable::Traversable;
|
||||
|
||||
/// Type-state trait for price-dependent data.
|
||||
///
|
||||
/// Implements the type-state pattern using associated types:
|
||||
/// - `Priced`: associated types resolve to concrete data types
|
||||
/// - `Unpriced`: associated types resolve to `()`
|
||||
///
|
||||
/// # Associated Types
|
||||
///
|
||||
/// | Type | Usage | Priced | Unpriced |
|
||||
/// |------|-------|--------|----------|
|
||||
/// | `Data` | Computer top-level | `PricingData` | `()` |
|
||||
/// | `PriceRef<'a>` | Function params | `&price::Vecs` | `()` |
|
||||
/// | `ComputedDollarsHeight` | Value wrappers (Height) | `ComputedBlock<Dollars>` | `()` |
|
||||
/// | `ComputedDollarsDateIndex` | Value wrappers (DateIndex) | `ComputedVecsDate<Dollars>` | `()` |
|
||||
/// | `StdDevBandsUsd` | StdDev USD bands | `StdDevBandsUsdData` | `()` |
|
||||
/// | `RatioUsd` | Ratio USD variants | `RatioUsdData` | `()` |
|
||||
/// | `BasePriced` | Base metrics | `BasePricedData` | `()` |
|
||||
/// | `ExtendedPriced` | Extended metrics | `ExtendedPricedData` | `()` |
|
||||
/// | `AdjustedPriced` | Adjusted metrics | `AdjustedPricedData` | `()` |
|
||||
/// | `RelToAllPriced` | Rel-to-all metrics | `RelToAllPricedData` | `()` |
|
||||
pub trait Pricing: 'static + Clone + Send + Sync {
|
||||
// === Top-level ===
|
||||
|
||||
/// Top-level pricing data - PricingData for Priced, () for Unpriced
|
||||
type Data: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Reference to price vecs for import functions
|
||||
type PriceRef<'a>: Copy;
|
||||
|
||||
// === Value wrappers (used in 20+ places) ===
|
||||
|
||||
/// Computed dollars with Height index
|
||||
type ComputedDollarsHeight: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Computed dollars with DateIndex index
|
||||
type ComputedDollarsDateIndex: Clone + Send + Sync + Traversable;
|
||||
|
||||
// === Specialized structs ===
|
||||
|
||||
/// StdDev USD bands (13 fields grouped)
|
||||
type StdDevBandsUsd: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Ratio USD data
|
||||
type RatioUsd: Clone + Send + Sync + Traversable;
|
||||
|
||||
// === Distribution metrics ===
|
||||
|
||||
/// Base-level priced metrics (realized + unrealized)
|
||||
type BasePriced: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Extended-level priced metrics
|
||||
type ExtendedPriced: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Adjusted metrics
|
||||
type AdjustedPriced: Clone + Send + Sync + Traversable;
|
||||
|
||||
/// Dollar-based relative-to-all metrics
|
||||
type RelToAllPriced: Clone + Send + Sync + Traversable;
|
||||
}
|
||||
|
||||
/// Marker type for priced data.
|
||||
///
|
||||
/// When `P = Priced`, all associated types resolve to their concrete
|
||||
/// data types containing price-denominated values.
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct Priced;
|
||||
|
||||
/// Marker type for unpriced data.
|
||||
///
|
||||
/// When `P = Unpriced`, all associated types resolve to `()`,
|
||||
/// effectively removing those fields at compile time with zero overhead.
|
||||
#[derive(Clone, Copy, Default, Debug)]
|
||||
pub struct Unpriced;
|
||||
|
||||
// Note: The actual type implementations for `Priced` and `Unpriced`
|
||||
// will be added in Phase 3 when we migrate the concrete data types.
|
||||
// For now, we provide placeholder implementations using () for all types
|
||||
// to allow incremental migration.
|
||||
|
||||
impl Pricing for Priced {
|
||||
// Placeholder implementations - will be replaced with concrete types in Phase 3
|
||||
type Data = ();
|
||||
type PriceRef<'a> = ();
|
||||
type ComputedDollarsHeight = ();
|
||||
type ComputedDollarsDateIndex = ();
|
||||
type StdDevBandsUsd = ();
|
||||
type RatioUsd = ();
|
||||
type BasePriced = ();
|
||||
type ExtendedPriced = ();
|
||||
type AdjustedPriced = ();
|
||||
type RelToAllPriced = ();
|
||||
}
|
||||
|
||||
impl Pricing for Unpriced {
|
||||
type Data = ();
|
||||
type PriceRef<'a> = ();
|
||||
type ComputedDollarsHeight = ();
|
||||
type ComputedDollarsDateIndex = ();
|
||||
type StdDevBandsUsd = ();
|
||||
type RatioUsd = ();
|
||||
type BasePriced = ();
|
||||
type ExtendedPriced = ();
|
||||
type AdjustedPriced = ();
|
||||
type RelToAllPriced = ();
|
||||
}
|
||||
Reference in New Issue
Block a user