For camgirls bitcoin is a good way to get your money transferred across borders without hassle. But when you receive it from a fan or a camsite, because many camsites now will payout in BTC, the question always is whether to cash it in right away, or to hold it.

The coin is famous for going up and down violently, and if it goes down you loose your hard earned cash, but at the same time when it goes up you can can double your money or more.

What is likely then? It depends on when, and what is happening but there is a reasonable history of going up over the long term.

Hence the graph above, which is also a good exercise for me in using my rather rusty coding skills to crunch some data and make it visible as a chart.

Want to see the code? I’m sure you do if you are as geeky as me.

import pandas as pd
import numpy as np
import matplotlib.pyplot as plt
from scipy import stats
from datetime import datetime, timezone



genesis_date = datetime(2009, 1, 3, tzinfo=timezone.utc)

def calculate_ceiling_price(log_days, log_prices):
    """Calculate the ceiling price trend line."""
    i = 0
    slope, intercept, r_value, _, _ = stats.linregress(log_days, log_prices)
    r_squared = r_value**2
    num_points = len(log_days)
    print(f"Ceiling Iteration {i+1}: slope = {slope:.5f}, intercept = {intercept:.5f}, R-squared = {r_squared:.5f}, Num points = {num_points}")

    while r_squared < 0.99 and len(log_days) > 0 and len(log_prices) > 0:
        mask = log_prices > (slope * log_days + intercept)
        log_days = log_days[mask]
        log_prices = log_prices[mask]
        
        if len(log_days) < 2:  # Check if we have enough points to continue
            print("Not enough points to continue. Exiting loop.")
            break
        
        slope, intercept, r_value, _, _ = stats.linregress(log_days, log_prices)
        r_squared = r_value**2
        num_points = len(log_days)
        i += 1
        print(f"Ceiling Iteration {i+1}: slope = {slope:.5f}, intercept = {intercept:.5f}, R-squared = {r_squared:.5f}, Num points = {num_points}")

    return slope, intercept, r_value

def calculate_floor_price(log_days, log_prices):
    """Calculate the floor price trend line."""
    i = 0
    slope, intercept, r_value, _, _ = stats.linregress(log_days, log_prices)
    r_squared = r_value**2
    num_points = len(log_days)
    print(f"Floor Iteration {i+1}: slope = {slope:.5f}, intercept = {intercept:.5f}, R-squared = {r_squared:.5f}, Num points = {num_points}")

    while r_squared < 0.995 and len(log_days) > 0 and len(log_prices) > 0:
        mask = log_prices < (slope * log_days + intercept)
        log_days = log_days[mask]
        log_prices = log_prices[mask]
        
        if len(log_days) < 2:  # Check if we have enough points to continue
            print("Not enough points to continue. Exiting loop.")
            break
        
        slope, intercept, r_value, _, _ = stats.linregress(log_days, log_prices)
        r_squared = r_value**2
        num_points = len(log_days)
        i += 1
        print(f"Floor Iteration {i+1}: slope = {slope:.5f}, intercept = {intercept:.5f}, R-squared = {r_squared:.5f}, Num points = {num_points}")

    return slope, intercept, r_value

def read_btc_price_history():
    df = pd.read_csv('btcpricehistory.csv')
    return df


def find_peaks_and_lows_by_line_crossing(df, slope, intercept, floor_slope, floor_intercept, ceiling_slope, ceiling_intercept, exit_tolerance_days=30):
    log_days = np.log(df['BlockDay'])
    trend_prices = np.exp(slope * log_days + intercept)
    floor_prices = np.exp(floor_slope * log_days + floor_intercept)
    ceiling_prices = np.exp(ceiling_slope * log_days + ceiling_intercept)

    peaks = []
    lows = []

    in_peak_range = False
    in_low_range = False
    days_outside_peak_range = 0
    days_outside_low_range = 0
    
    current_peak_range_start_index = None
    current_low_range_start_index = None



    for i in range(len(df)):
        price = df['Close'].iloc[i]
        date = df['Date'].iloc[i]

        # Peak range entry/exit logic
        if price > ceiling_prices[i]:  # In peak range
            if not in_peak_range:
                in_peak_range = True
                current_peak_range_start_index = i  # Store start index of range

            days_outside_peak_range = 0 # reset as we are still in the peak zone
        elif in_peak_range:  # Potentially exiting peak range
            days_outside_peak_range += 1
            if days_outside_peak_range >= exit_tolerance_days:
                in_peak_range = False
                # Find highest price within the range and mark the date
                peak_index = df['Close'].iloc[current_peak_range_start_index:i].idxmax()
                peaks.append(df['Date'].iloc[peak_index]) # append just one peak date to the peaks list
        
                
        # Low range entry/exit logic (similar)
        if price < floor_prices[i]:  # In low range
            if not in_low_range:
                in_low_range = True
                current_low_range_start_index = i
            days_outside_low_range = 0
        elif in_low_range: # Potentially exiting low range
            days_outside_low_range += 1
            if days_outside_low_range >= exit_tolerance_days:
                in_low_range = False
                # Find lowest price within the range
                low_index = df['Close'].iloc[current_low_range_start_index:i].idxmin()
                lows.append(df['Date'].iloc[low_index])
                

    return peaks, lows

def plot_btc_price_history(df):
    # Create a new figure with a size of 14x10 inches
    plt.figure(figsize=(14, 10))

    # Convert the 'Date' column to datetime objects
    df['Date'] = pd.to_datetime(df['Date'])
    
    # Define the start date for Bitcoin
    start_date = pd.to_datetime('2009-01-03')
    
    # Calculate the number of days since the start date for each row
    df['BlockDay'] = (df['Date'] - start_date).dt.days

    # Filter out any rows with negative BlockDay (shouldn't happen, but good practice)
    df = df[df['BlockDay'] >= 0]

    # Plot the actual Bitcoin price data on a log-log scale
    plt.loglog(df['BlockDay'], df['Close'], label='Bitcoin Price')

    # Calculate the logarithm of BlockDay and Close price for trend line calculation
    log_days = np.log(df['BlockDay'])
    log_prices = np.log(df['Close'])

    # Calculate the main trend line using linear regression
    slope, intercept, r_value, _, std_err = stats.linregress(log_days, log_prices)

    # Find the maximum BlockDay in the data
    max_block_day = df['BlockDay'].max()
    # Ensure the minimum BlockDay is at least 1 to avoid log(0) errors.
    min_block_day = max(1, df['BlockDay'].min())
    # Create an array of days for plotting the trend lines extending into the future.
    all_days = np.arange(min_block_day, max_block_day + (365 * 8))
    # Calculate the logarithm of the days for the trend line.
    x_fit = np.log(all_days)

    # Calculate the y-values for the trend line using the slope and intercept
    y_fit = slope * x_fit + intercept

    # Plot the trend line
    plt.plot(all_days, np.exp(y_fit), 'r-', label='Trend Line')

    # Calculate and Plot Floor Price Trend Line
    floor_slope, floor_intercept, floor_r_value = calculate_floor_price(log_days, log_prices)
    y_floor = floor_slope * x_fit + floor_intercept
    plt.plot(all_days, np.exp(y_floor), 'g-', label='Floor Price')

    # Calculate and Plot Ceiling Price Trend Line
    ceiling_slope, ceiling_intercept, ceiling_r_value = calculate_ceiling_price(log_days, log_prices)
    y_ceiling = ceiling_slope * x_fit + ceiling_intercept
    plt.plot(all_days, np.exp(y_ceiling), 'b-', label='Ceiling Price')

    # Create boolean masks to identify points above the ceiling and below the floor
    ceiling_mask = df['Close'] > np.exp(ceiling_slope * log_days + ceiling_intercept)
    floor_mask = df['Close'] < np.exp(floor_slope * log_days + floor_intercept)

     # Find peaks and lows 
    peak_dates, low_dates = find_peaks_and_lows_by_line_crossing(df, slope, intercept, floor_slope, floor_intercept, ceiling_slope, ceiling_intercept)
    dates_to_mark = sorted(peak_dates + low_dates)
    peaks_and_lows = sorted(peak_dates + low_dates)
    
    peaks_and_lows = sorted(peak_dates + low_dates)


    # Add vertical lines and annotations for peaks and lows
    for date in peaks_and_lows:

        days_since_start = (pd.to_datetime(date) - start_date).days
        plt.axvline(x=days_since_start, color='gray', linestyle='--', alpha=0.7)

        price_at_date = df[df['Date'] == date]['Close'].values[0]

        va = 'bottom' if date in peak_dates else 'top'
        y_offset = 10 if date in peak_dates else -10

        plt.annotate(f"${price_at_date:,.0f}", xy=(days_since_start, price_at_date), xytext=(10, y_offset), textcoords='offset points', ha='left', va=va, bbox=dict(boxstyle='round,pad=0.5', fc='yellow', alpha=0.5), arrowprops=dict(arrowstyle='->', connectionstyle='arc3,rad=0'))
        plt.text(days_since_start, plt.ylim()[0], date.strftime('%Y-%m-%d'), rotation=90, va='bottom', ha='right')
        
    # Highlight ceiling and floor points using scatter plots
    plt.scatter(df['BlockDay'][ceiling_mask], df['Close'][ceiling_mask], color='blue', alpha=0.7, s=30, label='Ceiling Points')
    plt.scatter(df['BlockDay'][floor_mask], df['Close'][floor_mask], color='green', alpha=0.7, s=30, label='Floor Points')


    plt.yscale('log') # Set y-axis to logarithmic scale


    plt.xlabel('Using 2009-01-03 as day 1')
    plt.ylabel('Price (USD)')
    plt.title('BTC Price, Trend, Floor, and Ceiling Prices based on Giovanni Santostasi\'s Power Law concept')

    # Set x-axis ticks to halving dates
    halving_block_days = [(pd.to_datetime(date) - start_date).days for date in dates_to_mark]
    plt.xticks(halving_block_days, [date.strftime('%Y-%m') for date in dates_to_mark], rotation=45)  # Format date labels

    # Annotations for formulas and R-squared values (top-left)
    plt.figtext(
        0.02, 0.98, 
        r"Trend: $y = e^{%.5f \cdot \ln(x) + %.5f}$, $R^2 = %.5f$" % (slope, intercept, r_value**2), 
        fontsize=8, va='top', ha='left'
    )
    plt.figtext(
        0.02, 0.95, 
        r"Floor: $y = e^{%.5f \cdot \ln(x) + %.5f}$, $R^2 = %.5f$" % (floor_slope, floor_intercept, floor_r_value**2),
        fontsize=8, va='top', ha='left'
    )
    plt.figtext(
        0.02, 0.92, 
        r"Ceiling: $y = e^{%.5f \cdot \ln(x) + %.5f}$, $R^2 = %.5f$" % (ceiling_slope, ceiling_intercept, ceiling_r_value**2),
        fontsize=8, va='top', ha='left'
    )

    # Copyright text (mid-right)
    plt.figtext(0.75, 0.5, "Chart by me", fontsize=10, ha='center', va='center', alpha=0.7) # Adjust position and alpha as needed

    plt.grid(axis='y', which='both', color='gray', linestyle='--', linewidth=0.5, alpha=0.7)


    plt.legend() # Show the legend

    plt.savefig('btc_price_graph-history_plot.png')
    print("Saved btc_price_graph-history_plot.png")


    return 


            

def main():
    df = read_btc_price_history()
    plot_btc_price_history(df)

if __name__ == '__main__':
    main()

Yes, messy code and as my Python isn’t very good I’m using AI to generate a lot of it so don’t judge.

The upside of using AI when coding is it helps with all the syntax I don’t know. As an old time programmer, I know the concepts of what I want and the AI can turn that into working code. But the bad part is it writes code I don’t really understand. But in this case I’m outputting a chart and so I can visually tell if it makes sense so that’s ok.

Do you use Python to create Bitcoin charts? Let me know in the comments and we can share some code!