brk-client 0.1.1__tar.gz → 0.1.2__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -1,6 +1,6 @@
1
1
  Metadata-Version: 2.4
2
2
  Name: brk-client
3
- Version: 0.1.1
3
+ Version: 0.1.2
4
4
  Summary: Python client for the Bitcoin Research Kit
5
5
  Project-URL: Homepage, https://bitcoinresearchkit.org
6
6
  Project-URL: Repository, https://github.com/bitcoinresearchkit/brk
@@ -3,11 +3,16 @@
3
3
 
4
4
  from __future__ import annotations
5
5
  from dataclasses import dataclass
6
- from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload
6
+ from typing import TypeVar, Generic, Any, Optional, List, Literal, TypedDict, Union, Protocol, overload, Iterator, Tuple, TYPE_CHECKING
7
7
  from http.client import HTTPSConnection, HTTPConnection
8
8
  from urllib.parse import urlparse
9
+ from datetime import date, timedelta
9
10
  import json
10
11
 
12
+ if TYPE_CHECKING:
13
+ import pandas as pd # type: ignore[import-not-found]
14
+ import polars as pl # type: ignore[import-not-found]
15
+
11
16
  T = TypeVar('T')
12
17
 
13
18
  # Type definitions
@@ -1043,16 +1048,114 @@ def _p(prefix: str, acc: str) -> str:
1043
1048
  return f"{prefix}_{acc}" if acc else prefix
1044
1049
 
1045
1050
 
1051
+ # Date conversion constants
1052
+ _GENESIS = date(2009, 1, 3) # dateindex 0, weekindex 0
1053
+ _DAY_ONE = date(2009, 1, 9) # dateindex 1 (6 day gap after genesis)
1054
+ _DATE_INDEXES = frozenset(['dateindex', 'weekindex', 'monthindex', 'yearindex', 'quarterindex', 'semesterindex', 'decadeindex'])
1055
+
1056
+ def is_date_index(index: str) -> bool:
1057
+ """Check if an index type is date-based."""
1058
+ return index in _DATE_INDEXES
1059
+
1060
+ def index_to_date(index: str, i: int) -> date:
1061
+ """Convert an index value to a date for date-based indexes."""
1062
+ if index == 'dateindex':
1063
+ return _GENESIS if i == 0 else _DAY_ONE + timedelta(days=i - 1)
1064
+ elif index == 'weekindex':
1065
+ return _GENESIS + timedelta(weeks=i)
1066
+ elif index == 'monthindex':
1067
+ return date(2009 + i // 12, i % 12 + 1, 1)
1068
+ elif index == 'yearindex':
1069
+ return date(2009 + i, 1, 1)
1070
+ elif index == 'quarterindex':
1071
+ m = i * 3
1072
+ return date(2009 + m // 12, m % 12 + 1, 1)
1073
+ elif index == 'semesterindex':
1074
+ m = i * 6
1075
+ return date(2009 + m // 12, m % 12 + 1, 1)
1076
+ elif index == 'decadeindex':
1077
+ return date(2009 + i * 10, 1, 1)
1078
+ else:
1079
+ raise ValueError(f"{index} is not a date-based index")
1080
+
1081
+
1046
1082
  @dataclass
1047
1083
  class MetricData(Generic[T]):
1048
1084
  """Metric data with range information."""
1049
1085
  version: int
1086
+ index: Index
1050
1087
  total: int
1051
1088
  start: int
1052
1089
  end: int
1053
1090
  stamp: str
1054
1091
  data: List[T]
1055
1092
 
1093
+ def dates(self) -> List[date]:
1094
+ """Convert index range to dates. Only works for date-based indexes."""
1095
+ return [index_to_date(self.index, i) for i in range(self.start, self.end)]
1096
+
1097
+ def indexes(self) -> List[int]:
1098
+ """Get index range as list."""
1099
+ return list(range(self.start, self.end))
1100
+
1101
+ def to_date_dict(self) -> dict[date, T]:
1102
+ """Return data as {date: value} dict. Only works for date-based indexes."""
1103
+ return dict(zip(self.dates(), self.data))
1104
+
1105
+ def to_index_dict(self) -> dict[int, T]:
1106
+ """Return data as {index: value} dict."""
1107
+ return dict(zip(range(self.start, self.end), self.data))
1108
+
1109
+ def date_items(self) -> List[Tuple[date, T]]:
1110
+ """Return data as [(date, value), ...] pairs. Only works for date-based indexes."""
1111
+ return list(zip(self.dates(), self.data))
1112
+
1113
+ def index_items(self) -> List[Tuple[int, T]]:
1114
+ """Return data as [(index, value), ...] pairs."""
1115
+ return list(zip(range(self.start, self.end), self.data))
1116
+
1117
+ def iter(self) -> Iterator[Tuple[int, T]]:
1118
+ """Iterate over (index, value) pairs."""
1119
+ return iter(zip(range(self.start, self.end), self.data))
1120
+
1121
+ def iter_dates(self) -> Iterator[Tuple[date, T]]:
1122
+ """Iterate over (date, value) pairs. Date-based indexes only."""
1123
+ return iter(zip(self.dates(), self.data))
1124
+
1125
+ def __iter__(self) -> Iterator[Tuple[int, T]]:
1126
+ """Default iteration over (index, value) pairs."""
1127
+ return self.iter()
1128
+
1129
+ def to_polars(self, with_dates: bool = True) -> pl.DataFrame:
1130
+ """Convert to Polars DataFrame. Requires polars to be installed.
1131
+
1132
+ Returns a DataFrame with columns:
1133
+ - 'date' (date) and 'value' (T) if with_dates=True and index is date-based
1134
+ - 'index' (int) and 'value' (T) otherwise
1135
+ """
1136
+ try:
1137
+ import polars as pl # type: ignore[import-not-found]
1138
+ except ImportError:
1139
+ raise ImportError("polars is required: pip install polars")
1140
+ if with_dates and self.index in _DATE_INDEXES:
1141
+ return pl.DataFrame({"date": self.dates(), "value": self.data})
1142
+ return pl.DataFrame({"index": list(range(self.start, self.end)), "value": self.data})
1143
+
1144
+ def to_pandas(self, with_dates: bool = True) -> pd.DataFrame:
1145
+ """Convert to Pandas DataFrame. Requires pandas to be installed.
1146
+
1147
+ Returns a DataFrame with columns:
1148
+ - 'date' (date) and 'value' (T) if with_dates=True and index is date-based
1149
+ - 'index' (int) and 'value' (T) otherwise
1150
+ """
1151
+ try:
1152
+ import pandas as pd # type: ignore[import-not-found]
1153
+ except ImportError:
1154
+ raise ImportError("pandas is required: pip install pandas")
1155
+ if with_dates and self.index in _DATE_INDEXES:
1156
+ return pd.DataFrame({"date": self.dates(), "value": self.data})
1157
+ return pd.DataFrame({"index": list(range(self.start, self.end)), "value": self.data})
1158
+
1056
1159
 
1057
1160
  # Type alias for non-generic usage
1058
1161
  AnyMetricData = MetricData[Any]
@@ -1089,9 +1192,8 @@ class _EndpointConfig:
1089
1192
  p = self.path()
1090
1193
  return f"{p}?{query}" if query else p
1091
1194
 
1092
- def get_json(self) -> MetricData:
1093
- data = self.client.get_json(self._build_path())
1094
- return MetricData(**data)
1195
+ def get_metric(self) -> MetricData:
1196
+ return MetricData(**self.client.get_json(self._build_path()))
1095
1197
 
1096
1198
  def get_csv(self) -> str:
1097
1199
  return self.client.get_text(self._build_path(format='csv'))
@@ -1105,7 +1207,7 @@ class RangeBuilder(Generic[T]):
1105
1207
 
1106
1208
  def fetch(self) -> MetricData[T]:
1107
1209
  """Fetch the range as parsed JSON."""
1108
- return self._config.get_json()
1210
+ return self._config.get_metric()
1109
1211
 
1110
1212
  def fetch_csv(self) -> str:
1111
1213
  """Fetch the range as CSV string."""
@@ -1120,7 +1222,7 @@ class SingleItemBuilder(Generic[T]):
1120
1222
 
1121
1223
  def fetch(self) -> MetricData[T]:
1122
1224
  """Fetch the single item."""
1123
- return self._config.get_json()
1225
+ return self._config.get_metric()
1124
1226
 
1125
1227
  def fetch_csv(self) -> str:
1126
1228
  """Fetch as CSV."""
@@ -1143,7 +1245,7 @@ class SkippedBuilder(Generic[T]):
1143
1245
 
1144
1246
  def fetch(self) -> MetricData[T]:
1145
1247
  """Fetch from skipped position to end."""
1146
- return self._config.get_json()
1248
+ return self._config.get_metric()
1147
1249
 
1148
1250
  def fetch_csv(self) -> str:
1149
1251
  """Fetch as CSV."""
@@ -1227,7 +1329,7 @@ class MetricEndpointBuilder(Generic[T]):
1227
1329
 
1228
1330
  def fetch(self) -> MetricData[T]:
1229
1331
  """Fetch all data as parsed JSON."""
1230
- return self._config.get_json()
1332
+ return self._config.get_metric()
1231
1333
 
1232
1334
  def fetch_csv(self) -> str:
1233
1335
  """Fetch all data as CSV string."""
@@ -2047,18 +2149,18 @@ class ClassDaysInLossPattern(Generic[T]):
2047
2149
 
2048
2150
  def __init__(self, client: BrkClientBase, acc: str):
2049
2151
  """Create pattern node with accumulated metric name."""
2050
- self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_days_in_profit'))
2051
- self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_days_in_profit'))
2052
- self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_days_in_profit'))
2053
- self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_days_in_profit'))
2054
- self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_days_in_profit'))
2055
- self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_days_in_profit'))
2056
- self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_days_in_profit'))
2057
- self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_days_in_profit'))
2058
- self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_days_in_profit'))
2059
- self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_days_in_profit'))
2060
- self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_days_in_profit'))
2061
- self._2026: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2026_days_in_profit'))
2152
+ self._2015: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2015_returns'))
2153
+ self._2016: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2016_returns'))
2154
+ self._2017: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2017_returns'))
2155
+ self._2018: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2018_returns'))
2156
+ self._2019: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2019_returns'))
2157
+ self._2020: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2020_returns'))
2158
+ self._2021: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2021_returns'))
2159
+ self._2022: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2022_returns'))
2160
+ self._2023: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2023_returns'))
2161
+ self._2024: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2024_returns'))
2162
+ self._2025: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2025_returns'))
2163
+ self._2026: MetricPattern4[T] = MetricPattern4(client, _m(acc, '2026_returns'))
2062
2164
 
2063
2165
  class BitcoinPattern:
2064
2166
  """Pattern struct for repeated tree structure."""
@@ -2094,22 +2196,6 @@ class DollarsPattern(Generic[T]):
2094
2196
  self.pct90: MetricPattern6[T] = MetricPattern6(client, _m(acc, 'pct90'))
2095
2197
  self.sum: MetricPattern2[T] = MetricPattern2(client, _m(acc, 'sum'))
2096
2198
 
2097
- class RelativePattern2:
2098
- """Pattern struct for repeated tree structure."""
2099
-
2100
- def __init__(self, client: BrkClientBase, acc: str):
2101
- """Create pattern node with accumulated metric name."""
2102
- self.neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap'))
2103
- self.neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl'))
2104
- self.net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap'))
2105
- self.net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl'))
2106
- self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply'))
2107
- self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply'))
2108
- self.unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap'))
2109
- self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl'))
2110
- self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap'))
2111
- self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl'))
2112
-
2113
2199
  class RelativePattern:
2114
2200
  """Pattern struct for repeated tree structure."""
2115
2201
 
@@ -2126,6 +2212,22 @@ class RelativePattern:
2126
2212
  self.unrealized_loss_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_market_cap'))
2127
2213
  self.unrealized_profit_rel_to_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_market_cap'))
2128
2214
 
2215
+ class RelativePattern2:
2216
+ """Pattern struct for repeated tree structure."""
2217
+
2218
+ def __init__(self, client: BrkClientBase, acc: str):
2219
+ """Create pattern node with accumulated metric name."""
2220
+ self.neg_unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_market_cap'))
2221
+ self.neg_unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss_rel_to_own_total_unrealized_pnl'))
2222
+ self.net_unrealized_pnl_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_market_cap'))
2223
+ self.net_unrealized_pnl_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl_rel_to_own_total_unrealized_pnl'))
2224
+ self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_loss_rel_to_own_supply'))
2225
+ self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'supply_in_profit_rel_to_own_supply'))
2226
+ self.unrealized_loss_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_market_cap'))
2227
+ self.unrealized_loss_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_loss_rel_to_own_total_unrealized_pnl'))
2228
+ self.unrealized_profit_rel_to_own_market_cap: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_market_cap'))
2229
+ self.unrealized_profit_rel_to_own_total_unrealized_pnl: MetricPattern1[StoredF32] = MetricPattern1(client, _m(acc, 'unrealized_profit_rel_to_own_total_unrealized_pnl'))
2230
+
2129
2231
  class CountPattern2(Generic[T]):
2130
2232
  """Pattern struct for repeated tree structure."""
2131
2233
 
@@ -2201,6 +2303,19 @@ class _0satsPattern:
2201
2303
  self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply'))
2202
2304
  self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc)
2203
2305
 
2306
+ class PeriodCagrPattern:
2307
+ """Pattern struct for repeated tree structure."""
2308
+
2309
+ def __init__(self, client: BrkClientBase, acc: str):
2310
+ """Create pattern node with accumulated metric name."""
2311
+ self._10y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('10y', acc))
2312
+ self._2y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('2y', acc))
2313
+ self._3y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('3y', acc))
2314
+ self._4y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('4y', acc))
2315
+ self._5y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('5y', acc))
2316
+ self._6y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('6y', acc))
2317
+ self._8y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('8y', acc))
2318
+
2204
2319
  class _100btcPattern:
2205
2320
  """Pattern struct for repeated tree structure."""
2206
2321
 
@@ -2240,32 +2355,6 @@ class _10yPattern:
2240
2355
  self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply'))
2241
2356
  self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc)
2242
2357
 
2243
- class UnrealizedPattern:
2244
- """Pattern struct for repeated tree structure."""
2245
-
2246
- def __init__(self, client: BrkClientBase, acc: str):
2247
- """Create pattern node with accumulated metric name."""
2248
- self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss'))
2249
- self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl'))
2250
- self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss'))
2251
- self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit'))
2252
- self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl'))
2253
- self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss'))
2254
- self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit'))
2255
-
2256
- class PeriodCagrPattern:
2257
- """Pattern struct for repeated tree structure."""
2258
-
2259
- def __init__(self, client: BrkClientBase, acc: str):
2260
- """Create pattern node with accumulated metric name."""
2261
- self._10y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('10y', acc))
2262
- self._2y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('2y', acc))
2263
- self._3y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('3y', acc))
2264
- self._4y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('4y', acc))
2265
- self._5y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('5y', acc))
2266
- self._6y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('6y', acc))
2267
- self._8y: MetricPattern4[StoredF32] = MetricPattern4(client, _p('8y', acc))
2268
-
2269
2358
  class _10yTo12yPattern:
2270
2359
  """Pattern struct for repeated tree structure."""
2271
2360
 
@@ -2279,6 +2368,19 @@ class _10yTo12yPattern:
2279
2368
  self.supply: SupplyPattern2 = SupplyPattern2(client, _m(acc, 'supply'))
2280
2369
  self.unrealized: UnrealizedPattern = UnrealizedPattern(client, acc)
2281
2370
 
2371
+ class UnrealizedPattern:
2372
+ """Pattern struct for repeated tree structure."""
2373
+
2374
+ def __init__(self, client: BrkClientBase, acc: str):
2375
+ """Create pattern node with accumulated metric name."""
2376
+ self.neg_unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'neg_unrealized_loss'))
2377
+ self.net_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'net_unrealized_pnl'))
2378
+ self.supply_in_loss: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_loss'))
2379
+ self.supply_in_profit: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'supply_in_profit'))
2380
+ self.total_unrealized_pnl: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'total_unrealized_pnl'))
2381
+ self.unrealized_loss: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_loss'))
2382
+ self.unrealized_profit: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'unrealized_profit'))
2383
+
2282
2384
  class AllPattern:
2283
2385
  """Pattern struct for repeated tree structure."""
2284
2386
 
@@ -2312,23 +2414,14 @@ class SplitPattern2(Generic[T]):
2312
2414
  self.low: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'low'))
2313
2415
  self.open: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'open'))
2314
2416
 
2315
- class UnclaimedRewardsPattern:
2316
- """Pattern struct for repeated tree structure."""
2317
-
2318
- def __init__(self, client: BrkClientBase, acc: str):
2319
- """Create pattern node with accumulated metric name."""
2320
- self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc'))
2321
- self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd'))
2322
- self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc)
2323
-
2324
- class _2015Pattern:
2417
+ class SegwitAdoptionPattern:
2325
2418
  """Pattern struct for repeated tree structure."""
2326
2419
 
2327
2420
  def __init__(self, client: BrkClientBase, acc: str):
2328
2421
  """Create pattern node with accumulated metric name."""
2329
- self.bitcoin: MetricPattern4[Bitcoin] = MetricPattern4(client, _m(acc, 'btc'))
2330
- self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd'))
2331
- self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc)
2422
+ self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc)
2423
+ self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative'))
2424
+ self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum'))
2332
2425
 
2333
2426
  class ActiveSupplyPattern:
2334
2427
  """Pattern struct for repeated tree structure."""
@@ -2339,32 +2432,32 @@ class ActiveSupplyPattern:
2339
2432
  self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, _m(acc, 'usd'))
2340
2433
  self.sats: MetricPattern1[Sats] = MetricPattern1(client, acc)
2341
2434
 
2342
- class CoinbasePattern:
2435
+ class _2015Pattern:
2343
2436
  """Pattern struct for repeated tree structure."""
2344
2437
 
2345
2438
  def __init__(self, client: BrkClientBase, acc: str):
2346
2439
  """Create pattern node with accumulated metric name."""
2347
- self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc'))
2348
- self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd'))
2349
- self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc)
2440
+ self.bitcoin: MetricPattern4[Bitcoin] = MetricPattern4(client, _m(acc, 'btc'))
2441
+ self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, _m(acc, 'usd'))
2442
+ self.sats: MetricPattern4[Sats] = MetricPattern4(client, acc)
2350
2443
 
2351
- class SegwitAdoptionPattern:
2444
+ class CoinbasePattern:
2352
2445
  """Pattern struct for repeated tree structure."""
2353
2446
 
2354
2447
  def __init__(self, client: BrkClientBase, acc: str):
2355
2448
  """Create pattern node with accumulated metric name."""
2356
- self.base: MetricPattern11[StoredF32] = MetricPattern11(client, acc)
2357
- self.cumulative: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'cumulative'))
2358
- self.sum: MetricPattern2[StoredF32] = MetricPattern2(client, _m(acc, 'sum'))
2449
+ self.bitcoin: BitcoinPattern = BitcoinPattern(client, _m(acc, 'btc'))
2450
+ self.dollars: DollarsPattern[Dollars] = DollarsPattern(client, _m(acc, 'usd'))
2451
+ self.sats: DollarsPattern[Sats] = DollarsPattern(client, acc)
2359
2452
 
2360
- class CostBasisPattern2:
2453
+ class UnclaimedRewardsPattern:
2361
2454
  """Pattern struct for repeated tree structure."""
2362
2455
 
2363
2456
  def __init__(self, client: BrkClientBase, acc: str):
2364
2457
  """Create pattern node with accumulated metric name."""
2365
- self.max: ActivePricePattern = ActivePricePattern(client, _m(acc, 'max_cost_basis'))
2366
- self.min: ActivePricePattern = ActivePricePattern(client, _m(acc, 'min_cost_basis'))
2367
- self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis'))
2458
+ self.bitcoin: BitcoinPattern2[Bitcoin] = BitcoinPattern2(client, _m(acc, 'btc'))
2459
+ self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd'))
2460
+ self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc)
2368
2461
 
2369
2462
  class CoinbasePattern2:
2370
2463
  """Pattern struct for repeated tree structure."""
@@ -2375,13 +2468,14 @@ class CoinbasePattern2:
2375
2468
  self.dollars: BlockCountPattern[Dollars] = BlockCountPattern(client, _m(acc, 'usd'))
2376
2469
  self.sats: BlockCountPattern[Sats] = BlockCountPattern(client, acc)
2377
2470
 
2378
- class RelativePattern4:
2471
+ class CostBasisPattern2:
2379
2472
  """Pattern struct for repeated tree structure."""
2380
2473
 
2381
2474
  def __init__(self, client: BrkClientBase, acc: str):
2382
2475
  """Create pattern node with accumulated metric name."""
2383
- self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'loss_rel_to_own_supply'))
2384
- self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'profit_rel_to_own_supply'))
2476
+ self.max: ActivePricePattern = ActivePricePattern(client, _m(acc, 'max_cost_basis'))
2477
+ self.min: ActivePricePattern = ActivePricePattern(client, _m(acc, 'min_cost_basis'))
2478
+ self.percentiles: PercentilesPattern = PercentilesPattern(client, _m(acc, 'cost_basis'))
2385
2479
 
2386
2480
  class ActivePricePattern:
2387
2481
  """Pattern struct for repeated tree structure."""
@@ -2391,13 +2485,13 @@ class ActivePricePattern:
2391
2485
  self.dollars: MetricPattern1[Dollars] = MetricPattern1(client, acc)
2392
2486
  self.sats: MetricPattern1[SatsFract] = MetricPattern1(client, _m(acc, 'sats'))
2393
2487
 
2394
- class _1dReturns1mSdPattern:
2488
+ class _0sdUsdPattern:
2395
2489
  """Pattern struct for repeated tree structure."""
2396
2490
 
2397
2491
  def __init__(self, client: BrkClientBase, acc: str):
2398
2492
  """Create pattern node with accumulated metric name."""
2399
- self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd'))
2400
- self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma'))
2493
+ self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, acc)
2494
+ self.sats: MetricPattern4[SatsFract] = MetricPattern4(client, _m(acc, 'sats'))
2401
2495
 
2402
2496
  class SupplyPattern2:
2403
2497
  """Pattern struct for repeated tree structure."""
@@ -2407,21 +2501,29 @@ class SupplyPattern2:
2407
2501
  self.halved: ActiveSupplyPattern = ActiveSupplyPattern(client, _m(acc, 'halved'))
2408
2502
  self.total: ActiveSupplyPattern = ActiveSupplyPattern(client, acc)
2409
2503
 
2410
- class _0sdUsdPattern:
2504
+ class CostBasisPattern:
2411
2505
  """Pattern struct for repeated tree structure."""
2412
2506
 
2413
2507
  def __init__(self, client: BrkClientBase, acc: str):
2414
2508
  """Create pattern node with accumulated metric name."""
2415
- self.dollars: MetricPattern4[Dollars] = MetricPattern4(client, acc)
2416
- self.sats: MetricPattern4[SatsFract] = MetricPattern4(client, _m(acc, 'sats'))
2509
+ self.max: ActivePricePattern = ActivePricePattern(client, _m(acc, 'max_cost_basis'))
2510
+ self.min: ActivePricePattern = ActivePricePattern(client, _m(acc, 'min_cost_basis'))
2417
2511
 
2418
- class CostBasisPattern:
2512
+ class _1dReturns1mSdPattern:
2419
2513
  """Pattern struct for repeated tree structure."""
2420
2514
 
2421
2515
  def __init__(self, client: BrkClientBase, acc: str):
2422
2516
  """Create pattern node with accumulated metric name."""
2423
- self.max: ActivePricePattern = ActivePricePattern(client, _m(acc, 'max_cost_basis'))
2424
- self.min: ActivePricePattern = ActivePricePattern(client, _m(acc, 'min_cost_basis'))
2517
+ self.sd: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sd'))
2518
+ self.sma: MetricPattern4[StoredF32] = MetricPattern4(client, _m(acc, 'sma'))
2519
+
2520
+ class RelativePattern4:
2521
+ """Pattern struct for repeated tree structure."""
2522
+
2523
+ def __init__(self, client: BrkClientBase, acc: str):
2524
+ """Create pattern node with accumulated metric name."""
2525
+ self.supply_in_loss_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'loss_rel_to_own_supply'))
2526
+ self.supply_in_profit_rel_to_own_supply: MetricPattern1[StoredF64] = MetricPattern1(client, _m(acc, 'profit_rel_to_own_supply'))
2425
2527
 
2426
2528
  class BlockCountPattern(Generic[T]):
2427
2529
  """Pattern struct for repeated tree structure."""
@@ -2447,19 +2549,19 @@ class SatsPattern(Generic[T]):
2447
2549
  self.ohlc: MetricPattern1[T] = MetricPattern1(client, _m(acc, 'ohlc_sats'))
2448
2550
  self.split: SplitPattern2[T] = SplitPattern2(client, _m(acc, 'sats'))
2449
2551
 
2450
- class OutputsPattern:
2552
+ class RealizedPriceExtraPattern:
2451
2553
  """Pattern struct for repeated tree structure."""
2452
2554
 
2453
2555
  def __init__(self, client: BrkClientBase, acc: str):
2454
2556
  """Create pattern node with accumulated metric name."""
2455
- self.utxo_count: MetricPattern1[StoredU64] = MetricPattern1(client, acc)
2557
+ self.ratio: MetricPattern4[StoredF32] = MetricPattern4(client, acc)
2456
2558
 
2457
- class RealizedPriceExtraPattern:
2559
+ class OutputsPattern:
2458
2560
  """Pattern struct for repeated tree structure."""
2459
2561
 
2460
2562
  def __init__(self, client: BrkClientBase, acc: str):
2461
2563
  """Create pattern node with accumulated metric name."""
2462
- self.ratio: MetricPattern4[StoredF32] = MetricPattern4(client, acc)
2564
+ self.utxo_count: MetricPattern1[StoredU64] = MetricPattern1(client, acc)
2463
2565
 
2464
2566
  # Metrics tree classes
2465
2567
 
@@ -3384,6 +3486,23 @@ class MetricsTree_Market_Dca_ClassDaysInLoss:
3384
3486
  self._2025: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2025_days_in_loss')
3385
3487
  self._2026: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2026_days_in_loss')
3386
3488
 
3489
+ class MetricsTree_Market_Dca_ClassDaysInProfit:
3490
+ """Metrics tree node."""
3491
+
3492
+ def __init__(self, client: BrkClientBase, base_path: str = ''):
3493
+ self._2015: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2015_days_in_profit')
3494
+ self._2016: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2016_days_in_profit')
3495
+ self._2017: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2017_days_in_profit')
3496
+ self._2018: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2018_days_in_profit')
3497
+ self._2019: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2019_days_in_profit')
3498
+ self._2020: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2020_days_in_profit')
3499
+ self._2021: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2021_days_in_profit')
3500
+ self._2022: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2022_days_in_profit')
3501
+ self._2023: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2023_days_in_profit')
3502
+ self._2024: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2024_days_in_profit')
3503
+ self._2025: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2025_days_in_profit')
3504
+ self._2026: MetricPattern4[StoredU32] = MetricPattern4(client, 'dca_class_2026_days_in_profit')
3505
+
3387
3506
  class MetricsTree_Market_Dca_ClassMaxDrawdown:
3388
3507
  """Metrics tree node."""
3389
3508
 
@@ -3418,23 +3537,6 @@ class MetricsTree_Market_Dca_ClassMaxReturn:
3418
3537
  self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_max_return')
3419
3538
  self._2026: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2026_max_return')
3420
3539
 
3421
- class MetricsTree_Market_Dca_ClassReturns:
3422
- """Metrics tree node."""
3423
-
3424
- def __init__(self, client: BrkClientBase, base_path: str = ''):
3425
- self._2015: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2015_returns')
3426
- self._2016: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2016_returns')
3427
- self._2017: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2017_returns')
3428
- self._2018: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2018_returns')
3429
- self._2019: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2019_returns')
3430
- self._2020: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2020_returns')
3431
- self._2021: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2021_returns')
3432
- self._2022: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2022_returns')
3433
- self._2023: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2023_returns')
3434
- self._2024: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2024_returns')
3435
- self._2025: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2025_returns')
3436
- self._2026: MetricPattern4[StoredF32] = MetricPattern4(client, 'dca_class_2026_returns')
3437
-
3438
3540
  class MetricsTree_Market_Dca_ClassStack:
3439
3541
  """Metrics tree node."""
3440
3542
 
@@ -3475,10 +3577,10 @@ class MetricsTree_Market_Dca:
3475
3577
  def __init__(self, client: BrkClientBase, base_path: str = ''):
3476
3578
  self.class_average_price: MetricsTree_Market_Dca_ClassAveragePrice = MetricsTree_Market_Dca_ClassAveragePrice(client)
3477
3579
  self.class_days_in_loss: MetricsTree_Market_Dca_ClassDaysInLoss = MetricsTree_Market_Dca_ClassDaysInLoss(client)
3478
- self.class_days_in_profit: ClassDaysInLossPattern[StoredU32] = ClassDaysInLossPattern(client, 'dca_class')
3580
+ self.class_days_in_profit: MetricsTree_Market_Dca_ClassDaysInProfit = MetricsTree_Market_Dca_ClassDaysInProfit(client)
3479
3581
  self.class_max_drawdown: MetricsTree_Market_Dca_ClassMaxDrawdown = MetricsTree_Market_Dca_ClassMaxDrawdown(client)
3480
3582
  self.class_max_return: MetricsTree_Market_Dca_ClassMaxReturn = MetricsTree_Market_Dca_ClassMaxReturn(client)
3481
- self.class_returns: MetricsTree_Market_Dca_ClassReturns = MetricsTree_Market_Dca_ClassReturns(client)
3583
+ self.class_returns: ClassDaysInLossPattern[StoredF32] = ClassDaysInLossPattern(client, 'dca_class')
3482
3584
  self.class_stack: MetricsTree_Market_Dca_ClassStack = MetricsTree_Market_Dca_ClassStack(client)
3483
3585
  self.period_average_price: MetricsTree_Market_Dca_PeriodAveragePrice = MetricsTree_Market_Dca_PeriodAveragePrice(client)
3484
3586
  self.period_cagr: PeriodCagrPattern = PeriodCagrPattern(client, 'dca_cagr')
@@ -4052,7 +4154,7 @@ class MetricsTree:
4052
4154
  class BrkClient(BrkClientBase):
4053
4155
  """Main BRK client with metrics tree and API methods."""
4054
4156
 
4055
- VERSION = "v0.1.0"
4157
+ VERSION = "v0.1.1"
4056
4158
 
4057
4159
  INDEXES = [
4058
4160
  "dateindex",
@@ -4958,6 +5060,14 @@ class BrkClient(BrkClientBase):
4958
5060
  """
4959
5061
  return MetricEndpointBuilder(self, metric, index)
4960
5062
 
5063
+ def index_to_date(self, index: Index, i: int) -> date:
5064
+ """Convert an index value to a date for date-based indexes."""
5065
+ return index_to_date(index, i)
5066
+
5067
+ def is_date_index(self, index: Index) -> bool:
5068
+ """Check if an index type is date-based."""
5069
+ return is_date_index(index)
5070
+
4961
5071
  def get_api(self) -> Any:
4962
5072
  """Compact OpenAPI specification.
4963
5073
 
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "brk-client"
3
- version = "0.1.1"
3
+ version = "0.1.2"
4
4
  description = "Python client for the Bitcoin Research Kit"
5
5
  readme = "README.md"
6
6
  requires-python = ">=3.9"
@@ -25,6 +25,8 @@ Repository = "https://github.com/bitcoinresearchkit/brk"
25
25
 
26
26
  [dependency-groups]
27
27
  dev = [
28
+ "pandas>=2.3.3",
29
+ "polars>=1.36.1",
28
30
  "pydoc-markdown>=4.8.2",
29
31
  "pytest",
30
32
  ]