myfxbook / app.py
Junivert's picture
Update app.py
526adbb verified
from flask import Flask, jsonify, request
import requests
from bs4 import BeautifulSoup
import urllib3
app = Flask(__name__)
urllib3.disable_warnings(urllib3.exceptions.InsecureRequestWarning)
BASE_URL = "https://www.myfxbook.com"
@app.route("/calendar")
def calendar():
url = BASE_URL + "/forex-economic-calendar"
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(url, headers=headers, verify=False)
soup = BeautifulSoup(response.text, "html.parser")
rows = soup.find_all("tr", class_="economicCalendarRow")
events = []
for row in rows:
try:
row_id = row.get("data-row-id")
date_div = row.find("div", attrs={"data-calendardatetd": True})
date_time = date_div.get("data-calendardatetd", "") if date_div else ""
date_text = date_div.get_text(strip=True) if date_div else ""
span = row.find("span", attrs={"name": "calendarLeft"})
time_left = span.get_text(strip=True) if span else ""
importance = int(span.get("importance", "0")) if span else 0
timestamp = int(span.get("time", "0")) if span else 0
is_holiday = span.get("data-is-holiday", "false") == "true" if span else False
is_passed = span.get("isPassed", "0") == "1" if span else False
tds = row.find_all("td")
if len(tds) < 9:
continue
country_div = row.find("i", title=True)
country = country_div.get("title", "").strip() if country_div else ""
currency = tds[3].get_text(strip=True)
event_link_tag = tds[4].select_one("a.calendar-event-link")
event_name = event_link_tag.get_text(strip=True) if event_link_tag else ""
event_url = event_link_tag.get("href", "") if event_link_tag else ""
impact_div = tds[5].find("div")
impact_level = impact_div.get_text(strip=True) if impact_div else ""
impact_class = " ".join(impact_div.get("class", [])) if impact_div else ""
impact = {"class": impact_class, "level": impact_level} if impact_div else None
previous = tds[6].get_text(strip=True)
consensus = tds[7].get_text(strip=True)
actual = tds[8].get_text(strip=True)
icon_done = row.select_one(".calendarEventDone")
icon_done_visible = icon_done and icon_done.get("style", "") != "display: none"
icon_holiday_divs = row.select("div")
icon_holiday_visible = (
len(icon_holiday_divs) > 2 and
icon_holiday_divs[2].get("style", "") != "display: none"
)
event_data = {
"id": row_id,
"date": date_text,
"date_time": date_time.replace(" ", "T"),
"time_left": time_left,
"importance": importance,
"timestamp": timestamp,
"is_holiday": is_holiday,
"is_passed": is_passed,
"country": country,
"currency": currency,
"event_name": event_name,
"event_url": event_url,
"impact": impact,
"previous": previous,
"consensus": consensus,
"actual": actual,
"icons": {
"done": icon_done_visible,
"holiday": icon_holiday_visible
}
}
events.append(event_data)
except:
continue
return jsonify(events)
@app.route("/detail")
def detail():
relative_url = request.args.get("url")
if not relative_url:
return jsonify({"error": "Missing 'url' query parameter"}), 400
full_url = relative_url if relative_url.startswith("http") else BASE_URL + relative_url
headers = {"User-Agent": "Mozilla/5.0"}
response = requests.get(full_url, headers=headers, verify=False)
if response.status_code != 200:
return jsonify({"error": f"Failed to fetch detail from {full_url}"}), 500
soup = BeautifulSoup(response.text, "html.parser")
paragraphs = soup.select("div.margin-top-15 p")
description = [p.get_text(strip=True) for p in paragraphs]
impact_el = soup.find("span", class_="impact_high")
impact = impact_el.get_text(strip=True) if impact_el else None
currency_el = soup.find("span", id="eventSymbol")
currency = currency_el.get_text(strip=True) if currency_el else None
source_label = soup.find("span", string="Source:")
source_el = source_label.find_next("a") if source_label else None
source = {
"name": source_el.get_text(strip=True) if source_el else None,
"url": source_el.get("href") if source_el else None
} if source_el else None
category_el = soup.find("a", href=lambda x: x and "category/" in x)
category = category_el.get_text(strip=True) if category_el else None
units = None
for unit_block in soup.select(".category-units .display-flex"):
label = unit_block.find("span")
if label and "Units:" in label.text:
value = label.find_next_sibling("span")
if value:
units = value.get_text(strip=True)
break
latest_block = soup.select_one(".calendar-info-release-block:nth-of-type(2)")
previous = latest_block.find("span", {"data-previous": True}) if latest_block else None
actual = latest_block.find("span", {"data-actual": True}) if latest_block else None
consensus_latest = latest_block.select_one(".calendar-info-release-line:nth-of-type(2) span:nth-of-type(2)") if latest_block else None
next_block = soup.select_one(".calendar-info-release-block:nth-of-type(3)")
next_date = next_block.select_one(".calendar-info-release-line:nth-of-type(1) span:nth-of-type(2)") if next_block else None
time_left = next_block.select_one(".calendar-info-release-line:nth-of-type(2) span:nth-of-type(2)") if next_block else None
consensus_next = next_block.select_one(".calendar-info-release-line:nth-of-type(3) span:nth-of-type(2)") if next_block else None
return jsonify({
"description": description,
"impact_level": impact,
"currency": currency,
"latest_release": {
"previous": previous.get_text(strip=True) if previous else None,
"consensus": consensus_latest.get_text(strip=True) if consensus_latest else None,
"actual": actual.get_text(strip=True) if actual else None
},
"next_release": {
"date": next_date.get_text(strip=True) if next_date else None,
"time_left": time_left.get_text(strip=True) if time_left else None,
"consensus": consensus_next.get_text(strip=True) if consensus_next else None
},
"meta": {
"source": source,
"category": category,
"units": units
}
})
if __name__ == "__main__":
app.run(host="0.0.0.0", port=7860, debug=True)