mytest2_app / utils /portfolio.py
cryman38's picture
Upload 14 files
aaa66d0 verified
import math
import pytz
from datetime import datetime
from concurrent.futures import ThreadPoolExecutor
import FinanceDataReader as fdr
from utils.css import load_css
def parse_input(text, cash_amount, cash_ratio):
lines = text.strip().split(',')
stock_inputs = []
total_target_weight = 0
for line in lines:
parts = line.split()
if len(parts) == 4:
stock_code, currency_code, quantity_expr, target_weight_expr = parts
quantity = math.floor(eval(quantity_expr.replace(' ', '')))
target_weight = eval(target_weight_expr.replace(' ', ''))
target_ratio = (1 - cash_ratio / 100) * target_weight
stock_inputs.append((currency_code, stock_code, quantity, target_weight, target_ratio))
total_target_weight += target_weight
cash_amount = math.floor(cash_amount) if cash_amount else 0
default_currency_cash = {'amount': cash_amount, 'target_weight': cash_ratio / 100.0}
stock_total_weight = total_target_weight
for i in range(len(stock_inputs)):
stock_inputs[i] = (stock_inputs[i][0], stock_inputs[i][1], stock_inputs[i][2], stock_inputs[i][3], (1 - default_currency_cash['target_weight']) * stock_inputs[i][3] / stock_total_weight)
return stock_inputs, default_currency_cash
def get_exchange_rate(currency_code, default_currency):
if currency_code.lower() == default_currency.lower():
return 1.0
ticker = f"{currency_code.upper()}{default_currency.upper()}=X"
data = fdr.DataReader(ticker)
if not data.empty:
return data['Close'].iloc[0]
else:
raise ValueError("Failed to retrieve exchange rate data.")
def get_exchange_reflected_stock_price(stock_code, currency_code, default_currency):
new_price = get_current_stock_price(stock_code)
exchange_rate = get_exchange_rate(currency_code, default_currency)
return math.floor(new_price * exchange_rate)
def get_current_stock_price(stock_code):
df = fdr.DataReader(stock_code)
return df['Close'].iloc[-1]
def build_portfolio(stock_inputs, default_currency_cash, default_currency):
portfolio = {}
target_weights = {}
with ThreadPoolExecutor() as executor:
results = executor.map(lambda x: (x[1], get_exchange_reflected_stock_price(x[1], x[0], default_currency), x[2], x[3], x[4], x[0]), stock_inputs)
for stock_code, new_price, quantity, target_weight, target_ratio, currency_code in results:
portfolio[stock_code] = {'quantity': quantity, 'price': new_price, 'target_weight': target_weight, 'currency': currency_code}
target_weights[stock_code] = target_ratio
return portfolio, target_weights, default_currency_cash
def format_quantity(quantity):
if quantity < 0:
return f"({-quantity:,})"
else:
return f"{quantity:,}"
def get_portfolio_rebalancing_info(portfolio, target_weights, krw_cash, default_currency):
css = load_css()
kst = pytz.timezone('Asia/Seoul')
current_time = datetime.now(kst).strftime("%I:%M %p %b-%d-%Y")
total_value = sum(stock['price'] * stock['quantity'] for stock in portfolio.values()) + krw_cash['amount']
total_new_stock_value = 0
total_trade_value = 0
adjustments = []
currency_symbols = {
"KRW": "₩",
"USD": "$",
"EUR": "€",
"JPY": "¥",
"GBP": "£",
"AUD": "A$",
"CAD": "C$",
"CHF": "CHF",
"CNY": "¥",
"INR": "₹",
"BRL": "R$",
"ZAR": "R",
"SGD": "S$",
"HKD": "HK$"
}
currency_symbol = currency_symbols.get(default_currency.upper(), "")
# Calculate current weights and values
current_weights = {stock_code: (stock['price'] * stock['quantity'] / total_value) * 100 for stock_code, stock in portfolio.items()}
current_values = {stock_code: stock['price'] * stock['quantity'] for stock_code, stock in portfolio.items()}
# Include cash in current weights and values
current_weights['CASH'] = (krw_cash['amount'] / total_value) * 100
current_values['CASH'] = krw_cash['amount']
# Sort stocks by current weight in descending order
sorted_stocks = sorted(current_weights.items(), key=lambda x: x[1], reverse=True)
# Display current weights and values section
current_info_html = "<h3>Your Portfolio Holdings</h3><div class='table-container'><table style='border-collapse: collapse;'>"
current_info_html += "<thead><tr><th style='border: 1px hidden #ddd; text-align: center;'>Stock Code</th><th style='border: 1px hidden #ddd; text-align: center;'>Current Weight (%)</th><th style='border: 1px hidden #ddd; text-align: center;'>Current Value</th></tr></thead><tbody>"
for stock_code, weight in sorted_stocks:
current_info_html += (
f"<tr>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{stock_code.upper()}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{weight:.1f}%</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{currency_symbol}{current_values[stock_code]:,.0f}</td>"
f"</tr>"
)
current_info_html += "</tbody></table></div><br>"
for stock_code, stock_data in portfolio.items():
current_value = stock_data['price'] * stock_data['quantity']
target_value = total_value * target_weights.get(stock_code, 0)
difference = target_value - current_value
trade_quantity = math.floor(difference / stock_data['price']) if difference > 0 else -math.ceil(-difference / stock_data['price'])
new_quantity = trade_quantity + stock_data['quantity']
new_value = new_quantity * stock_data['price']
trade_value = trade_quantity * stock_data['price']
total_trade_value += abs(trade_value)
total_new_stock_value += new_value
current_value_pct = (current_value / total_value) * 100
new_value_pct = (new_value / total_value) * 100
adjustments.append((difference, current_value, target_value, current_value_pct, trade_quantity, stock_code, stock_data['price'], new_value, trade_value, stock_data['quantity'], new_quantity, target_weights[stock_code], new_value_pct, stock_data['target_weight'], stock_data['currency']))
krw_new_amount = total_value - total_new_stock_value
krw_target_value = total_value * krw_cash['target_weight']
krw_difference = krw_new_amount - krw_cash['amount']
trade_quantity = krw_difference
new_quantity = krw_cash['amount'] + trade_quantity
new_value = new_quantity
trade_value = trade_quantity
current_value = krw_cash['amount']
current_value_pct = (current_value / total_value) * 100
new_value_pct = (new_value / total_value) * 100
adjustments.append((krw_difference, current_value, krw_target_value, current_value_pct, trade_quantity, 'CASH', 1, new_value, trade_value, krw_cash['amount'], new_quantity, krw_cash['target_weight'], new_value_pct, '', 'KRW'))
portfolio_info = css + f"""
<div><br>
<p><span class="wrap-text" style='font-size: 1.6rem; font-weight: bold; color: #1678fb;'>{currency_symbol}{total_value:,.0f}</span> as of <span style='color: #6e6e73;'>{current_time}</span></p>
<br></div>
"""
currency_totals = {stock_data['currency']: {'amount': 0, 'weight': 0} for stock_data in portfolio.values()}
for stock_code, stock_data in portfolio.items():
currency = stock_data['currency']
current_value = stock_data['price'] * stock_data['quantity']
currency_totals[currency]['amount'] += current_value
currency_totals[currency]['weight'] += current_value / total_value
currency_totals['CASH'] = {'amount': krw_cash['amount'], 'weight': krw_cash['amount'] / total_value}
sorted_currencies = sorted(currency_totals.items(), key=lambda x: x[1]['weight'], reverse=True)
currency_table = "<h3>Your Portfolio by Currency</h3><div class='table-container wrap-text'><table style='border-collapse: collapse;'>"
currency_table += "<thead><tr><th style='border: 1px hidden #ddd; text-align: center;'>Currency</th><th style='border: 1px hidden #ddd; text-align: center;'>Total Weight (%)</th><th style='border: 1px hidden #ddd; text-align: center;'>Total Value</th></tr></thead><tbody>"
for currency, data in sorted_currencies:
currency_table += (
f"<tr>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{currency.upper()}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{data['weight'] * 100:.1f}%</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{currency_symbol}{data['amount']:,}</td>"
f"</tr>"
)
currency_table += "</tbody></table></div><br>"
result_message = portfolio_info + current_info_html + currency_table + "<h3>Re-Balancing Analysis</h3><div class='table-container wrap-text'><table style='border-collapse: collapse;'>"
result_message += "<thead><tr><th style='border: 1px hidden #ddd; text-align: center;'>Stock Code</th><th style='border: 1px hidden #ddd; text-align: center;'>Current Weight (%)</th><th style='border: 1px hidden #ddd; text-align: center;'>Target Weight</th><th style='border: 1px hidden #ddd; text-align: center;'>Target Ratio (%)</th><th style='border: 1px hidden #ddd; text-align: center;'>Buy or Sell?</th><th style='border: 1px hidden #ddd; text-align: center;'>Trade Amount</th><th style='border: 1px hidden #ddd; text-align: center;'>Current Price per Share</th><th style='border: 1px hidden #ddd; text-align: center;'>Estimated # of<br> Shares to Buy or Sell</th><th style='border: 1px hidden #ddd; text-align: center;'>Quantity of Units</th><th style='border: 1px hidden #ddd; text-align: center;'>Market Value</th><th style='border: 1px hidden #ddd; text-align: center;'>% Asset Allocation</th></tr></thead><tbody>"
for adj in adjustments:
difference, current_value, target_value, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity, target_ratio, new_value_pct, target_weight, currency = adj
Buy_or_Sell = ""
if trade_quantity > 0:
Buy_or_Sell = f"<span class='buy-sell buy'>Buy</span>"
elif trade_quantity < 0:
Buy_or_Sell = f"<span class='buy-sell sell'>Sell</span>"
else:
Buy_or_Sell = f"<span></span>"
current_value_pct_str = f"{current_value_pct:.1f}%"
target_weight_str = f"<span class='highlight-edit'>{target_weight}</span>" if stock_code != 'CASH' else ''
target_ratio_str = f"<span class='highlight-edit'>{target_ratio * 100:.1f}%</span>" if stock_code == 'CASH' else f"{target_ratio * 100:.1f}%"
trade_value_str = f"<span class='highlight-sky'>{format_quantity(trade_value)}</span>" if trade_value != 0 else ''
price_str = f"{currency_symbol}{price:,.0f}" if stock_code != 'CASH' else ''
trade_quantity_str = (
f"<span class='highlight-sky'>{format_quantity(trade_quantity)}</span>"
if stock_code != 'CASH' and trade_value != 0 else ''
)
old_quantity_str = f"{old_quantity:,.0f}{new_quantity:,.0f}" if stock_code != 'CASH' else ''
new_value_str = f"{currency_symbol}{new_value:,.0f}"
new_value_pct_str = f"{new_value_pct:.1f}%"
result_message += (
f"<tr>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{stock_code.upper()}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{current_value_pct_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{target_weight_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{target_ratio_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{Buy_or_Sell}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{trade_value_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{price_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{trade_quantity_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{old_quantity_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{new_value_str}</td>"
f"<td style='border: 1px hidden #ddd; text-align: center;'>{new_value_pct_str}</td>"
f"</tr>"
)
result_message += "</tbody></table></div>"
return result_message
def rebalancing_tool(user_input, cash_amount, cash_ratio, default_currency):
try:
stock_inputs, default_currency_cash = parse_input(user_input, cash_amount, cash_ratio)
portfolio, target_weights, default_currency_cash = build_portfolio(stock_inputs, default_currency_cash, default_currency)
result = get_portfolio_rebalancing_info(portfolio, target_weights, default_currency_cash, default_currency)
return result
except Exception as e:
return str(e)