Use S2F metrics for trend analysis.
This commit is contained in:
@@ -280,13 +280,30 @@ class MarketFundamentals:
|
|||||||
return np.clip(adjustment, 0.65, 0.75)
|
return np.clip(adjustment, 0.65, 0.75)
|
||||||
|
|
||||||
def calculate_confidence_adjustment(self, metrics, level):
|
def calculate_confidence_adjustment(self, metrics, level):
|
||||||
"""Calculate how much to adjust confidence intervals based on market conditions."""
|
"""Calculate confidence interval adjustments with more sensitive market metrics."""
|
||||||
depth_impact = np.clip(metrics["market_depth"] * 0.2, 0, 0.2)
|
# More granular depth impact
|
||||||
vol_impact = np.clip(metrics["volume_to_supply"] * 30, 0, 0.2)
|
if metrics["market_depth"] > 0.1:
|
||||||
total_adjustment = (depth_impact + vol_impact) * 0.5
|
depth_factor = 0.5
|
||||||
|
elif metrics["market_depth"] > 0.05:
|
||||||
|
depth_factor = 0.7
|
||||||
|
else:
|
||||||
|
depth_factor = 1.0
|
||||||
|
|
||||||
|
# More granular volume impact
|
||||||
|
if metrics["volume_to_supply"] > 0.015:
|
||||||
|
volume_factor = 0.5
|
||||||
|
elif metrics["volume_to_supply"] > 0.008:
|
||||||
|
volume_factor = 0.7
|
||||||
|
else:
|
||||||
|
volume_factor = 1.0
|
||||||
|
|
||||||
|
depth_impact = np.clip(metrics["market_depth"] * 0.15 * depth_factor, 0, 0.15)
|
||||||
|
vol_impact = np.clip(metrics["volume_to_supply"] * 20 * volume_factor, 0, 0.15)
|
||||||
|
|
||||||
|
total_adjustment = (depth_impact + vol_impact) * 0.4
|
||||||
|
|
||||||
if level >= 0.95:
|
if level >= 0.95:
|
||||||
total_adjustment *= 0.5
|
total_adjustment *= 0.4
|
||||||
|
|
||||||
return level + (1 - level) * total_adjustment
|
return level + (1 - level) * total_adjustment
|
||||||
|
|
||||||
@@ -329,9 +346,10 @@ def compare_adjustments(df, fundamentals):
|
|||||||
|
|
||||||
def analyze_trends(df):
|
def analyze_trends(df):
|
||||||
"""
|
"""
|
||||||
Analyze Bitcoin price trends using log returns with asymmetric dampening.
|
Analyze Bitcoin price trends using log returns with S2F awareness.
|
||||||
"""
|
"""
|
||||||
df = df.copy()
|
df = df.copy()
|
||||||
|
fundamentals = MarketFundamentals()
|
||||||
|
|
||||||
# Get halving dates and calculate cycle position
|
# Get halving dates and calculate cycle position
|
||||||
halving_dates = get_halving_dates()
|
halving_dates = get_halving_dates()
|
||||||
@@ -340,41 +358,63 @@ def analyze_trends(df):
|
|||||||
)
|
)
|
||||||
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int)
|
df["Cycle_Days"] = (df["Cycle_Position"] * 4 * 365).round().astype(int)
|
||||||
|
|
||||||
# Calculate log returns
|
# Calculate S2F metrics for each date
|
||||||
|
supply_metrics = [
|
||||||
|
fundamentals.calculate_supply_metrics(date) for date in df["Date"]
|
||||||
|
]
|
||||||
|
df["S2F_Ratio"] = [m["stock_to_flow"] for m in supply_metrics]
|
||||||
|
df["S2F_Change"] = df["S2F_Ratio"].pct_change()
|
||||||
|
|
||||||
|
# Calculate log returns and basic cycle returns
|
||||||
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()
|
||||||
|
|
||||||
# Group by position in cycle
|
# Calculate cycle-based returns
|
||||||
position_returns = df.groupby("Cycle_Days")["Log_Return"].mean()
|
position_returns = df.groupby("Cycle_Days")["Log_Return"].mean()
|
||||||
|
|
||||||
# Simple moving average smoothing
|
# Calculate S2F impact on returns
|
||||||
|
s2f_impact = df.groupby("Cycle_Days")["S2F_Change"].mean()
|
||||||
|
|
||||||
|
# Smooth both components
|
||||||
window = 60
|
window = 60
|
||||||
smoothed_returns = position_returns.rolling(
|
smoothed_cycle_returns = position_returns.rolling(
|
||||||
window=window,
|
window=window,
|
||||||
center=True,
|
center=True,
|
||||||
min_periods=int(window / 2),
|
min_periods=int(window / 2),
|
||||||
).mean()
|
).mean()
|
||||||
|
|
||||||
# Fill any NaN values at the edges
|
smoothed_s2f_impact = s2f_impact.rolling(
|
||||||
smoothed_returns = smoothed_returns.fillna(method="bfill").fillna(method="ffill")
|
window=window,
|
||||||
|
center=True,
|
||||||
|
min_periods=int(window / 2),
|
||||||
|
).mean()
|
||||||
|
|
||||||
# Apply asymmetric dampening to extreme values
|
# Fill NaN values
|
||||||
returns_std = smoothed_returns.std()
|
smoothed_cycle_returns = smoothed_cycle_returns.fillna(method="bfill").fillna(
|
||||||
|
method="ffill"
|
||||||
|
)
|
||||||
|
smoothed_s2f_impact = smoothed_s2f_impact.fillna(method="bfill").fillna(
|
||||||
|
method="ffill"
|
||||||
|
)
|
||||||
|
|
||||||
def asymmetric_dampen(x):
|
# Combine cycle returns with S2F impact
|
||||||
if x > 2 * returns_std:
|
s2f_weight = 0.3 # Adjustable parameter
|
||||||
return x * 0.6 # Stronger dampening for positive extremes
|
combined_returns = (
|
||||||
elif x < -2 * returns_std:
|
smoothed_cycle_returns * (1 - s2f_weight) + smoothed_s2f_impact * s2f_weight
|
||||||
return x * 0.7 # Slightly less dampening for negative extremes
|
)
|
||||||
return x
|
|
||||||
|
|
||||||
smoothed_returns = smoothed_returns.map(asymmetric_dampen)
|
# Apply dampening using current market metrics
|
||||||
|
latest_metrics = fundamentals.get_market_maturity_metrics(df, df["Date"].max())
|
||||||
|
market_adjustment = fundamentals.calculate_volatility_adjustment(latest_metrics)
|
||||||
|
|
||||||
# Additional dampening based on absolute magnitude
|
def adaptive_dampen(x):
|
||||||
magnitude_factor = 0.9 # Global dampening factor
|
if x > 2 * combined_returns.std():
|
||||||
smoothed_returns = smoothed_returns * magnitude_factor
|
return x * (0.6 * market_adjustment)
|
||||||
|
elif x < -2 * combined_returns.std():
|
||||||
|
return x * (0.7 * market_adjustment)
|
||||||
|
return x * market_adjustment
|
||||||
|
|
||||||
return smoothed_returns
|
return combined_returns.map(adaptive_dampen)
|
||||||
|
|
||||||
|
|
||||||
def calculate_adaptive_volatility(
|
def calculate_adaptive_volatility(
|
||||||
@@ -615,56 +655,44 @@ def calculate_market_conditions(df, lookback_window=180):
|
|||||||
|
|
||||||
|
|
||||||
def get_projection_adjustments(days_forward, current_cycle_position, df):
|
def get_projection_adjustments(days_forward, current_cycle_position, df):
|
||||||
"""
|
"""Enhanced projection adjustments using market fundamentals."""
|
||||||
Enhanced projection adjustments with dynamic uncertainty scaling.
|
|
||||||
"""
|
|
||||||
adjustments = np.ones(days_forward)
|
adjustments = np.ones(days_forward)
|
||||||
|
fundamentals = MarketFundamentals()
|
||||||
|
|
||||||
# Get market condition metrics
|
# Pre-calculate metrics
|
||||||
conditions = calculate_market_conditions(df)
|
lookback = min(365, len(df))
|
||||||
|
historical_metrics = [
|
||||||
|
fundamentals.get_market_maturity_metrics(df, date)
|
||||||
|
for date in df["Date"].tail(lookback)
|
||||||
|
]
|
||||||
|
historical_avg_volume_to_supply = np.mean(
|
||||||
|
[m["volume_to_supply"] for m in historical_metrics]
|
||||||
|
)
|
||||||
|
|
||||||
# Base uncertainty varies with market conditions
|
current_metrics = fundamentals.get_market_maturity_metrics(df, df["Date"].max())
|
||||||
base_uncertainty = 0.016 # Standard rate
|
base_uncertainty = 0.016 * (1 + current_metrics["supply_growth_rate"] * 365)
|
||||||
|
vol_factor = 1 + 0.2 * abs(1 - current_metrics["volume_to_supply"] * 50)
|
||||||
|
market_depth_factor = 1 / np.sqrt(1 + current_metrics["market_depth"] * 5)
|
||||||
|
s2f_factor = 1 / np.log1p(current_metrics["stock_to_flow"])
|
||||||
|
regime_scale = np.clip(
|
||||||
|
current_metrics["volume_to_supply"] / historical_avg_volume_to_supply, 0.88, 1.2
|
||||||
|
)
|
||||||
|
|
||||||
# Increase uncertainty if volatility is unusually high or low
|
|
||||||
vol_factor = 1 + 0.2 * abs(1 - conditions["vol_ratio"])
|
|
||||||
|
|
||||||
# Increase uncertainty during strong trends (both up and down)
|
|
||||||
trend_factor = 1 + 0.15 * abs(conditions["trend_strength"])
|
|
||||||
|
|
||||||
# Increase uncertainty during significant drawdowns
|
|
||||||
drawdown_factor = 1 + 0.25 * conditions["drawdown"]
|
|
||||||
|
|
||||||
# Combine factors with cycle position
|
|
||||||
if current_cycle_position > 0.75:
|
|
||||||
cycle_factor = 1.15 # Higher uncertainty late in cycle
|
|
||||||
else:
|
|
||||||
cycle_factor = 1.0
|
|
||||||
|
|
||||||
# Calculate time-varying uncertainty
|
|
||||||
for i in range(days_forward):
|
for i in range(days_forward):
|
||||||
# Conservative growth with cycle and condition awareness
|
|
||||||
time_factor = min(
|
time_factor = min(
|
||||||
1
|
1
|
||||||
+ (i / 365)
|
+ (i / 365)
|
||||||
* base_uncertainty
|
* base_uncertainty
|
||||||
* vol_factor
|
* vol_factor
|
||||||
* trend_factor
|
* market_depth_factor
|
||||||
* drawdown_factor,
|
* s2f_factor,
|
||||||
1.20,
|
1.20,
|
||||||
)
|
)
|
||||||
|
|
||||||
# Update cycle position for this future point
|
|
||||||
cycle_position = (current_cycle_position + i / 1460) % 1
|
cycle_position = (current_cycle_position + i / 1460) % 1
|
||||||
if cycle_position > 0.75:
|
local_cycle_factor = 1.15 if cycle_position > 0.75 else 1.0
|
||||||
local_cycle_factor = 1.15
|
|
||||||
else:
|
|
||||||
local_cycle_factor = 1.0
|
|
||||||
|
|
||||||
# Apply all factors including the initial cycle factor
|
adjustments[i] = time_factor * local_cycle_factor * regime_scale
|
||||||
adjustments[i] = time_factor * local_cycle_factor * cycle_factor
|
|
||||||
|
|
||||||
# Add minimum floor to prevent overconfidence
|
|
||||||
adjustments[i] = max(adjustments[i], 1.02 + (i / 365) * 0.01)
|
adjustments[i] = max(adjustments[i], 1.02 + (i / 365) * 0.01)
|
||||||
|
|
||||||
return adjustments
|
return adjustments
|
||||||
@@ -765,21 +793,34 @@ def project_prices(
|
|||||||
|
|
||||||
# Calculate confidence intervals
|
# Calculate confidence intervals
|
||||||
for level in confidence_levels:
|
for level in confidence_levels:
|
||||||
# Get market metrics for confidence interval adjustment
|
|
||||||
metrics = fundamentals.get_market_maturity_metrics(df, current_date)
|
metrics = fundamentals.get_market_maturity_metrics(df, current_date)
|
||||||
|
|
||||||
# Calculate adjusted confidence level
|
# Calculate intervals by projection horizon
|
||||||
effective_level = fundamentals.calculate_confidence_adjustment(metrics, level)
|
lower_bounds = []
|
||||||
|
upper_bounds = []
|
||||||
|
|
||||||
lower_percentile = (1 - effective_level) * 100 / 2
|
for day in range(days_forward):
|
||||||
upper_percentile = 100 - lower_percentile
|
time_factor = (
|
||||||
|
0.85 if day > 365 else 0.9 if day > 180 else 0.95 if day > 90 else 1.0
|
||||||
|
)
|
||||||
|
|
||||||
results[f"Lower_{int(level*100)}"] = np.percentile(
|
effective_level = (
|
||||||
simulated_paths, lower_percentile, axis=1
|
fundamentals.calculate_confidence_adjustment(metrics, level)
|
||||||
)
|
* time_factor
|
||||||
results[f"Upper_{int(level*100)}"] = np.percentile(
|
)
|
||||||
simulated_paths, upper_percentile, axis=1
|
|
||||||
)
|
lower_percentile = (1 - effective_level) * 100 / 2
|
||||||
|
upper_percentile = 100 - lower_percentile
|
||||||
|
|
||||||
|
lower_bounds.append(
|
||||||
|
np.percentile(simulated_paths[day, :], lower_percentile)
|
||||||
|
)
|
||||||
|
upper_bounds.append(
|
||||||
|
np.percentile(simulated_paths[day, :], upper_percentile)
|
||||||
|
)
|
||||||
|
|
||||||
|
results[f"Lower_{int(level*100)}"] = lower_bounds
|
||||||
|
results[f"Upper_{int(level*100)}"] = upper_bounds
|
||||||
|
|
||||||
return results
|
return results
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user