Skip to content

Commit

Permalink
Backtesting strategies modified
Browse files Browse the repository at this point in the history
  • Loading branch information
AbYT101 committed Jun 22, 2024
1 parent c7b3224 commit 6601d4d
Show file tree
Hide file tree
Showing 8 changed files with 10,112 additions and 2 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -129,6 +129,8 @@ venv/
ENV/
env.bak/
venv.bak/
Genv/


# Spyder project settings
.spyderproject
Expand Down
2 changes: 1 addition & 1 deletion app/config.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
class Config:
SQLALCHEMY_DATABASE_URI = 'postgresql://test_user:password@localhost/test_db'
SQLALCHEMY_DATABASE_URI = ''
SQLALCHEMY_TRACK_MODIFICATIONS = False
JWT_SECRET_KEY = 'your_secret_key'
8,961 changes: 8,961 additions & 0 deletions notebooks/backtesting.ipynb

Large diffs are not rendered by default.

420 changes: 420 additions & 0 deletions notebooks/backtesting_yfinance.ipynb

Large diffs are not rendered by default.

501 changes: 501 additions & 0 deletions notebooks/data_exploration.ipynb

Large diffs are not rendered by default.

12 changes: 11 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
@@ -1,3 +1,10 @@
backtrader==1.9.78.123
matplotlib==3.9.0
numpy==2.0.0
packaging==24.1
pandas
yfinance
requests
flask
Flask-SQLAlchemy
Flask-JWT-Extended
Expand All @@ -6,4 +13,7 @@ psycopg2-binary
confluent-kafka
apache-airflow
mlflow
psycopg2
psycopg2
python-dotenv
ccxt
yfinance
169 changes: 169 additions & 0 deletions scripts/backtest_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,169 @@
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()
class MacdStrategy(bt.Strategy):
params = (
('macd1_period', 12),
('macd2_period', 26),
('signal_period', 9),
)

def __init__(self):
self.macd = bt.indicators.MACDHisto(period_me1=self.params.macd1_period, period_me2=self.params.macd2_period, period_signal=self.params.signal_period)

def next(self):
if not self.position:
if self.macd.lines.histo[0] > 0 and self.macd.lines.histo[-1] <= 0:
self.buy()
else:
if self.macd.lines.histo[0] < 0 and self.macd.lines.histo[-1] >= 0:
self.sell()

class StochasticOscillatorStrategy(bt.Strategy):
params = (
('stoch_period', 14),
('stoch_low', 20),
('stoch_high', 80),
)

def __init__(self):
self.stoch = bt.indicators.Stochastic(period=self.params.stoch_period)

def next(self):
if not self.position:
if self.stoch.lines.percK[0] < self.params.stoch_low and self.stoch.lines.percK[-1] >= self.params.stoch_low:
self.buy()
else:
if self.stoch.lines.percK[0] > self.params.stoch_high and self.stoch.lines.percK[-1] <= self.params.stoch_high:
self.sell()

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

data_feed = bt.feeds.PandasData(dataname=data)

# Initialize cerebro
cerebro = bt.Cerebro()

# Add strategy
cerebro.addstrategy(strategy)

# Add data feed
cerebro.adddata(data_feed)

# Set initial cash
cerebro.broker.set_cash(10000)

# Set commission
cerebro.broker.setcommission(commission=0.002)

# Add analyzers
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='tradeanalyzer')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.SharpeRatio_A, _name='sharpe')

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

# Run backtest
result = cerebro.run()

# Extracting backtest metrics
total_return = cerebro.broker.getvalue() / 10000 - 1
number_of_trades = result[0].analyzers.tradeanalyzer.get_analysis()['total']['closed']
winning_trades = result[0].analyzers.tradeanalyzer.get_analysis()['won']['total']
losing_trades = result[0].analyzers.tradeanalyzer.get_analysis()['lost']['total']
max_drawdown = result[0].analyzers.drawdown.get_analysis()['max']['drawdown']
sharpe_ratio = result[0].analyzers.sharpe.get_analysis()['sharperatio']

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

# Return results as a dictionary
return {
'backtest_id': 1,
'total_return': total_return,
'number_of_trades': number_of_trades,
'winning_trades': winning_trades,
'losing_trades': losing_trades,
'max_drawdown': max_drawdown,
'sharpe_ratio': sharpe_ratio
}

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

backtest_results_rsi = run_backtest(RsiBollingerBandsStrategy, symbol, start_date, end_date)
print("RSI Bollinger Bands Strategy Results:")
print(backtest_results_rsi)

backtest_results_macd = run_backtest(MacdStrategy, symbol, start_date, end_date)
print("MACD Strategy Results:")
print(backtest_results_macd)

backtest_results_stoch = run_backtest(StochasticOscillatorStrategy, symbol, start_date, end_date)
print("Stochastic Oscillator Strategy Results:")
print(backtest_results_stoch)
47 changes: 47 additions & 0 deletions scripts/data_ingestion.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
import os
from dotenv import load_dotenv
from sqlalchemy import create_engine
import yfinance as yf
import pandas as pd
from time import sleep

load_dotenv()

# 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')

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

def fetch_ohlcv(symbol, since):
try:
# Fetch data from Yahoo Finance
ticker = yf.Ticker(symbol)
ohlcv = ticker.history(period='1d', start=since)
return ohlcv.reset_index()
except Exception as e:
print(f"Error fetching data for {symbol}: {str(e)}")
return None

def store_dataframe(df, table_name):
df.to_sql(table_name, con=engine, if_exists='append', index=False)

# Fetch and store data for multiple symbols
symbols = ['BTC-USD', 'ETH-USD', 'BNB-USD', 'XRP-USD', 'ADA-USD', 'SOL1-USD', 'DOGE-USD', 'DOT1-USD', 'SHIB-USD', 'MATIC-USD', 'LTC-USD', 'UNI-USD', 'BCH-USD', 'LINK-USD', 'XLM-USD', 'ATOM-USD', 'VET-USD', 'ICP-USD', 'FIL-USD', 'THETA-USD']
since = '2023-06-20'

for symbol in symbols:
ohlcv = fetch_ohlcv(symbol, since)
if ohlcv is not None and not ohlcv.empty:
# Rename columns to match the existing structure if needed
df = ohlcv.rename(columns={'Date': 'timestamp', 'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume'})
store_dataframe(df, f'ohlcv_{symbol.replace("-", "_")}')
print(f'Stored data for {symbol}')
else:
print(f'Failed to fetch data for {symbol}')
sleep(1) # Add a delay to avoid hitting rate limits

0 comments on commit 6601d4d

Please sign in to comment.