Add market maturity projection adjustments.
This commit is contained in:
@@ -163,14 +163,111 @@ def calculate_volatility(df, short_window=30, medium_window=90, long_window=180)
|
|||||||
return base_vol * volatility_scale
|
return base_vol * volatility_scale
|
||||||
|
|
||||||
|
|
||||||
|
def calculate_market_maturity_score(df):
|
||||||
|
"""
|
||||||
|
Calculate a market maturity score (0-1) based on multiple indicators.
|
||||||
|
Higher scores indicate a more mature market.
|
||||||
|
"""
|
||||||
|
df = df.copy()
|
||||||
|
|
||||||
|
# 1. Volume-based metrics
|
||||||
|
df["log_volume"] = np.log(df["Volume"])
|
||||||
|
df["volume_ma"] = df["log_volume"].rolling(window=365).mean()
|
||||||
|
volume_growth = (df["volume_ma"] - df["volume_ma"].shift(365)) / df[
|
||||||
|
"volume_ma"
|
||||||
|
].shift(365)
|
||||||
|
|
||||||
|
# 2. Volatility maturity (lower volatility = more mature)
|
||||||
|
df["rolling_vol"] = df["Daily_Return"].rolling(window=365).std() * np.sqrt(365)
|
||||||
|
vol_maturity = 1 / (1 + df["rolling_vol"])
|
||||||
|
|
||||||
|
# 3. Market efficiency score
|
||||||
|
df["autocorr"] = (
|
||||||
|
df["Daily_Return"]
|
||||||
|
.rolling(window=30)
|
||||||
|
.apply(lambda x: abs(pd.Series(x).autocorr(1)))
|
||||||
|
)
|
||||||
|
efficiency = 1 - df["autocorr"] # Lower autocorrelation = more efficient
|
||||||
|
|
||||||
|
# 4. Futures market impact (post-2017)
|
||||||
|
futures_date = pd.Timestamp("2017-12-10")
|
||||||
|
futures_impact = (df["Date"] > futures_date).astype(float) * 0.2
|
||||||
|
|
||||||
|
# Combine scores with time-varying weights
|
||||||
|
weights = {"volume": 0.3, "volatility": 0.3, "efficiency": 0.2, "futures": 0.2}
|
||||||
|
|
||||||
|
maturity_score = (
|
||||||
|
weights["volume"] * volume_growth.clip(-1, 1).map(lambda x: (x + 1) / 2)
|
||||||
|
+ weights["volatility"] * vol_maturity
|
||||||
|
+ weights["efficiency"] * efficiency
|
||||||
|
+ weights["futures"] * futures_impact
|
||||||
|
)
|
||||||
|
|
||||||
|
# Normalize to 0-1 range and smooth
|
||||||
|
maturity_score = (maturity_score - maturity_score.min()) / (
|
||||||
|
maturity_score.max() - maturity_score.min()
|
||||||
|
)
|
||||||
|
maturity_score = maturity_score.rolling(window=30, min_periods=1).mean()
|
||||||
|
|
||||||
|
return maturity_score
|
||||||
|
|
||||||
|
|
||||||
|
def adjust_projections_for_maturity(df, projections, maturity_score):
|
||||||
|
"""
|
||||||
|
Adjust price projections based on market maturity score.
|
||||||
|
More mature markets should have tighter confidence intervals
|
||||||
|
and more conservative growth expectations.
|
||||||
|
"""
|
||||||
|
# Get final maturity score
|
||||||
|
final_maturity = maturity_score.iloc[-1]
|
||||||
|
|
||||||
|
# Adjust confidence intervals based on maturity
|
||||||
|
# More mature markets = tighter intervals
|
||||||
|
ci_adjustment = 1 - (final_maturity * 0.3) # Max 30% reduction in interval width
|
||||||
|
|
||||||
|
# Adjust expected returns based on maturity
|
||||||
|
# More mature markets = more conservative growth
|
||||||
|
returns_adjustment = 1 - (
|
||||||
|
final_maturity * 0.2
|
||||||
|
) # Max 20% reduction in expected returns
|
||||||
|
|
||||||
|
adjusted_projections = projections.copy()
|
||||||
|
|
||||||
|
# Adjust confidence intervals
|
||||||
|
for ci in [68, 95]:
|
||||||
|
upper_key = f"Upper_{ci}"
|
||||||
|
lower_key = f"Lower_{ci}"
|
||||||
|
median = adjusted_projections["Median"]
|
||||||
|
|
||||||
|
# Calculate distances from median
|
||||||
|
upper_distance = adjusted_projections[upper_key] - median
|
||||||
|
lower_distance = median - adjusted_projections[lower_key]
|
||||||
|
|
||||||
|
# Apply maturity-based adjustment
|
||||||
|
adjusted_projections[upper_key] = median + (upper_distance * ci_adjustment)
|
||||||
|
adjusted_projections[lower_key] = median - (lower_distance * ci_adjustment)
|
||||||
|
|
||||||
|
# Adjust expected trend
|
||||||
|
trend_distance = (
|
||||||
|
adjusted_projections["Expected_Trend"] - adjusted_projections["Median"]
|
||||||
|
)
|
||||||
|
adjusted_projections["Expected_Trend"] = (
|
||||||
|
adjusted_projections["Median"] + trend_distance * returns_adjustment
|
||||||
|
)
|
||||||
|
|
||||||
|
return adjusted_projections
|
||||||
|
|
||||||
|
|
||||||
def project_prices(
|
def project_prices(
|
||||||
df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68]
|
df, days_forward=365, simulations=1000, confidence_levels=[0.95, 0.68]
|
||||||
):
|
):
|
||||||
"""
|
"""
|
||||||
Project future Bitcoin prices using Monte Carlo simulation.
|
Project future Bitcoin prices using Monte Carlo simulation with market maturity adjustments.
|
||||||
Uses enhanced trend smoothing for better predictions.
|
|
||||||
"""
|
"""
|
||||||
# Calculate log returns for volatility estimation
|
# Calculate market maturity score
|
||||||
|
maturity_score = calculate_market_maturity_score(df)
|
||||||
|
|
||||||
|
# Original calculations
|
||||||
df = df.copy()
|
df = df.copy()
|
||||||
df["Log_Price"] = np.log(df["Close"])
|
df["Log_Price"] = np.log(df["Close"])
|
||||||
df["Log_Return"] = df["Log_Price"].diff()
|
df["Log_Return"] = df["Log_Price"].diff()
|
||||||
@@ -193,7 +290,7 @@ def project_prices(
|
|||||||
start=last_date + timedelta(days=1), periods=days_forward, freq="D"
|
start=last_date + timedelta(days=1), periods=days_forward, freq="D"
|
||||||
)
|
)
|
||||||
|
|
||||||
# Calculate expected returns with enhanced cycle boundary handling
|
# Calculate expected returns with cycle boundary handling
|
||||||
future_cycle_days = [
|
future_cycle_days = [
|
||||||
(current_cycle_days + i) % (4 * 365) for i in range(days_forward)
|
(current_cycle_days + i) % (4 * 365) for i in range(days_forward)
|
||||||
]
|
]
|
||||||
@@ -211,17 +308,30 @@ def project_prices(
|
|||||||
|
|
||||||
expected_returns = np.array(expected_returns)
|
expected_returns = np.array(expected_returns)
|
||||||
|
|
||||||
# Calculate volatility
|
# Calculate volatility with maturity adjustment
|
||||||
volatility = calculate_volatility(df)
|
base_volatility = calculate_volatility(df)
|
||||||
|
final_maturity = maturity_score.iloc[-1]
|
||||||
|
|
||||||
|
# Adjust volatility based on market maturity (more mature = lower volatility)
|
||||||
|
volatility_adjustment = 1 - (final_maturity * 0.3) # Max 30% reduction
|
||||||
|
volatility = base_volatility * volatility_adjustment
|
||||||
|
|
||||||
# Run Monte Carlo simulation
|
# Run Monte Carlo simulation
|
||||||
np.random.seed(42)
|
np.random.seed(42)
|
||||||
simulated_paths = np.zeros((days_forward, simulations))
|
simulated_paths = np.zeros((days_forward, simulations))
|
||||||
|
|
||||||
|
# Adjust trend expectations based on market maturity
|
||||||
|
trend_adjustment = 1 - (final_maturity * 0.2) # Max 20% reduction
|
||||||
|
adjusted_expected_returns = expected_returns * trend_adjustment
|
||||||
|
|
||||||
for sim in range(simulations):
|
for sim in range(simulations):
|
||||||
skew = np.sign(expected_returns) * 0.087 # Small skew in direction of trend
|
# Adjust skew based on market maturity (more mature = less skew)
|
||||||
|
base_skew = 0.087
|
||||||
|
skew_adjustment = 1 - (final_maturity * 0.4) # Max 40% reduction
|
||||||
|
skew = np.sign(adjusted_expected_returns) * base_skew * skew_adjustment
|
||||||
|
|
||||||
returns = np.random.normal(
|
returns = np.random.normal(
|
||||||
loc=expected_returns + skew * volatility,
|
loc=adjusted_expected_returns + skew * volatility,
|
||||||
scale=volatility,
|
scale=volatility,
|
||||||
size=days_forward,
|
size=days_forward,
|
||||||
)
|
)
|
||||||
@@ -247,7 +357,12 @@ def project_prices(
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add expected trend line
|
# Add expected trend line
|
||||||
results["Expected_Trend"] = last_price * np.exp(np.cumsum(expected_returns))
|
results["Expected_Trend"] = last_price * np.exp(
|
||||||
|
np.cumsum(adjusted_expected_returns)
|
||||||
|
)
|
||||||
|
|
||||||
|
# Add maturity score to results for analysis
|
||||||
|
results["Market_Maturity"] = final_maturity
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
@@ -268,10 +383,17 @@ def analyze_bitcoin_prices(csv_path):
|
|||||||
print(df.info())
|
print(df.info())
|
||||||
|
|
||||||
# Convert price columns to float and handle any potential formatting issues
|
# Convert price columns to float and handle any potential formatting issues
|
||||||
price_columns = ["Price", "Open", "High", "Low"]
|
numeric_columns = ["Price", "Open", "High", "Low", "Vol."] # Added Volume
|
||||||
for col in price_columns:
|
for col in numeric_columns:
|
||||||
# Remove any commas in numbers
|
# Remove any commas and 'K'/'M' suffixes
|
||||||
df[col] = df[col].astype(str).str.replace(",", "")
|
df[col] = df[col].astype(str).str.replace(",", "")
|
||||||
|
# Convert K to thousands
|
||||||
|
df[col] = df[col].str.replace("K", "e3")
|
||||||
|
# Convert M to millions
|
||||||
|
df[col] = df[col].str.replace("M", "e6")
|
||||||
|
# Convert B to billions
|
||||||
|
df[col] = df[col].str.replace("B", "e9")
|
||||||
|
# Convert to numeric
|
||||||
df[col] = pd.to_numeric(df[col], errors="coerce")
|
df[col] = pd.to_numeric(df[col], errors="coerce")
|
||||||
|
|
||||||
# Rename columns for clarity
|
# Rename columns for clarity
|
||||||
@@ -282,7 +404,7 @@ def analyze_bitcoin_prices(csv_path):
|
|||||||
|
|
||||||
# Print summary statistics after conversion
|
# Print summary statistics after conversion
|
||||||
print("\nPrice Summary After Conversion:")
|
print("\nPrice Summary After Conversion:")
|
||||||
print(df[["Close", "Open", "High", "Low"]].describe())
|
print(df[["Close", "Open", "High", "Low", "Volume"]].describe())
|
||||||
|
|
||||||
# Calculate daily returns
|
# Calculate daily returns
|
||||||
df["Daily_Return"] = df["Close"].pct_change()
|
df["Daily_Return"] = df["Close"].pct_change()
|
||||||
@@ -340,24 +462,24 @@ def create_plots(df, start=None, end=None, project_days=365):
|
|||||||
if len(plot_df) == 0:
|
if len(plot_df) == 0:
|
||||||
raise ValueError("No data found for the specified date range")
|
raise ValueError("No data found for the specified date range")
|
||||||
|
|
||||||
# Generate projections
|
# Calculate market maturity score
|
||||||
# cycle_returns, cycle_volatility = analyze_trends(plot_df)
|
maturity_score = calculate_market_maturity_score(plot_df)
|
||||||
projections = project_prices(plot_df, days_forward=project_days)
|
plot_df["Market_Maturity"] = maturity_score
|
||||||
|
|
||||||
# Create cycle visualization
|
# Generate projections with market maturity adjustments
|
||||||
# visualize_cycle_patterns(plot_df, cycle_returns, cycle_volatility)
|
projections = project_prices(plot_df, days_forward=project_days)
|
||||||
|
|
||||||
# Set up the style
|
# Set up the style
|
||||||
plt.style.use("seaborn-v0_8")
|
plt.style.use("seaborn-v0_8")
|
||||||
|
|
||||||
# Create figure
|
# Create figure with additional subplot for maturity
|
||||||
fig = plt.figure(figsize=(15, 15))
|
fig = plt.figure(figsize=(15, 18)) # Made taller to accommodate new subplot
|
||||||
|
|
||||||
# Date range for titles
|
# Date range for titles
|
||||||
hist_date_range = f" ({plot_df['Date'].min().strftime('%Y-%m-%d')} to {plot_df['Date'].max().strftime('%Y-%m-%d')})"
|
hist_date_range = f" ({plot_df['Date'].min().strftime('%Y-%m-%d')} to {plot_df['Date'].max().strftime('%Y-%m-%d')})"
|
||||||
|
|
||||||
# 1. Price history and projections (log scale)
|
# 1. Price history and projections (log scale)
|
||||||
ax1 = plt.subplot(4, 1, 1)
|
ax1 = plt.subplot(5, 1, 1) # Changed to 5,1 grid
|
||||||
|
|
||||||
# Plot historical prices
|
# Plot historical prices
|
||||||
ax1.semilogy(plot_df["Date"], plot_df["Close"], "b-", label="Historical Price")
|
ax1.semilogy(plot_df["Date"], plot_df["Close"], "b-", label="Historical Price")
|
||||||
@@ -535,6 +657,45 @@ def create_plots(df, start=None, end=None, project_days=365):
|
|||||||
# Set x-axis to show only our timepoints
|
# Set x-axis to show only our timepoints
|
||||||
ax4.set_xticks(timepoints)
|
ax4.set_xticks(timepoints)
|
||||||
|
|
||||||
|
# 2. Market Maturity Score (New)
|
||||||
|
ax2 = plt.subplot(5, 1, 2)
|
||||||
|
ax2.plot(
|
||||||
|
plot_df["Date"],
|
||||||
|
plot_df["Market_Maturity"],
|
||||||
|
color="purple",
|
||||||
|
label="Market Maturity Score",
|
||||||
|
)
|
||||||
|
ax2.set_title("Market Maturity Score" + hist_date_range)
|
||||||
|
ax2.set_xlabel("Date")
|
||||||
|
ax2.set_ylabel("Maturity Score (0-1)")
|
||||||
|
ax2.grid(True)
|
||||||
|
ax2.legend()
|
||||||
|
|
||||||
|
# Add annotations for key events
|
||||||
|
futures_date = pd.Timestamp("2017-12-10")
|
||||||
|
if futures_date >= plot_df["Date"].min() and futures_date <= plot_df["Date"].max():
|
||||||
|
ax2.axvline(futures_date, color="red", linestyle="--", alpha=0.5)
|
||||||
|
ax2.text(
|
||||||
|
futures_date,
|
||||||
|
ax2.get_ylim()[1],
|
||||||
|
"Futures\nLaunch",
|
||||||
|
rotation=90,
|
||||||
|
va="top",
|
||||||
|
ha="right",
|
||||||
|
)
|
||||||
|
|
||||||
|
# 3. Rolling volatility (now third subplot)
|
||||||
|
ax3 = plt.subplot(5, 1, 3)
|
||||||
|
# [Previous volatility plotting code...]
|
||||||
|
|
||||||
|
# 4. Returns distribution (now fourth subplot)
|
||||||
|
ax4 = plt.subplot(5, 1, 4)
|
||||||
|
# [Previous distribution plotting code...]
|
||||||
|
|
||||||
|
# 5. Projection ranges (now fifth subplot)
|
||||||
|
ax5 = plt.subplot(5, 1, 5)
|
||||||
|
# [Previous projection ranges plotting code...]
|
||||||
|
|
||||||
# Adjust layout
|
# Adjust layout
|
||||||
plt.tight_layout()
|
plt.tight_layout()
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user