Code app.py: import os
import datetime
import time
import json
from flask import Flask, render_template, request, redirect, url_for, session, send_from_directory
from flask_session import Session  # Import Flask-Session
from binance.client import Client
from binance.exceptions import BinanceAPIException
import pandas as pd
import plotly.graph_objs as go
from plotly.offline import plot
from werkzeug.utils import secure_filename


# Initialize the Flask application
app = Flask(__name__)


# Set a secret key for sessions
app.secret_key = 'your_secret_key_here'  # Replace with your secret key


# Configure server-side sessions
app.config['SESSION_TYPE'] = 'filesystem'  # Use filesystem-based sessions
app.config['SESSION_PERMANENT'] = False
app.config['SESSION_FILE_DIR'] = os.path.join(app.root_path, 'flask_session')
Session(app)  # Initialize the extension


# Configure Binance Client (No API key needed for public data)
client = Client()


# Default Symbols and Timeframes
default_symbols = [
    'BTCUSDT', 'ETHUSDT', 'XRPUSDT', 'DOGEUSDT', 'SOLUSDT',
    'BNBUSDT', 'SUIUSDT', 'CKBUSDT', 'FTMUSDT', 'RUNEUSDT',
    'UNIUSDT', 'AAVEUSDT', 'BCHUSDT', 'KEYUSDT', '1INCHUSDT',
    'NEARUSDT', 'ADAUSDT', 'ATOMUSDT', 'LTCUSDT', 'STXUSDT',
    'RENDERUSDT', 'FETUSDT', 'CRVUSDT', 'RSRUSDT', 'MINAUSDT',
    'WOOUSDT', 'WIFUSDT', 'CELOUSDT', 'CFXUSDT', 'GALAUSDT',
    'INJUSDT', 'IOTXUSDT', 'DYDXUSDT', 'XECUSDT', 'GASUSDT',
    'CAKEUSDT', 'SCUSDT', 'DOTUSDT', 'AVAXUSDT', 'VETUSDT',
    'ICPUSDT', 'FILUSDT', 'ROSEUSDT', 'ARBUSDT', 'RAYUSDT',
    'OPUSDT', 'LPTUSDT', 'TUSDT', 'MASKUSDT', 'BLURUSDT',
    'ZECUSDT', 'ARKMUSDT', 'ZRXUSDT', 'TIAUSDT', 'KSMUSDT',
    'EOSUSDT', 'BOMEUSDT', 'ETCUSDT', 'GRTUSDT', 'ZKUSDT',
    'MANAUSDT', 'JSTUSDT', 'TWTUSDT', 'ENJUSDT', 'JUPUSDT',
    'ARUSDT', 'BONKUSDT', 'SANDUSDT', 'ENSUSDT', 'AXSUSDT',
    'KAVAUSDT', 'JASMYUSDT', 'DASHUSDT', 'AXLUSDT', 'ORDIUSDT',
    'LUNAUSDT', 'PYTHUSDT', '1000SATSUSDT', 'ZROUSDT', 'NOTUSDT',
    'FLOKIUSDT', 'BEAMXUSDT', 'DEXEUSDT', 'COMPUSDT', 'SHIBUSDT'
]


default_timeframes = ['30m', '1h', '2h', '4h', '1d']


# Rate limit parameters
API_RATE_LIMIT = 150  # Max 150 requests per minute
REQUEST_INTERVAL = 60 / API_RATE_LIMIT  # Interval between requests


# Upload configuration
app.config['UPLOAD_FOLDER'] = os.path.join(app.root_path, 'uploads')
app.config['MAX_CONTENT_LENGTH'] = 16 * 1024 * 1024  # Limit upload size to 16MB


# Ensure the upload folder exists
if not os.path.exists(app.config['UPLOAD_FOLDER']):
    os.makedirs(app.config['UPLOAD_FOLDER'])


# Allowed file extensions
ALLOWED_EXTENSIONS = {'png', 'jpg', 'jpeg', 'gif'}


def allowed_file(filename):
    return '.' in filename and \
           filename.rsplit('.', 1)[1].lower() in ALLOWED_EXTENSIONS


# Function to fetch historical candlestick data
def get_historical_klines(symbol, interval, limit=150):
    try:
        # Sleep to respect the API rate limit
        time.sleep(REQUEST_INTERVAL)
        klines = client.get_klines(symbol=symbol, interval=interval, limit=limit)
        df = pd.DataFrame(klines, columns=[
            'timestamp', 'open', 'high', 'low', 'close', 'volume',
            'close_time', 'quote_asset_volume', 'number_of_trades',
            'taker_buy_base', 'taker_buy_quote', 'ignore'
        ])
        df['timestamp'] = pd.to_datetime(df['timestamp'], unit='ms')
        df[['open', 'high', 'low', 'close']] = df[['open', 'high', 'low', 'close']].astype(float)
        return df
    except BinanceAPIException as e:
        print(f"Error fetching data for {symbol}: {e}")
        return None


# Function to identify demand zones
def identify_demand_zones(df):
    demand_zones = []
    max_base_length = 6  # Maximum number of base candles
    min_base_length = 1  # Minimum number of base candles


    for i in range(1, len(df) - min_base_length - 1):
        # Check if the prior candle is a rally (bullish candle)
        if df['close'].iloc[i - 1] <= df['open'].iloc[i - 1]:
            continue  # Not a rally, skip to next index


        # Try base lengths from 1 to 6
        for base_length in range(min_base_length, min(max_base_length + 1, len(df) - i - 1)):
            base_end_index = i + base_length - 1
            next_candle_index = base_end_index + 1


            if next_candle_index >= len(df):
                break  # Reached the end of the DataFrame


            # Check if the next candle is a rally (bullish candle)
            if df['close'].iloc[next_candle_index] <= df['open'].iloc[next_candle_index]:
                continue  # Next candle is not a rally


            # Check base candles criteria
            base_candles = df.iloc[i:base_end_index + 1]
            base_bodies = abs(base_candles['close'] - base_candles['open'])


            # Ensure no base candle body is equal to or larger than rally candles
            body_rally_before = abs(df['close'].iloc[i - 1] - df['open'].iloc[i - 1])
            body_rally_after = abs(df['close'].iloc[next_candle_index] - df['open'].iloc[next_candle_index])


            valid_base = True
            for base_body in base_bodies:
                if (base_body >= body_rally_before) or (base_body >= body_rally_after):
                    valid_base = False
                    break  # Base candle body is not strictly smaller


            if not valid_base:
                continue  # Base candles do not meet criteria


            # If all base candles are valid, identify the zone
            lower_bound = base_candles['low'].min()
            upper_bound = base_candles[['open', 'close']].max().max()


            zone = {
                'start_time': df['timestamp'].iloc[i - 1],  # Start from the rally before the base
                'end_time': df['timestamp'].iloc[next_candle_index],  # End at the rally after the base
                'upper_bound': upper_bound,
                'lower_bound': lower_bound,
                'type': 'RBR',
                'formation_end_index': next_candle_index  # For checking re-entries after formation
            }


            # Avoid duplicate zones
            if zone not in demand_zones:
                demand_zones.append(zone)


    return demand_zones


# Function to identify supply zones
def identify_supply_zones(df):
    supply_zones = []
    max_base_length = 6  # Maximum number of base candles
    min_base_length = 1  # Minimum number of base candles


    for i in range(1, len(df) - min_base_length - 1):
        # Check if the prior candle is a drop (bearish candle)
        if df['close'].iloc[i - 1] >= df['open'].iloc[i - 1]:
            continue  # Not a drop, skip to next index


        # Try base lengths from 1 to 6
        for base_length in range(min_base_length, min(max_base_length + 1, len(df) - i - 1)):
            base_end_index = i + base_length - 1
            next_candle_index = base_end_index + 1


            if next_candle_index >= len(df):
                break  # Reached the end of the DataFrame


            # Check if the next candle is a drop (bearish candle)
            if df['close'].iloc[next_candle_index] >= df['open'].iloc[next_candle_index]:
                continue  # Next candle is not a drop


            # Check base candles criteria
            base_candles = df.iloc[i:base_end_index + 1]
            base_bodies = abs(base_candles['close'] - base_candles['open'])


            # Ensure no base candle body is equal to or larger than drop candles
            body_drop_before = abs(df['close'].iloc[i - 1] - df['open'].iloc[i - 1])
            body_drop_after = abs(df['close'].iloc[next_candle_index] - df['open'].iloc[next_candle_index])


            valid_base = True
            for base_body in base_bodies:
                if (base_body >= body_drop_before) or (base_body >= body_drop_after):
                    valid_base = False
                    break  # Base candle body is not strictly smaller


            if not valid_base:
                continue  # Base candles do not meet criteria


            # If all base candles are valid, identify the zone
            upper_bound = base_candles['high'].max()
            lower_bound = base_candles[['open', 'close']].min().min()


            zone = {
                'start_time': df['timestamp'].iloc[i - 1],  # Start from the drop before the base
                'end_time': df['timestamp'].iloc[next_candle_index],  # End at the drop after the base
                'upper_bound': upper_bound,
                'lower_bound': lower_bound,
                'type': 'DBD',
                'formation_end_index': next_candle_index  # For checking re-entries after formation
            }


            # Avoid duplicate zones
            if zone not in supply_zones:
                supply_zones.append(zone)


    return supply_zones


# Function to check if the zone is fresh, tested, or violated
def check_zone_freshness(df, zone):
    # Start checking from the candle after the formation ends
    formation_end_idx = zone['formation_end_index']
    zone_df = df.iloc[formation_end_idx + 1:]


    if zone_df.empty:
        zone['status'] = 'Fresh'
        return zone


    # Check if price has re-entered the zone after the formation
    reentries = zone_df[
        (zone_df['high'] >= zone['lower_bound']) & (zone_df['low'] <= zone['upper_bound'])
    ]


    if reentries.empty:
        zone['status'] = 'Fresh'
    else:
        # For supply zone
        if zone['type'] in ['DBD', 'RBD']:
            # Check if the zone is violated (price closed above upper bound)
            violations = reentries[reentries['close'] > zone['upper_bound']]
            if not violations.empty:
                zone['status'] = 'Violated'
            else:
                zone['status'] = 'Tested'
        # For demand zone
        elif zone['type'] in ['RBR', 'DBR']:
            # Check if the zone is violated (price closed below lower_bound)
            violations = reentries[reentries['close'] < zone['lower_bound']]
            if not violations.empty:
                zone['status'] = 'Violated'
            else:
                zone['status'] = 'Tested'
        else:
            zone['status'] = 'Unknown'
    return zone


# Function to plot the candlestick chart with supply and demand zones
def plot_chart(df, zones, symbol, interval):
    fig = go.Figure(data=[go.Candlestick(
        x=df['timestamp'],
        open=df['open'],
        high=df['high'],
        low=df['low'],
        close=df['close'],
        name='Candlesticks'
    )])


    # Add zones
    for zone in zones:
        if zone['status'] == 'Fresh':
            if zone['type'] in ['DBD', 'RBD']:
                color = 'red'  # Supply zone color
            else:
                color = 'green'  # Demand zone color
            opacity = 0.2
        elif zone['status'] == 'Tested':
            if zone['type'] in ['DBD', 'RBD']:
                color = 'orange'  # Supply zone tested color
            else:
                color = 'lightgreen'  # Demand zone tested color
            opacity = 0.2
        else:
            # Skip violated zones
            continue


        fig.add_shape(type='rect',
                      x0=zone['start_time'], y0=zone['lower_bound'],
                      x1=df['timestamp'].iloc[-1], y1=zone['upper_bound'],
                      fillcolor=color, opacity=opacity,
                      line=dict(width=0))


        fig.add_annotation(
            x=zone['start_time'],
            y=zone['upper_bound'],
            xref="x",
            yref="y",
            text=f"{zone['type']} ({zone['status']})",
            showarrow=True,
            arrowhead=1,
            font=dict(color=color)
        )


    fig.update_layout(title=f"{symbol} - {interval} Chart with Supply and Demand Zones",
                      xaxis_title='Time',
                      yaxis_title='Price',
                      xaxis_rangeslider_visible=False)


    return plot(fig, output_type='div')


# Define the Flask routes
@app.route('/', methods=['GET', 'POST'])
def index():
    if request.method == 'POST':
        # Get user inputs for symbols and timeframes
        symbol_input = request.form.get('symbols', '')
        interval_input = request.form.getlist('intervals')


        # Process the symbols input into a list
        user_symbols = [s.strip().upper() for s in symbol_input.split(',') if s.strip()]
        # Use default symbols if none provided
        if not user_symbols:
            user_symbols = default_symbols


        # Process the intervals input
        user_timeframes = interval_input or default_timeframes


        # Get user inputs for zone counts and inclusion of tested zones
        try:
            fresh_supply_count = int(request.form.get('fresh_supply_count', '2'))
            fresh_demand_count = int(request.form.get('fresh_demand_count', '2'))
            tested_supply_count = int(request.form.get('tested_supply_count', '1'))
            tested_demand_count = int(request.form.get('tested_demand_count', '1'))
        except ValueError:
            fresh_supply_count = 2
            fresh_demand_count = 2
            tested_supply_count = 1
            tested_demand_count = 1


        include_tested_supply = request.form.get('include_tested_supply') == 'on'
        include_tested_demand = request.form.get('include_tested_demand') == 'on'


        # Save to session
        session['symbols'] = user_symbols
        session['timeframes'] = user_timeframes
        session['fresh_supply_count'] = fresh_supply_count
        session['fresh_demand_count'] = fresh_demand_count
        session['tested_supply_count'] = tested_supply_count
        session['tested_demand_count'] = tested_demand_count
        session['include_tested_supply'] = include_tested_supply
        session['include_tested_demand'] = include_tested_demand


        # Now proceed to scan the selected symbols and intervals
        results = []  # To store the findings
        total_combinations = len(user_symbols) * len(user_timeframes)
        processed = 0
        for symbol in user_symbols:
            for interval in user_timeframes:
                # Fetch data
                df = get_historical_klines(symbol, interval, limit=150)
                if df is not None:
                    # Identify supply and demand zones
                    supply_zones = identify_supply_zones(df)
                    demand_zones = identify_demand_zones(df)
                    all_zones = supply_zones + demand_zones


                    # Check zone freshness
                    all_zones = [check_zone_freshness(df, zone) for zone in all_zones if zone is not None]


                    # Filter out violated zones
                    valid_zones = [zone for zone in all_zones if zone['status'] != 'Violated']


                    # Proceed only if there are valid zones
                    if valid_zones:
                        # Sort zones by start_time in descending order to get the most recent zones first
                        valid_zones.sort(key=lambda x: x['start_time'], reverse=True)


                        # Get supply zones
                        supply_zones_list = [zone for zone in valid_zones if zone['type'] in ['DBD', 'RBD']]


                        # Get demand zones
                        demand_zones_list = [zone for zone in valid_zones if zone['type'] in ['RBR', 'DBR']]


                        # Apply user's preferences for supply zones
                        fresh_supply_zones = [zone for zone in supply_zones_list if zone['status'] == 'Fresh'][:fresh_supply_count]


                        if include_tested_supply:
                            tested_supply_zones = [zone for zone in supply_zones_list if zone['status'] == 'Tested'][:tested_supply_count]
                        else:
                            tested_supply_zones = []


                        # Apply user's preferences for demand zones
                        fresh_demand_zones = [zone for zone in demand_zones_list if zone['status'] == 'Fresh'][:fresh_demand_count]


                        if include_tested_demand:
                            tested_demand_zones = [zone for zone in demand_zones_list if zone['status'] == 'Tested'][:tested_demand_count]
                        else:
                            tested_demand_zones = []


                        # Combine the selected zones
                        selected_zones = fresh_supply_zones + tested_supply_zones + fresh_demand_zones + tested_demand_zones


                        # Append results for symbol and interval
                        results.append({
                            'symbol': symbol,
                            'interval': interval,
                            'zones': selected_zones
                        })
                else:
                    print(f"Data not available for {symbol} on timeframe {interval}")
                processed += 1
                print(f"Processed {processed}/{total_combinations}")
        # After processing, group the results by symbol
        grouped_results = {}
        for result in results:
            symbol = result['symbol']
            if symbol not in grouped_results:
                grouped_results[symbol] = []
            grouped_results[symbol].append({
                'interval': result['interval'],
                'zones': result['zones']
            })
        session['grouped_results'] = grouped_results  # Save results in session
        return redirect(url_for('results'))
    else:
        return render_template('index.html', default_symbols=', '.join(default_symbols),
                               default_timeframes=default_timeframes,
                               current_year=datetime.datetime.now().year)


@app.route('/results')
def results():
    grouped_results = session.get('grouped_results', {})
    return render_template('results.html', grouped_results=grouped_results,
                           current_year=datetime.datetime.now().year)


@app.route('/charts/<symbol>', methods=['GET'])
def symbol_charts(symbol):
    # Retrieve intervals from session
    user_timeframes = session.get('timeframes', default_timeframes)
    # Retrieve user preferences
    fresh_supply_count = session.get('fresh_supply_count', 2)
    fresh_demand_count = session.get('fresh_demand_count', 2)
    tested_supply_count = session.get('tested_supply_count', 1)
    tested_demand_count = session.get('tested_demand_count', 1)
    include_tested_supply = session.get('include_tested_supply', True)
    include_tested_demand = session.get('include_tested_demand', True)


    # Generate charts for all intervals for this symbol
    charts = []
    for interval in user_timeframes:
        df = get_historical_klines(symbol, interval, limit=150)
        if df is not None:
            # Identify zones as before
            supply_zones = identify_supply_zones(df)
            demand_zones = identify_demand_zones(df)
            all_zones = supply_zones + demand_zones


            # Check zone freshness
            all_zones = [check_zone_freshness(df, zone) for zone in all_zones if zone is not None]


            # Filter out violated zones
            valid_zones = [zone for zone in all_zones if zone['status'] != 'Violated']


            # Sort zones by start_time in descending order to get the most recent zones first
            valid_zones.sort(key=lambda x: x['start_time'], reverse=True)


            # Get supply zones
            supply_zones_list = [zone for zone in valid_zones if zone['type'] in ['DBD', 'RBD']]


            # Get demand zones
            demand_zones_list = [zone for zone in valid_zones if zone['type'] in ['RBR', 'DBR']]


            # Apply user's preferences for supply zones
            fresh_supply_zones = [zone for zone in supply_zones_list if zone['status'] == 'Fresh'][:fresh_supply_count]


            if include_tested_supply:
                tested_supply_zones = [zone for zone in supply_zones_list if zone['status'] == 'Tested'][:tested_supply_count]
            else:
                tested_supply_zones = []


            # Apply user's preferences for demand zones
            fresh_demand_zones = [zone for zone in demand_zones_list if zone['status'] == 'Fresh'][:fresh_demand_count]


            if include_tested_demand:
                tested_demand_zones = [zone for zone in demand_zones_list if zone['status'] == 'Tested'][:tested_demand_count]
            else:
                tested_demand_zones = []


            # Combine the selected zones
            selected_zones = fresh_supply_zones + tested_supply_zones + fresh_demand_zones + tested_demand_zones


            chart_div = plot_chart(df, selected_zones, symbol, interval)
            charts.append({'interval': interval, 'chart_div': chart_div})
        else:
            print(f"Data not available for {symbol} on timeframe {interval}")
    return render_template('symbol_charts.html', symbol=symbol, charts=charts,
                           current_year=datetime.datetime.now().year)


# Route to handle file uploads
@app.route('/uploads/<filename>')
def uploaded_file(filename):
    return send_from_directory(app.config['UPLOAD_FOLDER'], filename)


# Route to submit formations (learning)
@app.route('/learn', methods=['GET', 'POST'])
def learn():
    if request.method == 'POST':
        # Get the number of base candles
        try:
            base_candles_count = int(request.form.get('base_candles', '1'))
        except ValueError:
            base_candles_count = 1


        # Total candles include preceding and following candles
        total_candles = base_candles_count + 2


        # Process the submitted formation data
        formation_type = request.form.get('formation_type')


        # Collect candle data based on the total number of candles
        candles = []
        for i in range(1, total_candles + 1):
            date = request.form.get(f'date_{i}')
            open_price = request.form.get(f'open_{i}')
            high = request.form.get(f'high_{i}')
            low = request.form.get(f'low_{i}')
            close = request.form.get(f'close_{i}')
            direction = request.form.get(f'direction_{i}')
            # If the date or open price is missing, skip this candle
            if not date or not open_price:
                continue
            candle = {
                'date': date,
                'open': float(open_price),
                'high': float(high),
                'low': float(low),
                'close': float(close),
                'direction': direction
            }
            candles.append(candle)


        # Handle file upload (existing code)
        file = request.files.get('screenshot')
        screenshot_filename = None
        if file and allowed_file(file.filename):
            filename = secure_filename(file.filename)
            screenshot_filename = f"{datetime.datetime.now().strftime('%Y%m%d%H%M%S')}_{filename}"
            file.save(os.path.join(app.config['UPLOAD_FOLDER'], screenshot_filename))
        else:
            # Handle the case where no file was uploaded or file type is not allowed
            pass  # You might want to set a message or flag here


        # Store the formation data along with screenshot filename
        formation_data = {
            'formation_type': formation_type,
            'base_candles_count': base_candles_count,
            'candles': candles,
            'screenshot': screenshot_filename,
            'timestamp': datetime.datetime.now().isoformat()
        }


        # Save the formation data (existing code)
        formations_file = 'formations.json'
        if os.path.exists(formations_file):
            with open(formations_file, 'r') as f:
                formations = json.load(f)
        else:
            formations = []


        formations.append(formation_data)
        with open(formations_file, 'w') as f:
            json.dump(formations, f, indent=4)


        # Redirect to a success page
        return render_template('learn_success.html', current_year=datetime.datetime.now().year)
    else:
        # Display the form
        return render_template('learn.html', current_year=datetime.datetime.now().year)


# Route to view and manage submitted formations
@app.route('/formations', methods=['GET', 'POST'])
def formations():
    formations_file = 'formations.json'
    if os.path.exists(formations_file):
        with open(formations_file, 'r') as f:
            formations = json.load(f)
    else:
        formations = []


    if request.method == 'POST':
        # Handle deletion of a formation
        formation_index = int(request.form.get('delete_index'))
        if 0 <= formation_index < len(formations):
            # Delete the associated screenshot
            screenshot_filename = formations[formation_index].get('screenshot')
            if screenshot_filename:
                screenshot_path = os.path.join(app.config['UPLOAD_FOLDER'], screenshot_filename)
                if os.path.exists(screenshot_path):
                    os.remove(screenshot_path)
            # Remove the formation
            formations.pop(formation_index)
            # Save the updated formations list
            with open(formations_file, 'w') as f:
                json.dump(formations, f, indent=4)


    return render_template('formations.html', formations=formations, current_year=datetime.datetime.now().year)


# Run the Flask application
if __name__ == '__main__':
    app.run(debug=True)


Style.css: 

/* style.css */


body {
    font-family: Arial, sans-serif;
    background-color: #f3f5f7;
    margin: 0;
    padding: 0;
}


header {
    background-color: #2c3e50;
    color: #fff;
    padding: 15px 0;
    text-align: center;
}


h1 {
    margin: 0;
    font-size: 24px;
}


.container {
    width: 90%;
    max-width: 800px;
    margin: 20px auto;
    padding: 20px 0;
}


form {
    background-color: #fff;
    padding: 20px;
    border-radius: 5px;
}


label {
    display: block;
    margin-top: 15px;
    font-weight: bold;
}


input[type="text"],
textarea,
input[type="number"],
select {
    width: 100%;
    padding: 8px;
    margin-top: 5px;
    box-sizing: border-box;
    border: 1px solid #ccc;
    border-radius: 3px;
}


input[type="checkbox"] {
    margin-right: 5px;
}


input[type="submit"] {
    background-color: #3498db;
    color: #fff;
    border: none;
    padding: 12px 20px;
    font-size: 16px;
    cursor: pointer;
    margin-top: 20px;
    border-radius: 5px;
    width: 100%;
}


input[type="submit"]:hover {
    background-color: #2980b9;
}


table {
    width: 100%;
    border-collapse: collapse;
    margin-top: 20px;
    background-color: #fff;
}


table th,
table td {
    padding: 12px;
    border: 1px solid #ccc;
    text-align: left;
}


table th {
    background-color: #eaeaea;
}


a {
    color: #3498db;
    text-decoration: none;
}


a:hover {
    text-decoration: underline;
}


.chart-container {
    margin-bottom: 50px;
}


footer {
    background-color: #2c3e50;
    color: #fff;
    padding: 10px 0;
    text-align: center;
    margin-top: 50px;
}


fieldset {
    border: 1px solid #ccc;
    padding: 10px;
    margin-top: 15px;
}


legend {
    font-weight: bold;
}


@media (max-width: 600px) {
    header h1 {
        font-size: 20px;
    }


    input[type="submit"] {
        font-size: 14px;
    }
}


Learn_success.html: 
<!DOCTYPE html>
<html>
<head>
    <title>Learning Success</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>Thank You!</h1>
    </header>
    <div class="container">
        <p>Your formation has been recorded and will help optimize the algorithm.</p>
        <p>
            <a href="{{ url_for('formations') }}">View Your Submitted Formations</a> |
            <a href="{{ url_for('index') }}">Back to Home</a>
        </p>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>


Learn.html: <!DOCTYPE html>
<html>
<head>
    <title>Learn Formation</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <!-- Include JavaScript to dynamically show/hide candle fields -->
    <script>
        function updateCandleFields() {
            const baseCandles = parseInt(document.getElementById('base_candles').value) || 1;
            const totalCandles = baseCandles + 2; // Including preceding and following candles
            const maxCandles = 10; // Assuming maximum of 10 candles
            for (let i = 1; i <= maxCandles; i++) {
                const fieldset = document.getElementById('candle_' + i);
                if (i <= totalCandles) {
                    fieldset.style.display = 'block';
                } else {
                    fieldset.style.display = 'none';
                }
                // Update the legend or label to indicate the type of candle
                const legend = fieldset.querySelector('legend');
                if (i === 1) {
                    legend.textContent = 'Preceding Candle (Rally/Drop Before Base)';
                } else if (i > 1 && i <= baseCandles + 1) {
                    legend.textContent = 'Base Candle ' + (i - 1);
                } else if (i === baseCandles + 2) {
                    legend.textContent = 'Following Candle (Rally/Drop After Base)';
                } else {
                    // Do nothing
                }
            }
        }
        window.onload = updateCandleFields;
    </script>
</head>
<body>
    <header>
        <h1>Learn Formation</h1>
    </header>
    <div class="container">
        <form action="/learn" method="post" enctype="multipart/form-data">
            <label for="formation_type">Formation Type:</label>
            <select name="formation_type" id="formation_type">
                <option value="RBR">Rally-Base-Rally</option>
                <option value="DBR">Drop-Base-Rally</option>
                <option value="RBD">Rally-Base-Drop</option>
                <option value="DBD">Drop-Base-Drop</option>
            </select>


            <label for="base_candles">Number of Base Candles:</label>
            <input type="number" name="base_candles" id="base_candles" min="1" max="6" value="1" onchange="updateCandleFields()">


            <h3>Candles:</h3>
            {% for i in range(1, 11) %}
                <fieldset id="candle_{{ i }}" style="display:none;">
                    <legend>Candle {{ i }}</legend>
                    <label for="date_{{ i }}">Date:</label>
                    <input type="text" name="date_{{ i }}" id="date_{{ i }}">
                    <label for="open_{{ i }}">Open:</label>
                    <input type="text" name="open_{{ i }}" id="open_{{ i }}">
                    <label for="high_{{ i }}">High:</label>
                    <input type="text" name="high_{{ i }}" id="high_{{ i }}">
                    <label for="low_{{ i }}">Low:</label>
                    <input type="text" name="low_{{ i }}" id="low_{{ i }}">
                    <label for="close_{{ i }}">Close:</label>
                    <input type="text" name="close_{{ i }}" id="close_{{ i }}">
                    <label for="direction_{{ i }}">Direction:</label>
                    <select name="direction_{{ i }}" id="direction_{{ i }}">
                        <option value="▲">Bullish (▲)</option>
                        <option value="▼">Bearish (▼)</option>
                    </select>
                </fieldset>
            {% endfor %}


            <label for="screenshot">Upload Screenshot:</label>
            <input type="file" name="screenshot" id="screenshot" accept="image/*">


            <input type="submit" value="Submit">
        </form>
        <br>
        <a href="{{ url_for('index') }}">Back to Home</a>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>


Index.html: <!DOCTYPE html>
<html>
<head>
    <title>Supply and Demand Zone Finder</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>Supply and Demand Zone Finder</h1>
    </header>
    <div class="container">
        <form action="/" method="post">
            <label for="symbols">Symbols (comma-separated):</label>
            <textarea name="symbols" id="symbols" rows="4">{{ default_symbols }}</textarea>


            <label for="intervals">Timeframes:</label>
            <div>
                {% for t in default_timeframes %}
                    <input type="checkbox" name="intervals" value="{{ t }}" checked> {{ t }}
                {% endfor %}
            </div>


            <h3>Zone Display Preferences:</h3>


            <label>Number of Fresh Supply Zones:</label>
            <input type="number" name="fresh_supply_count" value="2" min="0">


            <input type="checkbox" name="include_tested_supply" checked>
            <label>Include Tested Supply Zones</label>
            <label>Number of Tested Supply Zones:</label>
            <input type="number" name="tested_supply_count" value="1" min="0">


            <label>Number of Fresh Demand Zones:</label>
            <input type="number" name="fresh_demand_count" value="2" min="0">


            <input type="checkbox" name="include_tested_demand" checked>
            <label>Include Tested Demand Zones</label>
            <label>Number of Tested Demand Zones:</label>
            <input type="number" name="tested_demand_count" value="1" min="0">


            <input type="submit" value="Scan">
        </form>
        <p>
            Want to contribute to improving the algorithm? <a href="{{ url_for('learn') }}">Submit a Formation</a>
        </p>
        <p>
            <a href="{{ url_for('formations') }}">View Your Submitted Formations</a>
        </p>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>


Formations.html: <!DOCTYPE html>
<html>
<head>
    <title>Your Formations</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>Your Submitted Formations</h1>
    </header>
    <div class="container">
        {% if formations %}
            <table>
                <tr>
                    <th>Timestamp</th>
                    <th>Formation Type</th>
                    <th>Number of Base Candles</th>
                    <th>Screenshot</th>
                    <th>Actions</th>
                </tr>
                {% for formation in formations %}
                    <tr>
                        <td>{{ formation.timestamp }}</td>
                        <td>{{ formation.formation_type }}</td>
                        <td>{{ formation.base_candles_count }}</td>
                        <td>
                            {% if formation.screenshot %}
                                <img src="{{ url_for('uploaded_file', filename=formation.screenshot) }}" width="100">
                            {% else %}
                                No Screenshot
                            {% endif %}
                        </td>
                        <td>
                            <form action="/formations" method="post" style="display:inline;">
                                <input type="hidden" name="delete_index" value="{{ loop.index0 }}">
                                <input type="submit" value="Delete" onclick="return confirm('Are you sure you want to delete this formation?')">
                            </form>
                        </td>
                    </tr>
                {% endfor %}
            </table>
        {% else %}
            <p>You have not submitted any formations.</p>
        {% endif %}
        <br>
        <a href="{{ url_for('index') }}">Back to Home</a>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>


Symbol_charts.html: <!DOCTYPE html>
<html>
<head>
    <title>{{ symbol }} Charts</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
    <!-- Include Plotly.js CDN for charts -->
    <script src="https://cdn.plot.ly/plotly-latest.min.js"></script>
</head>
<body>
    <header>
        <h1>{{ symbol }} Charts</h1>
    </header>
    <div class="container">
        {% for chart in charts %}
            <div class="chart-container">
                <h2>Interval: {{ chart.interval }}</h2>
                {{ chart.chart_div|safe }}
            </div>
        {% endfor %}
        <br>
        <a href="{{ url_for('results') }}">Back to Results</a> |
        <a href="{{ url_for('index') }}">Back to Home</a>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>


Results.html:  <!DOCTYPE html>
<html>
<head>
    <title>Scan Results</title>
    <link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='style.css') }}">
</head>
<body>
    <header>
        <h1>Scan Results</h1>
    </header>
    <div class="container">
        {% if grouped_results %}
            <table>
                <tr>
                    <th>Symbol</th>
                    <th>Actions</th>
                </tr>
                {% for symbol, intervals_list in grouped_results.items() %}
                    <tr>
                        <td>{{ symbol }}</td>
                        <td><a href="{{ url_for('symbol_charts', symbol=symbol) }}">View All Charts</a></td>
                    </tr>
                {% endfor %}
            </table>
        {% else %}
            <p>No valid zones found. Try adjusting your selection.</p>
        {% endif %}
        <br>
        <a href="{{ url_for('index') }}">Back to Home</a>
    </div>
    <footer>
        &copy; {{ current_year }} Supply and Demand Zone Finder
    </footer>
</body>
</html>