Skip to content

Commit

Permalink
Merge pull request #41 from 10-academy-w-9/time-series-forcasting
Browse files Browse the repository at this point in the history
Time series forcasting using moirai model
  • Loading branch information
derejehinsermu committed Jun 26, 2024
2 parents 8bbe7be + a2eac42 commit d205d27
Show file tree
Hide file tree
Showing 5 changed files with 771 additions and 1 deletion.
224 changes: 224 additions & 0 deletions notebooks/moirai_forecast.ipynb

Large diffs are not rendered by default.

1 change: 1 addition & 0 deletions notebooks/uni2ts
Submodule uni2ts added at fac7a9
15 changes: 14 additions & 1 deletion requirements.txt
Original file line number Diff line number Diff line change
Expand Up @@ -18,4 +18,17 @@ python-dotenv
ccxt
yfinance
flask_cors
Flask-Migrate
safetensors
torch>=2.1
lightning>=2.0
gluonts~=0.14.3
# numpy~=1.26.0
einops==0.7.*
jaxtyping~=0.2.24
python-dotenv
hydra-core==1.3
huggingface_hub>=0.23.0
safetensors
jax[cpu]
Flask-Migrate

268 changes: 268 additions & 0 deletions scripts/forecast_backtest_runner.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,268 @@
import os
import sys
import matplotlib.pyplot as plt
root_path = os.path.abspath(os.path.join(os.getcwd(), '../notebooks/uni2ts/src'))
sys.path.append(root_path)

# root_path = os.path.abspath(os.path.join(os.getcwd(), './uni2ts/src'))
# sys.path.append(root_path)
import matplotlib.pyplot as plt
import pandas as pd
import numpy as np
import torch
from einops import rearrange
from gluonts.dataset.pandas import PandasDataset
from gluonts.dataset.split import split
from gluonts.torch.model.predictor import PyTorchPredictor
import yfinance as yf

from uni2ts.eval_util.plot import plot_single
from uni2ts.model.moirai import MoiraiForecast, MoiraiModule
import backtrader as bt

# Step 1: Fetch Data from Yahoo Finance
def fetch_data(symbol, start_date, end_date):
try:
ticker = yf.Ticker(symbol)
ohlcv = ticker.history(start=start_date, end=end_date)
ohlcv.reset_index(inplace=True)
ohlcv['timestamp'] = pd.to_datetime(ohlcv['Date'])
ohlcv.set_index('timestamp', inplace=True)
ohlcv = ohlcv[['Open', 'High', 'Low', 'Close', 'Volume']]
ohlcv.rename(columns={'Open': 'open', 'High': 'high', 'Low': 'low', 'Close': 'close', 'Volume': 'volume'}, inplace=True)

# Ensure the data is uniformly spaced by resampling
ohlcv = ohlcv.resample('D').ffill()

return ohlcv
except Exception as e:
print(f"Error fetching data for {symbol}: {str(e)}")
return None

def load_and_predict(data):
# Use only the 'close' price for forecasting
df = data[['close']].rename(columns={'close': 'target'})

# Ensure data is sorted
df = df.sort_index()

# Step 2: Prepare the Data for the Model
# Convert into GluonTS dataset
ds = PandasDataset(dict(df), freq="D")

# Split into train/test set
TEST = 100 # Define the length of the test set
PDT = 20 # Define the prediction length
train, test_template = split(ds, offset=-TEST)

# Ensure the length of the dataset is sufficient for rolling window evaluation
total_length = len(df)
if total_length < TEST + PDT:
raise ValueError(f"Not enough data points. Total length: {total_length}, TEST: {TEST}, PDT: {PDT}")

# Construct rolling window evaluation
test_data = test_template.generate_instances(
prediction_length=PDT,
windows=TEST // PDT,
distance=PDT
)

# Step 3: Load the Model and Make Predictions
SIZE = "small" # Model size
CTX = 200 # Context length
PSZ = "auto" # Patch size
BSZ = 32 # Batch size

# Prepare pre-trained model by downloading model weights from Hugging Face hub
model = MoiraiForecast(
module=MoiraiModule.from_pretrained(f"Salesforce/moirai-1.0-R-{SIZE}"),
prediction_length=PDT,
context_length=CTX,
patch_size=PSZ,
num_samples=100,
target_dim=1,
feat_dynamic_real_dim=ds.num_feat_dynamic_real,
past_feat_dynamic_real_dim=ds.num_past_feat_dynamic_real,
)

predictor = model.create_predictor(batch_size=BSZ)
forecasts = predictor.predict(test_data.input)

# Step 4: Plot the Results
input_it = iter(test_data.input)
label_it = iter(test_data.label)
forecast_it = iter(forecasts)

try:
inp = next(input_it)
label = next(label_it)
forecast = next(forecast_it)

plot_single(
inp,
label,
forecast,
context_length=CTX,
name="pred",
show_label=True,
)
plt.show()
except StopIteration:
print("Error: Not enough data points to generate forecasts.")

return forecasts

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

def __init__(self, predictions):
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)
self.predictions = predictions
self.current_prediction = next(iter(self.predictions))

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()

# Move to the next prediction if available
try:
self.current_prediction = next(iter(self.predictions))
except StopIteration:
pass

class MacdStrategy(bt.Strategy):
params = (
('macd1_period', 12),
('macd2_period', 26),
('signal_period', 9),
)

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

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()

# Move to the next prediction if available
try:
self.current_prediction = next(iter(self.predictions))
except StopIteration:
pass

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

def __init__(self, predictions):
self.stoch = bt.indicators.Stochastic(period=self.params.stoch_period)
self.predictions = predictions
self.current_prediction = next(iter(self.predictions))

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()

# Move to the next prediction if available
try:
self.current_prediction = next(iter(self.predictions))
except StopIteration:
pass

def run_backtest(strategy_class, symbol, start_date, end_date):
# Fetch data for backtesting
data = fetch_data(symbol, start_date, end_date)

# Load and make predictions
predictions = load_and_predict(data)

# Initialize cerebro
cerebro = bt.Cerebro()

# Add data feed
cerebro.adddata(bt.feeds.PandasData(dataname=data))

# Add strategy with predictions
cerebro.addstrategy(strategy_class, predictions=predictions)

# Set broker settings
cerebro.broker.set_cash(100000)
cerebro.broker.setcommission(commission=0.002)

# Add analyzers for performance metrics
cerebro.addanalyzer(bt.analyzers.SharpeRatio, _name='sharpe')
cerebro.addanalyzer(bt.analyzers.DrawDown, _name='drawdown')
cerebro.addanalyzer(bt.analyzers.TradeAnalyzer, _name='trades')
cerebro.addanalyzer(bt.analyzers.Returns, _name='returns')

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

# Run backtest
results = cerebro.run()

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

# Extracting backtest metrics
strat = results[0]

# Prepare results
result_dict = {
"Starting Portfolio Value": start_value,
"Ending Portfolio Value": end_value,
"Sharpe Ratio": strat.analyzers.sharpe.get_analysis().get('sharperatio', 'N/A'),
"Max Drawdown": strat.analyzers.drawdown.get_analysis().get('max', {}).get('drawdown', 'N/A'),
"Total Trades": strat.analyzers.trades.get_analysis().get('total', {}).get('total', 'N/A'),
"Winning Trades": strat.analyzers.trades.get_analysis().get('won', {}).get('total', 'N/A'),
"Losing Trades": strat.analyzers.trades.get_analysis().get('lost', {}).get('total', 'N/A'),
"Total Return": strat.analyzers.returns.get_analysis().get('rtot', 'N/A')
}

# Plot the results
cerebro.plot(style='candlestick')

# Print metrics
print("Metrics:")
for key, value in result_dict.items():
print(f"{key}: {value}")

return result_dict

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

strategies = [RsiBollingerBandsStrategy, MacdStrategy, StochasticOscillatorStrategy]

for strategy in strategies:
print(f"Running backtest for {strategy.__name__}")
run_backtest(strategy, symbol, start_date, end_date)
Loading

0 comments on commit d205d27

Please sign in to comment.