Spaces:
Sleeping
Sleeping
Update app.py
Browse files
app.py
CHANGED
|
@@ -14,6 +14,7 @@ import base64
|
|
| 14 |
from xhtml2pdf import pisa
|
| 15 |
import logging
|
| 16 |
import json
|
|
|
|
| 17 |
|
| 18 |
# Load environment variables
|
| 19 |
load_dotenv()
|
|
@@ -408,23 +409,24 @@ def get_unique_output_dir(base_dir, ticker):
|
|
| 408 |
return output_dir
|
| 409 |
|
| 410 |
def get_stock_symbols(file_path):
|
| 411 |
-
|
| 412 |
-
|
| 413 |
-
|
| 414 |
-
|
| 415 |
-
|
| 416 |
-
|
| 417 |
-
|
| 418 |
-
|
| 419 |
-
|
| 420 |
-
|
| 421 |
-
|
| 422 |
-
|
| 423 |
-
|
| 424 |
-
|
| 425 |
-
|
| 426 |
-
|
| 427 |
-
|
|
|
|
| 428 |
|
| 429 |
# --------------------- Streamlit App ---------------------
|
| 430 |
|
|
@@ -447,87 +449,205 @@ def main():
|
|
| 447 |
except Exception as e:
|
| 448 |
st.error(f"An error occurred while processing the uploaded file: {e}")
|
| 449 |
|
| 450 |
-
#
|
| 451 |
-
|
| 452 |
|
| 453 |
# Period selection
|
| 454 |
-
period = st.selectbox("Select the time period for analysis:", ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"])
|
| 455 |
-
|
| 456 |
-
|
| 457 |
-
|
| 458 |
-
|
| 459 |
-
|
| 460 |
-
|
| 461 |
-
|
| 462 |
-
|
| 463 |
-
|
| 464 |
-
|
| 465 |
-
|
| 466 |
-
|
| 467 |
-
|
| 468 |
-
|
| 469 |
-
|
| 470 |
-
|
| 471 |
-
|
| 472 |
-
|
| 473 |
-
|
| 474 |
-
|
| 475 |
-
|
| 476 |
-
|
| 477 |
-
|
| 478 |
-
|
| 479 |
-
|
| 480 |
-
|
| 481 |
-
|
| 482 |
-
|
| 483 |
-
|
| 484 |
-
|
| 485 |
-
|
| 486 |
-
|
| 487 |
-
|
| 488 |
-
|
| 489 |
-
|
| 490 |
-
|
| 491 |
-
|
| 492 |
-
|
| 493 |
-
|
| 494 |
-
|
| 495 |
-
|
| 496 |
-
|
| 497 |
-
|
| 498 |
-
|
| 499 |
-
|
| 500 |
-
|
| 501 |
-
|
| 502 |
-
|
| 503 |
-
|
| 504 |
-
|
| 505 |
-
# Get LLM response
|
| 506 |
-
llm_response = get_response(chat_session, prompt)
|
| 507 |
-
|
| 508 |
-
if llm_response:
|
| 509 |
-
# Generate PDF
|
| 510 |
-
pdf_filename = os.path.join(output_dir, f"technical_analysis_{selected_stock}.pdf")
|
| 511 |
-
markdown_to_pdf_xhtml2pdf(llm_response.text, pdf_filename)
|
| 512 |
-
|
| 513 |
-
# Offer PDF for download
|
| 514 |
-
with open(pdf_filename, "rb") as pdf_file:
|
| 515 |
-
pdf_bytes = pdf_file.read()
|
| 516 |
-
st.download_button(
|
| 517 |
-
label="Download Analysis PDF",
|
| 518 |
-
data=pdf_bytes,
|
| 519 |
-
file_name=f"technical_analysis_{selected_stock}.pdf",
|
| 520 |
-
mime="application/pdf"
|
| 521 |
)
|
| 522 |
-
|
| 523 |
-
|
| 524 |
-
|
| 525 |
-
|
| 526 |
-
|
| 527 |
-
|
| 528 |
-
|
| 529 |
-
|
| 530 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 531 |
|
| 532 |
if __name__ == "__main__":
|
| 533 |
main()
|
|
|
|
| 14 |
from xhtml2pdf import pisa
|
| 15 |
import logging
|
| 16 |
import json
|
| 17 |
+
import streamlit.components.v1 as components
|
| 18 |
|
| 19 |
# Load environment variables
|
| 20 |
load_dotenv()
|
|
|
|
| 409 |
return output_dir
|
| 410 |
|
| 411 |
def get_stock_symbols(file_path):
|
| 412 |
+
"""
|
| 413 |
+
Reads stock symbols from a JSON file.
|
| 414 |
+
|
| 415 |
+
Args:
|
| 416 |
+
file_path (str): The path to the JSON file.
|
| 417 |
+
|
| 418 |
+
Returns:
|
| 419 |
+
list: A list of stock symbols.
|
| 420 |
+
"""
|
| 421 |
+
try:
|
| 422 |
+
with open(file_path, 'r') as f:
|
| 423 |
+
data = json.load(f)
|
| 424 |
+
# Extract symbols directly from the JSON structure
|
| 425 |
+
symbols = [details["symbol"] for details in data.values()]
|
| 426 |
+
return symbols
|
| 427 |
+
except Exception as e:
|
| 428 |
+
st.error(f"Error reading stock symbols: {e}")
|
| 429 |
+
return []
|
| 430 |
|
| 431 |
# --------------------- Streamlit App ---------------------
|
| 432 |
|
|
|
|
| 449 |
except Exception as e:
|
| 450 |
st.error(f"An error occurred while processing the uploaded file: {e}")
|
| 451 |
|
| 452 |
+
# Use the uploaded symbols for dynamic selection
|
| 453 |
+
selected_symbol = st.sidebar.selectbox("Select a stock for analysis:", stock_symbols)
|
| 454 |
|
| 455 |
# Period selection
|
| 456 |
+
period = st.sidebar.selectbox("Select the time period for analysis:", ["1d", "5d", "1mo", "3mo", "6mo", "1y", "2y", "5y", "10y", "ytd", "max"])
|
| 457 |
+
|
| 458 |
+
# Button to trigger analysis
|
| 459 |
+
if st.sidebar.button("Analyze"):
|
| 460 |
+
if selected_symbol: # Check if a symbol is selected
|
| 461 |
+
with st.spinner("Performing technical analysis..."):
|
| 462 |
+
try:
|
| 463 |
+
# Create a unique output directory
|
| 464 |
+
output_dir = get_unique_output_dir(Config.OUTPUT_DIR, selected_symbol)
|
| 465 |
+
|
| 466 |
+
# Fetch and process data
|
| 467 |
+
stock_data = fetch_data(selected_symbol, period=period)
|
| 468 |
+
stock_data = calculate_moving_averages(stock_data)
|
| 469 |
+
stock_data = calculate_ema(stock_data)
|
| 470 |
+
stock_data = calculate_macd(stock_data)
|
| 471 |
+
stock_data = calculate_rsi(stock_data)
|
| 472 |
+
stock_data = calculate_adx(stock_data)
|
| 473 |
+
stock_data = calculate_atr(stock_data)
|
| 474 |
+
stock_data = calculate_bollinger_bands(stock_data)
|
| 475 |
+
stock_data = calculate_stochastic(stock_data)
|
| 476 |
+
|
| 477 |
+
# Get analysis output
|
| 478 |
+
analysis_output = print_indicator_outputs(stock_data, selected_symbol, output_dir)
|
| 479 |
+
|
| 480 |
+
# Plot and save charts
|
| 481 |
+
plot_stock_and_indicators(stock_data, selected_symbol, output_dir)
|
| 482 |
+
plot_volatility_indicators(stock_data, selected_symbol, output_dir)
|
| 483 |
+
|
| 484 |
+
# Generate prompt for LLM
|
| 485 |
+
image_paths = [os.path.join(output_dir, file) for file in os.listdir(output_dir) if file.endswith(('.png', '.jpg', '.jpeg'))]
|
| 486 |
+
image_paths = "\n".join(image_paths)
|
| 487 |
+
prompt = generate_prompt(analysis_output, image_paths)
|
| 488 |
+
|
| 489 |
+
# Save the prompt to a file
|
| 490 |
+
prompt_filename = os.path.join(output_dir, f"{selected_symbol}_prompt.txt")
|
| 491 |
+
with open(prompt_filename, 'w') as f:
|
| 492 |
+
f.write(prompt)
|
| 493 |
+
logging.info(f"Saved LLM prompt to: {prompt_filename}")
|
| 494 |
+
|
| 495 |
+
# Configure and create LLM model
|
| 496 |
+
genai.configure(api_key=Config.GOOGLE_API_KEY)
|
| 497 |
+
generation_config = {
|
| 498 |
+
"temperature": 0.9,
|
| 499 |
+
"top_p": 0.95,
|
| 500 |
+
"top_k": 40,
|
| 501 |
+
"max_output_tokens": 8192,
|
| 502 |
+
}
|
| 503 |
+
model = genai.GenerativeModel(
|
| 504 |
+
model_name="gemini-pro", # Use "gemini-pro" for text-only
|
| 505 |
+
generation_config=generation_config,
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 506 |
)
|
| 507 |
+
chat_session = model.start_chat()
|
| 508 |
+
|
| 509 |
+
# Get LLM response
|
| 510 |
+
llm_response = get_response(chat_session, prompt)
|
| 511 |
+
|
| 512 |
+
if llm_response:
|
| 513 |
+
# Generate PDF
|
| 514 |
+
pdf_filename = os.path.join(output_dir, f"technical_analysis_{selected_symbol}.pdf")
|
| 515 |
+
markdown_to_pdf_xhtml2pdf(llm_response.text, pdf_filename)
|
| 516 |
+
|
| 517 |
+
# Offer PDF for download
|
| 518 |
+
with open(pdf_filename, "rb") as pdf_file:
|
| 519 |
+
pdf_bytes = pdf_file.read()
|
| 520 |
+
st.download_button(
|
| 521 |
+
label="Download Analysis PDF",
|
| 522 |
+
data=pdf_bytes,
|
| 523 |
+
file_name=f"technical_analysis_{selected_symbol}.pdf",
|
| 524 |
+
mime="application/pdf"
|
| 525 |
+
)
|
| 526 |
+
# Display the PDF using an iframe
|
| 527 |
+
b64 = base64.b64encode(pdf_bytes).decode()
|
| 528 |
+
pdf_display = f'<iframe src="data:application/pdf;base64,{b64}" width="700" height="1000" type="application/pdf"></iframe>'
|
| 529 |
+
st.markdown(pdf_display, unsafe_allow_html=True)
|
| 530 |
+
else:
|
| 531 |
+
st.error("Could not generate PDF. LLM response is empty.")
|
| 532 |
+
|
| 533 |
+
except Exception as e:
|
| 534 |
+
st.error(f"An error occurred during analysis: {e}")
|
| 535 |
+
else:
|
| 536 |
+
st.sidebar.error("Please select a stock symbol.")
|
| 537 |
+
|
| 538 |
+
# Placeholder for the TradingView widget
|
| 539 |
+
st.header("TradingView Widget")
|
| 540 |
+
tradingview_placeholder = st.empty() # Create an empty placeholder
|
| 541 |
+
|
| 542 |
+
# --- TradingView Widget Code ---
|
| 543 |
+
tradingview_widget_code = f"""
|
| 544 |
+
<!-- TradingView Widget BEGIN -->
|
| 545 |
+
<div class="tradingview-widget-container" style="height:100%; width:100%">
|
| 546 |
+
<div id="tradingview_a1b2c" style="height:calc(100% - 32px); width:100%"></div>
|
| 547 |
+
<div class="tradingview-widget-copyright">
|
| 548 |
+
<a href="https://www.tradingview.com/" rel="noopener nofollow" target="_blank">
|
| 549 |
+
<span class="blue-text">Track all markets on TradingView</span>
|
| 550 |
+
</a>
|
| 551 |
+
</div>
|
| 552 |
+
<script type="text/javascript" src="https://s3.tradingview.com/tv.js"></script>
|
| 553 |
+
<script type="text/javascript">
|
| 554 |
+
new TradingView.widget(
|
| 555 |
+
{{
|
| 556 |
+
"autosize": true,
|
| 557 |
+
"symbol": "{selected_symbol}",
|
| 558 |
+
"interval": "D",
|
| 559 |
+
"timezone": "Etc/UTC",
|
| 560 |
+
"theme": "light",
|
| 561 |
+
"style": "1",
|
| 562 |
+
"locale": "en",
|
| 563 |
+
"toolbar_bg": "#f1f3f6",
|
| 564 |
+
"enable_publishing": false,
|
| 565 |
+
"allow_symbol_change": true,
|
| 566 |
+
"container_id": "tradingview_a1b2c"
|
| 567 |
+
}}
|
| 568 |
+
);
|
| 569 |
+
</script>
|
| 570 |
+
</div>
|
| 571 |
+
<!-- TradingView Widget END -->
|
| 572 |
+
"""
|
| 573 |
+
# ----------------------------------
|
| 574 |
+
|
| 575 |
+
# HTML, CSS, and JavaScript for Resizing (outside the if condition)
|
| 576 |
+
html_code = f"""
|
| 577 |
+
<style>
|
| 578 |
+
/* Make the container resizable */
|
| 579 |
+
#resizable-container {{
|
| 580 |
+
position: relative;
|
| 581 |
+
width: 700px; /* Initial width */
|
| 582 |
+
height: 600px; /* Initial height */
|
| 583 |
+
border: 2px solid #ccc;
|
| 584 |
+
overflow: hidden; /* Important for clipping the widget during resize */
|
| 585 |
+
}}
|
| 586 |
+
|
| 587 |
+
/* Style the resize handle (optional) */
|
| 588 |
+
.resize-handle {{
|
| 589 |
+
position: absolute;
|
| 590 |
+
width: 10px;
|
| 591 |
+
height: 10px;
|
| 592 |
+
background-color: #007bff; /* Example color */
|
| 593 |
+
cursor: se-resize; /* Diagonal resize cursor */
|
| 594 |
+
}}
|
| 595 |
+
|
| 596 |
+
/* Position the resize handle at the bottom-right corner */
|
| 597 |
+
#resize-handle-se {{
|
| 598 |
+
bottom: 0;
|
| 599 |
+
right: 0;
|
| 600 |
+
}}
|
| 601 |
+
</style>
|
| 602 |
+
|
| 603 |
+
<div id="resizable-container">
|
| 604 |
+
{tradingview_widget_code}
|
| 605 |
+
<div id="resize-handle-se" class="resize-handle"></div>
|
| 606 |
+
</div>
|
| 607 |
+
|
| 608 |
+
<script src="https://cdn.jsdelivr.net/npm/interactjs/dist/interact.min.js"></script>
|
| 609 |
+
<script>
|
| 610 |
+
// Target the resize handle
|
| 611 |
+
interact('#resize-handle-se')
|
| 612 |
+
.draggable({{
|
| 613 |
+
// Restrict movement to the bounds of the container
|
| 614 |
+
modifiers: [
|
| 615 |
+
interact.modifiers.restrictRect({{
|
| 616 |
+
restriction: 'parent',
|
| 617 |
+
endOnly: true
|
| 618 |
+
}})
|
| 619 |
+
],
|
| 620 |
+
inertia: true,
|
| 621 |
+
}})
|
| 622 |
+
.on('dragmove', function (event) {{
|
| 623 |
+
const container = document.getElementById('resizable-container');
|
| 624 |
+
|
| 625 |
+
// Update width and height based on drag movement
|
| 626 |
+
let newWidth = container.offsetWidth + event.dx;
|
| 627 |
+
let newHeight = container.offsetHeight + event.dy;
|
| 628 |
+
|
| 629 |
+
// Set minimum dimensions (adjust as needed)
|
| 630 |
+
const minWidth = 300;
|
| 631 |
+
const minHeight = 300;
|
| 632 |
+
if (newWidth < minWidth) {{
|
| 633 |
+
newWidth = minWidth;
|
| 634 |
+
}}
|
| 635 |
+
if (newHeight < minHeight) {{
|
| 636 |
+
newHeight = minHeight;
|
| 637 |
+
}}
|
| 638 |
+
|
| 639 |
+
container.style.width = newWidth + 'px';
|
| 640 |
+
container.style.height = newHeight + 'px';
|
| 641 |
+
|
| 642 |
+
// Trigger TradingView resize (if necessary - see note below)
|
| 643 |
+
// window.dispatchEvent(new Event('resize')); // You might not need this
|
| 644 |
+
}});
|
| 645 |
+
</script>
|
| 646 |
+
"""
|
| 647 |
+
|
| 648 |
+
# Embed the HTML code using components.html
|
| 649 |
+
with tradingview_placeholder:
|
| 650 |
+
components.html(html_code, height=600, width=700, scrolling=False)
|
| 651 |
|
| 652 |
if __name__ == "__main__":
|
| 653 |
main()
|