Spaces:
Runtime error
Runtime error
from flask import Blueprint, render_template, request, jsonify | |
from vnstock import Vnstock | |
import pandas as pd | |
from datetime import datetime | |
import os | |
from modules.utils import ( | |
detect_candlestick_patterns, calculate_fibonacci_levels, calculate_money_flow, | |
find_double_top_bottom, detect_w_double_bottom, detect_m_double_top, detect_cup_and_handle, | |
plot_candlestick_with_fibo_patterns, get_financial_valuation, calculate_dcf_valuation, | |
calculate_ddm_valuation, calculate_nav, calculate_residual_income, calculate_eva, safe_float, | |
analyze_financial_csv_with_groq, DATA_DIR | |
) | |
def calculate_dupont_analysis(df): | |
""" | |
Calculates DuPont analysis components from financial ratios. | |
""" | |
if 'roe' not in df.columns or 'asset_turnover' not in df.columns or 'net_profit_margin' not in df.columns or 'financial_leverage' not in df.columns: | |
return None | |
roe = df['roe'].iloc[-1] | |
asset_turnover = df['asset_turnover'].iloc[-1] | |
net_profit_margin = df['net_profit_margin'].iloc[-1] | |
financial_leverage = df['financial_leverage'].iloc[-1] | |
return { | |
'roe': roe, | |
'asset_turnover': asset_turnover, | |
'net_profit_margin': net_profit_margin, | |
'financial_leverage': financial_leverage | |
} | |
def calculate_financial_ratios(df): | |
""" | |
Calculates financial ratios from balance sheet and income statement data. | |
""" | |
ratios = {} | |
try: | |
ratios['current_ratio'] = df['current_assets'].iloc[-1] / df['current_liabilities'].iloc[-1] if 'current_assets' in df.columns and 'current_liabilities' in df.columns and df['current_liabilities'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['current_ratio'] = None | |
try: | |
ratios['quick_ratio'] = (df['current_assets'].iloc[-1] - df['inventory'].iloc[-1]) / df['current_liabilities'].iloc[-1] if 'current_assets' in df.columns and 'inventory' in df.columns and 'current_liabilities' in df.columns and df['current_liabilities'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['quick_ratio'] = None | |
try: | |
ratios['roa'] = df['net_income'].iloc[-1] / df['total_assets'].iloc[-1] if 'net_income' in df.columns and 'total_assets' in df.columns and df['total_assets'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['roa'] = None | |
try: | |
ratios['roe'] = df['net_income'].iloc[-1] / df['total_equity'].iloc[-1] if 'net_income' in df.columns and 'total_equity' in df.columns and df['total_equity'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['roe'] = None | |
try: | |
ratios['debt_to_equity'] = df['total_debt'].iloc[-1] / df['total_equity'].iloc[-1] if 'total_debt' in df.columns and 'total_equity' in df.columns and df['total_equity'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['debt_to_equity'] = None | |
try: | |
ratios['profit_margin'] = df['net_income'].iloc[-1] / df['revenue'].iloc[-1] if 'net_income' in df.columns and 'revenue' in df.columns and df['revenue'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['profit_margin'] = None | |
try: | |
ratios['asset_turnover'] = df['revenue'].iloc[-1] / df['total_assets'].iloc[-1] if 'revenue' in df.columns and 'total_assets' in df.columns and df['total_assets'].iloc[-1] != 0 else None | |
except KeyError: | |
ratios['asset_turnover'] = None | |
print(f"Financial Ratios: {ratios}") | |
print(f"Columns in DataFrame: {df.columns}") | |
return ratios | |
stock_analysis_bp = Blueprint('stock_analysis', __name__) | |
def stock_analysis(): | |
symbol = request.args.get('symbol', '').strip().upper() | |
if not symbol: | |
return render_template('stock_analysis.html', symbol='', error=None, chart_path_candle=None, chart_path_money=None, fibonacci_levels=None, pattern_results=None, double_tops=None, double_bottoms=None, cup_handle_patterns=None, financial_valuation=None, dcf_value=None, ddm_value=None, nav_value=None, residual_income=None, eva=None, tables={ | |
'bs_year': '', 'bs_quarter': '', 'is_year': '', 'is_quarter': '', 'cf_year': '', 'ratio_year': '', 'ratio_quarter': '' | |
}) | |
start = request.args.get('start', '2024-01-01') | |
end = request.args.get('end', datetime.now().strftime('%Y-%m-%d')) | |
stock = Vnstock().stock(symbol=symbol, source='VCI') | |
df = stock.quote.history(start=start, end=end, interval='1D') | |
if df is None or df.empty: | |
return render_template('stock_analysis.html', symbol=symbol, error="Không có dữ liệu cho mã cổ phiếu hoặc khoảng thời gian đã chọn.", chart_path_candle=None, chart_path_money=None, fibonacci_levels=None, pattern_results=None, double_tops=None, double_bottoms=None, cup_handle_patterns=None, financial_valuation=None, dcf_value=None, ddm_value=None, nav_value=None, residual_income=None, eva=None, tables={ | |
'bs_year': '', 'bs_quarter': '', 'is_year': '', 'is_quarter': '', 'cf_year': '', 'ratio_year': '', 'ratio_quarter': '' | |
}) | |
candlestick_patterns = detect_candlestick_patterns(df) | |
fibonacci_levels = calculate_fibonacci_levels(df) | |
df = calculate_money_flow(df) | |
double_tops, double_bottoms = find_double_top_bottom(df) | |
w_double_bottoms = detect_w_double_bottom(df) | |
m_double_tops = detect_m_double_top(df) | |
CHART_PATH_CANDLE = "static/images/stock_candle.png" | |
CHART_PATH_MONEY = "static/images/stock_money.png" | |
os.makedirs(os.path.dirname(CHART_PATH_CANDLE), exist_ok=True) | |
import matplotlib.pyplot as plt | |
plt.figure(figsize=(10, 6)) | |
plt.plot(df['time'], df['money_flow'], label='Money Flow') | |
plt.plot(df['time'], df['money_flow_20d_avg'], label='20-Day Avg Money Flow', linestyle='--') | |
plt.xlabel('Time') | |
plt.ylabel('Money Flow') | |
plt.title(f'Money Flow for {symbol}') | |
plt.legend() | |
plt.grid() | |
plt.savefig(CHART_PATH_MONEY) | |
plt.close() | |
pattern_results = {} | |
for pattern_name, pattern_data in candlestick_patterns.items(): | |
if pattern_data is not None and len(pattern_data) == len(df): | |
idx = pattern_data[pattern_data != 0].index | |
if len(idx) > 0: | |
last_idx = idx[-1] | |
last_date = df.loc[last_idx, 'time'] | |
pattern_results[pattern_name] = last_date | |
else: | |
pattern_results[pattern_name] = None | |
else: | |
pattern_results[pattern_name] = None | |
plot_candlestick_with_fibo_patterns( | |
df, fibonacci_levels, pattern_results, symbol, chart_path=CHART_PATH_CANDLE, | |
double_tops=double_tops, double_bottoms=double_bottoms, | |
cup_handle_patterns=None, w_double_bottoms=w_double_bottoms, m_double_tops=m_double_tops | |
) | |
cup_handle_patterns = detect_cup_and_handle(df) | |
financial_valuation = get_financial_valuation(stock) | |
dcf_value = ddm_value = nav_value = residual_income = eva = None | |
# Define file paths | |
bs_year_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'DFbalance_sheet_year.csv') | |
bs_quarter_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'DFbalance_sheet_quarter.csv') | |
is_year_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'DFincome_statement_year.csv') | |
is_quarter_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'DFincome_statement_quarter.csv') | |
cf_year_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'dfcash_flow_year.csv') | |
ratio_year_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'dfratio_year.csv') | |
ratio_quarter_path = os.path.join('/workspaces/vn-stock-analysis-app/vn-stock-analysis-app', 'dfratio_quarter.csv') | |
try: | |
df_bs_year = pd.read_csv(bs_year_path) | |
df_bs_year = df_bs_year[df_bs_year['ticker'].str.upper() == symbol] | |
except: | |
df_bs_year = pd.DataFrame() | |
try: | |
df_bs_quarter = pd.read_csv(bs_quarter_path) | |
df_bs_quarter = df_bs_quarter[df_bs_quarter['ticker'].str.upper() == symbol] | |
except: | |
df_bs_quarter = pd.DataFrame() | |
try: | |
df_is_year = pd.read_csv(is_year_path) | |
df_is_year = df_is_year[df_is_year['ticker'].str.upper() == symbol] | |
except: | |
df_is_year = pd.DataFrame() | |
try: | |
df_is_quarter = pd.read_csv(is_quarter_path) | |
df_is_quarter = df_is_quarter[df_is_quarter['ticker'].str.upper() == symbol] | |
except: | |
df_is_quarter = pd.DataFrame() | |
try: | |
dfcash_flow_year = pd.read_csv(cf_year_path) | |
dfcash_flow_year = dfcash_flow_year[dfcash_flow_year['ticker'].str.upper() == symbol] | |
except: | |
dfcash_flow_year = pd.DataFrame() | |
try: | |
df_ratio_year = pd.read_csv(ratio_year_path) | |
df_ratio_year = df_ratio_year[df_ratio_year['ticker'].str.upper() == symbol] | |
except: | |
df_ratio_year = pd.DataFrame() | |
try: | |
df_ratio_quarter = pd.read_csv(ratio_quarter_path) | |
df_ratio_quarter = df_ratio_quarter[df_ratio_quarter['ticker'].str.upper() == symbol] | |
except: | |
df_ratio_quarter = pd.DataFrame() | |
tables = { | |
'bs_year': df_bs_year.to_html(classes='table table-bordered table-hover', index=False) if not df_bs_year.empty else '', | |
'bs_quarter': df_bs_quarter.to_html(classes='table table-bordered table-hover', index=False) if not df_bs_quarter.empty else '', | |
'is_year': df_is_year.to_html(classes='table table-bordered table-hover', index=False) if not df_is_year.empty else '', | |
'is_quarter': df_is_quarter.to_html(classes='table table-bordered table-hover', index=False) if not df_is_quarter.empty else '', | |
'cf_year': dfcash_flow_year.to_html(classes='table table-bordered table-hover', index=False) if not dfcash_flow_year.empty else '', | |
'ratio_year': df_ratio_year.to_html(classes='table table-bordered table-hover', index=False) if not df_ratio_year.empty else '', | |
'ratio_quarter': df_ratio_quarter.to_html(classes='table table-bordered table-hover', index=False) if not df_ratio_quarter.empty else '' | |
} | |
return render_template( | |
'stock_analysis.html', | |
symbol=symbol, | |
chart_path_candle=CHART_PATH_CANDLE, | |
chart_path_money=CHART_PATH_MONEY, | |
fibonacci_levels=fibonacci_levels, | |
pattern_results=pattern_results, | |
double_tops=double_tops, | |
double_bottoms=double_bottoms, | |
cup_handle_patterns=cup_handle_patterns, | |
financial_valuation=financial_valuation, | |
dcf_value=dcf_value, | |
ddm_value=ddm_value, | |
nav_value=nav_value, | |
residual_income=residual_income, | |
eva=eva, | |
tables=tables, | |
price_corr_with_vnindex=None, | |
dupont_analysis_year=dupont_analysis_year, | |
dupont_analysis_quarter=dupont_analysis_quarter, | |
financial_ratios_year=financial_ratios_year, | |
financial_ratios_quarter=financial_ratios_quarter | |
) | |
def stock_valuation_groq(): | |
symbol = request.args.get('symbol') or ''.upper() | |
if not symbol: | |
return jsonify({'error': 'Thiếu mã cổ phiếu'}) | |
try: | |
csv_files = [ | |
('RATIO_YEAR', 'dfratio_year.csv'), | |
('RATIO_QUARTER', 'dfratio_quarter.csv'), | |
('BALANCE_SHEET_YEAR', 'DFbalance_sheet_year.csv'), | |
('BALANCE_SHEET_QUARTER', 'DFbalance_sheet_quarter.csv'), | |
('INCOME_STATEMENT_YEAR', 'DFincome_statement_year.csv'), | |
('INCOME_STATEMENT_QUARTER', 'DFincome_statement_quarter.csv'), | |
('CASH_FLOW_YEAR', 'dfcash_flow_year.csv'), | |
] | |
csv_content = '' | |
for label, fname in csv_files: | |
fpath = os.path.join(DATA_DIR, fname) | |
if os.path.exists(fpath): | |
df = pd.read_csv(fpath) | |
symbol_col = None | |
for col in ['ticker', 'cp', 'mã', 'stock', 'symbol']: | |
if col in df.columns: | |
symbol_col = col | |
break | |
if symbol_col: | |
df = df[df[symbol_col].str.upper() == symbol] | |
if not df.empty: | |
csv_content += f'{label}\n' + df.to_csv(index=False) + '\n' | |
if not csv_content: | |
return jsonify({'error': 'Không có dữ liệu tài chính cho mã này trong file CSV.'}) | |
# Prepare additional data for Groq AI | |
additional_data = "" | |
if dupont_analysis_year: | |
additional_data += f"DuPont Analysis (Yearly):\n{dupont_analysis_year}\n" | |
if dupont_analysis_quarter: | |
additional_data += f"DuPont Analysis (Quarterly):\n{dupont_analysis_quarter}\n" | |
if financial_ratios_year: | |
additional_data += f"Financial Ratios (Yearly):\n{financial_ratios_year}\n" | |
if financial_ratios_quarter: | |
additional_data += f"Financial Ratios (Quarterly):\n{financial_ratios_quarter}\n" | |
user_question = f"Hãy phân tích và định giá cổ phiếu {symbol} dựa trên các dữ liệu tài chính, sử dụng các mô hình DCF, DDM, P/B, P/E, NAV, EVA, Residual Income, phân tích DuPont và các chỉ số tài chính (thanh khoản, hiệu quả, khả năng trả nợ, sinh lời). Đưa ra giá trị nội tại, margin of safety, nhận định đầu tư, và giải thích ngắn gọn từng mô hình và kết quả phân tích." | |
groq_result = analyze_financial_csv_with_groq(csv_content, user_question + additional_data) | |
return jsonify({'groq_valuation': groq_result}) | |
except Exception as e: | |
return jsonify({'error': str(e)}) | |