|
import gradio as gr |
|
import pandas as pd |
|
import backtrader as bt |
|
import requests |
|
|
|
class TrendFollowingStrategy(bt.Strategy): |
|
params = (('ma_period', 15),) |
|
|
|
def __init__(self): |
|
self.ma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.ma_period) |
|
self.crossover = bt.ind.CrossOver(self.data.close, self.ma) |
|
self.last_signal = None |
|
self.last_signal_timeframe = self.data._timeframe |
|
|
|
|
|
self.trade_count = 0 |
|
self.win_count = 0 |
|
self.loss_count = 0 |
|
|
|
def next(self): |
|
|
|
if not self.position: |
|
|
|
if self.crossover > 0: |
|
self.buy() |
|
self.last_signal = 'CALL' |
|
elif self.crossover < 0: |
|
self.sell() |
|
self.last_signal = 'PUT' |
|
else: |
|
|
|
if self.position.size > 0 and self.crossover < 0: |
|
|
|
self.close() |
|
elif self.position.size < 0 and self.crossover > 0: |
|
|
|
self.close() |
|
|
|
|
|
def notify_trade(self, trade): |
|
if trade.isclosed: |
|
outcome = 'win' if trade.pnl > 0 else 'loss' |
|
self.log_trade(self.last_signal, outcome) |
|
|
|
def log_trade(self, trade_type, outcome): |
|
""" |
|
Log the details of each trade. |
|
""" |
|
self.trade_count += 1 |
|
if outcome == 'win': |
|
self.win_count += 1 |
|
elif outcome == 'loss': |
|
self.loss_count += 1 |
|
print(f"Trade {self.trade_count}: {trade_type} - {outcome}") |
|
|
|
def fetch_forex_intraday(api_key, from_symbol, to_symbol, interval, outputsize='compact'): |
|
url = f'https://www.alphavantage.co/query?function=FX_INTRADAY&from_symbol={from_symbol}&to_symbol={to_symbol}&interval={interval}&apikey={api_key}&outputsize={outputsize}' |
|
response = requests.get(url) |
|
data = response.json() |
|
|
|
|
|
time_series_key = 'Time Series FX (' + interval + ')' |
|
forex_data = pd.DataFrame(data[time_series_key]).T |
|
forex_data.columns = ['Open', 'High', 'Low', 'Close'] |
|
|
|
|
|
forex_data.index = pd.to_datetime(forex_data.index) |
|
forex_data.sort_index(inplace=True) |
|
|
|
|
|
forex_data = forex_data.apply(pd.to_numeric) |
|
|
|
return forex_data |
|
|
|
def analyze_sentiment(json_response, target_ticker): |
|
""" |
|
Analyze the sentiment data for a specific ticker. |
|
|
|
:param json_response: The JSON response from the API. |
|
:param target_ticker: The ticker symbol to analyze (e.g., base_ticker). |
|
:return: A string describing the overall sentiment for the target ticker. |
|
""" |
|
if not json_response or "feed" not in json_response: |
|
return "No data available for analysis" |
|
|
|
sentiment_label = "Neutral" |
|
highest_relevance = 0 |
|
|
|
|
|
for item in json_response.get("feed", []): |
|
|
|
for ticker_data in item.get("ticker_sentiment", []): |
|
if ticker_data["ticker"] == target_ticker: |
|
relevance_score = float(ticker_data.get("relevance_score", 0)) |
|
sentiment_score = float(ticker_data.get("ticker_sentiment_score", 0)) |
|
|
|
|
|
if relevance_score > highest_relevance: |
|
highest_relevance = relevance_score |
|
if sentiment_score <= -0.35: |
|
sentiment_label = "Bearish" |
|
elif -0.35 < sentiment_score <= -0.15: |
|
sentiment_label = "Somewhat-Bearish" |
|
elif -0.15 < sentiment_score < 0.15: |
|
sentiment_label = "Neutral" |
|
elif 0.15 <= sentiment_score < 0.35: |
|
sentiment_label = "Somewhat_Bullish" |
|
elif sentiment_score >= 0.35: |
|
sentiment_label = "Bullish" |
|
|
|
return sentiment_label |
|
|
|
def make_trade_decision(base_currency, quote_currency, quote_sentiment, base_sentiment): |
|
""" |
|
Make a trade decision based on sentiment analysis and forex signal parameters. |
|
|
|
:param quote_sentiment: Sentiment analysis result for {base_currency}. |
|
:param base_sentiment: Sentiment analysis result for {quote_currency}. |
|
:param entry: Entry price for the trade. |
|
:param stop_loss: Stop loss price. |
|
:param take_profit: Take profit price. |
|
:return: A decision to make the trade or not, along with sentiment analysis results. |
|
""" |
|
trade_decision = "No trade" |
|
decision_reason = f"{base_currency} Sentiment: {quote_sentiment}, {quote_currency} Sentiment: {base_sentiment}" |
|
|
|
|
|
bullish_sentiments = ["Bullish", "Somewhat_Bullish"] |
|
bearish_sentiments = ["Bearish", "Somewhat-Bearish"] |
|
|
|
if quote_sentiment in bullish_sentiments and base_sentiment not in bullish_sentiments: |
|
trade_decision = f"Sell {base_currency}/{quote_currency}" |
|
elif base_sentiment in bullish_sentiments and quote_sentiment not in bullish_sentiments: |
|
trade_decision = f"Buy {base_currency}/{quote_currency}" |
|
elif quote_sentiment in bearish_sentiments and base_sentiment not in bearish_sentiments: |
|
trade_decision = f"Buy {base_currency}/{quote_currency}" |
|
elif base_sentiment in bearish_sentiments and quote_sentiment not in bearish_sentiments: |
|
trade_decision = f"Sell {base_currency}/{quote_currency}" |
|
|
|
|
|
return trade_decision, decision_reason |
|
|
|
def fetch_sentiment_data(api_endpoint, ticker, api_key, sort='LATEST', limit=50): |
|
|
|
params = { |
|
'function': 'NEWS_SENTIMENT', |
|
'tickers': ticker, |
|
'apikey': api_key, |
|
'sort': sort, |
|
'limit': limit |
|
} |
|
|
|
|
|
response = requests.get(api_endpoint, params=params) |
|
|
|
|
|
if response.status_code == 200: |
|
|
|
return response.json() |
|
else: |
|
|
|
return f"Error fetching data: {response.status_code}" |
|
|
|
def load_data(api_key, from_symbol, to_symbol, interval): |
|
|
|
forex_data = fetch_forex_intraday(api_key, from_symbol, to_symbol, interval) |
|
|
|
|
|
data = bt.feeds.PandasData(dataname=forex_data) |
|
return data |
|
|
|
def should_trade(strategy, api_endpoint, api_key, base_currency, quote_currency): |
|
consistent_periods = 3 |
|
if len(strategy) < consistent_periods: |
|
return False, None, None, "Insufficient data" |
|
|
|
if strategy.last_signal_timeframe in bt.TimeFrame.Names: |
|
timeframe = bt.TimeFrame.getname(strategy.last_signal_timeframe) |
|
else: |
|
timeframe = "Unknown Timeframe" |
|
|
|
base_ticker = f"FOREX:{base_currency}" |
|
quote_ticker = f"FOREX:{quote_currency}" |
|
|
|
|
|
json_response = fetch_sentiment_data(api_endpoint, f"{base_ticker}", api_key) |
|
print(fetch_sentiment_data(api_endpoint, f"{quote_currency}", api_key)) |
|
print(json_response) |
|
base_sentiment = analyze_sentiment(json_response, base_ticker) |
|
quote_sentiment = analyze_sentiment(json_response, quote_currency) |
|
|
|
|
|
trade_decision, decision_reason = make_trade_decision(base_currency, quote_currency, quote_sentiment, base_sentiment) |
|
|
|
signal = strategy.crossover[0] |
|
if all(strategy.crossover[-i] == signal for i in range(1, consistent_periods + 1)): |
|
|
|
return True, trade_decision, timeframe, decision_reason |
|
return False, None, None, "Not enough consistent signals or conflicting sentiment " +decision_reason+"." |
|
|
|
|
|
def run_backtest(api_key, from_symbol, to_symbol, interval): |
|
|
|
cerebro = bt.Cerebro() |
|
cerebro.addstrategy(TrendFollowingStrategy) |
|
|
|
|
|
data = load_data(api_key, from_symbol, to_symbol, interval) |
|
cerebro.adddata(data) |
|
|
|
|
|
cerebro.broker.set_cash(10000) |
|
|
|
|
|
strategy_instance = cerebro.run()[0] |
|
api_endpoint = "https://www.alphavantage.co/query" |
|
|
|
|
|
total_trades = strategy_instance.trade_count |
|
total_wins = strategy_instance.win_count |
|
total_losses = strategy_instance.loss_count |
|
|
|
win_percentage = (total_wins / total_trades) * 100 |
|
loss_percentage = (total_losses / total_trades) * 100 |
|
|
|
|
|
if win_percentage > loss_percentage: |
|
signal = "Buy" |
|
color = "green" |
|
else: |
|
signal = "Sell" |
|
color = "red" |
|
|
|
return f"Signal: <span style='color: {color}'>{signal}</span>" |
|
|
|
|
|
|
|
from_currency_choices = ['EUR', 'GBP', 'USD', 'AUD', 'JPY'] |
|
to_currency_choices = ['USD', 'JPY', 'GBP', 'AUD', 'CAD'] |
|
|
|
|
|
from gradio import inputs |
|
|
|
api_key_link = "https://www.alphavantage.co/support/#api-key" |
|
|
|
|
|
|
|
gr.Interface( |
|
fn=run_backtest, |
|
inputs=[ |
|
gr.inputs.Textbox(label="API Key", placeholder="Enter your API key"), |
|
gr.inputs.Dropdown(label="From Currency", choices=['EUR', 'GBP', 'USD', 'AUD', 'JPY']), |
|
gr.inputs.Dropdown(label="To Currency", choices=['USD', 'JPY', 'GBP', 'AUD', 'CAD']), |
|
gr.inputs.Radio(label="Interval", choices=["1min", "5min", "15min", "30min", "60min"]) |
|
], |
|
outputs="html", |
|
live=True, |
|
title="Trading Signal", |
|
description="Run Backtest and Get Trading Signal" |
|
).launch() |