updating code
Browse files- app.py +256 -2
- requirements.txt +4 -1
app.py
CHANGED
@@ -1,7 +1,261 @@
|
|
1 |
import gradio as gr
|
|
|
|
|
|
|
2 |
|
3 |
-
|
4 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
5 |
|
6 |
iface = gr.Interface(fn=greet, inputs="text", outputs="text")
|
7 |
iface.launch()
|
|
|
1 |
import gradio as gr
|
2 |
+
import pandas as pd
|
3 |
+
import backtrader as bt
|
4 |
+
import requests
|
5 |
|
6 |
+
class TrendFollowingStrategy(bt.Strategy):
|
7 |
+
params = (('ma_period', 15),)
|
8 |
+
|
9 |
+
def __init__(self):
|
10 |
+
self.ma = bt.indicators.SimpleMovingAverage(self.data.close, period=self.params.ma_period)
|
11 |
+
self.crossover = bt.ind.CrossOver(self.data.close, self.ma)
|
12 |
+
self.last_signal = None
|
13 |
+
self.last_signal_timeframe = self.data._timeframe
|
14 |
+
|
15 |
+
# Additional tracking
|
16 |
+
self.trade_count = 0
|
17 |
+
self.win_count = 0
|
18 |
+
self.loss_count = 0
|
19 |
+
|
20 |
+
def next(self):
|
21 |
+
# Check if we are in the market
|
22 |
+
if not self.position:
|
23 |
+
# We are not in the market, look for a signal to enter
|
24 |
+
if self.crossover > 0:
|
25 |
+
self.buy() # Execute a buy order
|
26 |
+
self.last_signal = 'CALL'
|
27 |
+
elif self.crossover < 0:
|
28 |
+
self.sell() # Execute a sell order
|
29 |
+
self.last_signal = 'PUT'
|
30 |
+
else:
|
31 |
+
# We are in the market, look for a signal to close
|
32 |
+
if self.position.size > 0 and self.crossover < 0:
|
33 |
+
# We are long and get a sell signal
|
34 |
+
self.close() # Close the long position
|
35 |
+
elif self.position.size < 0 and self.crossover > 0:
|
36 |
+
# We are short and get a buy signal
|
37 |
+
self.close() # Close the short position
|
38 |
+
|
39 |
+
|
40 |
+
def notify_trade(self, trade):
|
41 |
+
if trade.isclosed:
|
42 |
+
outcome = 'win' if trade.pnl > 0 else 'loss'
|
43 |
+
self.log_trade(self.last_signal, outcome)
|
44 |
+
|
45 |
+
def log_trade(self, trade_type, outcome):
|
46 |
+
"""
|
47 |
+
Log the details of each trade.
|
48 |
+
"""
|
49 |
+
self.trade_count += 1
|
50 |
+
if outcome == 'win':
|
51 |
+
self.win_count += 1
|
52 |
+
elif outcome == 'loss':
|
53 |
+
self.loss_count += 1
|
54 |
+
print(f"Trade {self.trade_count}: {trade_type} - {outcome}")
|
55 |
+
|
56 |
+
def fetch_forex_intraday(api_key, from_symbol, to_symbol, interval, outputsize='compact'):
|
57 |
+
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}'
|
58 |
+
response = requests.get(url)
|
59 |
+
data = response.json()
|
60 |
+
|
61 |
+
# Extracting the time series data from the JSON object
|
62 |
+
time_series_key = 'Time Series FX (' + interval + ')'
|
63 |
+
forex_data = pd.DataFrame(data[time_series_key]).T
|
64 |
+
forex_data.columns = ['Open', 'High', 'Low', 'Close']
|
65 |
+
|
66 |
+
# Convert index to datetime and sort data
|
67 |
+
forex_data.index = pd.to_datetime(forex_data.index)
|
68 |
+
forex_data.sort_index(inplace=True)
|
69 |
+
|
70 |
+
# Convert columns to numeric
|
71 |
+
forex_data = forex_data.apply(pd.to_numeric)
|
72 |
+
|
73 |
+
return forex_data
|
74 |
+
|
75 |
+
def analyze_sentiment(json_response, target_ticker):
|
76 |
+
"""
|
77 |
+
Analyze the sentiment data for a specific ticker.
|
78 |
+
|
79 |
+
:param json_response: The JSON response from the API.
|
80 |
+
:param target_ticker: The ticker symbol to analyze (e.g., base_ticker).
|
81 |
+
:return: A string describing the overall sentiment for the target ticker.
|
82 |
+
"""
|
83 |
+
if not json_response or "feed" not in json_response:
|
84 |
+
return "No data available for analysis"
|
85 |
+
|
86 |
+
sentiment_label = "Neutral" # Default sentiment
|
87 |
+
highest_relevance = 0 # Track the highest relevance score
|
88 |
+
|
89 |
+
# Loop through each news item in the feed
|
90 |
+
for item in json_response.get("feed", []):
|
91 |
+
# Check each ticker sentiment in the item
|
92 |
+
for ticker_data in item.get("ticker_sentiment", []):
|
93 |
+
if ticker_data["ticker"] == target_ticker:
|
94 |
+
relevance_score = float(ticker_data.get("relevance_score", 0))
|
95 |
+
sentiment_score = float(ticker_data.get("ticker_sentiment_score", 0))
|
96 |
+
|
97 |
+
# Determine the sentiment label based on the score
|
98 |
+
if relevance_score > highest_relevance:
|
99 |
+
highest_relevance = relevance_score
|
100 |
+
if sentiment_score <= -0.35:
|
101 |
+
sentiment_label = "Bearish"
|
102 |
+
elif -0.35 < sentiment_score <= -0.15:
|
103 |
+
sentiment_label = "Somewhat-Bearish"
|
104 |
+
elif -0.15 < sentiment_score < 0.15:
|
105 |
+
sentiment_label = "Neutral"
|
106 |
+
elif 0.15 <= sentiment_score < 0.35:
|
107 |
+
sentiment_label = "Somewhat_Bullish"
|
108 |
+
elif sentiment_score >= 0.35:
|
109 |
+
sentiment_label = "Bullish"
|
110 |
+
|
111 |
+
return sentiment_label
|
112 |
+
|
113 |
+
def make_trade_decision(base_currency, quote_currency, quote_sentiment, base_sentiment):
|
114 |
+
"""
|
115 |
+
Make a trade decision based on sentiment analysis and forex signal parameters.
|
116 |
+
|
117 |
+
:param quote_sentiment: Sentiment analysis result for {base_currency}.
|
118 |
+
:param base_sentiment: Sentiment analysis result for {quote_currency}.
|
119 |
+
:param entry: Entry price for the trade.
|
120 |
+
:param stop_loss: Stop loss price.
|
121 |
+
:param take_profit: Take profit price.
|
122 |
+
:return: A decision to make the trade or not, along with sentiment analysis results.
|
123 |
+
"""
|
124 |
+
trade_decision = "No trade"
|
125 |
+
decision_reason = f"{base_currency} Sentiment: {quote_sentiment}, {quote_currency} Sentiment: {base_sentiment}"
|
126 |
+
|
127 |
+
# Adjust the logic to account for somewhat bullish/bearish sentiments
|
128 |
+
bullish_sentiments = ["Bullish", "Somewhat_Bullish"]
|
129 |
+
bearish_sentiments = ["Bearish", "Somewhat-Bearish"]
|
130 |
+
|
131 |
+
if quote_sentiment in bullish_sentiments and base_sentiment not in bullish_sentiments:
|
132 |
+
trade_decision = f"Sell {base_currency}/{quote_currency}"
|
133 |
+
elif base_sentiment in bullish_sentiments and quote_sentiment not in bullish_sentiments:
|
134 |
+
trade_decision = f"Buy {base_currency}/{quote_currency}"
|
135 |
+
elif quote_sentiment in bearish_sentiments and base_sentiment not in bearish_sentiments:
|
136 |
+
trade_decision = f"Buy {base_currency}/{quote_currency}"
|
137 |
+
elif base_sentiment in bearish_sentiments and quote_sentiment not in bearish_sentiments:
|
138 |
+
trade_decision = f"Sell {base_currency}/{quote_currency}"
|
139 |
+
|
140 |
+
|
141 |
+
return trade_decision, decision_reason
|
142 |
+
|
143 |
+
def fetch_sentiment_data(api_endpoint, ticker, api_key, sort='LATEST', limit=50):
|
144 |
+
# Prepare the query parameters
|
145 |
+
params = {
|
146 |
+
'function': 'NEWS_SENTIMENT',
|
147 |
+
'tickers': ticker,
|
148 |
+
'apikey': api_key,
|
149 |
+
'sort': sort,
|
150 |
+
'limit': limit
|
151 |
+
}
|
152 |
+
|
153 |
+
# Make the API request
|
154 |
+
response = requests.get(api_endpoint, params=params)
|
155 |
+
|
156 |
+
# Check if the request was successful
|
157 |
+
if response.status_code == 200:
|
158 |
+
# Return the JSON response
|
159 |
+
return response.json()
|
160 |
+
else:
|
161 |
+
# Return an error message
|
162 |
+
return f"Error fetching data: {response.status_code}"
|
163 |
+
|
164 |
+
def load_data(api_key, from_symbol, to_symbol, interval):
|
165 |
+
# Fetch data using the Alpha Vantage API
|
166 |
+
forex_data = fetch_forex_intraday(api_key, from_symbol, to_symbol, interval)
|
167 |
+
|
168 |
+
# Convert the pandas dataframe to a Backtrader data feed
|
169 |
+
data = bt.feeds.PandasData(dataname=forex_data)
|
170 |
+
return data
|
171 |
+
|
172 |
+
def should_trade(strategy, api_endpoint, api_key, base_currency, quote_currency):
|
173 |
+
consistent_periods = 3
|
174 |
+
if len(strategy) < consistent_periods:
|
175 |
+
return False, None, None, "Insufficient data"
|
176 |
+
|
177 |
+
if strategy.last_signal_timeframe in bt.TimeFrame.Names:
|
178 |
+
timeframe = bt.TimeFrame.getname(strategy.last_signal_timeframe)
|
179 |
+
else:
|
180 |
+
timeframe = "Unknown Timeframe"
|
181 |
+
|
182 |
+
base_ticker = f"FOREX:{base_currency}"
|
183 |
+
quote_ticker = f"FOREX:{quote_currency}"
|
184 |
+
|
185 |
+
# Fetch and analyze sentiment data
|
186 |
+
json_response = fetch_sentiment_data(api_endpoint, f"{base_ticker}", api_key)
|
187 |
+
print(fetch_sentiment_data(api_endpoint, f"{quote_currency}", api_key))
|
188 |
+
print(json_response)
|
189 |
+
base_sentiment = analyze_sentiment(json_response, base_ticker)
|
190 |
+
quote_sentiment = analyze_sentiment(json_response, quote_currency)
|
191 |
+
|
192 |
+
# Make a trade decision based on technical and sentiment analysis
|
193 |
+
trade_decision, decision_reason = make_trade_decision(base_currency, quote_currency, quote_sentiment, base_sentiment)
|
194 |
+
|
195 |
+
signal = strategy.crossover[0]
|
196 |
+
if all(strategy.crossover[-i] == signal for i in range(1, consistent_periods + 1)):
|
197 |
+
#timeframe = bt.TimeFrame.getname(strategy.last_signal_timeframe) if strategy.last_signal_timeframe else "Unknown Timeframe"
|
198 |
+
return True, trade_decision, timeframe, decision_reason
|
199 |
+
return False, None, None, "Not enough consistent signals or conflicting sentiment " +decision_reason+"."
|
200 |
+
|
201 |
+
# Define a function to run the backtest and provide trading signals
|
202 |
+
def run_backtest(api_key, from_symbol, to_symbol, interval):
|
203 |
+
# Set up Cerebro engine
|
204 |
+
cerebro = bt.Cerebro()
|
205 |
+
cerebro.addstrategy(TrendFollowingStrategy)
|
206 |
+
|
207 |
+
# Add data feed to Cerebro
|
208 |
+
data = load_data(api_key, from_symbol, to_symbol, interval)
|
209 |
+
cerebro.adddata(data)
|
210 |
+
|
211 |
+
# Set initial cash (optional)
|
212 |
+
cerebro.broker.set_cash(10000)
|
213 |
+
|
214 |
+
# Run the backtest
|
215 |
+
strategy_instance = cerebro.run()[0]
|
216 |
+
api_endpoint = "https://www.alphavantage.co/query" # Replace with actual endpoint
|
217 |
+
|
218 |
+
# Calculate win and loss percentages
|
219 |
+
total_trades = strategy_instance.trade_count
|
220 |
+
total_wins = strategy_instance.win_count
|
221 |
+
total_losses = strategy_instance.loss_count
|
222 |
+
|
223 |
+
win_percentage = (total_wins / total_trades) * 100
|
224 |
+
loss_percentage = (total_losses / total_trades) * 100
|
225 |
+
|
226 |
+
# Determine if it's a buy or sell based on percentages
|
227 |
+
if win_percentage > loss_percentage:
|
228 |
+
signal = "Buy"
|
229 |
+
color = "green"
|
230 |
+
else:
|
231 |
+
signal = "Sell"
|
232 |
+
color = "red"
|
233 |
+
|
234 |
+
return f"Signal: <span style='color: {color}'>{signal}</span>"
|
235 |
+
|
236 |
+
|
237 |
+
# Define a list of popular currency pairs for the dropdowns
|
238 |
+
from_currency_choices = ['EUR', 'GBP', 'USD', 'AUD', 'JPY']
|
239 |
+
to_currency_choices = ['USD', 'JPY', 'GBP', 'AUD', 'CAD']
|
240 |
+
|
241 |
+
# Placeholder link for API key
|
242 |
+
api_key_link = "https://www.alphavantage.co/support/#api-key"
|
243 |
+
|
244 |
+
# Run the Gradio app
|
245 |
+
if __name__ == "__main__":
|
246 |
+
gr.Interface(
|
247 |
+
fn=run_backtest,
|
248 |
+
inputs=[
|
249 |
+
gr.inputs.Textbox(label="API Key", placeholder=api_key_link),
|
250 |
+
gr.inputs.Dropdown(label="From Currency", choices=from_currency_choices),
|
251 |
+
gr.inputs.Dropdown(label="To Currency", choices=to_currency_choices),
|
252 |
+
gr.inputs.Radio(label="Interval", choices=["1min", "5min", "15min", "30min", "60min"])
|
253 |
+
],
|
254 |
+
outputs="html", # Output as HTML to display colored text
|
255 |
+
live=True,
|
256 |
+
title="Trading Signal",
|
257 |
+
description="Run Backtest and Get Trading Signal",
|
258 |
+
).launch()
|
259 |
|
260 |
iface = gr.Interface(fn=greet, inputs="text", outputs="text")
|
261 |
iface.launch()
|
requirements.txt
CHANGED
@@ -1 +1,4 @@
|
|
1 |
-
gradio
|
|
|
|
|
|
|
|
1 |
+
gradio
|
2 |
+
pandas
|
3 |
+
backtrader
|
4 |
+
requests
|