Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -1,3 +1,373 @@
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1 |
if not history:
|
| 2 |
logger.warning("History is empty, initializing with user message.")
|
| 3 |
history = [{"role": "user", "content": ""}]
|
|
|
|
| 1 |
+
import os
|
| 2 |
+
import sys
|
| 3 |
+
import subprocess
|
| 4 |
+
import re
|
| 5 |
+
import multiprocessing
|
| 6 |
+
from collections.abc import Iterator
|
| 7 |
+
import gradio as gr
|
| 8 |
+
from huggingface_hub import hf_hub_download, login
|
| 9 |
+
import logging
|
| 10 |
+
|
| 11 |
+
# Set up logging
|
| 12 |
+
logging.basicConfig(level=logging.INFO)
|
| 13 |
+
logger = logging.getLogger(__name__)
|
| 14 |
+
|
| 15 |
+
# Install llama-cpp-python if not present
|
| 16 |
+
try:
|
| 17 |
+
from llama_cpp import Llama
|
| 18 |
+
except ModuleNotFoundError:
|
| 19 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "llama-cpp-python"])
|
| 20 |
+
from llama_cpp import Llama
|
| 21 |
+
|
| 22 |
+
# Install yfinance if not present (for CAGR calculations)
|
| 23 |
+
try:
|
| 24 |
+
import yfinance as yf
|
| 25 |
+
except ModuleNotFoundError:
|
| 26 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "yfinance"])
|
| 27 |
+
import yfinance as yf
|
| 28 |
+
|
| 29 |
+
# Import pandas for handling DataFrame column structures
|
| 30 |
+
import pandas as pd
|
| 31 |
+
|
| 32 |
+
# Additional imports for visualization and file handling
|
| 33 |
+
try:
|
| 34 |
+
import matplotlib.pyplot as plt
|
| 35 |
+
from PIL import Image
|
| 36 |
+
import io
|
| 37 |
+
except ModuleNotFoundError:
|
| 38 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "matplotlib", "pillow"])
|
| 39 |
+
|
| 40 |
+
import matplotlib.pyplot as plt
|
| 41 |
+
from PIL import Image
|
| 42 |
+
import io
|
| 43 |
+
|
| 44 |
+
# Additional imports for PEFT fine-tuning
|
| 45 |
+
try:
|
| 46 |
+
import torch
|
| 47 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
|
| 48 |
+
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
| 49 |
+
from trl import SFTTrainer
|
| 50 |
+
from datasets import load_dataset
|
| 51 |
+
import accelerate
|
| 52 |
+
except ModuleNotFoundError:
|
| 53 |
+
subprocess.check_call([sys.executable, "-m", "pip", "install", "torch", "transformers", "peft", "trl", "datasets", "accelerate", "bitsandbytes"])
|
| 54 |
+
|
| 55 |
+
import torch
|
| 56 |
+
from transformers import AutoModelForCausalLM, AutoTokenizer, BitsAndBytesConfig, TrainingArguments
|
| 57 |
+
from peft import LoraConfig, get_peft_model, prepare_model_for_kbit_training
|
| 58 |
+
from trl import SFTTrainer
|
| 59 |
+
from datasets import load_dataset
|
| 60 |
+
|
| 61 |
+
MAX_MAX_NEW_TOKENS = 512
|
| 62 |
+
DEFAULT_MAX_NEW_TOKENS = 128
|
| 63 |
+
MAX_INPUT_TOKEN_LENGTH = int(os.getenv("MAX_INPUT_TOKEN_LENGTH", "256"))
|
| 64 |
+
|
| 65 |
+
DESCRIPTION = """\
|
| 66 |
+
# FinChat: Investing Q&A (CPU-Only, Ultra-Fast Optimization)
|
| 67 |
+
This application delivers an interactive chat interface powered by a highly efficient, small AI model adapted for addressing investing and finance inquiries through specialized prompt engineering. It ensures rapid, reasoned responses to user queries. Duplicate this Space for customization or queue-free deployment.
|
| 68 |
+
<p>Running on CPU 🥶 Inference is heavily optimized for responses in under 10 seconds for simple queries, with output limited to 128 tokens maximum. For longer responses, increase 'Max New Tokens' in Advanced Settings. Brief delays may occur in free-tier environments due to shared resources, but typical generation speeds reach 20-40 tokens per second. CAGR calculations for stocks are computed accurately using historical data.</p>
|
| 69 |
+
"""
|
| 70 |
+
|
| 71 |
+
LICENSE = """\
|
| 72 |
+
<p/>
|
| 73 |
+
---
|
| 74 |
+
This application employs the Llama-2-7B-Chat model, fine-tuned on financial Q&A data, governed by Meta AI's Terms of Use. Refer to the [model card](https://huggingface.co/TheBloke/Llama-2-7B-Chat-GGUF) for details.
|
| 75 |
+
"""
|
| 76 |
+
|
| 77 |
+
# Define paths
|
| 78 |
+
base_model_id = "meta-llama/Llama-2-7b-chat-hf"
|
| 79 |
+
fine_tuned_model_path = "fine_tuned_llama2.gguf"
|
| 80 |
+
quantized_model_path = "llama-2-7b-chat-finetuned.Q4_K_M.gguf"
|
| 81 |
+
lora_adapter_path = "lora_adapter"
|
| 82 |
+
|
| 83 |
+
# Hugging Face login (required for fine-tuning)
|
| 84 |
+
hf_token = os.getenv("HF_TOKEN")
|
| 85 |
+
if hf_token:
|
| 86 |
+
login(hf_token)
|
| 87 |
+
else:
|
| 88 |
+
logger.warning("HF_TOKEN not set. Fine-tuning may fail if authentication is required.")
|
| 89 |
+
|
| 90 |
+
# One-time fine-tuning process if the fine-tuned GGUF does not exist
|
| 91 |
+
if not os.path.exists(quantized_model_path):
|
| 92 |
+
logger.info("Attempting one-time PEFT fine-tuning on Finance-Alpaca dataset...")
|
| 93 |
+
try:
|
| 94 |
+
tokenizer = AutoTokenizer.from_pretrained(base_model_id)
|
| 95 |
+
model = AutoModelForCausalLM.from_pretrained(
|
| 96 |
+
base_model_id,
|
| 97 |
+
torch_dtype=torch.bfloat16,
|
| 98 |
+
device_map="cpu"
|
| 99 |
+
)
|
| 100 |
+
dataset = load_dataset("gbharti/finance-alpaca", split="train[0:500]")
|
| 101 |
+
|
| 102 |
+
def formatting_func(example):
|
| 103 |
+
text = f"<s>[INST] {example['instruction']}\n{example['input']} [/INST] {example['output']} </s>"
|
| 104 |
+
return {"text": text}
|
| 105 |
+
|
| 106 |
+
dataset = dataset.map(formatting_func)
|
| 107 |
+
|
| 108 |
+
lora_config = LoraConfig(
|
| 109 |
+
r=8,
|
| 110 |
+
lora_alpha=16,
|
| 111 |
+
target_modules=["q_proj", "v_proj", "k_proj", "o_proj"],
|
| 112 |
+
lora_dropout=0.05,
|
| 113 |
+
bias="none",
|
| 114 |
+
task_type="CAUSAL_LM"
|
| 115 |
+
)
|
| 116 |
+
|
| 117 |
+
model = prepare_model_for_kbit_training(model)
|
| 118 |
+
model = get_peft_model(model, lora_config)
|
| 119 |
+
|
| 120 |
+
training_args = TrainingArguments(
|
| 121 |
+
output_dir=lora_adapter_path,
|
| 122 |
+
num_train_epochs=1,
|
| 123 |
+
per_device_train_batch_size=1,
|
| 124 |
+
gradient_accumulation_steps=4,
|
| 125 |
+
learning_rate=2e-4,
|
| 126 |
+
fp16=False,
|
| 127 |
+
save_steps=100,
|
| 128 |
+
logging_steps=10,
|
| 129 |
+
optim="adamw_torch",
|
| 130 |
+
report_to="none"
|
| 131 |
+
)
|
| 132 |
+
|
| 133 |
+
trainer = SFTTrainer(
|
| 134 |
+
model=model,
|
| 135 |
+
tokenizer=tokenizer,
|
| 136 |
+
train_dataset=dataset,
|
| 137 |
+
dataset_text_field="text",
|
| 138 |
+
max_seq_length=512,
|
| 139 |
+
args=training_args
|
| 140 |
+
)
|
| 141 |
+
|
| 142 |
+
trainer.train()
|
| 143 |
+
|
| 144 |
+
model = model.merge_and_unload()
|
| 145 |
+
model.save_pretrained("merged_model")
|
| 146 |
+
tokenizer.save_pretrained("merged_model")
|
| 147 |
+
|
| 148 |
+
subprocess.check_call(["git", "clone", "https://github.com/ggerganov/llama.cpp"])
|
| 149 |
+
os.chdir("llama.cpp")
|
| 150 |
+
subprocess.check_call(["make"])
|
| 151 |
+
subprocess.check_call([sys.executable, "convert_hf_to_gguf.py", "--outfile", "../" + fine_tuned_model_path, "--outtype", "f16", "../merged_model"])
|
| 152 |
+
subprocess.check_call(["./quantize", "../" + fine_tuned_model_path, "../" + quantized_model_path, "Q4_K_M"])
|
| 153 |
+
os.chdir("..")
|
| 154 |
+
|
| 155 |
+
logger.info("Fine-tuning and conversion complete. Using fine-tuned model.")
|
| 156 |
+
except Exception as e:
|
| 157 |
+
logger.error(f"Error during fine-tuning: {str(e)}")
|
| 158 |
+
print("Falling back to the pre-trained model without fine-tuning.")
|
| 159 |
+
|
| 160 |
+
# Load the model
|
| 161 |
+
try:
|
| 162 |
+
model_path = quantized_model_path if os.path.exists(quantized_model_path) else hf_hub_download(
|
| 163 |
+
repo_id="TheBloke/Llama-2-7B-Chat-GGUF",
|
| 164 |
+
filename="llama-2-7b-chat.Q4_K_M.gguf"
|
| 165 |
+
)
|
| 166 |
+
llm = Llama(
|
| 167 |
+
model_path=model_path,
|
| 168 |
+
n_ctx=256,
|
| 169 |
+
n_batch=512,
|
| 170 |
+
n_threads=multiprocessing.cpu_count(),
|
| 171 |
+
n_gpu_layers=0,
|
| 172 |
+
chat_format="llama-2"
|
| 173 |
+
)
|
| 174 |
+
logger.info("Model loaded successfully.")
|
| 175 |
+
except Exception as e:
|
| 176 |
+
logger.error(f"Error loading model: {str(e)}")
|
| 177 |
+
raise
|
| 178 |
+
|
| 179 |
+
DEFAULT_SYSTEM_PROMPT = """You are FinChat, a knowledgeable AI assistant specializing in investing and finance. Provide accurate, helpful, reasoned, and concise answers to investing questions. Always base responses on reliable information and advise users to consult professionals for personalized advice.
|
| 180 |
+
Always respond exclusively in English. Use bullet points for clarity.
|
| 181 |
+
Example:
|
| 182 |
+
User: average return for TSLA between 2010 and 2020
|
| 183 |
+
Assistant:
|
| 184 |
+
- TSLA CAGR (2010-2020): ~63.01%
|
| 185 |
+
- Represents average annual return with compounding
|
| 186 |
+
- Past performance not indicative of future results
|
| 187 |
+
- Consult a financial advisor"""
|
| 188 |
+
|
| 189 |
+
def generate(
|
| 190 |
+
message: str,
|
| 191 |
+
chat_history: list[dict],
|
| 192 |
+
system_prompt: str = DEFAULT_SYSTEM_PROMPT,
|
| 193 |
+
max_new_tokens: int = DEFAULT_MAX_NEW_TOKENS,
|
| 194 |
+
temperature: float = 0.6,
|
| 195 |
+
top_p: float = 0.9,
|
| 196 |
+
top_k: int = 50,
|
| 197 |
+
repetition_penalty: float = 1.2,
|
| 198 |
+
) -> Iterator[str]:
|
| 199 |
+
logger.info(f"Generating response for message: {message}")
|
| 200 |
+
|
| 201 |
+
lower_message = message.lower().strip()
|
| 202 |
+
if lower_message in ["hi", "hello"]:
|
| 203 |
+
response = "I'm FinChat, your financial advisor. Ask me anything finance-related!"
|
| 204 |
+
logger.info("Quick response for 'hi'/'hello' generated.")
|
| 205 |
+
yield response
|
| 206 |
+
return
|
| 207 |
+
|
| 208 |
+
# Check for CAGR/average return queries
|
| 209 |
+
match = re.match(r'(?:average return|cagr) for ([\w\s,]+(?:and [\w\s,]+)?) between (\d{4}) and (\d{4})', lower_message)
|
| 210 |
+
if match:
|
| 211 |
+
tickers_str, start_year, end_year = match.groups()
|
| 212 |
+
tickers = [t.strip().upper() for t in re.split(r',|\band\b', tickers_str) if t.strip()]
|
| 213 |
+
responses = []
|
| 214 |
+
if int(end_year) <= int(start_year):
|
| 215 |
+
yield "The specified time period is invalid (end year must be after start year)."
|
| 216 |
+
return
|
| 217 |
+
for ticker in tickers:
|
| 218 |
+
try:
|
| 219 |
+
data = yf.download(ticker, start=f"{start_year}-01-01", end=f"{end_year}-12-31")
|
| 220 |
+
if not data.empty:
|
| 221 |
+
if isinstance(data.columns, pd.MultiIndex):
|
| 222 |
+
data = data.droplevel('Ticker', axis=1)
|
| 223 |
+
initial = data['Close'].iloc[0]
|
| 224 |
+
final = data['Close'].iloc[-1]
|
| 225 |
+
start_date = data.index[0]
|
| 226 |
+
end_date = data.index[-1]
|
| 227 |
+
days = (end_date - start_date).days
|
| 228 |
+
years = days / 365.25
|
| 229 |
+
if years > 0:
|
| 230 |
+
cagr = ((final / initial) ** (1 / years) - 1) * 100
|
| 231 |
+
responses.append(f"- {ticker}: ~{cagr:.2f}%")
|
| 232 |
+
else:
|
| 233 |
+
responses.append(f"- {ticker}: Invalid period (no elapsed time).")
|
| 234 |
+
else:
|
| 235 |
+
responses.append(f"- {ticker}: No historical data available between {start_year} and {end_year}.")
|
| 236 |
+
except Exception as e:
|
| 237 |
+
responses.append(f"- {ticker}: Error calculating CAGR - {str(e)}")
|
| 238 |
+
full_response = f"CAGR for the requested stocks from {start_year} to {end_year}:\n" + "\n".join(responses) + "\n- Represents average annual returns with compounding\n- Past performance not indicative of future results\n- Consult a financial advisor"
|
| 239 |
+
logger.info("CAGR response generated.")
|
| 240 |
+
yield full_response
|
| 241 |
+
return
|
| 242 |
+
|
| 243 |
+
# Build conversation messages
|
| 244 |
+
conversation = [{"role": "system", "content": system_prompt}]
|
| 245 |
+
for msg in chat_history[-5:]: # Limit history to last 5 exchanges
|
| 246 |
+
if msg["role"] == "user":
|
| 247 |
+
conversation.append({"role": "user", "content": msg["content"]})
|
| 248 |
+
elif msg["role"] == "assistant":
|
| 249 |
+
conversation.append({"role": "assistant", "content": msg["content"]})
|
| 250 |
+
conversation.append({"role": "user", "content": message})
|
| 251 |
+
|
| 252 |
+
# Approximate token length check
|
| 253 |
+
prompt_text = "\n".join(d["content"] for d in conversation)
|
| 254 |
+
input_tokens = llm.tokenize(prompt_text.encode("utf-8"), add_bos=False)
|
| 255 |
+
|
| 256 |
+
# Generate response
|
| 257 |
+
try:
|
| 258 |
+
response = ""
|
| 259 |
+
stream = llm.create_chat_completion(
|
| 260 |
+
messages=conversation,
|
| 261 |
+
max_tokens=max_new_tokens,
|
| 262 |
+
temperature=temperature,
|
| 263 |
+
top_p=top_p,
|
| 264 |
+
top_k=top_k,
|
| 265 |
+
repeat_penalty=repetition_penalty,
|
| 266 |
+
stream=True
|
| 267 |
+
)
|
| 268 |
+
for chunk in stream:
|
| 269 |
+
delta = chunk["choices"][0]["delta"]
|
| 270 |
+
if "content" in delta and delta["content"] is not None:
|
| 271 |
+
response += delta["content"]
|
| 272 |
+
yield response
|
| 273 |
+
if chunk["choices"][0]["finish_reason"] is not None:
|
| 274 |
+
break
|
| 275 |
+
logger.info("Response generation completed.")
|
| 276 |
+
except Exception as e:
|
| 277 |
+
logger.error(f"Error during response generation: {str(e)}")
|
| 278 |
+
yield f"Error generating response: {str(e)}"
|
| 279 |
+
|
| 280 |
+
def process_portfolio(ticker1, shares1, cost1, price1, ticker2, shares2, cost2, price2, ticker3, shares3, cost3, price3, growth_rate):
|
| 281 |
+
portfolio = {}
|
| 282 |
+
if ticker1:
|
| 283 |
+
value1 = shares1 * price1
|
| 284 |
+
portfolio[ticker1.upper()] = {'shares': shares1, 'cost': cost1, 'price': price1, 'value': value1}
|
| 285 |
+
if ticker2:
|
| 286 |
+
value2 = shares2 * price2
|
| 287 |
+
portfolio[ticker2.upper()] = {'shares': shares2, 'cost': cost2, 'price': price2, 'value': value2}
|
| 288 |
+
if ticker3:
|
| 289 |
+
value3 = shares3 * price3
|
| 290 |
+
portfolio[ticker3.upper()] = {'shares': shares3, 'cost': cost3, 'price': price3, 'value': value3}
|
| 291 |
+
if not portfolio:
|
| 292 |
+
return "", None
|
| 293 |
+
|
| 294 |
+
total_value_now = sum(v['value'] for v in portfolio.values())
|
| 295 |
+
allocations = {k: v['value'] / total_value_now for k, v in portfolio.items()} if total_value_now > 0 else {}
|
| 296 |
+
|
| 297 |
+
fig_alloc, ax_alloc = plt.subplots()
|
| 298 |
+
ax_alloc.pie(allocations.values(), labels=allocations.keys(), autopct='%1.1f%%')
|
| 299 |
+
ax_alloc.set_title('Portfolio Allocation')
|
| 300 |
+
buf_alloc = io.BytesIO()
|
| 301 |
+
fig_alloc.savefig(buf_alloc, format='png')
|
| 302 |
+
buf_alloc.seek(0)
|
| 303 |
+
chart_alloc = Image.open(buf_alloc)
|
| 304 |
+
plt.close(fig_alloc) # Close the figure to free memory
|
| 305 |
+
|
| 306 |
+
def project_value(value, years, rate):
|
| 307 |
+
return value * (1 + rate / 100) ** years
|
| 308 |
+
|
| 309 |
+
total_value_1yr = sum(project_value(v['value'], 1, growth_rate) for v in portfolio.values())
|
| 310 |
+
total_value_2yr = sum(project_value(v['value'], 2, growth_rate) for v in portfolio.values())
|
| 311 |
+
total_value_5yr = sum(project_value(v['value'], 5, growth_rate) for v in portfolio.values())
|
| 312 |
+
total_value_10yr = sum(project_value(v['value'], 10, growth_rate) for v in portfolio.values())
|
| 313 |
+
|
| 314 |
+
data_str = (
|
| 315 |
+
"User portfolio:\n" +
|
| 316 |
+
"\n".join(f"- {k}: {v['shares']} shares, avg cost {v['cost']}, current price {v['price']}, value ${v['value']:,.2f}" for k, v in portfolio.items()) +
|
| 317 |
+
f"\nTotal value now: ${total_value_now:,.2f}\nProjected (at {growth_rate}% annual growth):\n"
|
| 318 |
+
f"- 1 year: ${total_value_1yr:,.2f}\n- 2 years: ${total_value_2yr:,.2f}\n- 5 years: ${total_value_5yr:,.2f}\n- 10 years: ${total_value_10yr:,.2f}"
|
| 319 |
+
)
|
| 320 |
+
return data_str, chart_alloc
|
| 321 |
+
|
| 322 |
+
# Gradio interface setup
|
| 323 |
+
with gr.Blocks(css="""#chatbot {height: 800px; overflow: auto;}""") as demo:
|
| 324 |
+
gr.Markdown(DESCRIPTION)
|
| 325 |
+
chatbot = gr.Chatbot(label="FinChat", type="messages")
|
| 326 |
+
msg = gr.Textbox(label="Ask a finance question", placeholder="e.g., 'What is CAGR?' or 'Average return for AAPL between 2010 and 2020'")
|
| 327 |
+
|
| 328 |
+
with gr.Row():
|
| 329 |
+
with gr.Column():
|
| 330 |
+
ticker1 = gr.Textbox(label="Ticker 1")
|
| 331 |
+
shares1 = gr.Number(label="Shares 1")
|
| 332 |
+
cost1 = gr.Number(label="Avg Cost/Share 1")
|
| 333 |
+
price1 = gr.Number(label="Current Price 1")
|
| 334 |
+
with gr.Column():
|
| 335 |
+
ticker2 = gr.Textbox(label="Ticker 2")
|
| 336 |
+
shares2 = gr.Number(label="Shares 2")
|
| 337 |
+
cost2 = gr.Number(label="Avg Cost/Share 2")
|
| 338 |
+
price2 = gr.Number(label="Current Price 2")
|
| 339 |
+
with gr.Column():
|
| 340 |
+
ticker3 = gr.Textbox(label="Ticker 3")
|
| 341 |
+
shares3 = gr.Number(label="Shares 3")
|
| 342 |
+
cost3 = gr.Number(label="Avg Cost/Share 3")
|
| 343 |
+
price3 = gr.Number(label="Current Price 3")
|
| 344 |
+
|
| 345 |
+
growth_rate = gr.Slider(minimum=5, maximum=50, step=5, value=10, label="Annual Growth Rate (%)", interactive=True, info="Selected Growth Rate: 10%")
|
| 346 |
+
growth_rate_label = gr.Markdown("**Selected Growth Rate: 10%**")
|
| 347 |
+
|
| 348 |
+
with gr.Row():
|
| 349 |
+
submit = gr.Button("Submit")
|
| 350 |
+
clear = gr.Button("Clear")
|
| 351 |
+
|
| 352 |
+
with gr.Accordion("Advanced Settings", open=False):
|
| 353 |
+
system_prompt = gr.Textbox(label="System Prompt", value=DEFAULT_SYSTEM_PROMPT, lines=6)
|
| 354 |
+
temperature = gr.Slider(label="Temperature", value=0.6, minimum=0.0, maximum=1.0, step=0.05)
|
| 355 |
+
top_p = gr.Slider(label="Top P", value=0.9, minimum=0.0, maximum=1.0, step=0.05)
|
| 356 |
+
top_k = gr.Slider(label="Top K", value=50, minimum=1, maximum=100, step=1)
|
| 357 |
+
repetition_penalty = gr.Slider(label="Repetition Penalty", value=1.2, minimum=1.0, maximum=2.0, step=0.05)
|
| 358 |
+
max_new_tokens = gr.Slider(label="Max New Tokens", value=DEFAULT_MAX_NEW_TOKENS, minimum=1, maximum=MAX_MAX_NEW_TOKENS, step=1)
|
| 359 |
+
|
| 360 |
+
gr.Markdown(LICENSE)
|
| 361 |
+
|
| 362 |
+
def update_growth_rate_label(growth_rate):
|
| 363 |
+
return f"**Selected Growth Rate: {growth_rate}%**"
|
| 364 |
+
|
| 365 |
+
def user(message, history):
|
| 366 |
+
if not message:
|
| 367 |
+
return "", history
|
| 368 |
+
return "", history + [{"role": "user", "content": message}]
|
| 369 |
+
|
| 370 |
+
def bot(history, sys_prompt, temp, tp, tk, rp, mnt, ticker1, shares1, cost1, price1, ticker2, shares2, cost2, price2, ticker3, shares3, cost3, price3, growth_rate):
|
| 371 |
if not history:
|
| 372 |
logger.warning("History is empty, initializing with user message.")
|
| 373 |
history = [{"role": "user", "content": ""}]
|