vsa / vn-stock-analysis-app /modules /stock_analysis.py
danghungithp's picture
Upload 1398 files
bec48e1 verified
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__)
@stock_analysis_bp.route('/stock_analysis', methods=['GET'])
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
)
@stock_analysis_bp.route('/api/stock_valuation_groq')
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)})