|
import io |
|
import matplotlib.pyplot as plt |
|
import FinanceDataReader as fdr |
|
import pandas as pd |
|
from concurrent.futures import ThreadPoolExecutor, as_completed |
|
|
|
def get_stock_prices(stock_code, days): |
|
try: |
|
df = fdr.DataReader(stock_code) |
|
end_date = pd.to_datetime('today') |
|
start_date = pd.date_range(end=end_date, periods=days, freq='B')[0] |
|
df = df[(df.index >= start_date) & (df.index <= end_date)] |
|
if df.empty: |
|
print(f"<p style='color: red;'>No data available for {stock_code}</p>") |
|
return None |
|
return df['Close'] |
|
except Exception as e: |
|
print(f"<p style='color: red;'>Failed to fetch data for {stock_code}: {e}</p>") |
|
return None |
|
|
|
def share_price_trend(stock_codes, days): |
|
stock_prices = {} |
|
with ThreadPoolExecutor(max_workers=10) as executor: |
|
futures = {executor.submit(get_stock_prices, stock_code.strip(), int(days)): stock_code.strip() for stock_code in stock_codes.split(',')} |
|
for future in as_completed(futures): |
|
stock_code = futures[future] |
|
try: |
|
prices = future.result() |
|
if prices is not None: |
|
stock_prices[stock_code] = prices |
|
except Exception as e: |
|
print(f"<p style='color: red;'>Failed to fetch data for {stock_code}: {e}</p>") |
|
|
|
if not stock_prices: |
|
return "<p style='color: red;'>No data available for the provided stock codes.</p>" |
|
|
|
plt.switch_backend('agg') |
|
plt.style.use('tableau-colorblind10') |
|
|
|
fig, ax = plt.subplots(figsize=(8, 4.5)) |
|
for stock_code, prices in stock_prices.items(): |
|
relative_prices = prices / prices.iloc[0] |
|
ax.plot(prices.index, relative_prices, label=stock_code.upper()) |
|
|
|
|
|
ax.spines['top'].set_visible(False) |
|
ax.spines['right'].set_visible(False) |
|
ax.spines['left'].set_visible(False) |
|
ax.spines['bottom'].set_visible(False) |
|
|
|
ax.xaxis.set_visible(False) |
|
ax.yaxis.set_visible(False) |
|
|
|
|
|
ax.grid(True, which='both', linestyle='--', linewidth=0.5) |
|
|
|
ax.legend() |
|
plt.tight_layout() |
|
|
|
svg_graph = io.StringIO() |
|
plt.savefig(svg_graph, format='svg') |
|
svg_graph.seek(0) |
|
svg_data = svg_graph.getvalue() |
|
plt.close() |
|
|
|
svg_data = svg_data.replace('<svg ', '<svg width="100%" height="100%" ') |
|
svg_data = svg_data.replace('</svg>', ''' |
|
<defs> |
|
<linearGradient id="grad1" x1="0%" y1="0%" x2="100%" y2="0%"> |
|
<stop offset="0%" style="stop-color:rgb(173,216,230);stop-opacity:1" /> |
|
<stop offset="100%" style="stop-color:rgb(0,191,255);stop-opacity:1" /> |
|
</linearGradient> |
|
<filter id="dropshadow" height="130%"> |
|
<feGaussianBlur in="SourceAlpha" stdDeviation="3"/> |
|
<feOffset dx="2" dy="2" result="offsetblur"/> |
|
<feMerge> |
|
<feMergeNode/> |
|
<feMergeNode in="SourceGraphic"/> |
|
</feMerge> |
|
</filter> |
|
</defs> |
|
<style> |
|
@keyframes lineAnimation { |
|
from { |
|
stroke-dasharray: 0, 1000; |
|
} |
|
to { |
|
stroke-dasharray: 1000, 0; |
|
} |
|
} |
|
path { |
|
animation: lineAnimation 1s linear forwards; |
|
} |
|
</style> |
|
</svg>''') |
|
|
|
svg_data = svg_data.replace('stroke="#1f77b4"', 'stroke="url(#grad1)" filter="url(#dropshadow)"') |
|
|
|
html_table = "<h3>Stock Prices Data</h3><div class='table-container'><table>" |
|
html_table += "<thead><tr><th>Date</th>" |
|
for stock_code in stock_prices.keys(): |
|
html_table += f"<th>{stock_code.upper()}</th>" |
|
html_table += "</tr></thead><tbody>" |
|
|
|
|
|
all_dates = pd.date_range(start=min(df.index.min() for df in stock_prices.values()), end=pd.to_datetime('today'), freq='B') |
|
all_dates = all_dates[::-1] |
|
|
|
for date in all_dates: |
|
html_table += f"<tr><td>{date.strftime('%Y-%m-%d')}</td>" |
|
for stock_code in stock_prices.keys(): |
|
price = stock_prices[stock_code].get(date, None) |
|
if price is not None: |
|
html_table += f"<td>{price:,.2f}</td>" |
|
else: |
|
html_table += "<td>N/A</td>" |
|
html_table += "</tr>" |
|
|
|
html_table += "</tbody></table></div>" |
|
|
|
graph_html = f'<h3>Relative Stock Prices Over the Last {days} Days</h3>{svg_data}' |
|
return graph_html + html_table |
|
|
|
|
|
|
|
|
|
|
|
|
|
|