mirror of
https://github.com/bitcoinresearchkit/brk.git
synced 2026-07-27 18:58:10 -07:00
global: returns (lump sum vs dca)
This commit is contained in:
@@ -108,7 +108,7 @@ pub fn run(config: RunConfig) -> color_eyre::Result<()> {
|
||||
};
|
||||
|
||||
thread::Builder::new()
|
||||
.stack_size(64 * 1024 * 1024)
|
||||
.stack_size(128 * 1024 * 1024)
|
||||
.spawn(f)?
|
||||
.join()
|
||||
.unwrap()
|
||||
|
||||
@@ -333,7 +333,7 @@ impl ComputedRatioVecsFromDateIndex {
|
||||
if price == Dollars::ZERO {
|
||||
(i, StoredF32::from(1.0))
|
||||
} else {
|
||||
(i, *close / price)
|
||||
(i, StoredF32::from(*close / price))
|
||||
}
|
||||
},
|
||||
exit,
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -3,7 +3,7 @@ use std::ops::{Add, Div, Mul};
|
||||
use serde::Serialize;
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::Sats;
|
||||
use super::{Sats, StoredF64};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -53,6 +53,12 @@ impl From<f64> for Bitcoin {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredF64> for Bitcoin {
|
||||
fn from(value: StoredF64) -> Self {
|
||||
Self(*value)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<Bitcoin> for f64 {
|
||||
fn from(value: Bitcoin) -> Self {
|
||||
value.0
|
||||
|
||||
@@ -81,3 +81,10 @@ impl Mul<Cents> for Cents {
|
||||
Self(self.0 * rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<usize> for Cents {
|
||||
type Output = Cents;
|
||||
fn mul(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 * rhs as u64)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -7,7 +7,7 @@ use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use super::{Bitcoin, Cents, Close, Sats, StoredF32};
|
||||
use super::{Bitcoin, Cents, Close, Sats, StoredF32, StoredF64};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -27,6 +27,10 @@ pub struct Dollars(f64);
|
||||
|
||||
impl Dollars {
|
||||
pub const ZERO: Self = Self(0.0);
|
||||
|
||||
pub const fn mint(dollars: f64) -> Self {
|
||||
Self(dollars)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<f32> for Dollars {
|
||||
@@ -79,9 +83,23 @@ impl Add for Dollars {
|
||||
}
|
||||
|
||||
impl Div<Dollars> for Dollars {
|
||||
type Output = StoredF32;
|
||||
type Output = StoredF64;
|
||||
fn div(self, rhs: Dollars) -> Self::Output {
|
||||
StoredF32::from((self.0 / rhs.0) as f32)
|
||||
StoredF64::from(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Close<Dollars>> for Dollars {
|
||||
type Output = StoredF64;
|
||||
fn div(self, rhs: Close<Dollars>) -> Self::Output {
|
||||
StoredF64::from(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Dollars> for Close<Dollars> {
|
||||
type Output = StoredF64;
|
||||
fn div(self, rhs: Dollars) -> Self::Output {
|
||||
StoredF64::from(self.0 / rhs.0)
|
||||
}
|
||||
}
|
||||
|
||||
@@ -92,6 +110,13 @@ impl Div<usize> for Dollars {
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<Bitcoin> for Dollars {
|
||||
type Output = Self;
|
||||
fn div(self, rhs: Bitcoin) -> Self::Output {
|
||||
Self(f64::from(self) / f64::from(rhs))
|
||||
}
|
||||
}
|
||||
|
||||
impl Eq for Dollars {}
|
||||
|
||||
#[allow(clippy::derive_ord_xor_partial_ord)]
|
||||
@@ -121,6 +146,13 @@ impl Mul<StoredF32> for Dollars {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<usize> for Dollars {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: usize) -> Self::Output {
|
||||
Self::from(Cents::from(self) * rhs)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<u128> for Dollars {
|
||||
fn from(value: u128) -> Self {
|
||||
Self::from(Cents::from(value))
|
||||
|
||||
@@ -6,7 +6,7 @@ use zerocopy_derive::{FromBytes, Immutable, IntoBytes, KnownLayout};
|
||||
|
||||
use crate::CheckedSub;
|
||||
|
||||
use super::Dollars;
|
||||
use super::{Dollars, StoredF64};
|
||||
|
||||
#[derive(
|
||||
Debug,
|
||||
@@ -36,6 +36,12 @@ impl From<f64> for StoredF32 {
|
||||
}
|
||||
}
|
||||
|
||||
impl From<StoredF64> for StoredF32 {
|
||||
fn from(value: StoredF64) -> Self {
|
||||
Self(*value as f32)
|
||||
}
|
||||
}
|
||||
|
||||
impl From<usize> for StoredF32 {
|
||||
fn from(value: usize) -> Self {
|
||||
Self(value as f32)
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
use std::ops::{Add, Div};
|
||||
use std::ops::{Add, Div, Mul};
|
||||
|
||||
use derive_deref::Deref;
|
||||
use serde::Serialize;
|
||||
@@ -40,6 +40,13 @@ impl CheckedSub<StoredF64> for StoredF64 {
|
||||
}
|
||||
}
|
||||
|
||||
impl Mul<usize> for StoredF64 {
|
||||
type Output = Self;
|
||||
fn mul(self, rhs: usize) -> Self::Output {
|
||||
Self(self.0 * rhs as f64)
|
||||
}
|
||||
}
|
||||
|
||||
impl Div<usize> for StoredF64 {
|
||||
type Output = Self;
|
||||
fn div(self, rhs: usize) -> Self::Output {
|
||||
@@ -68,3 +75,9 @@ impl Ord for StoredF64 {
|
||||
self.0.partial_cmp(&other.0).unwrap()
|
||||
}
|
||||
}
|
||||
|
||||
impl CheckedSub<usize> for StoredF64 {
|
||||
fn checked_sub(self, rhs: usize) -> Option<Self> {
|
||||
Some(Self(self.0 - rhs as f64))
|
||||
}
|
||||
}
|
||||
|
||||
@@ -3,12 +3,14 @@ use std::{
|
||||
cmp::Ordering,
|
||||
f32,
|
||||
fmt::Debug,
|
||||
ops::{Add, Div},
|
||||
ops::{Add, Div, Mul},
|
||||
path::{Path, PathBuf},
|
||||
time::Duration,
|
||||
};
|
||||
|
||||
use brk_core::{Bitcoin, CheckedSub, Close, Dollars, Height, Sats, StoredUsize, TxIndex};
|
||||
use brk_core::{
|
||||
Bitcoin, CheckedSub, Close, DateIndex, Dollars, Height, Sats, StoredUsize, TxIndex,
|
||||
};
|
||||
use brk_exit::Exit;
|
||||
use log::info;
|
||||
|
||||
@@ -21,6 +23,7 @@ use crate::{
|
||||
const ONE_KIB: usize = 1024;
|
||||
const ONE_MIB: usize = ONE_KIB * ONE_KIB;
|
||||
const MAX_CACHE_SIZE: usize = 210 * ONE_MIB;
|
||||
const DCA_AMOUNT: Dollars = Dollars::mint(100.0);
|
||||
|
||||
#[derive(Debug, Clone)]
|
||||
pub struct EagerVec<I, T> {
|
||||
@@ -176,6 +179,88 @@ where
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
|
||||
pub fn compute_divide<T2, T3, T4>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
divided: &impl AnyIterableVec<I, T2>,
|
||||
divider: &impl AnyIterableVec<I, T3>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
T2: StoredType + Div<T3, Output = T4>,
|
||||
T3: StoredType,
|
||||
T4: Mul<usize, Output = T4> + CheckedSub<usize>,
|
||||
T: From<T4>,
|
||||
{
|
||||
self.compute_divide_(max_from, divided, divider, exit, false, false)
|
||||
}
|
||||
|
||||
pub fn compute_percentage<T2, T3, T4>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
divided: &impl AnyIterableVec<I, T2>,
|
||||
divider: &impl AnyIterableVec<I, T3>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
T2: StoredType + Div<T3, Output = T4>,
|
||||
T3: StoredType,
|
||||
T4: Mul<usize, Output = T4> + CheckedSub<usize>,
|
||||
T: From<T4>,
|
||||
{
|
||||
self.compute_divide_(max_from, divided, divider, exit, true, false)
|
||||
}
|
||||
|
||||
pub fn compute_percentage_difference<T2, T3, T4>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
divided: &impl AnyIterableVec<I, T2>,
|
||||
divider: &impl AnyIterableVec<I, T3>,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
T2: StoredType + Div<T3, Output = T4>,
|
||||
T3: StoredType,
|
||||
T4: Mul<usize, Output = T4> + CheckedSub<usize>,
|
||||
T: From<T4>,
|
||||
{
|
||||
self.compute_divide_(max_from, divided, divider, exit, true, true)
|
||||
}
|
||||
|
||||
pub fn compute_divide_<T2, T3, T4>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
divided: &impl AnyIterableVec<I, T2>,
|
||||
divider: &impl AnyIterableVec<I, T3>,
|
||||
exit: &Exit,
|
||||
as_percentage: bool,
|
||||
as_difference: bool,
|
||||
) -> Result<()>
|
||||
where
|
||||
T2: StoredType + Div<T3, Output = T4>,
|
||||
T3: StoredType,
|
||||
T4: Mul<usize, Output = T4> + CheckedSub<usize>,
|
||||
T: From<T4>,
|
||||
{
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ZERO + self.inner.version() + divided.version() + divider.version(),
|
||||
)?;
|
||||
|
||||
let index = max_from.min(I::from(self.len()));
|
||||
let multiplier = if as_percentage { 100 } else { 1 };
|
||||
let subtract = if as_difference { multiplier } else { 0 };
|
||||
|
||||
let mut divider_iter = divider.iter();
|
||||
divided.iter_at(index).try_for_each(|(i, divided)| {
|
||||
let v = (divided.into_inner() / divider_iter.unwrap_get_inner(i) * multiplier)
|
||||
.checked_sub(subtract)
|
||||
.unwrap();
|
||||
self.forced_push_at(i, T::from(v), exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
|
||||
pub fn compute_inverse_more_to_less(
|
||||
&mut self,
|
||||
max_from: T,
|
||||
@@ -474,6 +559,186 @@ where
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
|
||||
pub fn compute_previous_value<T2>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
source: &impl AnyIterableVec<I, T2>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
I: CheckedSub,
|
||||
T2: StoredType + Default,
|
||||
f32: From<T2>,
|
||||
T: From<f32>,
|
||||
{
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ZERO + self.inner.version() + source.version(),
|
||||
)?;
|
||||
|
||||
let index = max_from.min(I::from(self.len()));
|
||||
let mut source_iter = source.iter();
|
||||
(index.to_usize()?..source.len()).try_for_each(|i| {
|
||||
let i = I::from(i);
|
||||
|
||||
let previous_value = i
|
||||
.checked_sub(I::from(len))
|
||||
.map(|prev_i| f32::from(source_iter.unwrap_get_inner(prev_i)))
|
||||
.unwrap_or(f32::NAN);
|
||||
|
||||
self.forced_push_at(i, T::from(previous_value), exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
|
||||
pub fn compute_percentage_change<T2>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
source: &impl AnyIterableVec<I, T2>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
I: CheckedSub,
|
||||
T2: StoredType + Default,
|
||||
f32: From<T2>,
|
||||
T: From<f32>,
|
||||
{
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ZERO + self.inner.version() + source.version(),
|
||||
)?;
|
||||
|
||||
let index = max_from.min(I::from(self.len()));
|
||||
let mut source_iter = source.iter();
|
||||
source.iter_at(index).try_for_each(|(i, b)| {
|
||||
let previous_value = f32::from(
|
||||
i.checked_sub(I::from(len))
|
||||
.map(|prev_i| source_iter.unwrap_get_inner(prev_i))
|
||||
.unwrap_or_default(),
|
||||
);
|
||||
|
||||
let last_value = f32::from(b.into_inner());
|
||||
|
||||
let percentage_change = ((last_value / previous_value) - 1.0) * 100.0;
|
||||
|
||||
self.forced_push_at(i, T::from(percentage_change), exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
|
||||
pub fn compute_cagr<T2>(
|
||||
&mut self,
|
||||
max_from: I,
|
||||
percentage_returns: &impl AnyIterableVec<I, T2>,
|
||||
days: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()>
|
||||
where
|
||||
I: CheckedSub,
|
||||
T2: StoredType + Default,
|
||||
f32: From<T2>,
|
||||
T: From<f32>,
|
||||
{
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ZERO + self.inner.version() + percentage_returns.version(),
|
||||
)?;
|
||||
|
||||
if days % 365 != 0 {
|
||||
panic!("bad days");
|
||||
}
|
||||
|
||||
let years = days / 365;
|
||||
let index = max_from.min(I::from(self.len()));
|
||||
percentage_returns
|
||||
.iter_at(index)
|
||||
.try_for_each(|(i, percentage)| {
|
||||
let percentage = percentage.into_inner();
|
||||
|
||||
let cagr = (((f32::from(percentage) / 100.0 + 1.0).powf(1.0 / years as f32)) - 1.0)
|
||||
* 100.0;
|
||||
|
||||
self.forced_push_at(i, T::from(cagr), exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
}
|
||||
|
||||
impl EagerVec<DateIndex, Sats> {
|
||||
pub fn compute_dca_stack(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
closes: &impl AnyIterableVec<DateIndex, Close<Dollars>>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ZERO + self.inner.version() + closes.version(),
|
||||
)?;
|
||||
|
||||
let mut other_iter = closes.iter();
|
||||
let mut prev = None;
|
||||
|
||||
let index = max_from.min(DateIndex::from(self.len()));
|
||||
closes.iter_at(index).try_for_each(|(i, closes)| {
|
||||
let price = *closes.into_inner();
|
||||
let i_usize = i.unwrap_to_usize();
|
||||
if prev.is_none() {
|
||||
if i_usize == 0 {
|
||||
prev.replace(Sats::ZERO);
|
||||
} else {
|
||||
prev.replace(self.into_iter().unwrap_get_inner_(i_usize - 1));
|
||||
}
|
||||
}
|
||||
|
||||
let mut stack = Sats::ZERO;
|
||||
|
||||
if price != Dollars::ZERO {
|
||||
stack = prev.unwrap() + Sats::from(Bitcoin::from(DCA_AMOUNT / price));
|
||||
if i_usize >= len {
|
||||
let prev_price = *other_iter.unwrap_get_inner_(i_usize - len);
|
||||
if prev_price != Dollars::ZERO {
|
||||
stack = stack
|
||||
.checked_sub(Sats::from(Bitcoin::from(DCA_AMOUNT / prev_price)))
|
||||
.unwrap();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
prev.replace(stack);
|
||||
|
||||
self.forced_push_at(i, stack, exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
}
|
||||
|
||||
impl EagerVec<DateIndex, Dollars> {
|
||||
pub fn compute_dca_avg_price(
|
||||
&mut self,
|
||||
max_from: DateIndex,
|
||||
stacks: &impl AnyIterableVec<DateIndex, Sats>,
|
||||
len: usize,
|
||||
exit: &Exit,
|
||||
) -> Result<()> {
|
||||
self.validate_computed_version_or_reset_file(
|
||||
Version::ONE + self.inner.version() + stacks.version(),
|
||||
)?;
|
||||
|
||||
let index = max_from.min(DateIndex::from(self.len()));
|
||||
|
||||
stacks.iter_at(index).try_for_each(|(i, stack)| {
|
||||
let stack = stack.into_inner();
|
||||
let avg_price = DCA_AMOUNT * len.min(i.unwrap_to_usize() + 1) / Bitcoin::from(stack);
|
||||
self.forced_push_at(i, avg_price, exit)
|
||||
})?;
|
||||
|
||||
self.safe_flush(exit)
|
||||
}
|
||||
}
|
||||
|
||||
impl<I> EagerVec<I, Bitcoin>
|
||||
|
||||
Reference in New Issue
Block a user