Simplify way to generate report templates (#1788)

* simplify way to generate report templates

* add documentation

Co-authored-by: James Maslek <jmaslek11@gmail.com>
This commit is contained in:
didierlopes.eth
2022-05-10 07:22:24 +01:00
committed by GitHub
parent d3c4919993
commit f47e10918a
2 changed files with 235 additions and 149 deletions

View File

@@ -434,8 +434,7 @@
"outputs": [],
"source": [
"df_sentiment_finbrain = openbb.stocks.ba.models.finbrain.get_sentiment(ticker)\n",
"finbrain_sentiment_val = float(df_sentiment_finbrain.values[-1][0])\n",
"finbrain_sentiment_val = 10"
"finbrain_sentiment_val = float(df_sentiment_finbrain.values[-1][0])"
]
},
{
@@ -583,160 +582,64 @@
"outputs": [],
"source": [
"body = \"\"\n",
"body += f'''\n",
" <div style=\"display:flex; margin-bottom:1cm;\">\n",
" <img src=\"data:image/png;base64,{openbb_image_encoded.decode()}\" alt=\"OpenBB\" style=\"width:144px;height:90px;\">\n",
" <div style=\"margin-left:2em\">\n",
" <p><b>Analyst:</b> {author}</p>\n",
" <p><b>Date :</b> {report_date}</p>\n",
" <p><b>Time :</b> {report_time} {report_timezone}</p>\n",
" <br/>\n",
" <p><b>INVESTMENT RESEARCH REPORT:</b> {ticker}</p>\n",
" </div>\n",
" </div>'''\n",
"\n",
"body += '''\n",
" <div class=\"tab\">\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'SUMMARY')\">SUMMARY</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Overview')\">Overview</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Analysts Opinions')\">Analysts Opinions</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Fundamental Analysis')\">Fundamental Analysis</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Dark Pool and Shorts')\">Dark Pool and Shorts</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Technical Analysis')\">Technical Analysis</button>\n",
" </br>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Insider Trading')\">Insider Trading</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Behavioural Analysis')\">Behavioural Analysis</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Quantitative Analysis')\">Quantitative Analysis</button>\n",
" <button class=\"tablinks\" onclick=\"menu(event, 'Prediction Techniques')\">Prediction Techniques</button> \n",
" </div>'''\n",
"img = f'<img src=\"data:image/png;base64,{openbb_image_encoded.decode()}\" alt=\"OpenBB\" style=\"width:144px;\">'\n",
"body += openbb.widgets.header(img, author, report_date, report_time, report_timezone, f'<b>INVESTMENT RESEARCH REPORT:</b> {ticker}')\n",
"\n",
"body += '<div id=\"SUMMARY\" class=\"tabcontent\">'\n",
"body += '<h3>KPIs:</h3>'\n",
"if avg_ratings_last_30_days < last_price:\n",
" body += f'<p style=\"color:red\">&#10060; Last closing price ({last_price}) > Average price ratings last 30 days ({avg_ratings_last_30_days})</p>'\n",
"else:\n",
" body += f'<p style=\"color:green\">&#x2705; Average price ratings last 30 days ({avg_ratings_last_30_days}) > Last closing price ({last_price})</p>'\n",
"body += openbb.widgets.tablinks([\"SUMMARY\", \"Overview\", \"Analyst Opinions\", \"Fundamental Analysis\", \"Dark Pool and Shorts\", \"Technical Analysis\", \"Insider Trading\", \"Behavioural Analysis\", \"Quantitative Analysis\", \"Prediction Techniques\"])\n",
"\n",
"if regression_slope > 0:\n",
" body += f'<p style=\"color:green\">&#x2705; Regression on the last 30 market days ({regression_slope} dollars/market day) is positive</p>'\n",
"else:\n",
" body += f'<p style=\"color:red\">&#10060; Regression on the last 30 market days ({regression_slope} dollars/market day) is negative</p>'\n",
" \n",
"if rsi_value < 30:\n",
" body += f'<p style=\"color:green\">&#x2705; RSI level is OVERSOLD. {rsi_value} < 30</p>'\n",
"elif rsi_value > 70:\n",
" body += f'<p style=\"color:red\">&#10060; RSI level is OVERBOUGHT. {rsi_value} > 70</p>'\n",
"else:\n",
" body += f'<p style=\"color:orange\">&#128993; RSI level is NORMAL. 30 < {rsi_value} < 70</p>'\n",
" \n",
"if last_10_insider_trading > 0:\n",
" body += f'<p style=\"color:green\">&#x2705; Last 10 insider trading. {last_10_insider_trading} M was bought in value.</p>'\n",
"else:\n",
" body += f'<p style=\"color:red\">&#10060; Last 10 insider trading. {last_10_insider_trading} M was sold in value</p>'\n",
" \n",
"if finbrain_sentiment_val > .1:\n",
" body += f'<p style=\"color:green\">&#x2705; Last FinBrain sentiment is BULLISH: {finbrain_sentiment_val} </p>'\n",
"elif finbrain_sentiment_val < -.1:\n",
" body += f'<p style=\"color:red\">&#10060; Last FinBrain sentiment is BEARISH: {finbrain_sentiment_val} </p>'\n",
"else:\n",
" body += f'<p style=\"color:orange\">&#128993; Last FinBrain sentiment is NEUTRAL: {finbrain_sentiment_val} </p>'\n",
" \n",
"if (regression_val - last_price) > 0:\n",
" body += f'<p style=\"color:green\">&#x2705; Regression for next 20th market price ({regression_val}) > Last closing price ({last_price})</p>'\n",
"else:\n",
" body += f'<p style=\"color:red\">&#10060; Regression for next 20th market price ({regression_val}) < Last closing price ({last_price})</p>'\n",
" \n",
"body += '<h3>Opinion:</h3>'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\";></p>'\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.h(3, \"KPIs\")\n",
"htmlcode += openbb.widgets.kpi([last_price], [\"Last closing price is above the average price ratings of last 30 days\", \"Average price ratings of last 30 day is above last closing price\"], avg_ratings_last_30_days)\n",
"htmlcode += openbb.widgets.kpi([0], [\"Regression (dollars per market day) on last 30 market days is negative\", \"Regression (dollars per market day) on last 30 market days is positive\"], regression_slope)\n",
"htmlcode += openbb.widgets.kpi([30, 70], [\"RSI level is oversold\", \"RSI level is normal\", \"RSI level is overbought\"], rsi_value) \n",
"htmlcode += openbb.widgets.kpi([0], [\"The sum of last 10 insider trading (in millions) was negative\", \"The sum of last 10 insider trading (in millions) was positive\"], last_10_insider_trading)\n",
"htmlcode += openbb.widgets.kpi([-.1, .1], [\"Last FinBrain sentiment is bearish\", \" Last FinBrain sentiment is neutral\", \"Last FinBrain sentiment is bullish\"], finbrain_sentiment_val) \n",
"htmlcode += openbb.widgets.kpi([0], [\"The regression for the next 20th market price is below closing price\", \"The regression for the next 20th market price is above closing price\"], round(regression_val - last_price, 2)) \n",
"body += openbb.widgets.add_tab(\"SUMMARY\", htmlcode)\n",
"\n",
"body += '<div id=\"Overview\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([price_chart])\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Description\") + openbb.widgets.p(overview)])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([price_chart])\n",
"htmlcode += openbb.widgets.row([openbb.widgets.h(3, \"Description\") + openbb.widgets.p(overview)])\n",
"body += openbb.widgets.add_tab(\"Overview\", htmlcode)\n",
"\n",
"body += '<div id=\"Analysts Opinions\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([price_target_chart])\n",
"body += openbb.widgets.row([ratings_over_time_chart])\n",
"body += openbb.widgets.row([df_analyst.to_html()])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([price_target_chart])\n",
"htmlcode += openbb.widgets.row([ratings_over_time_chart])\n",
"htmlcode += openbb.widgets.row([df_analyst.to_html()])\n",
"body += openbb.widgets.add_tab(\"Analyst Opinions\", htmlcode)\n",
"\n",
"body += '<div id=\"Fundamental Analysis\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Estimates\") + df_year_estimates.head().to_html()])\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Earnings\") + df_quarter_earnings.head().to_html()])\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Revenues\") + df_quarter_revenues.head().to_html()])\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"SEC filings\") + df_sec_filings.head().to_html()])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([openbb.widgets.h(3, \"Estimates\") + df_year_estimates.head().to_html()])\n",
"htmlcode += openbb.widgets.row([openbb.widgets.h(3, \"Earnings\") + df_quarter_earnings.head().to_html()])\n",
"htmlcode += openbb.widgets.row([openbb.widgets.h(3, \"Revenues\") + df_quarter_revenues.head().to_html()])\n",
"htmlcode += openbb.widgets.row([openbb.widgets.h(3, \"SEC filings\") + df_sec_filings.head().to_html()])\n",
"body += openbb.widgets.add_tab(\"Fundamental Analysis\", htmlcode)\n",
" \n",
"htmlcode = openbb.widgets.row([net_short_position])\n",
"htmlcode += openbb.widgets.row([price_vs_short_interest])\n",
"htmlcode += openbb.widgets.row([dark_pools])\n",
"htmlcode += openbb.widgets.row([volexch])\n",
"body += openbb.widgets.add_tab(\"Dark Pool and Shorts\", htmlcode)\n",
"\n",
"body += '<div id=\"Dark Pool and Shorts\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([net_short_position])\n",
"body += openbb.widgets.row([price_vs_short_interest])\n",
"body += openbb.widgets.row([dark_pools])\n",
"body += openbb.widgets.row([volexch])\n",
"body += '</div>'\n",
"body += openbb.widgets.add_tab(\"Technical Analysis\", openbb.widgets.row([ta_rsi]))\n",
"\n",
"body += '<div id=\"Technical Analysis\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([ta_rsi])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([openbb.widgets.h(3, \"Last Activity\") + df_insider.head(10).to_html(col_space='75px')])\n",
"body += openbb.widgets.add_tab(\"Insider Trading\", htmlcode)\n",
"\n",
"body += '<div id=\"Insider Trading\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Last Activity\") + df_insider.head(10).to_html(col_space='75px')])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([finbrain_sentiment])\n",
"htmlcode += openbb.widgets.row([snews])\n",
"htmlcode += openbb.widgets.row([openbb.widgets.h(3, \"Stocktwits\") + stocktwits_sentiment])\n",
"body += openbb.widgets.add_tab(\"Behavioural Analysis\", htmlcode)\n",
"\n",
"htmlcode = openbb.widgets.row([bw_month])\n",
"htmlcode += openbb.widgets.row([bw_year])\n",
"body += openbb.widgets.add_tab(\"Quantitative Analysis\", htmlcode)\n",
"\n",
"body += '<div id=\"Behavioural Analysis\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([finbrain_sentiment])\n",
"body += openbb.widgets.row([snews])\n",
"body += openbb.widgets.row([openbb.widgets.h(3, \"Stocktwits\") + stocktwits_sentiment])\n",
"body += '</div>'\n",
"htmlcode = openbb.widgets.row([regression])\n",
"htmlcode += openbb.widgets.row([mc])\n",
"body += openbb.widgets.add_tab(\"Prediction Techniques\", htmlcode)\n",
"\n",
"body += '<div id=\"Quantitative Analysis\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([bw_month])\n",
"body += openbb.widgets.row([bw_year])\n",
"body += '</div>'\n",
"\n",
"body += '<div id=\"Prediction Techniques\" class=\"tabcontent\">'\n",
"body += '</br> <p style=\"border:3px; border-style:solid; border-color:#000000; padding: 1em; width: 1050px;\" contentEditable=\"true\">No comment.</p>'\n",
"body += openbb.widgets.row([regression])\n",
"body += openbb.widgets.row([mc])\n",
"body += '</div>'\n",
"\n",
"\n",
"body += '''\n",
" <script>\n",
" function menu(evt, menu_name) {\n",
" var i, tabcontent, tablinks;\n",
" tabcontent = document.getElementsByClassName(\"tabcontent\");\n",
" for (i = 0; i < tabcontent.length; i++) {\n",
" tabcontent[i].style.display = \"none\";\n",
" }\n",
" tablinks = document.getElementsByClassName(\"tablinks\");\n",
" for (i = 0; i < tablinks.length; i++) {\n",
" tablinks[i].className = tablinks[i].className.replace(\" active\", \"\");\n",
" tablinks[i].style.backgroundColor = \"white\";\n",
" tablinks[i].style.color = \"black\";\n",
" }\n",
" document.getElementById(menu_name).style.display = \"block\";\n",
" \n",
" evt.currentTarget.className += \" active\";\n",
" evt.currentTarget.style.backgroundColor = \"black\";\n",
" evt.currentTarget.style.color = \"white\";\n",
" }\n",
"\n",
" window.onload=function(){\n",
" menu(event, 'SUMMARY');\n",
" };\n",
" </script>'''\n",
"body += openbb.widgets.tab_clickable_evt()\n",
"\n",
"report = openbb.widgets.html_report(title=report_name, stylesheet=stylesheet, body=body)\n",
"\n",
"# to save the results\n",
"with open(report_name + \".html\", \"w\") as fh:\n",
" fh.write(report)"
@@ -745,7 +648,7 @@
{
"cell_type": "code",
"execution_count": null,
"id": "c692b8cf-8b9f-420f-bc01-c7b9cc7e7dd4",
"id": "e15cef06-0d10-4153-b6d3-9762be0ed1dd",
"metadata": {},
"outputs": [],
"source": []
@@ -757,7 +660,8 @@
"hash": "aa1c4d436101635411b2afa2ce061827695b6e3d0efa7f05338cbf11ad26165e"
},
"kernelspec": {
"display_name": "Python 3.8.12 64-bit ('gst': conda)",
"display_name": "Python 3 (ipykernel)",
"language": "python",
"name": "python3"
},
"language_info": {
@@ -770,7 +674,7 @@
"name": "python",
"nbconvert_exporter": "python",
"pygments_lexer": "ipython3",
"version": "3.8.12"
"version": "3.8.6"
}
},
"nbformat": 4,

View File

@@ -75,23 +75,205 @@ def html_report(title: str = "", stylesheet: str = "", body: str = "") -> str:
os.path.join(os.path.dirname(os.path.abspath(__file__)), "widgets", "report.j2")
) as f:
template = Template(f.read())
card = template.render(title=title, stylesheet=stylesheet, body=body)
return card
return template.render(title=title, stylesheet=stylesheet, body=body)
def h(level: int, text: str) -> str:
"""Wrap text into an HTML `h` tag."""
"""Wrap text into an HTML `h` tag.
Parameters
----------
level : int
HTML `h` level tag
text : str
Contents for `h` level tag
Returns
-------
str
HTML code as string
"""
return f"<h{str(level)}>{text}</h{level}>"
def p(text: str) -> str:
"""Wrap text into an HTML `p` tag."""
"""Wrap text into an HTML `p` tag.
Parameters
----------
text : str
Contents for HTML `p` tag
Returns
-------
str
HTML code as string
"""
return f"<p>{text}</p>"
def row(elements: List) -> str:
"""HTML code elements to add in a single row
Parameters
----------
elements : List
List of HTML code elements to add in a row
Returns
-------
str
HTML code as string
"""
with open(
os.path.join(os.path.dirname(os.path.abspath(__file__)), "widgets", "row.j2")
) as f:
template = Template(f.read())
return template.render(elements=elements)
def kpi(thresholds: List[float], sentences: List[str], value: float) -> str:
"""Add new key performance indicator to main page of report
Parameters
----------
thresholds : List[float]
List of thresholds to take into account
sentences : List[str]
List of sentences to take into account. len(sentences) = len(thresholds)+1
value : float
Current value for the KPI in question
Returns
-------
str
HTML code as string
"""
if len(thresholds) == 1 and len(sentences) == 2:
if value < thresholds[0]:
return f'<p style="color:red">&#10060; {sentences[0]}. {value} < {thresholds[0]} </p>'
return f'<p style="color:green">&#x2705; {sentences[1]}. {value} > {thresholds[0]} </p>'
if len(thresholds) == 2 and len(sentences) == 3:
if value < thresholds[0]:
return f'<p style="color:red">&#10060; {sentences[0]}. {value} < {thresholds[0]} </p>'
if value > thresholds[1]:
return f'<p style="color:green">&#x2705; {sentences[2]}. {value} > {thresholds[1]} </p>'
return f'<p style="color:orange">&#128993; {sentences[1]}. {thresholds[0]} < {value} < {thresholds[1]} </p>'
print("Error. KPI condition is not correctly set")
return ""
def add_tab(title: str, htmlcode: str) -> str:
"""Add new tab section for the report. By default adds an opinion editable box at the start.
Parameters
----------
title : str
Title associated with this tab / section
htmlcode : str
All HTML code contain within this section
Returns
-------
str
HTML code as string
"""
return f"""<div id="{title}" class="tabcontent"></br>
<p style="border:3px; border-style:solid;
border-color:#000000; padding: 1em; width: 1050px;" contentEditable="true">
No comment.
</p>{htmlcode}
</div>"""
def tab_clickable_evt() -> str:
"""Adds javascript code within HTML at the bottom that allows the interactivity with tabs.
Returns
-------
str
javascript code in HTML to process interactive tabs
"""
return """
<script>
function menu(evt, menu_name) {
var i, tabcontent, tablinks;
tabcontent = document.getElementsByClassName("tabcontent");
for (i = 0; i < tabcontent.length; i++) {
tabcontent[i].style.display = "none";
}
tablinks = document.getElementsByClassName("tablinks");
for (i = 0; i < tablinks.length; i++) {
tablinks[i].className = tablinks[i].className.replace(" active", "");
tablinks[i].style.backgroundColor = "white";
tablinks[i].style.color = "black";
}
document.getElementById(menu_name).style.display = "block";
evt.currentTarget.className += " active";
evt.currentTarget.style.backgroundColor = "black";
evt.currentTarget.style.color = "white";
}
window.onload=function(){
menu(event, 'SUMMARY');
};
</script>"""
def tablinks(tabs: List[str]) -> str:
"""Adds list of tabs/sections for the reports that are able to be clicked. For every 6 tabs we push them onto a new line.
Parameters
----------
tabs : List[str]
List of tabs/sections for the reports.
Returns
-------
str
HTML code for interactive tabs
"""
htmlcode = '<div class="tab">'
for idx, tab in enumerate(tabs):
htmlcode += f"""<button class="tablinks" onclick="menu(event, '{tab}')">{tab}</button>"""
if ((idx + 1) % 5) == 0:
htmlcode += "</br>"
htmlcode += "</div>"
return htmlcode
def header(img, author, report_date, report_time, report_tz, title) -> str:
"""Creates reports header
Parameters
----------
img : str
Image for customizable report
author : str
Name of author responsible by report
report_date : str
Date when report is run
report_time : str
Time when report is run
report_tz : str
Timezone associated with datetime of report being run
title : str
Title of the report
Returns
-------
str
HTML code for interactive tabs
"""
return f"""
<div style="display:flex; margin-bottom:1cm;">
{img}
<div style="margin-left:2em">
<p><b>Analyst:</b> {author}</p>
<p><b>Date :</b> {report_date}</p>
<p><b>Time :</b> {report_time} {report_tz}</p>
<br/>
<p>{title}</p>
</div>
</div>"""