cryman38 commited on
Commit
61d33b7
·
verified ·
1 Parent(s): 01a9790

Upload 2 files

Browse files
Files changed (2) hide show
  1. app.py +200 -0
  2. requirements.txt +6 -0
app.py ADDED
@@ -0,0 +1,200 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ import gradio as gr
2
+ import logging
3
+ import math
4
+ import FinanceDataReader as fdr
5
+ import requests
6
+ import ssl
7
+ from datetime import datetime
8
+
9
+ # 현재 날짜를 "Jun-20-2024" 형식으로 가져오기
10
+ current_date = datetime.now().strftime("%b-%d-%Y")
11
+
12
+ # SSL 인증서 검증 비활성화
13
+ ssl._create_default_https_context = ssl._create_unverified_context
14
+
15
+ # 로깅 설정
16
+ logging.basicConfig(level=logging.INFO)
17
+ logger = logging.getLogger(__name__)
18
+
19
+ exchange_rates = {}
20
+
21
+ class StockCodeNotFoundError(Exception):
22
+ pass
23
+
24
+ class CurrencyConversionError(Exception):
25
+ pass
26
+
27
+ def parse_input(text):
28
+ logger.info(f"Parsing input: {text}")
29
+ try:
30
+ lines = text.strip().split(',')
31
+ stock_inputs = []
32
+ total_target_weight = 0
33
+ krw_cash = None
34
+
35
+ for line in lines:
36
+ parts = line.split()
37
+ if len(parts) == 4:
38
+ country_code, stock_code, quantity_expr, target_weight_expr = parts
39
+ quantity = math.floor(eval(quantity_expr.replace(' ', '')))
40
+ target_weight = eval(target_weight_expr.replace(' ', ''))
41
+ stock_inputs.append((country_code, stock_code, quantity, target_weight))
42
+ total_target_weight += target_weight
43
+ elif len(parts) == 2:
44
+ cash_amount_expr, target_weight_expr = parts
45
+ cash_amount = math.floor(eval(cash_amount_expr.replace(' ', '')))
46
+ krw_cash = {'amount': cash_amount, 'target_weight': eval(target_weight_expr.replace(' ', ''))}
47
+ elif len(parts) == 1:
48
+ cash_amount_expr = parts[0]
49
+ cash_amount = math.floor(eval(cash_amount_expr.replace(' ', '')))
50
+ krw_cash = {'amount': cash_amount, 'target_weight': 0}
51
+ else:
52
+ raise ValueError("Invalid input format.")
53
+
54
+ if krw_cash is None:
55
+ krw_cash = {'amount': 0, 'target_weight': 0}
56
+
57
+ cash_ratio = krw_cash['target_weight']
58
+ stock_total_weight = total_target_weight
59
+
60
+ for i in range(len(stock_inputs)):
61
+ stock_inputs[i] = (stock_inputs[i][0], stock_inputs[i][1], stock_inputs[i][2], (1 - cash_ratio) * stock_inputs[i][3] / stock_total_weight)
62
+
63
+ logger.info(f"Parsed stock inputs: {stock_inputs}, KRW cash: {krw_cash}")
64
+ return stock_inputs, krw_cash
65
+ except (ValueError, SyntaxError) as e:
66
+ logger.error("Error parsing input", exc_info=e)
67
+ raise ValueError("Invalid input format. Example: krw 458730 530 8, krw 368590 79 2, 518192")
68
+
69
+ def get_exchange_rate(country_code):
70
+ if country_code.upper() in exchange_rates:
71
+ return exchange_rates[country_code.upper()]
72
+
73
+ logger.info(f"Fetching exchange rate - {country_code}")
74
+ if country_code.upper() == 'KRW':
75
+ return 1
76
+
77
+ url = f"https://quotation-api-cdn.dunamu.com/v1/forex/recent?codes=FRX.KRW{country_code.upper()}"
78
+ try:
79
+ response = requests.get(url, verify=False)
80
+ response.raise_for_status()
81
+ data = response.json()
82
+ exchange_rate = data[0]['basePrice']
83
+ exchange_rates[country_code.upper()] = exchange_rate
84
+ logger.info(f"Exchange rate - {country_code}: {exchange_rate}")
85
+ return exchange_rate
86
+ except (requests.RequestException, IndexError) as e:
87
+ logger.error(f"Error fetching exchange rate - {country_code}", exc_info=e)
88
+ raise CurrencyConversionError("Error fetching exchange rate. Please enter a valid country code.")
89
+
90
+ def get_exchange_reflected_stock_price(stock_code, country_code):
91
+ logger.info(f"Fetching exchange reflected stock price - {stock_code} in {country_code}")
92
+ try:
93
+ current_price = get_current_stock_price(stock_code)
94
+ exchange_rate = get_exchange_rate(country_code)
95
+ reflected_price = math.floor(current_price * exchange_rate)
96
+ logger.info(f"Reflected stock price - {stock_code}: {reflected_price}")
97
+ return reflected_price
98
+ except (StockCodeNotFoundError, CurrencyConversionError) as e:
99
+ logger.error(f"Error fetching reflected stock price - {stock_code} in {country_code}", exc_info=e)
100
+ raise e
101
+
102
+ def get_current_stock_price(stock_code):
103
+ logger.info(f"Fetching current stock price - {stock_code}")
104
+ try:
105
+ df = fdr.DataReader(stock_code)
106
+ current_price = df['Close'].iloc[-1]
107
+ logger.info(f"Current stock price - {stock_code}: {current_price}")
108
+ return current_price
109
+ except ValueError as e:
110
+ logger.error(f"Error fetching stock price - {stock_code}", exc_info=e)
111
+ raise StockCodeNotFoundError("Stock code not found. Please enter a valid stock code.")
112
+
113
+ def build_portfolio(stock_inputs, krw_cash):
114
+ portfolio = {}
115
+ target_weights = {}
116
+
117
+ logger.info(f"Building portfolio - stock inputs: {stock_inputs}, KRW cash: {krw_cash}")
118
+
119
+ for stock_input in stock_inputs:
120
+ country_code, stock_code, quantity, target_weight = stock_input
121
+ current_price = get_exchange_reflected_stock_price(stock_code, country_code)
122
+ portfolio[stock_code] = {'quantity': quantity, 'price': current_price, 'country_code': country_code}
123
+ target_weights[stock_code] = target_weight
124
+
125
+ if krw_cash is None:
126
+ krw_cash = {'amount': 0, 'target_weight': 0}
127
+
128
+ logger.info(f"Portfolio built: {portfolio}, target weights: {target_weights}, KRW cash: {krw_cash}")
129
+ return portfolio, target_weights, krw_cash
130
+
131
+ def get_portfolio_rebalancing_info(portfolio, target_weights, krw_cash):
132
+ logger.info("Calculating portfolio rebalancing information")
133
+
134
+ total_value = sum(stock['price'] * stock['quantity'] for stock in portfolio.values()) + krw_cash['amount']
135
+ total_new_stock_value = 0
136
+ total_trade_value = 0
137
+ adjustments = []
138
+
139
+ for stock_code, stock_data in portfolio.items():
140
+ current_value = stock_data['price'] * stock_data['quantity']
141
+ target_weight = target_weights.get(stock_code, 0)
142
+ target_value = total_value * target_weights.get(stock_code, 0)
143
+ difference = target_value - current_value
144
+ trade_quantity = math.floor(difference / stock_data['price']) if difference > 0 else -math.ceil(-difference / stock_data['price'])
145
+ new_quantity = trade_quantity + stock_data['quantity']
146
+ trade_value = trade_quantity * stock_data['price']
147
+ total_trade_value += abs(trade_value)
148
+ new_value = trade_value + current_value
149
+ total_new_stock_value += new_value
150
+ current_value_pct = (current_value / total_value) * 100
151
+
152
+ adjustments.append((difference, current_value, target_weight, current_value_pct, trade_quantity, stock_code, stock_data['price'], new_value, trade_value, stock_data['quantity'], new_quantity))
153
+
154
+ result_message = ""
155
+ for adjustment in adjustments:
156
+ difference, current_value, target_weight, current_value_pct, trade_quantity, stock_code, price, new_value, trade_value, old_quantity, new_quantity = adjustment
157
+ new_value_pct = (new_value / total_value) * 100
158
+ formatted_trade_quantity = f"{trade_quantity:+,}" if trade_quantity != 0 else "0"
159
+ result_message += (
160
+ f"{stock_code.upper()} @{price:,}\n"
161
+ f" {current_value:,} [{current_value_pct:.1f}%]\n"
162
+ f" [{target_weight * 100:.1f}%] {formatted_trade_quantity}\n\n"
163
+ )
164
+
165
+ if krw_cash:
166
+ krw_new_amount = total_value - total_new_stock_value
167
+ krw_target_weight = krw_cash['target_weight']
168
+ krw_difference = krw_new_amount - krw_cash['amount']
169
+ krw_new_pct = (krw_new_amount / total_value) * 100
170
+ formatted_krw_difference = f"{krw_difference:+,}" if krw_difference != 0 else "0"
171
+ result_message += (
172
+ f"Cash\n"
173
+ f" {krw_cash['amount']:,} [{(krw_cash['amount'] / total_value) * 100:.1f}%]\n"
174
+ f" [{krw_target_weight * 100:.1f}%] {formatted_krw_difference}\n\n"
175
+ )
176
+
177
+ result_message += f"Total Portfolio Value: {total_value:,}"
178
+ logger.info("Portfolio rebalancing information calculated")
179
+ return result_message
180
+
181
+ def rebalance_portfolio(input_text):
182
+ try:
183
+ stock_inputs, krw_cash = parse_input(input_text)
184
+ portfolio, target_weights, krw_cash = build_portfolio(stock_inputs, krw_cash)
185
+ result_message = get_portfolio_rebalancing_info(portfolio, target_weights, krw_cash)
186
+ return result_message
187
+ except Exception as e:
188
+ return str(e)
189
+
190
+ # Gradio 인터페이스 설정
191
+ interface = gr.Interface(
192
+ fn=rebalance_portfolio,
193
+ inputs="text",
194
+ outputs="text",
195
+ title=f"Re-Balancing Analysis | Your Portfolio Holdings as of {current_date} | All Accounts",
196
+ description="Main currency: KRW | Enter portfolio data in the format: usd schd 21 8, krw 368590 530 2, 518192 1/3"
197
+ )
198
+
199
+ if __name__ == "__main__":
200
+ interface.launch(share=True)
requirements.txt ADDED
@@ -0,0 +1,6 @@
 
 
 
 
 
 
 
1
+ python-telegram-bot==13.15
2
+ beautifulsoup4
3
+ finance-datareader
4
+ requests
5
+ plotly
6
+ gradio