Skip to content

Commit

Permalink
Merge branch 'backtesting-feature'
Browse files Browse the repository at this point in the history
  • Loading branch information
SelamT94 committed Jun 21, 2024
2 parents be32889 + 997150d commit fe815e6
Show file tree
Hide file tree
Showing 4 changed files with 659 additions and 43 deletions.
36 changes: 36 additions & 0 deletions app/routes/backtest.py
Original file line number Diff line number Diff line change
Expand Up @@ -53,3 +53,39 @@ def callback(message):


consume_backtest_scenes() # Start consuming Kafka messages in a separate thread

# from flask import Flask, request, jsonify
# import sys
# import os
# sys.path.append(os.path.abspath(os.path.join(os.path.dirname(__file__), '..', '..')))

# from scripts.backtest_runner import run_backtest, RsiBollingerBandsStrategy
# app = Flask(__name__)

# @app.route('/backtest', methods=['GET','POST'])
# def backtest():
# data = request.get_json()
# symbol = data['symbol']
# start_date = data['start_date']
# end_date = data['end_date']
# strategy_name = data['strategy']

# strategies = {
# 'rsi_bollinger': RsiBollingerBandsStrategy
# }

# strategy = strategies.get(strategy_name.lower())
# if not strategy:
# return jsonify({'error': 'Invalid strategy name'}), 400

# try:
# stats = run_backtest(strategy, symbol, start_date, end_date)
# return jsonify(stats)
# except Exception as e:
# return jsonify({'error': str(e)}), 400

# # def backtest():
# # return 'Backtest endpoint accessed successfully'

# if __name__ == '__main__':
# app.run(debug=True)
553 changes: 510 additions & 43 deletions notebooks/backtesting.ipynb

Large diffs are not rendered by default.

103 changes: 103 additions & 0 deletions scripts/backtest_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,103 @@
import pandas as pd
from sqlalchemy import create_engine
import backtrader as bt
import os

# RDS connection information
rds_host = os.getenv('PG_HOST')
rds_port = os.getenv('PG_PORT')
rds_db = os.getenv('PG_DATABASE')
rds_user = os.getenv('PG_USER')
rds_password = os.getenv('PG_PASSWORD')

engine = create_engine(f'postgresql+psycopg2://{rds_user}:{rds_password}@{rds_host}:{rds_port}/{rds_db}')

def fetch_data(symbol, start_date, end_date):
query = f"""
SELECT timestamp AS date, open AS open, high AS high, low AS low, close AS close, volume AS volume
FROM public."ohlcv_{symbol.replace('/', '_')}"
WHERE timestamp >= '{start_date}' AND timestamp <= '{end_date}';
"""
try:
print(f"Executing query:\n{query}\n") # Print the SQL query for debugging purposes

data = pd.read_sql(query, con=engine)
print(f"Fetched data:\n{data.head()}\n") # Print the first few rows of fetched data for debugging

# Check if data is empty
if data.empty:
raise ValueError("No data returned from query.")

# Convert 'date' column to datetime
data['date'] = pd.to_datetime(data['date'], format='%Y-%m-%d')

# Set 'date' column as index
data.set_index('date', inplace=True)

# Ensure column names are correctly capitalized for Backtrader
data.rename(columns={'open': 'Open', 'high': 'High', 'low': 'Low', 'close': 'Close', 'volume': 'Volume'}, inplace=True)

return data
except Exception as e:
print(f"Error fetching data: {e}")
raise

class RsiBollingerBandsStrategy(bt.Strategy):
params = (
('rsi_period', 14),
('bb_period', 20),
('bb_dev', 2),
('oversold', 30),
('overbought', 70),
)

def __init__(self):
self.rsi = bt.indicators.RelativeStrengthIndex(period=self.params.rsi_period)
self.bbands = bt.indicators.BollingerBands(period=self.params.bb_period, devfactor=self.params.bb_dev)

def next(self):
if not self.position:
if self.rsi < self.params.oversold and self.data.close <= self.bbands.lines.bot:
self.buy()
else:
if self.rsi > self.params.overbought or self.data.close >= self.bbands.lines.top:
self.sell()

def run_backtest(strategy, symbol, start_date, end_date):
data = fetch_data(symbol, start_date, end_date)

# Create a data feed
data_feed = bt.feeds.PandasData(dataname=data)

# Initialize cerebro
cerebro = bt.Cerebro()
cerebro.addstrategy(strategy)
cerebro.adddata(data_feed)
cerebro.broker.set_cash(10000)
cerebro.broker.setcommission(commission=0.002)

# Print starting conditions
print(f'Starting Portfolio Value: {cerebro.broker.getvalue():.2f}')

# Run backtest
cerebro.run()

# Print ending conditions
print(f'Ending Portfolio Value: {cerebro.broker.getvalue():.2f}')

# Plot the results
cerebro.plot()

if __name__ == "__main__":
symbol = 'BTC/USDT'
start_date = '2023-06-20'
end_date = '2024-06-20'

for strategy in [RsiBollingerBandsStrategy]:
run_backtest(strategy, symbol, start_date, end_date)

try:
data = fetch_data(symbol, start_date, end_date)
print(data.head())
except Exception as e:
print(f"Error running backtest: {e}")
10 changes: 10 additions & 0 deletions tests/unit/test_backtest_service.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import unittest
from app.services.backtest_service import BacktestService

class TestBacktestService(unittest.TestCase):
def test_execute_backtest(self):
result = BacktestService.execute_backtest('BTC/USDT', '2023-06-20', '2024-06-20')
self.assertIn('Ending Portfolio Value', result)

if __name__ == '__main__':
unittest.main()

0 comments on commit fe815e6

Please sign in to comment.