This Python 3 example uses only the standard library and exact rational probabilities. Moving blocks accumulate the distribution of return sums; stationary bootstrap accumulates probability by last source index and sum. Integer returns are percentage-point units, so a printed 0.5 means 0.5%, not 50%. The code is designed for this small discrete dataset; ordinary longer series need a suitable bootstrap implementation, a recorded random seed and enough replications to assess quantile stability.
Exact conditional means and percentile endpoints for the eight-return teaching samplepython
from collections import defaultdict
from fractions import Fraction as F
# Hypothetical daily returns in percentage points, in time order.
r = [-1, -1, 0, 0, 1, 2, 2, 1]
n = len(r)
def moving(length):
if not isinstance(length, int) or not 1 <= length <= n:
raise ValueError('Fixed block length must be an integer in [1, n]')
starts = range(n - length + 1)
dist = {0: F(1)}
for filled in range(0, n, length):
take = min(length, n - filled)
nxt = defaultdict(F)
for total, prob in dist.items():
for start in starts:
nxt[total + sum(r[start:start + take])] += prob / len(starts)
dist = nxt
return dist
def stationary(mean_length):
if not isinstance(mean_length, int) or mean_length < 1:
raise ValueError('This example requires an integer mean length >= 1')
p = F(1, mean_length)
states = {(i, r[i]): F(1, n) for i in range(n)}
for _ in range(n - 1):
nxt = defaultdict(F)
for (last, total), prob in states.items():
following = (last + 1) % n
nxt[(following, total + r[following])] += prob * (1 - p)
for start in range(n):
nxt[(start, total + r[start])] += prob * p / n
states = nxt
dist = defaultdict(F)
for (_, total), prob in states.items():
dist[total] += prob
return dist
def quantile(dist, q):
cumulative = F(0)
for total, prob in sorted(dist.items()):
cumulative += prob
if cumulative >= q:
return F(total, n)
raise ValueError('Invalid probability distribution')
def summary(dist):
assert sum(dist.values()) == 1
center = sum(F(total, n) * prob for total, prob in dist.items())
lo, hi = (quantile(dist, q) for q in (F(1, 40), F(39, 40)))
return tuple(float(x) for x in (center, lo, hi))
if __name__ == '__main__':
print('Observed mean:', sum(r) / n)
for length in (1, 2, 4):
print('Moving', length, summary(moving(length)))
print('Stationary', length, summary(stationary(length)))