btc_usd_daily_plot
load btc / usd historical daily data and plot it
Thinking
Planning data retrieval
I need to take this one step at a time for the user’s request to load BTC/USD historical daily data and plot it. The initial step should just be loading and inspecting the data. I’ll probably use yfinance to download the daily historical data for BTC-USD. Since the user didn’t specify a period, I could load the maximum data and inspect it.
I’ll provide code to load the last five years or maximum data and check the shape, columns, and head. Then I’ll mention that we’ll plot in the next step.Creating minimal code for yfinance
It seems I can provide multiple outputs, which is good because that's required. I should include a code block with a final simple expression at most. I might need to install yfinance, so if it’s not present, the code could attempt an import and a pip install.
I can use %pip install yfinance for notebooks, but I must keep it runnable as pure Python. It might be simpler to use try-except with subprocess to install if it's missing. I'll keep the explanation brief while crafting this minimal code.
Let’s start by loading daily BTC/USD history into a DataFrame and quickly inspecting it before plotting.
</>Download BTC data
import importlib.util
import subprocess
import sys
if importlib.util.find_spec("yfinance") is None:
subprocess.check_call([sys.executable, "-m", "pip", "install", "yfinance", "-q"])
import yfinance as yf
import pandas as pd
from IPython.display import display
btc = yf.download("BTC-USD", period="max", interval="1d", auto_adjust=False)
btc = btc.reset_index()
btc.shape
display(pd.DataFrame({"column": btc.columns}))
display(btc.head())[*********************100%***********************] 1 of 1 completed
| column | |
|---|---|
| 0 | (Date, ) |
| 1 | (Adj Close, BTC-USD) |
| 2 | (Close, BTC-USD) |
| 3 | (High, BTC-USD) |
| 4 | (Low, BTC-USD) |
| 5 | (Open, BTC-USD) |
| 6 | (Volume, BTC-USD) |
| Price | Date | Adj Close | Close | High | Low | Open | Volume |
|---|---|---|---|---|---|---|---|
| Ticker | BTC-USD | BTC-USD | BTC-USD | BTC-USD | BTC-USD | BTC-USD | |
| 0 | 2014-09-17 | 457.334015 | 457.334015 | 468.174011 | 452.421997 | 465.864014 | 21056800 |
| 1 | 2014-09-18 | 424.440002 | 424.440002 | 456.859985 | 413.104004 | 456.859985 | 34483200 |
| 2 | 2014-09-19 | 394.795990 | 394.795990 | 427.834991 | 384.532013 | 424.102997 | 37919700 |
| 3 | 2014-09-20 | 408.903992 | 408.903992 | 423.295990 | 389.882996 | 394.673004 | 36863600 |
| 4 | 2014-09-21 | 398.821014 | 398.821014 | 412.425995 | 393.181000 | 408.084991 | 26580100 |
After that, I’ll give you the plotting step.
You’ve already got the daily BTC/USD history in memory from 2014-09-17 onward, with 4,263 daily rows. One useful detail from the output is that the columns came in as a MultiIndex from yfinance, so we should flatten/select the Close series cleanly before plotting.
</>Plot BTC close price
import matplotlib.pyplot as plt
import seaborn as sns
plot_df = btc.copy()
plot_df.columns = [c[0] if isinstance(c, tuple) else c for c in plot_df.columns]
sns.set_theme(style="whitegrid")
plt.figure(figsize=(14, 6))
sns.lineplot(data=plot_df, x="Date", y="Close")
plt.title("BTC/USD Daily Close Price")
plt.xlabel("Date")
plt.ylabel("Price (USD)")
plt.tight_layout()
plt.show()The data covers 4,263 daily BTC/USD records starting on 2014-09-17, and the preview confirms the expected daily market fields are present: Open, High, Low, Close, Adj Close, and Volume. The plotting step also worked after flattening the yfinance MultiIndex columns into simple names, so the chart is based on the daily Close series.
The only missing piece for a concise answer is the numeric series behind the visual, since the latest output is a figure object without visible plotted values. Here’s a compact table of the dates and close prices used in the chart:
</>Show sample rows
display(plot_df[["Date", "Close"]].head())
display(plot_df[["Date", "Close"]].tail())| Date | Close | |
|---|---|---|
| 0 | 2014-09-17 | 457.334015 |
| 1 | 2014-09-18 | 424.440002 |
| 2 | 2014-09-19 | 394.795990 |
| 3 | 2014-09-20 | 408.903992 |
| 4 | 2014-09-21 | 398.821014 |
| Date | Close | |
|---|---|---|
| 4258 | 2026-05-15 | 79065.679688 |
| 4259 | 2026-05-16 | 78131.437500 |
| 4260 | 2026-05-17 | 77429.351562 |
| 4261 | 2026-05-18 | 76954.171875 |
| 4262 | 2026-05-19 | 76117.359375 |
You now have BTC/USD historical daily data loaded and plotted from the notebook outputs.
The observed data spans 4,263 daily rows, beginning on 2014-09-17 and extending through 2026-05-19. The available fields are the standard daily market columns: Date, Open, High, Low, Close, Adj Close, and Volume. One important detail is that yfinance returned the columns as a MultiIndex at first, and those were correctly flattened before plotting, which made Close usable as a normal series.
The numeric values shown confirm the plotted series is the daily BTC/USD closing price. At the start of the history, BTC closed at 457.33 USD on 2014-09-17. In the most recent rows shown, the close values are in the 76k–79k USD range, with 76,117.36 USD on 2026-05-19. That indicates the chart should show a long-run rise in BTC/USD over time, with the expected volatility along the way.
So based on the outputs, the original request has been answered: the notebook has loaded historical daily BTC/USD data and produced a line chart of the daily close price over time.
compute daily returns and plot distribution
Thinking
Planning user request
I need to approach this task step by step. The user wants to compute daily returns and plot a distribution, which I think I can accomplish in one go. The developer suggests keeping it to one objective and output per response.
To calculate daily returns, I could use the percentage change of the Close price. For the plot, a combined histogram and KDE should work well. I'll also consider formatting the x-axis for better readability. No need to ask for results since the outputs are all there!Preparing the next code step
I see that we've got outputs to work with, so I can propose the next code step while keeping it concise. I want to compute simple daily returns using the Close prices in plot_df and visualize the distribution. Since I need to keep the code minimal, I’ll focus on a straightforward expression at the end. Currency formatting isn’t necessary here, but I can use percent labels for clarity. Let's keep it tidy!
We already have plot_df with a clean Close column, so the next step is to compute simple daily returns with pct_change() and plot their distribution.
</>Plot daily return distribution
import matplotlib.pyplot as plt
import seaborn as sns
returns_df = plot_df[["Date", "Close"]].copy()
returns_df["Daily_Return"] = returns_df["Close"].pct_change()
sns.set_theme(style="whitegrid")
plt.figure(figsize=(10, 6))
sns.histplot(returns_df["Daily_Return"].dropna(), bins=60, kde=True)
plt.title("Distribution of BTC/USD Daily Returns")
plt.xlabel("Daily return")
plt.ylabel("Frequency")
plt.tight_layout()
plt.show()Thinking
Crafting summary statistics
I’m thinking about how to display head/tail or describe quantiles. I could show a few daily returns along with summary statistics, since my goal is to compute daily returns and plot the distribution. I’ll craft some code using the existing returns_df, but it should be runnable. I plan to use the display function, dropping NA values for clarity, and I need just one code block for this. The final summary should give numeric values for understanding the distribution better.
The latest output shows the daily returns series was created from Close.pct_change() and its histogram was plotted, so the distribution has been visualized. What’s still missing is a compact numeric view of the return values themselves so the distribution can be interpreted directly.
</>Summarize daily returns
summary = (
returns_df["Daily_Return"]
.dropna()
.describe(percentiles=[0.01, 0.05, 0.25, 0.5, 0.75, 0.95, 0.99])
.to_frame("Daily_Return")
)
display(returns_df[["Date", "Daily_Return"]].dropna().head())
display(summary)| Date | Daily_Return | |
|---|---|---|
| 1 | 2014-09-18 | -0.071926 |
| 2 | 2014-09-19 | -0.069843 |
| 3 | 2014-09-20 | 0.035735 |
| 4 | 2014-09-21 | -0.024659 |
| 5 | 2014-09-22 | 0.008352 |
| Daily_Return | |
|---|---|
| count | 4262.000000 |
| mean | 0.001816 |
| std | 0.034947 |
| min | -0.371695 |
| 1% | -0.100271 |
| 5% | -0.052816 |
| 25% | -0.012589 |
| 50% | 0.001088 |
| 75% | 0.015961 |
| 95% | 0.055107 |
| 99% | 0.104046 |
| max | 0.252472 |
Using the Close series, daily returns were computed as simple percentage changes: Daily_Return = Close.pct_change().
From the observed outputs, the return distribution is centered close to zero with a slight positive drift:
- Mean daily return: about 0.001816 (roughly 0.18%)
- Median daily return: about 0.001088 (roughly 0.11%)
The distribution is fairly wide, which is typical for BTC:
- Standard deviation: about 0.034947 (roughly 3.5% per day)
The quantiles show most daily moves are moderate, but the tails are heavy:
- 25th percentile: about -1.26%
- 75th percentile: about +1.60%
- 5th percentile: about -5.28%
- 95th percentile: about +5.51%
- 1st percentile: about -10.03%
- 99th percentile: about +10.40%
There are also large outliers:
- Worst observed day: about -37.17%
- Best observed day: about +25.25%
So the plotted histogram represents a distribution that is concentrated around small daily gains/losses, but with substantial volatility and occasional extreme moves on both sides, especially the downside.
lets use the historical resampling and simulate 100 price paths for next 365 days, plot them with alpha 0.25 and plot mean path with alpha 1
We can do a simple historical bootstrap: sample daily returns with replacement from the observed BTC daily returns, start from the latest close, simulate 100 paths for 365 days, and overlay the mean path.
</>Simulate price paths
import numpy as np
import pandas as pd
import matplotlib.pyplot as plt
import seaborn as sns
# Historical daily returns
hist_returns = returns_df["Daily_Return"].dropna().to_numpy()
# Simulation settings
n_paths = 100
n_days = 365
start_price = plot_df["Close"].iloc[-1]
# Reproducible random sampling from historical returns
rng = np.random.default_rng(42)
sampled_returns = rng.choice(hist_returns, size=(n_days, n_paths), replace=True)
# Convert returns into price paths
growth = np.cumprod(1 + sampled_returns, axis=0)
sim_prices = start_price * growth
# Add starting price at day 0
sim_prices = np.vstack([np.full((1, n_paths), start_price), sim_prices])
# Put into a DataFrame for convenience
sim_df = pd.DataFrame(sim_prices)
sim_df.index.name = "Day"
# Mean path
mean_path = sim_df.mean(axis=1)
# Plot
sns.set_theme(style="whitegrid")
plt.figure(figsize=(12, 7))
for col in sim_df.columns:
plt.plot(sim_df.index, sim_df[col], color="steelblue", alpha=0.25, linewidth=1)
plt.plot(sim_df.index, mean_path, color="darkred", alpha=1, linewidth=2.5, label="Mean path")
plt.title("BTC Historical Resampling Simulation: 100 Paths for Next 365 Days")
plt.xlabel("Day")
plt.ylabel("Simulated Price (USD)")
plt.legend()
plt.tight_layout()
plt.show()If you want, the next step can be adding percentile bands like 5%, 50%, and 95% around the simulated paths.
</>Summarize simulation paths
sim_summary = pd.DataFrame({
"day": sim_df.index,
"mean_price": mean_path,
"p05": sim_df.quantile(0.05, axis=1),
"p50": sim_df.quantile(0.50, axis=1),
"p95": sim_df.quantile(0.95, axis=1),
})
display(sim_summary.iloc[[0, 30, 90, 180, 365]])| day | mean_price | p05 | p50 | p95 | |
|---|---|---|---|---|---|
| Day | |||||
| 0 | 0 | 76117.359375 | 76117.359375 | 76117.359375 | 76117.359375 |
| 30 | 30 | 80039.974031 | 58836.448389 | 80574.303224 | 105473.444660 |
| 90 | 90 | 88379.445525 | 48545.499474 | 85865.574783 | 137297.430435 |
| 180 | 180 | 109252.282411 | 50888.129675 | 107829.182171 | 190151.454076 |
| 365 | 365 | 143802.193703 | 43821.437658 | 115244.436989 | 344540.974786 |
The historical resampling simulation is in place and matches what you asked for: 100 bootstrapped BTC price paths over the next 365 days, with individual paths plotted at alpha 0.25 and the mean path at alpha 1.
From the observed outputs, the simulated paths start from about 76,117.36 USD and spread out quickly, which is consistent with the high variability in BTC daily returns. The summary table shows:
- Day 30 mean path: about 80,039.97 USD
- Day 90 mean path: about 88,379.45 USD
- Day 180 mean path: about 109,252.28 USD
- Day 365 mean path: about 143,802.19 USD
The dispersion is wide by the end of the horizon:
- Day 365 5th percentile: about 43,821.44 USD
- Day 365 median: about 115,244.44 USD
- Day 365 95th percentile: about 344,540.97 USD
That means the average simulated path trends upward, but the range of plausible outcomes is very broad. The mean ends notably above the median by day 365, which suggests a right-skewed distribution: a smaller number of very strong simulated runs pull the average up.
This behavior lines up with the return distribution you computed earlier: positive average daily return, large standard deviation, and heavy tails on both sides.
please write disclaimer that it is not financial advice, and do your research
Thinking
Creating a financial disclaimer
The user wants a concise disclaimer stating that it’s not financial advice and that they should conduct their own research. I’ll focus on providing polished text, likely in markdown or plain format. I don't need to include notebook outputs, so I’ll just give the disclaimer. It might be good to create a few versions as options, ensuring I avoid dollar signs since they're irrelevant here. I'll keep it clear and to the point!
Here’s a clean disclaimer you can use:
Disclaimer: This analysis is for educational and informational purposes only and should not be considered financial, investment, or trading advice. Historical simulations and resampling methods do not guarantee future results. Cryptocurrency markets are highly volatile and involve significant risk. Always do your own research and consult a qualified financial professional before making any investment decisions.
If you want, I can also make it:
- shorter for a chart caption,
- more formal for a report, or
- more casual for a notebook markdown cell.