0% found this document useful (0 votes)
23 views110 pages

ALGO1

The document outlines a Flask application for trading strategies using the Alpaca API, featuring endpoints for market status, trading symbols, historical data, account information, and strategy management. It includes utility modules for backtesting and data fetching, and supports saving and loading trading strategies. The application is structured with directories for data, static files, templates, and utility scripts, and employs logging for error handling and debugging.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
0% found this document useful (0 votes)
23 views110 pages

ALGO1

The document outlines a Flask application for trading strategies using the Alpaca API, featuring endpoints for market status, trading symbols, historical data, account information, and strategy management. It includes utility modules for backtesting and data fetching, and supports saving and loading trading strategies. The application is structured with directories for data, static files, templates, and utility scripts, and employs logging for error handling and debugging.
Copyright
© © All Rights Reserved
We take content rights seriously. If you suspect this is your content, claim it here.
Available Formats
Download as PDF, TXT or read online on Scribd
You are on page 1/ 110

ALGO...

├── data

│ ├── saved_strategies/

│ └── historical_data.csv

├── static

│ ├── CSS

│ │ └── style.css

│ └── js

│ └── script.js

├── templates

│ └── index.html

├── utils

│ ├── __pycache__/

│ ├── __init__.py

│ ├── backtest_engine.py

│ ├── data_fetcher.py

│ └── strategy_parser.py

├── .env

├── app.py

└── requirements.txt

App.py

import os
import json

import time

import logging

from flask import Flask, render_template, request, jsonify

from dotenv import load_dotenv

import alpaca_trade_api as tradeapi

from utils.backtest_engine import BacktestEngine

from utils.data_fetcher import DataFetcher

from utils.strategy_parser import StrategyParser

# Configure logging

logging.basicConfig(level=logging.INFO)

logger = logging.getLogger(__name__)

# Load environment variables

load_dotenv()

# Initialize Flask app

app = Flask(__name__)

# Get Alpaca credentials from environment variables

ALPACA_API_KEY = os.getenv('APCA_API_KEY_ID')

ALPACA_SECRET_KEY = os.getenv('APCA_API_SECRET_KEY')

SECRET_KEY = os.getenv('SECRET_KEY', 'your_default_secret_key')

# Set Flask secret key

app.config['SECRET_KEY'] = SECRET_KEY
# Initialize Alpaca API client

api = tradeapi.REST(

ALPACA_API_KEY,

ALPACA_SECRET_KEY,

base_url='https://paper-api.alpaca.markets'

# Initialize our service classes

data_fetcher = DataFetcher(api)

backtest_engine = BacktestEngine(data_fetcher)

# Ensure strategy save directory exists

os.makedirs('data/saved_strategies', exist_ok=True)

@app.route('/')

def index():

"""Render the main dashboard"""

return render_template('index.html')

@app.route('/api/markets', methods=['GET'])

def get_market_status():

"""Get current market status"""

try:

clock = api.get_clock()

return jsonify({

'is_open': clock.is_open,

'next_open': clock.next_open.isoformat(),

'next_close': clock.next_close.isoformat()
})

except Exception as e:

logger.error(f"Error fetching market status: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/symbols', methods=['GET'])

def get_available_symbols():

"""Get available trading symbols"""

try:

assets = api.list_assets(status='active')

symbols = []

for asset in assets:

if asset.tradable and asset.asset_class == 'us_equity':

symbols.append({

'symbol': asset.symbol,

'name': asset.name

})

# Return the first 100 symbols for performance

return jsonify(symbols[:100])

except Exception as e:

logger.error(f"Error fetching symbols: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/search-symbols', methods=['GET'])

def search_symbols():

"""Search for trading symbols"""

query = request.args.get('query', '').upper()

try:
assets = api.list_assets(status='active')

symbols = [

{"symbol": asset.symbol, "name": asset.name}

for asset in assets

if asset.tradable and asset.asset_class == 'us_equity' and

(query in asset.symbol or (asset.name and query in asset.name.upper()))

return jsonify(symbols[:20]) # Return top 20 matches

except Exception as e:

logger.error(f"Error searching symbols: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/historical-data', methods=['GET'])

def get_historical_data():

"""Get historical price data for a symbol"""

symbol = request.args.get('symbol', 'AAPL')

timeframe = request.args.get('timeframe', '1D')

period = request.args.get('period', '1Y')

try:

data = data_fetcher.get_historical_data(symbol, timeframe, period)

return jsonify(data)

except Exception as e:

logger.error(f"Error fetching historical data: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/account', methods=['GET'])

def get_account():

"""Get account information"""


try:

account = api.get_account()

return jsonify({

'cash': float(account.cash),

'equity': float(account.equity),

'buying_power': float(account.buying_power),

'portfolio_value': float(account.portfolio_value),

'status': account.status

})

except Exception as e:

logger.error(f"Error fetching account: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/positions', methods=['GET'])

def get_positions():

"""Get current positions"""

try:

positions = api.list_positions()

formatted_positions = []

for position in positions:

formatted_positions.append({

'symbol': position.symbol,

'qty': position.qty,

'avg_entry_price': position.avg_entry_price,

'current_price': position.current_price,

'market_value': position.market_value,

'unrealized_pl': position.unrealized_pl,

'unrealized_plpc': position.unrealized_plpc
})

return jsonify(formatted_positions)

except Exception as e:

logger.error(f"Error fetching positions: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/backtest', methods=['POST'])

def run_backtest():

"""Run backtest for a strategy"""

try:

if not request.is_json:

return jsonify({"error": "Invalid content type, JSON required"}), 400

strategy_config = request.get_json()

# Retrieve backtest parameters

symbol = strategy_config.get('symbol', 'AAPL')

start_date = strategy_config.get('startDate', '2024-01-01')

end_date = strategy_config.get('endDate', '2025-04-19')

initial_capital = float(strategy_config.get('capital', 10000))

# Log the received configuration

logger.info(f"Running backtest for {symbol} from {start_date} to {end_date}")

# Get blocks data

blocks = strategy_config.get('blocks')

# Log the blocks for debugging


logger.debug(f"Received blocks: {json.dumps(blocks)}")

# Parse strategy blocks

parser = StrategyParser(blocks)

strategy = parser.parse_blocks()

# Log the parsed strategy

logger.info(f"Parsed strategy: {json.dumps(strategy)}")

# Make sure we have some entry/exit rules

if not strategy['entry_rules'] or not strategy['exit_rules']:

logger.warning("No entry or exit rules found, adding default rules")

# Add default rules if none exist

if not strategy['entry_rules'] and len(strategy['indicators']) > 0:

# Try to create a rule based on the first indicator

for ind in strategy['indicators']:

if ind['type'] == 'SMA':

period = ind['parameters']['period']

strategy['entry_rules'] = [

{'indicator': f"SMA_{period}", 'operator': '>', 'value': 'close'}

break

elif ind['type'] == 'RSI':

period = ind['parameters']['period']

strategy['entry_rules'] = [

{'indicator': f"RSI_{period}", 'operator': '>', 'value': '50'}

]
break

elif ind['type'] == 'EMA':

period = ind['parameters']['period']

strategy['entry_rules'] = [

{'indicator': f"EMA_{period}", 'operator': '>', 'value': 'close'}

break

# Add default exit rules if none exist

if not strategy['exit_rules'] and strategy['entry_rules']:

# Mirror the entry rules with opposite conditions

for rule in strategy['entry_rules']:

exit_rule = rule.copy()

if exit_rule['operator'] == '>':

exit_rule['operator'] = '<'

elif exit_rule['operator'] == '<':

exit_rule['operator'] = '>'

elif exit_rule['operator'] == '>=':

exit_rule['operator'] = '<='

elif exit_rule['operator'] == '<=':

exit_rule['operator'] = '>='

strategy['exit_rules'].append(exit_rule)

# Run the backtest

results = backtest_engine.run_backtest(

strategy=strategy,

symbol=symbol,

start_date=start_date,
end_date=end_date,

initial_capital=initial_capital

# Log the results summary

logger.info(f"Backtest completed: {results['total_trades']} trades,


{results['total_return']}% return")

return jsonify(results)

except Exception as e:

logger.error(f"Error in backtest: {str(e)}", exc_info=True)

return jsonify({'error': str(e)}), 500

@app.route('/api/paper-trade', methods=['POST'])

def submit_paper_trade():

"""Submit a paper trade order"""

try:

if not request.is_json:

return jsonify({"error": "Invalid content type, JSON required"}), 400

order_data = request.json

# Validate required fields

required_fields = ['symbol', 'quantity', 'side']

for field in required_fields:

if field not in order_data:

return jsonify({"error": f"Missing required field: {field}"}), 400


# Submit order to Alpaca

order = api.submit_order(

symbol=order_data.get('symbol'),

qty=order_data.get('quantity'),

side=order_data.get('side', 'buy'),

type=order_data.get('orderType', 'market'),

time_in_force=order_data.get('timeInForce', 'day')

# Return order details

return jsonify({

'order_id': order.id,

'client_order_id': order.client_order_id,

'symbol': order.symbol,

'side': order.side,

'qty': order.qty,

'type': order.type,

'status': order.status

})

except Exception as e:

logger.error(f"Error submitting paper trade: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/save-strategy', methods=['POST'])

def save_strategy():

"""Save a trading strategy configuration as JSON"""

try:
if not request.is_json:

return jsonify({"error": "Invalid content type, JSON required"}), 400

strategy_data = request.get_json()

# Validate required field 'blocks'

if 'blocks' not in strategy_data or not strategy_data['blocks']:

return jsonify({"error": "Strategy blocks are required"}), 400

# Get or generate a strategy name

strategy_name = strategy_data.get('name', f"strategy_{int(time.time())}")

# Clean filename to avoid potential path issues

strategy_name = ''.join(c for c in strategy_name if c.isalnum() or c in '._- ')

# Build the file path

save_dir = 'data/saved_strategies'

filepath = os.path.join(save_dir, f"{strategy_name}.json")

# Save the strategy to file

with open(filepath, 'w') as f:

json.dump(strategy_data, f, indent=4)

logger.info(f"Strategy saved: {filepath}")

return jsonify({

"success": True,

"message": f"Strategy saved as {strategy_name}",


"filename": f"{strategy_name}.json"

})

except Exception as e:

logger.error(f"Error saving strategy: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/load-strategy', methods=['GET'])

def load_strategy():

"""Load a saved trading strategy"""

try:

strategy_name = request.args.get('name')

if not strategy_name:

# If no specific strategy is requested, list available strategies

strategy_files = os.listdir('data/saved_strategies')

strategies = [f.replace('.json', '') for f in strategy_files if f.endswith('.json')]

return jsonify({"strategies": strategies})

filepath = os.path.join('data/saved_strategies', f"{strategy_name}.json")

if not os.path.exists(filepath):

return jsonify({"error": f"Strategy '{strategy_name}' not found"}), 404

with open(filepath, 'r') as f:

strategy_data = json.load(f)

return jsonify(strategy_data)

except Exception as e:
logger.error(f"Error loading strategy: {str(e)}")

return jsonify({'error': str(e)}), 500

@app.route('/api/list-strategies', methods=['GET'])

def list_strategies():

"""List all saved strategies"""

try:

if not os.path.exists('data/saved_strategies'):

return jsonify({"strategies": []})

strategy_files = os.listdir('data/saved_strategies')

strategies = [f.replace('.json', '') for f in strategy_files if f.endswith('.json')]

return jsonify({"strategies": strategies})

except Exception as e:

logger.error(f"Error listing strategies: {str(e)}")

return jsonify({'error': str(e)}), 500

if __name__ == '__main__':

app.run(debug=True, port=5000)

strategyparcer.py

import json

import logging

logger = logging.getLogger(__name__)

class StrategyParser:
"""Parser for block-based trading strategies"""

def __init__(self, blocks):

"""

Initialize with strategy blocks

Args:

blocks: List of block configurations or JSON string

"""

logger.info("Initializing StrategyParser")

# If blocks is a string, try to parse it as JSON

if isinstance(blocks, str):

try:

self.blocks = json.loads(blocks)

logger.info("Parsed blocks from JSON string")

except json.JSONDecodeError:

logger.error("Failed to parse blocks from JSON string")

# Default to empty list if parsing fails

self.blocks = []

else:

self.blocks = blocks

# Initialize lists for storing parsed components

self.indicators = []

self.entry_rules = []

self.exit_rules = []
def parse_blocks(self):

"""

Parse blocks into a strategy configuration

Returns:

dict: Strategy configuration with indicators and rules

"""

logger.info(f"Parsing {len(self.blocks) if self.blocks else 0} blocks")

# Process each block based on its type

if self.blocks:

for block in self.blocks:

# Skip if block is not a dictionary (guard against incorrect input)

if not isinstance(block, dict):

logger.warning(f"Skipping non-dict block: {block}")

continue

block_type = block.get('type', '')

logger.debug(f"Processing block of type: {block_type}")

if block_type == 'indicator':

self._parse_indicator(block)

elif block_type == 'entry':

self._parse_rule(block, is_entry=True)

elif block_type == 'exit':

self._parse_rule(block, is_entry=False)

else:

logger.warning(f"Unknown block type: {block_type}")


# If no indicators were found, add a default SMA indicator

if not self.indicators:

logger.info("No indicators found, adding default SMA(20)")

self.indicators.append({

'type': 'SMA',

'parameters': {

'period': 20,

'price': 'close'

})

# If no entry rules were found, add a default entry rule

if not self.entry_rules:

logger.info("No entry rules found, adding default rule")

indicator_name = "SMA_20" # Default indicator

# Find an indicator we can use for the default rule

for ind in self.indicators:

if ind['type'] == 'SMA':

period = ind['parameters']['period']

indicator_name = f"SMA_{period}"

break

elif ind['type'] == 'RSI':

period = ind['parameters']['period']

indicator_name = f"RSI_{period}"

break
# Add default entry rule based on the indicator

if "RSI" in indicator_name:

self.entry_rules.append({

'indicator': indicator_name,

'operator': '>',

'value': '50'

})

else:

self.entry_rules.append({

'indicator': indicator_name,

'operator': '>',

'value': 'close'

})

# If no exit rules were found, add a default exit rule

if not self.exit_rules:

logger.info("No exit rules found, adding default rule")

# Mirror the first entry rule with opposite condition

if self.entry_rules:

entry_rule = self.entry_rules[0]

exit_rule = entry_rule.copy()

# Flip the operator

if exit_rule['operator'] == '>':

exit_rule['operator'] = '<'

elif exit_rule['operator'] == '<':

exit_rule['operator'] = '>'
elif exit_rule['operator'] == '>=':

exit_rule['operator'] = '<='

elif exit_rule['operator'] == '<=':

exit_rule['operator'] = '>='

self.exit_rules.append(exit_rule)

else:

# Fallback if somehow there are no entry rules

self.exit_rules.append({

'indicator': 'SMA_20',

'operator': '<',

'value': 'close'

})

# Return the complete strategy configuration

strategy = {

'indicators': self.indicators,

'entry_rules': self.entry_rules,

'exit_rules': self.exit_rules

logger.info(f"Parsed strategy with {len(self.indicators)} indicators, {len(self.entry_rules)}


entry rules, and {len(self.exit_rules)} exit rules")

return strategy

def _parse_indicator(self, block):

"""
Parse an indicator block

Args:

block (dict): Block configuration

"""

# Get indicator type, checking both formats that might be used

indicator_type = block.get('indicatorType', '') or block.get('indicator_type', '')

if not indicator_type:

logger.warning("Block missing indicator type")

return

logger.debug(f"Parsing indicator: {indicator_type}")

if indicator_type == 'SMA':

period = int(block.get('period', 20))

self.indicators.append({

'type': 'SMA',

'parameters': {

'period': period,

'price': 'close'

})

logger.info(f"Added SMA indicator with period {period}")

elif indicator_type == 'EMA':

period = int(block.get('period', 20))

self.indicators.append({
'type': 'EMA',

'parameters': {

'period': period,

'price': 'close'

})

logger.info(f"Added EMA indicator with period {period}")

elif indicator_type == 'RSI':

period = int(block.get('period', 14))

self.indicators.append({

'type': 'RSI',

'parameters': {

'period': period,

'price': 'close'

})

logger.info(f"Added RSI indicator with period {period}")

elif indicator_type == 'MACD':

fast_period = int(block.get('fastPeriod', 12))

slow_period = int(block.get('slowPeriod', 26))

signal_period = int(block.get('signalPeriod', 9))

self.indicators.append({

'type': 'MACD',

'parameters': {

'fast_period': fast_period,
'slow_period': slow_period,

'signal_period': signal_period,

'price': 'close'

})

logger.info(f"Added MACD indicator with parameters: fast={fast_period},


slow={slow_period}, signal={signal_period}")

else:

logger.warning(f"Unsupported indicator type: {indicator_type}")

def _parse_rule(self, block, is_entry=True):

"""

Parse a rule block (entry or exit)

Args:

block (dict): Block configuration

is_entry (bool): True if this is an entry rule, False for exit rule

"""

rule_type = "entry" if is_entry else "exit"

logger.debug(f"Parsing {rule_type} rule block")

conditions = block.get('conditions', [])

if not conditions:

logger.warning(f"No conditions found in {rule_type} rule block")

return
for condition in conditions:

if not isinstance(condition, dict):

logger.warning(f"Skipping non-dict condition: {condition}")

continue

# Extract condition parameters

indicator = condition.get('indicator', '')

operator = condition.get('operator', '>')

value = condition.get('value', 0)

if not indicator:

logger.warning(f"Missing indicator in condition")

continue

# Create rule object

rule = {

'indicator': indicator,

'operator': operator,

'value': value

# Add to appropriate rules list

if is_entry:

self.entry_rules.append(rule)

logger.info(f"Added entry rule: {indicator} {operator} {value}")

else:

self.exit_rules.append(rule)

logger.info(f"Added exit rule: {indicator} {operator} {value}")


data.fetcher.py

import pandas as pd

import numpy as np

import logging

from datetime import datetime, timedelta

logger = logging.getLogger(__name__)

class DataFetcher:

"""Class for fetching market data from Alpaca"""

def __init__(self, api):

"""Initialize with an Alpaca API client"""

self.api = api

self.cache = {} # Simple cache for historical data

def get_historical_data(self, symbol, timeframe='1D', period='1Y'):

"""

Get historical price data for a symbol

Args:

symbol (str): Trading symbol (e.g., 'AAPL')

timeframe (str): Timeframe for the data ('1D', '1H', '15Min', '5Min', '1Min')

period (str): Time period to fetch ('1D', '1W', '1M', '3M', '6M', '1Y', '5Y')

Returns:

list: A list of dictionaries containing historical price data


"""

# Generate cache key

cache_key = f"{symbol}_{timeframe}_{period}"

# Return cached data if available and not expired

if cache_key in self.cache:

cache_time, data = self.cache[cache_key]

# Cache for 1 hour

if datetime.now() - cache_time < timedelta(hours=1):

logger.info(f"Using cached data for {cache_key}")

return data

# Calculate start and end dates based on period

end_date = datetime.now()

if period == '1D':

start_date = end_date - timedelta(days=1)

elif period == '1W':

start_date = end_date - timedelta(weeks=1)

elif period == '1M':

start_date = end_date - timedelta(days=30)

elif period == '3M':

start_date = end_date - timedelta(days=90)

elif period == '6M':

start_date = end_date - timedelta(days=180)

elif period == '1Y':

start_date = end_date - timedelta(days=365)

elif period == '2Y':


start_date = end_date - timedelta(days=365*2)

else: # '5Y'

start_date = end_date - timedelta(days=365 * 5)

# Format dates for API call

start = start_date.strftime('%Y-%m-%d')

end = end_date.strftime('%Y-%m-%d')

# Map timeframe to Alpaca format

timeframe_map = {

'1Min': '1Min',

'5Min': '5Min',

'15Min': '15Min',

'1H': '1Hour',

'1D': '1Day'

alpaca_timeframe = timeframe_map.get(timeframe, '1Day')

logger.info(f"Fetching {symbol} data from {start} to {end} with timeframe


{alpaca_timeframe}")

# Get data from Alpaca

try:

# Try using the newer bars API first

try:

bars = self.api.get_bars(

symbol=symbol,

timeframe=alpaca_timeframe,
start=start,

end=end,

limit=1000

data = []

for bar in bars:

data.append({

'time': bar.t.strftime('%Y-%m-%d %H:%M:%S'),

'open': bar.o,

'high': bar.h,

'low': bar.l,

'close': bar.c,

'volume': bar.v

})

# Cache the results

self.cache[cache_key] = (datetime.now(), data)

return data

except AttributeError:

# Fall back to older barset API

bars = self.api.get_barset(

symbols=symbol,

timeframe=timeframe,

start=start,

end=end,
limit=1000

# Convert to list of dictionaries

if symbol in bars:

data = []

for bar in bars[symbol]:

data.append({

'time': bar.t.strftime('%Y-%m-%d %H:%M:%S'),

'open': bar.o,

'high': bar.h,

'low': bar.l,

'close': bar.c,

'volume': bar.v

})

# Cache the results

self.cache[cache_key] = (datetime.now(), data)

return data

except Exception as e:

logger.error(f"Error fetching data for {symbol}: {str(e)}")

# Fall back to sample data if the API call fails

logger.warning(f"Falling back to sample data for {symbol}")

sample_data = self._get_sample_data(symbol)
# Cache the sample data too

self.cache[cache_key] = (datetime.now(), sample_data)

return sample_data

def get_real_time_quote(self, symbol):

"""

Get real-time quote for a symbol

Args:

symbol (str): Trading symbol (e.g., 'AAPL')

Returns:

dict: Real-time quote data

"""

try:

# Try using the newer quotes API first

try:

quote = self.api.get_latest_quote(symbol)

return {

'symbol': symbol,

'price': (quote.ap + quote.bp) / 2, # Midpoint price

'ask': quote.ap,

'bid': quote.bp,

'timestamp': quote.t.strftime('%Y-%m-%d %H:%M:%S'),

'size': quote.s

except AttributeError:
# Fall back to bars for paper trading

bars = self.api.get_barset(symbol, 'minute', limit=1)

if symbol in bars and len(bars[symbol]) > 0:

bar = bars[symbol][0]

return {

'symbol': symbol,

'price': bar.c,

'timestamp': bar.t.strftime('%Y-%m-%d %H:%M:%S'),

'volume': bar.v

except Exception as e:

logger.error(f"Error fetching real-time quote for {symbol}: {str(e)}")

# Return None if we couldn't get the data

return None

def _get_sample_data(self, symbol):

"""Generate sample data for testing when API is unavailable"""

# Create a date range for the past year

end_date = datetime.now()

start_date = end_date - timedelta(days=365)

dates = pd.date_range(start=start_date, end=end_date, freq='B') # Business days

# Set a random seed based on the symbol for consistent results

seed = sum(ord(c) for c in symbol)

np.random.seed(seed)

# Generate sample data with realistic price action


base_price = 100.0 + (seed % 400) # Different base price per symbol

volatility = 0.01 + (seed % 100) * 0.0001 # Different volatility per symbol

data = []

current_price = base_price

# Generate an uptrend, downtrend, or sideways pattern

trend = np.random.choice(['up', 'down', 'sideways'])

if trend == 'up':

drift = 0.0005

elif trend == 'down':

drift = -0.0005

else:

drift = 0.0

for date in dates:

# Random daily volatility with drift

price_change = np.random.normal(drift, volatility) * current_price

current_price = max(current_price + price_change, 1.0) # Ensure price doesn't go


below 1

# Daily high and low with realistic relationship to open/close

if np.random.random() > 0.5: # Bullish day

open_price = current_price * (1 - np.random.random() * volatility)

close_price = current_price

high_price = close_price * (1 + np.random.random() * volatility)

low_price = open_price * (1 - np.random.random() * volatility)

else: # Bearish day


open_price = current_price * (1 + np.random.random() * volatility)

close_price = current_price

high_price = open_price * (1 + np.random.random() * volatility)

low_price = close_price * (1 - np.random.random() * volatility)

# Random volume

volume = int(np.random.randint(100000, 10000000))

data.append({

'time': date.strftime('%Y-%m-%d'),

'open': round(open_price, 2),

'high': round(high_price, 2),

'low': round(low_price, 2),

'close': round(close_price, 2),

'volume': volume

})

return data

backtest.py

import pandas as pd

import numpy as np

import logging

from datetime import datetime

logger = logging.getLogger(__name__)

class BacktestEngine:
"""Engine for backtesting trading strategies with Alpaca data"""

def __init__(self, data_fetcher):

"""

Initialize the backtest engine

Args:

data_fetcher: Instance of DataFetcher to get market data

"""

self.data_fetcher = data_fetcher

def run_backtest(self, strategy, symbol='AAPL', start_date=None, end_date=None,


initial_capital=10000.0):

"""

Run a backtest for the given strategy

Args:

strategy (dict): Strategy configuration with indicators and rules

symbol (str): Trading symbol

start_date (str): Start date for backtest (YYYY-MM-DD)

end_date (str): End date for backtest (YYYY-MM-DD)

initial_capital (float): Initial capital amount

Returns:

dict: Backtest results including metrics and trades

"""

# Log the start of backtest

logger.info(f"Starting backtest for {symbol} with {len(strategy['indicators'])} indicators")


# Get historical data

historical_data = self.data_fetcher.get_historical_data(

symbol=symbol,

timeframe='1D', # Daily data for backtesting

period='2Y' # Get enough data for calculations

if not historical_data or len(historical_data) < 30:

logger.warning(f"Insufficient historical data for {symbol}")

return {

'error': f"Insufficient historical data for {symbol}",

'initial_capital': initial_capital,

'final_equity': initial_capital,

'total_return': 0.0,

'sharpe_ratio': 0.0,

'max_drawdown': 0.0,

'total_trades': 0,

'trades': [],

'equity_curve': [initial_capital]

# Convert to DataFrame

df = pd.DataFrame(historical_data)

df['time'] = pd.to_datetime(df['time'])

df.set_index('time', inplace=True)

# Filter by date range if provided


if start_date:

df = df[df.index >= start_date]

if end_date:

df = df[df.index <= end_date]

# Check if we have enough data after filtering

if len(df) < 20:

logger.warning(f"Insufficient data after date filtering")

return {

'error': "Insufficient data after date filtering",

'initial_capital': initial_capital,

'final_equity': initial_capital,

'total_return': 0.0,

'sharpe_ratio': 0.0,

'max_drawdown': 0.0,

'total_trades': 0,

'trades': [],

'equity_curve': [initial_capital]

# Apply indicators based on strategy

df = self._apply_indicators(df, strategy['indicators'])

# Log available indicators after calculation

logger.info(f"Available columns after indicator calculation: {df.columns.tolist()}")

# Initialize variables for simulation

cash = initial_capital
shares = 0

trades = []

equity_curve = [initial_capital]

# Run simulation day by day

for i in range(len(df)):

if i < 20: # Skip first few rows for indicator calculation

continue

date = df.index[i].strftime('%Y-%m-%d')

price = df['close'].iloc[i]

# Get yesterday's row for signal calculation (to avoid lookahead bias)

yesterday_data = df.iloc[i-1]

# Check for NaN values in the row

if yesterday_data.isnull().any():

continue

# Check entry conditions when we have no position

if shares == 0:

# Check if entry conditions are met

entry_signal = self._evaluate_conditions(yesterday_data, strategy['entry_rules'])

if entry_signal:

# Calculate position size (simple approach: use all available cash)

shares_to_buy = int(cash / price)

cost = shares_to_buy * price


if shares_to_buy > 0:

# Log the trade

logger.info(f"BUY signal triggered on {date}: {shares_to_buy} shares at


${price:.2f}")

# Record the trade

trades.append({

'date': date,

'type': 'BUY',

'price': price,

'shares': shares_to_buy,

'value': cost

})

# Update portfolio

cash -= cost

shares = shares_to_buy

# Check exit conditions when we have a position

elif shares > 0:

# Check if exit conditions are met

exit_signal = self._evaluate_conditions(yesterday_data, strategy['exit_rules'])

if exit_signal:

# Calculate sale value

sale_value = shares * price


# Log the trade

logger.info(f"SELL signal triggered on {date}: {shares} shares at ${price:.2f}")

# Record the trade

trades.append({

'date': date,

'type': 'SELL',

'price': price,

'shares': shares,

'value': sale_value

})

# Update portfolio

cash += sale_value

shares = 0

# Update equity curve

current_equity = cash + (shares * price)

equity_curve.append(current_equity)

# Calculate performance metrics

initial_equity = equity_curve[0]

final_equity = equity_curve[-1]

total_return = ((final_equity / initial_equity) - 1) * 100

# Calculate other metrics

daily_returns = np.diff(equity_curve) / equity_curve[:-1]

sharpe_ratio = 0.0
if len(daily_returns) > 0 and np.std(daily_returns) > 0:

sharpe_ratio = np.mean(daily_returns) / np.std(daily_returns) * np.sqrt(252)

max_drawdown = self._calculate_max_drawdown(equity_curve)

# Log backtest summary

logger.info(f"Backtest completed: {len(trades)} trades, {total_return:.2f}% return")

# Prepare and return results

return {

'initial_capital': initial_capital,

'final_equity': round(final_equity, 2),

'total_return': round(total_return, 2),

'sharpe_ratio': round(sharpe_ratio, 2),

'max_drawdown': round(max_drawdown, 2),

'total_trades': len(trades),

'trades': trades,

'equity_curve': [round(eq, 2) for eq in equity_curve]

def _apply_indicators(self, df, indicators):

"""

Apply technical indicators to the DataFrame

Args:

df (DataFrame): Price data

indicators (list): List of indicator configurations


Returns:

DataFrame: DataFrame with indicators added

"""

for indicator in indicators:

try:

if indicator['type'] == 'SMA':

period = indicator['parameters']['period']

df[f'SMA_{period}'] = df['close'].rolling(window=period).mean()

logger.info(f"Calculated SMA_{period}")

elif indicator['type'] == 'EMA':

period = indicator['parameters']['period']

df[f'EMA_{period}'] = df['close'].ewm(span=period, adjust=False).mean()

logger.info(f"Calculated EMA_{period}")

elif indicator['type'] == 'RSI':

period = indicator['parameters']['period']

delta = df['close'].diff()

gain = delta.where(delta > 0, 0).rolling(window=period).mean()

loss = -delta.where(delta < 0, 0).rolling(window=period).mean()

# Avoid division by zero

loss = loss.replace(0, np.nan)

rs = gain / loss

rs = rs.fillna(0)

df[f'RSI_{period}'] = 100 - (100 / (1 + rs))

logger.info(f"Calculated RSI_{period}")
elif indicator['type'] == 'MACD':

fast_period = indicator['parameters']['fast_period']

slow_period = indicator['parameters']['slow_period']

signal_period = indicator['parameters']['signal_period']

# Calculate MACD line

df[f'EMA_{fast_period}'] = df['close'].ewm(span=fast_period,
adjust=False).mean()

df[f'EMA_{slow_period}'] = df['close'].ewm(span=slow_period,
adjust=False).mean()

df['MACD'] = df[f'EMA_{fast_period}'] - df[f'EMA_{slow_period}']

# Calculate signal line

df['MACD_Signal'] = df['MACD'].ewm(span=signal_period, adjust=False).mean()

# Calculate histogram

df['MACD_Hist'] = df['MACD'] - df['MACD_Signal']

logger.info(f"Calculated MACD with parameters: fast={fast_period},


slow={slow_period}, signal={signal_period}")

except Exception as e:

logger.error(f"Error calculating indicator {indicator['type']}: {str(e)}")

return df

def _evaluate_conditions(self, row, conditions):

"""
Evaluate if trading conditions are met

Args:

row (Series): DataFrame row with indicator values

conditions (list): List of condition configurations

Returns:

bool: True if conditions are met, False otherwise

"""

if not conditions:

return False

results = []

for condition in conditions:

indicator = condition['indicator']

operator = condition['operator']

value = condition['value']

# If indicator isn't in the row, skip this condition

if indicator not in row:

logger.warning(f"Indicator '{indicator}' not found in data. Available:


{row.index.tolist()}")

continue

# Get the indicator value

indicator_value = row[indicator]
# Handle None or NaN values

if pd.isna(indicator_value):

logger.warning(f"Indicator '{indicator}' has NaN value")

continue

# Process the value to compare against

compare_value = None

# If value is another column name, get its value

if isinstance(value, str) and value in row:

compare_value = row[value]

else:

# Handle special values

if value == 'close':

compare_value = row['close']

elif value == 'open':

compare_value = row['open']

elif value == 'high':

compare_value = row['high']

elif value == 'low':

compare_value = row['low']

else:

# Try to convert to float

try:

compare_value = float(value)

except (ValueError, TypeError):

logger.warning(f"Could not convert value '{value}' to a number")

continue
# Handle None or NaN values in the comparison value

if pd.isna(compare_value):

logger.warning(f"Comparison value '{value}' is NaN")

continue

# Apply operator

try:

if operator == '>':

results.append(indicator_value > compare_value)

elif operator == '<':

results.append(indicator_value < compare_value)

elif operator == '==':

results.append(indicator_value == compare_value)

elif operator == '>=':

results.append(indicator_value >= compare_value)

elif operator == '<=':

results.append(indicator_value <= compare_value)

else:

logger.warning(f"Unsupported operator: {operator}")

except Exception as e:

logger.error(f"Error evaluating condition: {str(e)}")

continue

# If no conditions could be evaluated, return False

if not results:

return False
# Return True if all conditions are met

return all(results)

def _calculate_max_drawdown(self, equity_curve):

"""

Calculate maximum drawdown percentage

Args:

equity_curve (list): List of equity values over time

Returns:

float: Maximum drawdown percentage

"""

# Convert to numpy array for easier calculations

equity = np.array(equity_curve)

# Calculate the running maximum

running_max = np.maximum.accumulate(equity)

# Calculate the drawdown at each point

drawdowns = (running_max - equity) / running_max

# Handle any NaN values (if running_max is 0)

drawdowns = np.nan_to_num(drawdowns)

# Calculate the maximum drawdown as a percentage

max_drawdown = np.max(drawdowns) * 100


return max_drawdown

utilis/init.py

# Initialize utils package

from utils.backtest_engine import BacktestEngine

from utils.data_fetcher import DataFetcher

from utils.strategy_parser import StrategyParser

__all__ = ['BacktestEngine', 'DataFetcher', 'StrategyParser']

index.html

<!DOCTYPE html>

<html lang="en">

<head>

<meta charset="UTF-8">

<meta name="viewport" content="width=device-width, initial-scale=1.0">

<title>AlgoBlocks - Algorithmic Trading Platform</title>

<!-- Bootstrap CSS -->

<link href="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/css/bootstrap.min.css"
rel="stylesheet">

<!-- Bootstrap Icons -->

<link rel="stylesheet" href="https://cdn.jsdelivr.net/npm/bootstrap-


icons@1.10.0/font/bootstrap-icons.css">

<!-- TradingView Widget -->

<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>

<!-- Custom CSS -->

<link rel="stylesheet" href="{{ url_for('static', filename='css/style.css') }}">

</head>

<body>
<nav class="navbar navbar-expand-lg navbar-dark bg-dark">

<div class="container-fluid">

<a class="navbar-brand" href="#">

<i class="bi bi-bar-chart-fill me-2"></i>AlgoBlocks

</a>

<button class="navbar-toggler" type="button" data-bs-toggle="collapse" data-bs-


target="#navbarNav">

<span class="navbar-toggler-icon"></span>

</button>

<div class="collapse navbar-collapse" id="navbarNav">

<ul class="navbar-nav ms-auto">

<li class="nav-item">

<span class="nav-link" id="marketStatus">Market Status: Loading...</span>

</li>

<li class="nav-item">

<span class="nav-link" id="currentDate"></span>

</li>

</ul>

</div>

</div>

</nav>

<div class="container-fluid">

<div class="row">

<!-- Left Sidebar -->

<div class="col-md-3 col-lg-2 d-md-block bg-light sidebar">

<div class="position-sticky pt-3">

<h5 class="sidebar-heading d-flex justify-content-between align-items-center px-


3 mt-4 mb-1 text-muted">
<span>Strategy Blocks</span>

</h5>

<div class="block-container px-3">

<!-- Indicators -->

<div class="block-group">

<h6 class="fw-bold">Indicators</h6>

<div class="block indicator-block" draggable="true" data-type="indicator"


data-indicator-type="SMA">

<i class="bi bi-graph-up me-1"></i> Simple Moving Average (SMA)

</div>

<div class="block indicator-block" draggable="true" data-type="indicator"


data-indicator-type="EMA">

<i class="bi bi-graph-up-arrow me-1"></i> Exponential Moving Average


(EMA)

</div>

<div class="block indicator-block" draggable="true" data-type="indicator"


data-indicator-type="RSI">

<i class="bi bi-activity me-1"></i> Relative Strength Index (RSI)

</div>

<div class="block indicator-block" draggable="true" data-type="indicator"


data-indicator-type="MACD">

<i class="bi bi-bar-chart-line me-1"></i> MACD

</div>

</div>

<!-- Rules -->

<div class="block-group">

<h6 class="fw-bold">Rules</h6>

<div class="block rule-block entry-block" draggable="true" data-


type="entry">
<i class="bi bi-arrow-up-circle me-1"></i> Entry Rule

</div>

<div class="block rule-block exit-block" draggable="true" data-type="exit">

<i class="bi bi-arrow-down-circle me-1"></i> Exit Rule

</div>

</div>

</div>

<h5 class="sidebar-heading d-flex justify-content-between align-items-center px-


3 mt-4 mb-1 text-muted">

<span>Backtest Settings</span>

</h5>

<form id="backtestForm" class="px-3">

<div class="mb-3">

<label for="symbolSearch" class="form-label">Symbol</label>

<div class="input-group mb-1">

<input type="text" class="form-control" id="symbolSearch"


placeholder="Search symbols...">

<button class="btn btn-outline-secondary" type="button"


id="searchSymbolBtn">

<i class="bi bi-search"></i>

</button>

</div>

<select class="form-select" id="symbol">

<option value="AAPL">AAPL - Apple Inc</option>

<option value="MSFT">MSFT - Microsoft Corp</option>

<option value="GOOGL">GOOGL - Alphabet Inc</option>

<option value="AMZN">AMZN - Amazon.com Inc</option>

<option value="META">META - Meta Platforms Inc</option>


<option value="TSLA">TSLA - Tesla Inc</option>

<option value="NVDA">NVDA - NVIDIA Corp</option>

<option value="JPM">JPM - JPMorgan Chase & Co</option>

<option value="V">V - Visa Inc</option>

<option value="JNJ">JNJ - Johnson & Johnson</option>

</select>

</div>

<div class="mb-3">

<label for="startDate" class="form-label">Start Date</label>

<input type="date" class="form-control" id="startDate">

</div>

<div class="mb-3">

<label for="endDate" class="form-label">End Date</label>

<input type="date" class="form-control" id="endDate">

</div>

<div class="mb-3">

<label for="initialCapital" class="form-label">Initial Capital</label>

<div class="input-group">

<span class="input-group-text">$</span>

<input type="number" class="form-control" id="initialCapital"


value="10000">

</div>

</div>

<button type="button" class="btn btn-primary w-100 mb-2"


id="runBacktestBtn">

<i class="bi bi-play-fill"></i> Run Backtest

</button>

<button type="button" class="btn btn-success w-100" id="paperTradeBtn">

<i class="bi bi-cash-coin"></i> Paper Trade


</button>

</form>

</div>

</div>

<!-- Main Content Area -->

<div class="col-md-9 ms-sm-auto col-lg-10 px-md-4">

<div class="d-flex justify-content-between flex-wrap flex-md-nowrap align-items-


center pt-3 pb-2 mb-3 border-bottom">

<h1 class="h2">Strategy Builder</h1>

<div class="btn-toolbar mb-2 mb-md-0">

<div class="btn-group me-2">

<button type="button" class="btn btn-sm btn-outline-secondary"


id="saveStrategyBtn">

<i class="bi bi-save"></i> Save

</button>

<button type="button" class="btn btn-sm btn-outline-secondary"


id="loadStrategyBtn">

<i class="bi bi-folder2-open"></i> Load

</button>

<button type="button" class="btn btn-sm btn-outline-secondary"


id="clearStrategyBtn">

<i class="bi bi-trash"></i> Clear

</button>

</div>

</div>

</div>

<!-- Strategy Canvas -->


<div class="row mb-4">

<div class="col-12">

<div id="strategyCanvas" class="strategy-canvas">

<div class="canvas-placeholder">

<i class="bi bi-plus-circle-dotted fs-1"></i>

<p>Drag and drop blocks here to build your strategy</p>

</div>

</div>

</div>

</div>

<!-- Chart Container -->

<div class="row mb-4">

<div class="col-12">

<div class="card">

<div class="card-header">

<h5 class="card-title mb-0">Price Chart</h5>

</div>

<div class="card-body p-0">

<div id="tradingViewChart" style="height: 400px;"></div>

</div>

</div>

</div>

</div>

<!-- Results & Metrics -->

<div class="row">

<!-- Performance Metrics -->


<div class="col-md-4 mb-4">

<div class="card">

<div class="card-header">

<h5 class="card-title mb-0">Performance Metrics</h5>

</div>

<div class="card-body">

<div id="performanceMetrics">

<p class="text-muted text-center">

<i class="bi bi-arrow-clockwise"></i> Run a backtest to see metrics

</p>

</div>

</div>

</div>

</div>

<!-- Equity Curve -->

<div class="col-md-8 mb-4">

<div class="card">

<div class="card-header">

<h5 class="card-title mb-0">Equity Curve</h5>

</div>

<div class="card-body">

<canvas id="equityCurveChart" height="250"></canvas>

</div>

</div>

</div>

</div>
<!-- Trades Table -->

<div class="row mb-4">

<div class="col-12">

<div class="card">

<div class="card-header">

<h5 class="card-title mb-0">Trades</h5>

</div>

<div class="card-body">

<div class="table-responsive">

<table class="table table-striped table-sm">

<thead>

<tr>

<th>Date</th>

<th>Type</th>

<th>Price</th>

<th>Shares</th>

<th>Value</th>

</tr>

</thead>

<tbody id="tradesTableBody">

<!-- Trades will be inserted here -->

<tr>

<td colspan="5" class="text-center text-muted">No trades to


display</td>

</tr>

</tbody>

</table>

</div>
</div>

</div>

</div>

</div>

</div>

</div>

</div>

<!-- Block Configuration Modal -->

<div class="modal fade" id="blockConfigModal" tabindex="-1" aria-hidden="true">

<div class="modal-dialog">

<div class="modal-content">

<div class="modal-header">

<h5 class="modal-title">Configure Block</h5>

<button type="button" class="btn-close" data-bs-dismiss="modal" aria-


label="Close"></button>

</div>

<div class="modal-body" id="blockConfigBody">

<!-- Modal content will be dynamically inserted here -->

</div>

<div class="modal-footer">

<button type="button" class="btn btn-secondary" data-bs-


dismiss="modal">Cancel</button>

<button type="button" class="btn btn-primary"


id="saveBlockConfigBtn">Save</button>

</div>

</div>

</div>

</div>
<!-- Strategy Load Modal -->

<div class="modal fade" id="loadStrategyModal" tabindex="-1" aria-hidden="true">

<div class="modal-dialog">

<div class="modal-content">

<div class="modal-header">

<h5 class="modal-title">Load Strategy</h5>

<button type="button" class="btn-close" data-bs-dismiss="modal" aria-


label="Close"></button>

</div>

<div class="modal-body">

<div id="strategyList" class="list-group">

<!-- Strategy list will be inserted here -->

<div class="text-center py-3">

<div class="spinner-border text-primary" role="status"></div>

<p class="mt-2">Loading strategies...</p>

</div>

</div>

</div>

<div class="modal-footer">

<button type="button" class="btn btn-secondary" data-bs-


dismiss="modal">Cancel</button>

</div>

</div>

</div>

</div>

<!-- Paper Trade Modal -->

<div class="modal fade" id="paperTradeModal" tabindex="-1" aria-hidden="true">


<div class="modal-dialog">

<div class="modal-content">

<div class="modal-header">

<h5 class="modal-title">Paper Trade</h5>

<button type="button" class="btn-close" data-bs-dismiss="modal" aria-


label="Close"></button>

</div>

<div class="modal-body">

<form id="paperTradeForm">

<div class="mb-3">

<label for="tradeSymbol" class="form-label">Symbol</label>

<input type="text" class="form-control" id="tradeSymbol" readonly>

</div>

<div class="mb-3">

<label for="tradeQuantity" class="form-label">Quantity</label>

<input type="number" class="form-control" id="tradeQuantity" min="1"


value="1">

</div>

<div class="mb-3">

<label for="tradeSide" class="form-label">Side</label>

<select class="form-select" id="tradeSide">

<option value="buy">Buy</option>

<option value="sell">Sell</option>

</select>

</div>

<div class="mb-3">

<label for="tradeType" class="form-label">Order Type</label>

<select class="form-select" id="tradeType">

<option value="market">Market</option>
<option value="limit">Limit</option>

</select>

</div>

<div class="mb-3 d-none" id="limitPriceGroup">

<label for="limitPrice" class="form-label">Limit Price</label>

<div class="input-group">

<span class="input-group-text">$</span>

<input type="number" class="form-control" id="limitPrice" step="0.01">

</div>

</div>

</form>

</div>

<div class="modal-footer">

<button type="button" class="btn btn-secondary" data-bs-


dismiss="modal">Cancel</button>

<button type="button" class="btn btn-success" id="submitTradeBtn">Submit


Order</button>

</div>

</div>

</div>

</div>

<!-- Bootstrap JS -->

<script
src="https://cdn.jsdelivr.net/npm/bootstrap@5.2.3/dist/js/bootstrap.bundle.min.js"></scrip
t>

<!-- Chart.js -->

<script src="https://cdn.jsdelivr.net/npm/chart.js"></script>

<!-- Custom JavaScript -->


<script src="{{ url_for('static', filename='js/script.js') }}"></script>

</body>

</html>

Script.js

// AlgoBlocks main JavaScript functionality

document.addEventListener('DOMContentLoaded', function() {

// DOM Elements

const symbolSearch = document.getElementById('symbolSearch');

const searchSymbolBtn = document.getElementById('searchSymbolBtn');

const symbolSelect = document.getElementById('symbol');

const startDateInput = document.getElementById('startDate');

const endDateInput = document.getElementById('endDate');

const initialCapitalInput = document.getElementById('initialCapital');

const runBacktestBtn = document.getElementById('runBacktestBtn');

const paperTradeBtn = document.getElementById('paperTradeBtn');

const saveStrategyBtn = document.getElementById('saveStrategyBtn');

const loadStrategyBtn = document.getElementById('loadStrategyBtn');

const clearStrategyBtn = document.getElementById('clearStrategyBtn');

const strategyCanvas = document.getElementById('strategyCanvas');

const performanceMetrics = document.getElementById('performanceMetrics');

const tradesTableBody = document.getElementById('tradesTableBody');

const marketStatus = document.getElementById('marketStatus');

const currentDate = document.getElementById('currentDate');

// Block management

let nextBlockId = 1;

let blocks = [];


let selectedBlock = null;

let chartInstance = null;

let tradingViewWidget = null;

// Initialize date inputs with reasonable defaults

const today = new Date();

const oneYearAgo = new Date();

oneYearAgo.setFullYear(today.getFullYear() - 1);

startDateInput.value = formatDate(oneYearAgo);

endDateInput.value = formatDate(today);

// Display current date

currentDate.textContent = today.toLocaleDateString('en-US', {

weekday: 'long',

year: 'numeric',

month: 'long',

day: 'numeric'

});

// Initialize TradingView Chart

initTradingViewChart(symbolSelect.value);

// Fetch market status

fetchMarketStatus();

setInterval(fetchMarketStatus, 60000); // Update every minute

// Set up drag and drop functionality

setupDragAndDrop();
// Set up button event listeners

runBacktestBtn.addEventListener('click', runBacktest);

paperTradeBtn.addEventListener('click', openPaperTradeModal);

saveStrategyBtn.addEventListener('click', saveStrategy);

loadStrategyBtn.addEventListener('click', openLoadStrategyModal);

clearStrategyBtn.addEventListener('click', confirmClearStrategy);

symbolSelect.addEventListener('change', updateSymbol);

// Set up symbol search

searchSymbolBtn.addEventListener('click', searchSymbols);

symbolSearch.addEventListener('keypress', function(e) {

if (e.key === 'Enter') {

searchSymbols();

e.preventDefault();

});

// Block configuration modal elements and event listener

const blockConfigModal = new


bootstrap.Modal(document.getElementById('blockConfigModal'));

const blockConfigBody = document.getElementById('blockConfigBody');

const saveBlockConfigBtn = document.getElementById('saveBlockConfigBtn');

saveBlockConfigBtn.addEventListener('click', saveBlockConfig);

// Strategy load modal

const loadStrategyModal = new


bootstrap.Modal(document.getElementById('loadStrategyModal'));
// Paper trade modal

const paperTradeModal = new


bootstrap.Modal(document.getElementById('paperTradeModal'));

const tradeTypeSelect = document.getElementById('tradeType');

const limitPriceGroup = document.getElementById('limitPriceGroup');

const submitTradeBtn = document.getElementById('submitTradeBtn');

// Event listener for order type changes

tradeTypeSelect.addEventListener('change', function() {

if (this.value === 'limit') {

limitPriceGroup.classList.remove('d-none');

} else {

limitPriceGroup.classList.add('d-none');

});

// Submit trade button

submitTradeBtn.addEventListener('click', submitPaperTrade);

// Initialize TradingView Chart

function initTradingViewChart(symbol) {

if (tradingViewWidget) {

// If widget already exists, clear the container

document.getElementById('tradingViewChart').innerHTML = '';

tradingViewWidget = new TradingView.widget({

container_id: 'tradingViewChart',
symbol: symbol,

interval: 'D',

timezone: 'Etc/UTC',

theme: 'light',

style: '1',

locale: 'en',

toolbar_bg: '#f1f3f6',

enable_publishing: false,

hide_top_toolbar: false,

hide_legend: false,

save_image: false,

height: '100%',

width: '100%'

});

// Fetch market status

function fetchMarketStatus() {

fetch('/api/markets')

.then(response => response.json())

.then(data => {

if (data.is_open) {

marketStatus.innerHTML = 'Market Status: <span class="badge bg-


success">Open</span>';

} else {

const nextOpen = new Date(data.next_open);

marketStatus.innerHTML = `Market Status: <span class="badge bg-


danger">Closed</span> (Opens ${formatDateTime(nextOpen)})`;

}
})

.catch(error => {

console.error('Error fetching market status:', error);

marketStatus.textContent = 'Market Status: Unknown';

});

// Update TradingView chart symbol

function updateSymbol() {

const symbol = symbolSelect.value;

console.log("Updating chart to symbol:", symbol);

// Re-initialize the TradingView widget with the new symbol

initTradingViewChart(symbol);

// Search for symbols

function searchSymbols() {

const query = symbolSearch.value.trim();

if (query.length < 1) return;

// Show loading state

symbolSelect.innerHTML = '<option value="">Loading...</option>';

fetch(`/api/search-symbols?query=${encodeURIComponent(query)}`)

.then(response => response.json())

.then(data => {

// Clear current options


symbolSelect.innerHTML = '';

if (data.error) {

symbolSelect.innerHTML = `<option value="">Error: ${data.error}</option>`;

return;

// Add new options from search results

if (data.length === 0) {

symbolSelect.innerHTML = '<option value="">No results found</option>';

} else {

data.forEach(item => {

const option = document.createElement('option');

option.value = item.symbol;

option.textContent = `${item.symbol} - ${item.name}`;

symbolSelect.appendChild(option);

});

// Select the first result and update chart

symbolSelect.selectedIndex = 0;

updateSymbol();

})

.catch(error => {

console.error('Error searching symbols:', error);

symbolSelect.innerHTML = '<option value="">Error searching symbols</option>';

});

}
// Setup drag and drop functionality

function setupDragAndDrop() {

const draggableBlocks = document.querySelectorAll('.block');

// Make blocks draggable

draggableBlocks.forEach(block => {

block.addEventListener('dragstart', handleDragStart);

});

// Setup canvas as drop target

strategyCanvas.addEventListener('dragover', handleDragOver);

strategyCanvas.addEventListener('drop', handleDrop);

// Handle drag start

function handleDragStart(e) {

e.dataTransfer.setData('text/plain', JSON.stringify({

type: this.dataset.type,

indicatorType: this.dataset.indicatorType || null

}));

// Add dragging class

this.classList.add('dragging');

// Remove class after drag ends

this.addEventListener('dragend', function() {

this.classList.remove('dragging');
}, { once: true });

// Handle drag over

function handleDragOver(e) {

e.preventDefault();

e.dataTransfer.dropEffect = 'copy';

// Handle drop

function handleDrop(e) {

e.preventDefault();

const canvasRect = strategyCanvas.getBoundingClientRect();

const x = e.clientX - canvasRect.left;

const y = e.clientY - canvasRect.top;

try {

const data = JSON.parse(e.dataTransfer.getData('text/plain'));

createBlock(data.type, data.indicatorType, x, y);

} catch (err) {

console.error('Error parsing drag data:', err);

// Create a block on the canvas

function createBlock(type, indicatorType, x, y) {

const blockId = `block-${nextBlockId++}`;

const block = document.createElement('div');


block.id = blockId;

block.className = `canvas-block ${type}`;

block.style.left = `${x}px`;

block.style.top = `${y}px`;

let blockData = {

id: blockId,

type: type,

x: x,

y: y

};

let blockTitle, blockSettings, blockIcon;

if (type === 'indicator' && indicatorType) {

blockData.indicatorType = indicatorType;

// Set icon based on indicator type

if (indicatorType === 'SMA') {

blockIcon = 'bi-graph-up';

blockData.period = 20;

blockTitle = 'Simple Moving Average (SMA)';

blockSettings = `Period: ${blockData.period}`;

} else if (indicatorType === 'EMA') {

blockIcon = 'bi-graph-up-arrow';

blockData.period = 20;

blockTitle = 'Exponential Moving Average (EMA)';

blockSettings = `Period: ${blockData.period}`;


} else if (indicatorType === 'RSI') {

blockIcon = 'bi-activity';

blockData.period = 14;

blockTitle = 'Relative Strength Index (RSI)';

blockSettings = `Period: ${blockData.period}`;

} else if (indicatorType === 'MACD') {

blockIcon = 'bi-bar-chart-line';

blockData.fastPeriod = 12;

blockData.slowPeriod = 26;

blockData.signalPeriod = 9;

blockTitle = 'MACD';

blockSettings = `Fast: ${blockData.fastPeriod}, Slow: ${blockData.slowPeriod},


Signal: ${blockData.signalPeriod}`;

} else {

blockIcon = 'bi-question-circle';

blockTitle = indicatorType;

blockSettings = '';

} else if (type === 'entry') {

blockIcon = 'bi-arrow-up-circle';

blockData.conditions = [];

blockTitle = 'Entry Rule';

blockSettings = 'No conditions set';

} else if (type === 'exit') {

blockIcon = 'bi-arrow-down-circle';

blockData.conditions = [];

blockTitle = 'Exit Rule';

blockSettings = 'No conditions set';


}

block.innerHTML = `

<div class="block-header">

<span><i class="bi ${blockIcon} me-1"></i> ${blockTitle}</span>

<span class="block-remove" data-id="${blockId}">&times;</span>

</div>

<div class="block-content">

<div class="block-settings">${blockSettings}</div>

</div>

`;

// Make the block draggable within the canvas

block.draggable = true;

// Add drag start handler

block.addEventListener('dragstart', function(e) {

// Store the current position

const rect = this.getBoundingClientRect();

const offsetX = e.clientX - rect.left;

const offsetY = e.clientY - rect.top;

e.dataTransfer.setData('text/plain', JSON.stringify({

id: this.id,

offsetX: offsetX,

offsetY: offsetY

}));

});
// Add drag over handler for the canvas

strategyCanvas.addEventListener('dragover', function(e) {

e.preventDefault();

});

// Add drop handler for the canvas

strategyCanvas.addEventListener('drop', function(e) {

e.preventDefault();

try {

const data = JSON.parse(e.dataTransfer.getData('text/plain'));

// If this is an existing block being moved

if (data.id && document.getElementById(data.id)) {

const block = document.getElementById(data.id);

const canvasRect = strategyCanvas.getBoundingClientRect();

// Calculate new position

const x = e.clientX - canvasRect.left - data.offsetX;

const y = e.clientY - canvasRect.top - data.offsetY;

// Update block position

block.style.left = `${x}px`;

block.style.top = `${y}px`;

// Update position in blocks array

const blockData = blocks.find(b => b.id === data.id);


if (blockData) {

blockData.x = x;

blockData.y = y;

} catch (err) {

console.error('Error processing drag:', err);

});

// Add click event to configure the block

block.addEventListener('click', function() {

const id = this.id;

const block = blocks.find(b => b.id === id);

if (block) {

selectedBlock = block;

showBlockConfig(block);

});

// Add remove button functionality

block.querySelector('.block-remove').addEventListener('click', function(e) {

e.stopPropagation();

removeBlock(this.dataset.id);

});

strategyCanvas.appendChild(block);

blocks.push(blockData);
// Remove the placeholder if it exists

const placeholder = strategyCanvas.querySelector('.canvas-placeholder');

if (placeholder) {

placeholder.style.display = 'none';

// Show block configuration modal

function showBlockConfig(block) {

let content = '';

// Generate form based on block type

if (block.type === 'indicator') {

const indicatorType = block.indicatorType;

if (indicatorType === 'SMA' || indicatorType === 'EMA' || indicatorType === 'RSI') {

content = `

<div class="mb-3">

<label class="form-label">Period</label>

<input type="number" class="form-control" id="config-period"


value="${block.period}" min="1" max="200">

<div class="form-text">Number of periods used in the calculation.</div>

</div>

`;

} else if (indicatorType === 'MACD') {

content = `

<div class="mb-3">
<label class="form-label">Fast Period</label>

<input type="number" class="form-control" id="config-fast-period"


value="${block.fastPeriod}" min="1" max="100">

<div class="form-text">Number of periods for the fast EMA.</div>

</div>

<div class="mb-3">

<label class="form-label">Slow Period</label>

<input type="number" class="form-control" id="config-slow-period"


value="${block.slowPeriod}" min="1" max="100">

<div class="form-text">Number of periods for the slow EMA.</div>

</div>

<div class="mb-3">

<label class="form-label">Signal Period</label>

<input type="number" class="form-control" id="config-signal-period"


value="${block.signalPeriod}" min="1" max="100">

<div class="form-text">Number of periods for the signal line.</div>

</div>

`;

} else if (block.type === 'entry' || block.type === 'exit') {

// Default values for the condition

let indicator = '';

let operator = '>';

let value = '';

// Use existing condition if available

if (block.conditions && block.conditions.length > 0) {

indicator = block.conditions[0].indicator || '';

operator = block.conditions[0].operator || '>';


value = block.conditions[0].value || '';

// Generate options for indicators

let indicatorOptions = '<option value="">Select an indicator</option>';

// Add price options

indicatorOptions += `

<option value="close" ${indicator === 'close' ? 'selected' : ''}>Price (Close)</option>

<option value="open" ${indicator === 'open' ? 'selected' : ''}>Price (Open)</option>

<option value="high" ${indicator === 'high' ? 'selected' : ''}>Price (High)</option>

<option value="low" ${indicator === 'low' ? 'selected' : ''}>Price (Low)</option>

`;

// Add indicator options from blocks

blocks.filter(b => b.type === 'indicator').forEach(b => {

let indicatorId = '';

if (b.indicatorType === 'SMA' || b.indicatorType === 'EMA') {

indicatorId = `${b.indicatorType}_${b.period}`;

} else if (b.indicatorType === 'RSI') {

indicatorId = `RSI_${b.period}`;

} else if (b.indicatorType === 'MACD') {

indicatorId = 'MACD';

// Add MACD components

indicatorOptions += `

<option value="MACD" ${indicator === 'MACD' ? 'selected' : ''}>MACD


Line</option>
<option value="MACD_Signal" ${indicator === 'MACD_Signal' ? 'selected' :
''}>MACD Signal</option>

<option value="MACD_Hist" ${indicator === 'MACD_Hist' ? 'selected' :


''}>MACD Histogram</option>

`;

return; // Skip the default option below

if (indicatorId) {

indicatorOptions += `<option value="${indicatorId}" ${indicator === indicatorId ?


'selected' : ''}>${indicatorId}</option>`;

});

content = `

<div class="mb-3">

<label class="form-label">Indicator</label>

<select class="form-select" id="config-indicator">

${indicatorOptions}

</select>

</div>

<div class="mb-3">

<label class="form-label">Operator</label>

<select class="form-select" id="config-operator">

<option value=">" ${operator === '>' ? 'selected' : ''}>Greater than (>)</option>

<option value=">=" ${operator === '>=' ? 'selected' : ''}>Greater than or equal


to (>=)</option>

<option value="<" ${operator === '<' ? 'selected' : ''}>Less than (<)</option>


<option value="<=" ${operator === '<=' ? 'selected' : ''}>Less than or equal to
(<=)</option>

<option value="==" ${operator === '==' ? 'selected' : ''}>Equal to (==)</option>

</select>

</div>

<div class="mb-3">

<label class="form-label">Value</label>

<div class="input-group">

<input type="text" class="form-control" id="config-value" value="${value}">

<button class="btn btn-outline-secondary dropdown-toggle" type="button"


data-bs-toggle="dropdown">Add</button>

<ul class="dropdown-menu dropdown-menu-end">

<li><h6 class="dropdown-header">Price</h6></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='close'">Close</a></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='open'">Open</a></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='high'">High</a></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='low'">Low</a></li>

<li><hr class="dropdown-divider"></li>

<li><h6 class="dropdown-header">Common Values</h6></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='0'">0</a></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='50'">50</a></li>

<li><a class="dropdown-item cursor-pointer"


onclick="document.getElementById('config-value').value='100'">100</a></li>

</ul>

</div>
<div class="form-text">Enter a number, indicator name, or price point (open,
high, low, close)</div>

</div>

`;

// Update modal content and show it

blockConfigBody.innerHTML = content;

blockConfigModal.show();

// Save block configuration

function saveBlockConfig() {

if (!selectedBlock) return;

// Update block based on type

if (selectedBlock.type === 'indicator') {

if (selectedBlock.indicatorType === 'SMA' || selectedBlock.indicatorType === 'EMA' ||


selectedBlock.indicatorType === 'RSI') {

const period = parseInt(document.getElementById('config-period').value) || 14;

selectedBlock.period = period;

// Update block display

const blockElement = document.getElementById(selectedBlock.id);

blockElement.querySelector('.block-settings').textContent = `Period: ${period}`;

} else if (selectedBlock.indicatorType === 'MACD') {

const fastPeriod = parseInt(document.getElementById('config-fast-period').value) ||


12;
const slowPeriod = parseInt(document.getElementById('config-slow-period').value)
|| 26;

const signalPeriod = parseInt(document.getElementById('config-signal-


period').value) || 9;

selectedBlock.fastPeriod = fastPeriod;

selectedBlock.slowPeriod = slowPeriod;

selectedBlock.signalPeriod = signalPeriod;

// Update block display

const blockElement = document.getElementById(selectedBlock.id);

blockElement.querySelector('.block-settings').textContent = `Fast: ${fastPeriod},


Slow: ${slowPeriod}, Signal: ${signalPeriod}`;

} else if (selectedBlock.type === 'entry' || selectedBlock.type === 'exit') {

const indicator = document.getElementById('config-indicator').value;

const operator = document.getElementById('config-operator').value;

const value = document.getElementById('config-value').value;

// Update or create condition

if (indicator) {

if (selectedBlock.conditions.length === 0) {

selectedBlock.conditions.push({ indicator, operator, value });

} else {

selectedBlock.conditions[0] = { indicator, operator, value };

// Update block display

const blockElement = document.getElementById(selectedBlock.id);


blockElement.querySelector('.block-settings').textContent = `${indicator}
${operator} ${value}`;

blockConfigModal.hide();

// Remove a block from the canvas

function removeBlock(blockId) {

const blockElement = document.getElementById(blockId);

if (blockElement) {

blockElement.remove();

blocks = blocks.filter(block => block.id !== blockId);

// Show placeholder if no blocks left

if (blocks.length === 0) {

const placeholder = strategyCanvas.querySelector('.canvas-placeholder');

if (placeholder) {

placeholder.style.display = 'flex';

// Run backtest with current strategy - FIXED FUNCTION

function runBacktest() {
if (blocks.length === 0) {

alert('Please add at least one block to create a strategy.');

return;

// Make sure we have at least one indicator, one entry rule, and one exit rule

const indicators = blocks.filter(b => b.type === 'indicator');

const entryRules = blocks.filter(b => b.type === 'entry');

const exitRules = blocks.filter(b => b.type === 'exit');

if (indicators.length === 0) {

alert('Please add at least one indicator block.');

return;

if (entryRules.length === 0) {

alert('Please add at least one entry rule block.');

return;

if (exitRules.length === 0) {

alert('Please add at least one exit rule block.');

return;

// Make sure all entry and exit rules have conditions

let unconfiguredRules = [];

entryRules.forEach(rule => {
if (!rule.conditions || rule.conditions.length === 0) {

unconfiguredRules.push(`Entry rule at position (${Math.round(rule.x)},


${Math.round(rule.y)})`);

});

exitRules.forEach(rule => {

if (!rule.conditions || rule.conditions.length === 0) {

unconfiguredRules.push(`Exit rule at position (${Math.round(rule.x)},


${Math.round(rule.y)})`);

});

if (unconfiguredRules.length > 0) {

alert(`Please configure the following rules:\n${unconfiguredRules.join('\n')}`);

return;

// Show loading state

performanceMetrics.innerHTML = '<div class="text-center"><div class="spinner-border


text-primary" role="status"></div><p class="mt-2">Running backtest...</p></div>';

// Send to backend for processing - FIXED: sending the blocks array directly

fetch('/api/backtest', {

method: 'POST',

headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify({
blocks: blocks, // Send the blocks array directly

symbol: symbolSelect.value,

startDate: startDateInput.value,

endDate: endDateInput.value,

capital: initialCapitalInput.value

})

})

.then(response => response.json())

.then(results => {

displayBacktestResults(results);

})

.catch(error => {

console.error('Error running backtest:', error);

performanceMetrics.innerHTML = '<p class="text-danger">Error running backtest.


Please try again.</p>';

});

// Build strategy configuration from blocks

function buildStrategyConfig() {

// Temporary structure for storing indicators

const indicators = [];

const entryRules = [];

const exitRules = [];

// Process indicator blocks

blocks.filter(block => block.type === 'indicator').forEach(block => {

if (block.indicatorType === 'SMA') {


indicators.push({

type: 'SMA',

parameters: {

period: block.period

});

} else if (block.indicatorType === 'EMA') {

indicators.push({

type: 'EMA',

parameters: {

period: block.period

});

} else if (block.indicatorType === 'RSI') {

indicators.push({

type: 'RSI',

parameters: {

period: block.period

});

} else if (block.indicatorType === 'MACD') {

indicators.push({

type: 'MACD',

parameters: {

fast_period: block.fastPeriod,

slow_period: block.slowPeriod,

signal_period: block.signalPeriod

}
});

});

// Process rule blocks

blocks.filter(block => block.type === 'entry').forEach(block => {

block.conditions.forEach(condition => {

entryRules.push(condition);

});

});

blocks.filter(block => block.type === 'exit').forEach(block => {

block.conditions.forEach(condition => {

exitRules.push(condition);

});

});

return {

indicators: indicators,

entry_rules: entryRules,

exit_rules: exitRules

};

// Display backtest results

function displayBacktestResults(results) {

// Generate HTML for performance metrics

let metricsHtml = '';


if (results.error) {

metricsHtml = `<p class="text-danger">${results.error}</p>`;

} else {

metricsHtml = `

<div class="metric-row">

<span class="metric-label">Initial Capital</span>

<span class="metric-value">$${formatNumber(results.initial_capital)}</span>

</div>

<div class="metric-row">

<span class="metric-label">Final Equity</span>

<span class="metric-value">$${formatNumber(results.final_equity)}</span>

</div>

<div class="metric-row">

<span class="metric-label">Total Return</span>

<span class="metric-value ${results.total_return >= 0 ? 'positive' : 'negative'}">

${formatNumber(results.total_return)}%

</span>

</div>

<div class="metric-row">

<span class="metric-label">Sharpe Ratio</span>

<span class="metric-value">${formatNumber(results.sharpe_ratio)}</span>

</div>

<div class="metric-row">

<span class="metric-label">Max Drawdown</span>

<span class="metric-value
negative">${formatNumber(results.max_drawdown)}%</span>

</div>
<div class="metric-row">

<span class="metric-label">Total Trades</span>

<span class="metric-value">${results.total_trades}</span>

</div>

`;

performanceMetrics.innerHTML = metricsHtml;

// Display trades

if (results.trades && results.trades.length > 0) {

let tradesHtml = '';

results.trades.forEach(trade => {

tradesHtml += `

<tr>

<td>${trade.date}</td>

<td>${trade.type}</td>

<td>$${formatNumber(trade.price)}</td>

<td>${trade.shares}</td>

<td>$${formatNumber(trade.value)}</td>

</tr>

`;

});

tradesTableBody.innerHTML = tradesHtml;

} else {
tradesTableBody.innerHTML = '<tr><td colspan="5" class="text-center">No trades
generated</td></tr>';

// Update equity curve chart

updateEquityCurveChart(results.equity_curve);

// Update equity curve chart

function updateEquityCurveChart(equityCurve) {

if (!equityCurve || equityCurve.length === 0) return;

// Generate labels (simple count for now)

const labels = Array.from({ length: equityCurve.length }, (_, i) => i);

// Destroy old chart if exists

if (chartInstance) {

chartInstance.destroy();

// Create new chart

const ctx = document.getElementById('equityCurveChart').getContext('2d');

chartInstance = new Chart(ctx, {

type: 'line',

data: {

labels: labels,

datasets: [{

label: 'Equity',
data: equityCurve,

borderColor: '#007bff',

backgroundColor: 'rgba(0, 123, 255, 0.1)',

fill: true,

tension: 0.1

}]

},

options: {

responsive: true,

scales: {

y: {

beginAtZero: false,

title: {

display: true,

text: 'Equity ($)'

},

x: {

title: {

display: true,

text: 'Trading Days'

},

plugins: {

legend: {

display: false

},
tooltip: {

callbacks: {

label: function(context) {

return `Equity: $${formatNumber(context.raw)}`;

});

// Paper trade modal functions

function openPaperTradeModal() {

if (blocks.length === 0) {

alert('Please add at least one block to create a strategy.');

return;

document.getElementById('tradeSymbol').value = symbolSelect.value;

document.getElementById('tradeQuantity').value = 1;

document.getElementById('tradeSide').value = 'buy';

document.getElementById('tradeType').value = 'market';

// Hide limit price field for market orders

limitPriceGroup.classList.add('d-none');

paperTradeModal.show();
}

// Submit a paper trade

function submitPaperTrade() {

const symbol = document.getElementById('tradeSymbol').value;

const quantity = parseInt(document.getElementById('tradeQuantity').value);

const side = document.getElementById('tradeSide').value;

const orderType = document.getElementById('tradeType').value;

let orderData = {

symbol: symbol,

quantity: quantity,

side: side,

orderType: orderType

};

// Add limit price if limit order

if (orderType === 'limit') {

const limitPrice = parseFloat(document.getElementById('limitPrice').value);

if (isNaN(limitPrice) || limitPrice <= 0) {

alert('Please enter a valid limit price.');

return;

orderData.limitPrice = limitPrice;

fetch('/api/paper-trade', {

method: 'POST',
headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify(orderData)

})

.then(response => response.json())

.then(result => {

if (result.error) {

alert(`Error: ${result.error}`);

} else {

alert(`Order successfully submitted.\nOrder ID: ${result.order_id}\nStatus:


${result.status}`);

paperTradeModal.hide();

})

.catch(error => {

console.error('Error submitting order:', error);

alert('Error submitting order. Please try again.');

});

// Save Strategy

function saveStrategy() {

if (blocks.length === 0) {

alert('Please add at least one block to your strategy before saving.');

return;

}
const strategyName = prompt('Enter a name for this strategy:');

if (!strategyName) return;

fetch('/api/save-strategy', {

method: 'POST',

headers: {

'Content-Type': 'application/json'

},

body: JSON.stringify({

name: strategyName,

blocks: blocks,

symbol: symbolSelect.value,

date: new Date().toISOString()

})

})

.then(response => response.json())

.then(result => {

if (result.error) {

alert(`Error saving strategy: ${result.error}`);

} else {

alert(`Strategy "${strategyName}" saved successfully.`);

})

.catch(error => {

console.error('Error saving strategy:', error);

alert('Error saving strategy. Please try again.');

});

}
// Open the Load Strategy modal

function openLoadStrategyModal() {

const strategyList = document.getElementById('strategyList');

strategyList.innerHTML = '<div class="text-center py-3"><div class="spinner-border text-


primary" role="status"></div><p class="mt-2">Loading strategies...</p></div>';

loadStrategyModal.show();

// Fetch available strategies

fetch('/api/list-strategies')

.then(response => response.json())

.then(data => {

if (data.strategies && data.strategies.length > 0) {

strategyList.innerHTML = '';

data.strategies.forEach(strategy => {

const item = document.createElement('a');

item.className = 'list-group-item list-group-item-action';

item.textContent = strategy;

item.addEventListener('click', function() {

loadSpecificStrategy(strategy);

});

strategyList.appendChild(item);

});

} else {

strategyList.innerHTML = '<p class="text-center py-3">No saved strategies


found.</p>';

})
.catch(error => {

console.error('Error loading strategies:', error);

strategyList.innerHTML = '<p class="text-center text-danger py-3">Error loading


strategies. Please try again.</p>';

});

// Load a specific strategy

function loadSpecificStrategy(strategyName) {

fetch(`/api/load-strategy?name=${encodeURIComponent(strategyName)}`)

.then(response => response.json())

.then(data => {

if (data.error) {

alert(`Error: ${data.error}`);

return;

// Clear current strategy

clearStrategy();

// Load blocks

if (data.blocks && Array.isArray(data.blocks)) {

data.blocks.forEach(block => {

createBlock(block.type, block.indicatorType, block.x, block.y);

// Get the newly created block and update its properties

const newBlock = blocks[blocks.length - 1];


// Copy all properties from the saved block

for (let key in block) {

if (key !== 'id') { // Preserve the new ID

newBlock[key] = block[key];

// Update block display

const blockElement = document.getElementById(newBlock.id);

if (blockElement) {

let settingsText = '';

if (block.type === 'indicator') {

if (block.indicatorType === 'SMA' || block.indicatorType === 'EMA' ||


block.indicatorType === 'RSI') {

settingsText = `Period: ${block.period}`;

} else if (block.indicatorType === 'MACD') {

settingsText = `Fast: ${block.fastPeriod}, Slow: ${block.slowPeriod},


Signal: ${block.signalPeriod}`;

} else if (block.type === 'entry' || block.type === 'exit') {

if (block.conditions && block.conditions.length > 0) {

const condition = block.conditions[0];

settingsText = `${condition.indicator} ${condition.operator}


${condition.value}`;

} else {

settingsText = 'No conditions set';

}
blockElement.querySelector('.block-settings').textContent = settingsText;

});

// Set symbol if provided

if (data.symbol) {

// Find and select the symbol in the dropdown

const options = Array.from(symbolSelect.options);

const matchingOption = options.find(option => option.value === data.symbol);

if (matchingOption) {

symbolSelect.value = data.symbol;

updateSymbol();

loadStrategyModal.hide();

})

.catch(error => {

console.error('Error loading strategy:', error);

alert('Error loading strategy. Please try again.');

});

// Confirm before clearing strategy

function confirmClearStrategy() {

if (blocks.length === 0) return;


if (confirm('Are you sure you want to clear the current strategy? All unsaved changes
will be lost.')) {

clearStrategy();

// Clear strategy

function clearStrategy() {

// Remove all blocks from the canvas

blocks.forEach(block => {

const blockElement = document.getElementById(block.id);

if (blockElement) {

blockElement.remove();

});

// Reset blocks array

blocks = [];

// Show placeholder

const placeholder = strategyCanvas.querySelector('.canvas-placeholder');

if (placeholder) {

placeholder.style.display = 'flex';

// Clear results

performanceMetrics.innerHTML = '<p class="text-muted text-center"><i class="bi bi-


arrow-clockwise"></i> Run a backtest to see metrics</p>';
tradesTableBody.innerHTML = '<tr><td colspan="5" class="text-center text-muted">No
trades to display</td></tr>';

// Clear chart

if (chartInstance) {

chartInstance.destroy();

chartInstance = null;

// Utility: Format date as YYYY-MM-DD

function formatDate(date) {

const year = date.getFullYear();

const month = String(date.getMonth() + 1).padStart(2, '0');

const day = String(date.getDate()).padStart(2, '0');

return `${year}-${month}-${day}`;

// Utility: Format date and time

function formatDateTime(date) {

return date.toLocaleString();

// Utility: Format number with commas and decimals

function formatNumber(value) {

return parseFloat(value).toLocaleString(undefined, {

minimumFractionDigits: 2,

maximumFractionDigits: 2
});

});

Style.css

/* AlgoBlocks Style Sheet */

/* General Styles */

body {

background-color: #f8f9fa;

font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;

/* Sidebar Styles */

.sidebar {

padding: 0;

box-shadow: inset -1px 0 0 rgba(0, 0, 0, .1);

height: calc(100vh - 56px);

overflow-y: auto;

.sidebar-heading {

font-size: 0.85rem;

text-transform: uppercase;

letter-spacing: 0.1rem;

.sidebar .form-control, .sidebar .form-select {


font-size: 0.9rem;

/* Block Styles */

.block-container {

margin-bottom: 20px;

.block-group {

margin-bottom: 15px;

.block-group h6 {

margin-top: 15px;

margin-bottom: 10px;

font-size: 0.9rem;

.block {

padding: 10px 12px;

margin-bottom: 8px;

border-radius: 6px;

cursor: grab;

font-size: 0.9rem;

transition: all 0.2s ease;

user-select: none;

}
.block:hover {

transform: translateY(-2px);

box-shadow: 0 2px 5px rgba(0,0,0,0.15);

.indicator-block {

background-color: #e3f2fd;

border-left: 4px solid #1976d2;

color: #0d47a1;

.entry-block {

background-color: #e8f5e9;

border-left: 4px solid #43a047;

color: #1b5e20;

.exit-block {

background-color: #ffebee;

border-left: 4px solid #e53935;

color: #b71c1c;

/* Strategy Canvas */

.strategy-canvas {

min-height: 300px;

border: 1px dashed #ced4da;

border-radius: 8px;
padding: 20px;

margin-bottom: 20px;

position: relative;

background-color: #fff;

.canvas-placeholder {

display: flex;

flex-direction: column;

justify-content: center;

align-items: center;

height: 260px;

color: #adb5bd;

.canvas-placeholder i {

margin-bottom: 10px;

.canvas-block {

position: absolute;

min-width: 180px;

padding: 12px;

border-radius: 6px;

background-color: white;

box-shadow: 0 3px 10px rgba(0,0,0,0.15);

z-index: 10;

cursor: move;
}

.canvas-block.indicator {

border-left: 4px solid #1976d2;

.canvas-block.entry {

border-left: 4px solid #43a047;

.canvas-block.exit {

border-left: 4px solid #e53935;

.block-header {

display: flex;

justify-content: space-between;

font-weight: bold;

margin-bottom: 8px;

padding-bottom: 6px;

border-bottom: 1px solid #f0f0f0;

.block-content {

font-size: 0.85rem;

color: #555;

}
.block-remove {

color: #dc3545;

cursor: pointer;

font-size: 1rem;

padding: 2px;

.block-remove:hover {

color: #bd2130;

/* Metrics Display */

.metric-row {

display: flex;

justify-content: space-between;

padding: 8px 0;

border-bottom: 1px dotted #eee;

.metric-label {

font-weight: 500;

color: #495057;

.metric-value {

font-weight: 600;

}
.metric-value.positive {

color: #28a745;

.metric-value.negative {

color: #dc3545;

/* Card Styles */

.card {

box-shadow: 0 0.125rem 0.25rem rgba(0, 0, 0, 0.075);

border: none;

margin-bottom: 1.5rem;

.card-header {

background-color: rgba(0, 0, 0, 0.02);

border-bottom: 1px solid rgba(0, 0, 0, 0.05);

padding: 0.75rem 1.25rem;

.card-title {

margin-bottom: 0;

font-size: 1rem;

font-weight: 500;

/* Table Styles */
.table th {

font-weight: 500;

border-top: none;

font-size: 0.85rem;

text-transform: uppercase;

letter-spacing: 0.05rem;

color: #495057;

.table td {

vertical-align: middle;

font-size: 0.9rem;

/* Modal Styles */

.modal-title {

font-weight: 500;

color: #343a40;

.modal-body {

padding: 1.5rem;

/* Strategy List */

.list-group-item {

cursor: pointer;

transition: background-color 0.2s;


}

.list-group-item:hover {

background-color: #f8f9fa;

.list-group-item.active {

background-color: #007bff;

border-color: #007bff;

/* Form Controls */

.form-label {

font-weight: 500;

font-size: 0.9rem;

margin-bottom: 0.3rem;

color: #495057;

/* Responsive Adjustments */

@media (max-width: 768px) {

.sidebar {

height: auto;

max-height: 300px;

overflow-y: auto;

.canvas-block {
width: 90%;

/* Animations */

.animate-pulse {

animation: pulse 2s cubic-bezier(0.4, 0, 0.6, 1) infinite;

@keyframes pulse {

0%, 100% {

opacity: 1;

50% {

opacity: 0.5;

/* Status Indicators */

.status-indicator {

width: 10px;

height: 10px;

border-radius: 50%;

display: inline-block;

margin-right: 5px;

.status-indicator.green {
background-color: #28a745;

.status-indicator.red {

background-color: #dc3545;

/* Utility Classes */

.cursor-pointer {

cursor: pointer;

.text-sm {

font-size: 0.85rem;

.bg-light-subtle {

background-color: rgba(248, 249, 250, 0.5);

You might also like

pFad - Phonifier reborn

Pfad - The Proxy pFad of © 2024 Garber Painting. All rights reserved.

Note: This service is not intended for secure transactions such as banking, social media, email, or purchasing. Use at your own risk. We assume no liability whatsoever for broken pages.


Alternative Proxies:

Alternative Proxy

pFad Proxy

pFad v3 Proxy

pFad v4 Proxy