From 26a35ee3556db3df66a1fcca6e0b07a7ddf2c364 Mon Sep 17 00:00:00 2001 From: ajaysi Date: Wed, 15 May 2024 15:53:15 +0530 Subject: [PATCH] AI finance TA writer, yfinance, pandas_ta, WIP --- alwrity.py | 8 +- .../finance_data_researcher.py | 335 ++++++++++++++++++ lib/ai_writers/ai_financial_writer.py | 91 +++++ lib/utils/alwrity_utils.py | 24 ++ requirements.txt | 2 + 5 files changed, 457 insertions(+), 3 deletions(-) create mode 100644 lib/ai_web_researcher/finance_data_researcher.py create mode 100644 lib/ai_writers/ai_financial_writer.py diff --git a/alwrity.py b/alwrity.py index 5a575576..ebc15d1e 100644 --- a/alwrity.py +++ b/alwrity.py @@ -17,7 +17,7 @@ print("Loading, required libraries..") app = typer.Typer() from lib.utils.alwrity_utils import blog_from_audio, blog_from_keyword, do_web_research, do_web_research, ai_news_writer -from lib.utils.alwrity_utils import write_story, essay_writer, blog_tools +from lib.utils.alwrity_utils import write_story, essay_writer, blog_tools, ai_finance_ta_writer from lib.utils.alwrity_utils import content_planning, competitor_analysis, image_to_text_writer, image_generator @@ -37,9 +37,9 @@ def write_blog_options(): ("AI Story Writer", "AI Story Writer"), ("AI Essay Writer", "AI Essay writer"), ("AI News Articles", "News - AI News article writer, factual trusted sources"), + ("AI Finance TA report", "AI TA report - Write stocks Techincal Analysis report."), ("Programming", "Programming - Write technical blogs on latest topics"), ("Scholar", "Scholar - Research Reports from google scholar, arxiv articles."), - ("Finance/TBD", "Finance/TBD"), ("Quit", "Quit") ] selected_blog_type = radiolist_dialog(title="Choose a blog type:", values=choices).run() @@ -62,7 +62,7 @@ def start_interactive_mode(): ("Online Blog Tools/Apps", "Online AI Apps - Content & Digital marketing"), ("Create Blog Images", "Create Images - Stability, Dalle3"), ("AI Social Media(TBD)", "AI Social Media(TBD)"), - ("AI Code Writer(TBD)", "AI Code Writer(TBD)"), + ("AI CopyWriter(TBD)", "AI CopyWriter(TBD)"), ("Quit", "Quit") ] mode = radiolist_dialog(title="Choose an option:", values=choices).run() @@ -178,6 +178,8 @@ def write_blog(): blog_from_audio() elif blog_type == 'AI News Articles': ai_news_writer() + elif blog_type == 'AI Finance TA report': + ai_finance_ta_writer() elif blog_type == 'GitHub': github = prompt("Enter GitHub URL, CSV file, or topic:") print(f"Write blog based on GitHub: {github}") diff --git a/lib/ai_web_researcher/finance_data_researcher.py b/lib/ai_web_researcher/finance_data_researcher.py new file mode 100644 index 00000000..7c7595ce --- /dev/null +++ b/lib/ai_web_researcher/finance_data_researcher.py @@ -0,0 +1,335 @@ +import matplotlib.pyplot as plt +import pandas as pd +import yfinance as yf +#from yahoo_fin import options, stock_info +import pandas_ta as ta +import matplotlib.dates as mdates +from datetime import datetime, timedelta +import logging + +# Configure logging +logging.basicConfig(level=logging.INFO, format='%(asctime)s - %(levelname)s - %(message)s') + + +def calculate_technical_indicators(data): + """ + Calculates a suite of technical indicators using pandas_ta. + + Args: + data (pd.DataFrame): DataFrame containing historical stock price data. + + Returns: + pd.DataFrame: DataFrame with added technical indicators. + """ + try: + # Moving Averages + data.ta.macd(append=True) + data.ta.sma(length=20, append=True) + data.ta.ema(length=50, append=True) + + # Momentum Indicators + data.ta.rsi(append=True) + data.ta.stoch(append=True) + + # Volatility Indicators + data.ta.bbands(append=True) + data.ta.adx(append=True) + + # Other Indicators + data.ta.obv(append=True) + data.ta.willr(append=True) + data.ta.cmf(append=True) + data.ta.psar(append=True) + + # Custom Calculations + data['OBV_in_million'] = data['OBV'] / 1e6 + data['MACD_histogram_12_26_9'] = data['MACDh_12_26_9'] + + logging.info("Technical indicators calculated successfully.") + return data + except Exception as e: + logging.error(f"Error during technical indicator calculation: {e}") + return None + + +def get_last_day_summary(data): + """ + Extracts and summarizes technical indicators for the last trading day. + + Args: + data (pd.DataFrame): DataFrame with calculated technical indicators. + + Returns: + pd.Series: Summary of technical indicators for the last day. + """ + try: + last_day_summary = data.iloc[-1][[ + 'Adj Close', 'MACD_12_26_9', 'MACD_histogram_12_26_9', 'RSI_14', + 'BBL_5_2.0', 'BBM_5_2.0', 'BBU_5_2.0', 'SMA_20', 'EMA_50', + 'OBV_in_million', 'STOCHk_14_3_3', 'STOCHd_14_3_3', 'ADX_14', + 'WILLR_14', 'CMF_20', 'PSARl_0.02_0.2', 'PSARs_0.02_0.2' + ]] + logging.info("Last day summary extracted.") + return last_day_summary + except KeyError as e: + logging.error(f"Missing columns in data: {e}") + return None + except Exception as e: + logging.error(f"Error extracting last day summary: {e}") + return None + + +def analyze_stock(ticker_symbol, start_date, end_date): + """ + Fetches stock data, calculates technical indicators, and provides a summary. + + Args: + ticker_symbol (str): The stock symbol. + start_date (datetime): Start date for data retrieval. + end_date (datetime): End date for data retrieval. + + Returns: + pd.Series: Summary of technical indicators for the last day. + """ + try: + # Fetch stock data + stock_data = yf.download(ticker_symbol, start=start_date, end=end_date) + logging.info(f"Stock data fetched for {ticker_symbol} from {start_date} to {end_date}") + + # Calculate technical indicators + stock_data = calculate_technical_indicators(stock_data) + + # Get last day summary + if stock_data is not None: + last_day_summary = get_last_day_summary(stock_data) + if last_day_summary is not None: + print("Summary of Technical Indicators for the Last Day:") + print(last_day_summary) + + # Plot the technical indicators + plt.figure(figsize=(14, 8)) + + # Price Trend Chart + plt.subplot(3, 3, 1) + plt.plot(stock_data.index, stock_data['Adj Close'], label='Adj Close', color='blue') + plt.plot(stock_data.index, stock_data['EMA_50'], label='EMA 50', color='green') + plt.plot(stock_data.index, stock_data['SMA_20'], label='SMA_20', color='orange') + plt.title("Price Trend") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.legend() + plt.show() + + # On-Balance Volume Chart + plt.subplot(3, 3, 2) + plt.plot(stock_data['OBV'], label='On-Balance Volume') + plt.title('On-Balance Volume (OBV) Indicator') + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.legend() + + # MACD Plot + plt.subplot(3, 3, 3) + plt.plot(stock_data['MACD_12_26_9'], label='MACD') + plt.plot(stock_data['MACDh_12_26_9'], label='MACD Histogram') + plt.title('MACD Indicator') + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.title("MACD") + plt.legend() + + # RSI Plot + plt.subplot(3, 3, 4) + plt.plot(stock_data['RSI_14'], label='RSI') + plt.axhline(y=70, color='r', linestyle='--', label='Overbought (70)') + plt.axhline(y=30, color='g', linestyle='--', label='Oversold (30)') + plt.legend() + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.title('RSI Indicator') + + # Bollinger Bands Plot + plt.subplot(3, 3, 5) + plt.plot(stock_data.index, stock_data['BBU_5_2.0'], label='Upper BB') + plt.plot(stock_data.index, stock_data['BBM_5_2.0'], label='Middle BB') + plt.plot(stock_data.index, stock_data['BBL_5_2.0'], label='Lower BB') + plt.plot(stock_data.index, stock_data['Adj Close'], label='Adj Close', color='brown') + plt.title("Bollinger Bands") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.legend() + + # Stochastic Oscillator Plot + plt.subplot(3, 3, 6) + plt.plot(stock_data.index, stock_data['STOCHk_14_3_3'], label='Stoch %K') + plt.plot(stock_data.index, stock_data['STOCHd_14_3_3'], label='Stoch %D') + plt.title("Stochastic Oscillator") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + plt.legend() + + # Williams %R Plot + plt.subplot(3, 3, 7) + plt.plot(stock_data.index, stock_data['WILLR_14']) + plt.title("Williams %R") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + + # ADX Plot + plt.subplot(3, 3, 8) + plt.plot(stock_data.index, stock_data['ADX_14']) + plt.title("Average Directional Index (ADX)") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + + # CMF Plot + plt.subplot(3, 3, 9) + plt.plot(stock_data.index, stock_data['CMF_20']) + plt.title("Chaikin Money Flow (CMF)") + plt.gca().xaxis.set_major_formatter(mdates.DateFormatter('%b%d')) # Format date as "Jun14" + plt.xticks(rotation=45, fontsize=8) # Adjust font size + + # Show the plots + plt.tight_layout() + plt.show() + + return last_day_summary + else: + logging.error("Stock data is None, unable to calculate indicators.") + return None + except Exception as e: + logging.error(f"Error during analysis: {e}") + return None + + +def get_finance_data(symbol): + # FIXME: Expose them to end users. + end_date = datetime.today() + start_date = end_date - timedelta(days=120) + + # Perform analysis + last_day_summary = analyze_stock(symbol, start_date, end_date) + return last_day_summary + + + +def analyze_options_data(ticker, expiry_date): + """ + Analyzes option data for a given ticker and expiry date. + + Args: + ticker (str): The stock ticker symbol. + expiry_date (str): The option expiry date. + + Returns: + tuple: A tuple containing calculated metrics for call and put options. + """ + + call_df = options.get_calls(ticker, expiry_date) + put_df = options.get_puts(ticker, expiry_date) + + # Implied Volatility Analysis: + avg_call_iv = call_df["Implied Volatility"].str.rstrip("%").astype(float).mean() + avg_put_iv = put_df["Implied Volatility"].str.rstrip("%").astype(float).mean() + logging.info(f"Average Implied Volatility for Call Options: {avg_call_iv}%") + logging.info(f"Average Implied Volatility for Put Options: {avg_put_iv}%") + + # Option Prices Analysis: + avg_call_last_price = call_df["Last Price"].mean() + avg_put_last_price = put_df["Last Price"].mean() + logging.info(f"Average Last Price for Call Options: {avg_call_last_price}") + logging.info(f"Average Last Price for Put Options: {avg_put_last_price}") + + # Strike Price Analysis: + min_call_strike = call_df["Strike"].min() + max_call_strike = call_df["Strike"].max() + min_put_strike = put_df["Strike"].min() + max_put_strike = put_df["Strike"].max() + logging.info(f"Minimum Strike Price for Call Options: {min_call_strike}") + logging.info(f"Maximum Strike Price for Call Options: {max_call_strike}") + logging.info(f"Minimum Strike Price for Put Options: {min_put_strike}") + logging.info(f"Maximum Strike Price for Put Options: {max_put_strike}") + + # Volume Analysis: + total_call_volume = call_df["Volume"].str.replace('-', '0').astype(float).sum() + total_put_volume = put_df["Volume"].str.replace('-', '0').astype(float).sum() + logging.info(f"Total Volume for Call Options: {total_call_volume}") + logging.info(f"Total Volume for Put Options: {total_put_volume}") + + # Open Interest Analysis: + call_df['Open Interest'] = call_df['Open Interest'].str.replace('-', '0').astype(float) + put_df['Open Interest'] = put_df['Open Interest'].str.replace('-', '0').astype(float) + total_call_open_interest = call_df["Open Interest"].sum() + total_put_open_interest = put_df["Open Interest"].sum() + logging.info(f"Total Open Interest for Call Options: {total_call_open_interest}") + logging.info(f"Total Open Interest for Put Options: {total_put_open_interest}") + + # Convert Implied Volatility to float + call_df['Implied Volatility'] = call_df['Implied Volatility'].str.replace('%', '').astype(float) + put_df['Implied Volatility'] = put_df['Implied Volatility'].str.replace('%', '').astype(float) + + # Calculate Put-Call Ratio + put_call_ratio = total_put_volume / total_call_volume + logging.info(f"Put-Call Ratio: {put_call_ratio}") + + # Calculate Implied Volatility Percentile + call_iv_percentile = (call_df['Implied Volatility'] > call_df['Implied Volatility'].mean()).mean() * 100 + put_iv_percentile = (put_df['Implied Volatility'] > put_df['Implied Volatility'].mean()).mean() * 100 + logging.info(f"Call Option Implied Volatility Percentile: {call_iv_percentile}") + logging.info(f"Put Option Implied Volatility Percentile: {put_iv_percentile}") + + # Calculate Implied Volatility Skew + implied_vol_skew = call_df['Implied Volatility'].mean() - put_df['Implied Volatility'].mean() + logging.info(f"Implied Volatility Skew: {implied_vol_skew}") + + # Determine market sentiment + is_bullish_sentiment = call_df['Implied Volatility'].mean() > put_df['Implied Volatility'].mean() + sentiment = "bullish" if is_bullish_sentiment else "bearish" + logging.info(f"The overall sentiment of {ticker} is {sentiment}.") + + return (avg_call_iv, avg_put_iv, avg_call_last_price, avg_put_last_price, + min_call_strike, max_call_strike, min_put_strike, max_put_strike, + total_call_volume, total_put_volume, total_call_open_interest, total_put_open_interest, + put_call_ratio, call_iv_percentile, put_iv_percentile, implied_vol_skew, sentiment) + + +def get_fin_options_data(ticker): + """ Function to get information for Options & Futures Trading """ + current_price = round(stock_info.get_live_price(ticker), 3) + option_expiry_dates = options.get_expiration_dates(ticker) + nearest_expiry = option_expiry_dates[0] + + results = analyze_options_data(ticker, nearest_expiry) + + # Unpack the results tuple + (avg_call_iv, avg_put_iv, avg_call_last_price, avg_put_last_price, + min_call_strike, max_call_strike, min_put_strike, max_put_strike, + total_call_volume, total_put_volume, total_call_open_interest, total_put_open_interest, + put_call_ratio, call_iv_percentile, put_iv_percentile, implied_vol_skew, sentiment) = results + + # Create a list of complete sentences with the results + results_sentences = [ + f"Average Implied Volatility for Call Options: {avg_call_iv}%", + f"Average Implied Volatility for Put Options: {avg_put_iv}%", + f"Average Last Price for Call Options: {avg_call_last_price}", + f"Average Last Price for Put Options: {avg_put_last_price}", + f"Minimum Strike Price for Call Options: {min_call_strike}", + f"Maximum Strike Price for Call Options: {max_call_strike}", + f"Minimum Strike Price for Put Options: {min_put_strike}", + f"Maximum Strike Price for Put Options: {max_put_strike}", + f"Total Volume for Call Options: {total_call_volume}", + f"Total Volume for Put Options: {total_put_volume}", + f"Total Open Interest for Call Options: {total_call_open_interest}", + f"Total Open Interest for Put Options: {total_put_open_interest}", + f"Put-Call Ratio: {put_call_ratio}", + f"Call Option Implied Volatility Percentile: {call_iv_percentile}", + f"Put Option Implied Volatility Percentile: {put_iv_percentile}", + f"Implied Volatility Skew: {implied_vol_skew}", + f"The overall sentiment of {ticker} is {sentiment}." + ] + + # Print each sentence + for sentence in results_sentences: + logging.info(sentence) + + return results_sentences diff --git a/lib/ai_writers/ai_financial_writer.py b/lib/ai_writers/ai_financial_writer.py new file mode 100644 index 00000000..70ae8011 --- /dev/null +++ b/lib/ai_writers/ai_financial_writer.py @@ -0,0 +1,91 @@ +import sys +import os +from textwrap import dedent +from pathlib import Path +from datetime import datetime + +from loguru import logger +logger.remove() +logger.add(sys.stdout, + colorize=True, + format="{level}|{file}:{line}:{function}| {message}" + ) + +from ..ai_web_researcher.finance_data_researcher import get_finance_data, get_fin_options_data +from ..gpt_providers.text_generation.main_text_generation import llm_text_gen + + +def write_basic_ta_report(symbol): + """ Write financial TA for given ticker symbol """ + try: + symbol_fin_data = get_finance_data(symbol) + #get_visual_reports + fin_report = gen_finta_report(symbol_fin_data, symbol) + logger.info(f"Done: Final Technical Analysis for {symbol}:\n\n") + except Exception as err: + logger.error(f"Error: Failed to generate Financial report: {err}") + + #fin_options_data = get_fin_options_data(symbol) + #options_report = gen_options_report(fin_options_data, symbol) + + + +def gen_options_report(results_sentences, ticker): + """ Call LLM to generate options report """ + prompt = f""" + You are a financial expert specializing in options trading and market sentiment analysis. + You have been provided with the following technical analysis of options data for the ticker symbol {ticker} with the nearest expiry date: + + {chr(10).join(results_sentences)} + + Based on this data, provide a comprehensive analysis of the options market for {ticker}. + + Your analysis should include: + + 1. **Implied Volatility Interpretation:** Discuss the significance of the average implied volatility for both call and put options. What does it suggest about market expectations of future price movements? + 2. **Volume and Open Interest Insights:** Analyze the volume and open interest for call and put options. What does this data reveal about current market positioning and potential future trading activity? + 3. **Sentiment Analysis:** Evaluate the put-call ratio, implied volatility skew, and overall market sentiment. What do these indicators suggest about trader sentiment and potential future price direction? + 4. **Potential Trading Strategies:** Based on your analysis, suggest potential options trading strategies that could be employed for {ticker}, considering the current market conditions and sentiment. + + Please provide your analysis in a clear and concise manner, suitable for someone with a good understanding of options trading. + """ + logger.info("Generating Financial Technical report..") + try: + response = llm_text_gen(prompt) + return response + except Exception as err: + logger.error(f"Exit: Failed to get response from LLM: {err}") + exit(1) + + +def gen_finta_report(last_day_summary, symbol): + """ Get AI to write TA report from given data """ + prompt = f""" + You are a seasoned Technical Analysis (TA) expert, rivaling legends like Charles Dow, John Bollinger, and Alan Andrews. + Your deep understanding of market dynamics, coupled with mastery of technical indicators, + allows you to decipher complex patterns and offer precise predictions. + + Your expertise extends to practical tools like the pandas_ta module, enabling you to extract valuable insights from raw data. + + **Objective:** + Analyze the provided technical indicators for {symbol} on its last trading day and predict its price movement over the next few trading sessions. + + **Instructions:** + 1. **Identify Potential Trading Signals:** Highlight specific indicators suggesting bullish, bearish, or neutral signals. Explain the rationale behind each signal, referencing historical patterns or comparable market scenarios. + 2. **Detect Patterns and Divergences:** Analyze the interplay between different indicators. Detect patterns like moving average crossovers, candlestick formations, or divergences between price action and indicators. Explain the significance of each pattern. + 3. **Price Movement Prediction:** Based on your analysis, provide a clear prediction for {symbol}'s price movement in the next few days. State the expected direction (up, down, sideways) and potential price targets if identifiable. + 4. **Risk Assessment:** Briefly discuss any potential risks or factors that could invalidate your predictions, promoting a balanced and informed perspective. + + **Technical Indicators for {symbol} on the Last Trading Day:** + {last_day_summary} + + Remember, your analysis should be detailed, insightful, and actionable for traders seeking to capitalize on market movements. + """ + + logger.info("Generating Financial Technical report..") + try: + response = llm_text_gen(prompt) + return response + except Exception as err: + logger.error(f"Exit: Failed to get response from LLM: {err}") + exit(1) diff --git a/lib/utils/alwrity_utils.py b/lib/utils/alwrity_utils.py index 87a14c96..c5022f62 100644 --- a/lib/utils/alwrity_utils.py +++ b/lib/utils/alwrity_utils.py @@ -16,6 +16,7 @@ from lib.ai_writers.speech_to_blog.main_audio_to_blog import generate_audio_blog from lib.ai_writers.long_form_ai_writer import long_form_generator from lib.ai_writers.ai_news_article_writer import ai_news_generation from lib.ai_writers.ai_agents_crew_writer import ai_agents_writers +from lib.ai_writers.ai_financial_writer import write_basic_ta_report from lib.gpt_providers.text_generation.ai_story_writer import ai_story_generator from lib.gpt_providers.text_generation.ai_essay_writer import ai_essay_generator from lib.gpt_providers.text_to_image_generation.main_generate_image_from_prompt import generate_image @@ -46,6 +47,29 @@ def blog_from_audio(): break +def ai_finance_ta_writer(): + """ Call upon AI finance writer with user inputs. """ + print("________________________________________________________________") + content_keywords = input_dialog( + title='Enter Ticker Symbol For TA.', + text='👋 Be sure of ticker symbol, Else no results:(Examples:IBM, BABA, HDFCBANK.NS, TATAMOTORS.NS etc)', + ).run() + + # If the user cancels, exit the loop + if content_keywords.strip(): + try: + write_basic_ta_report(content_keywords) + except Exception as err: + print(f"🚫 Check ticker symbol: Failed to write Financial Technical Analysis.") + exit(1) + else: + message_dialog( + title='Error', + text='🚫 Provide Symbol Ticker. Dont waste my time.' + ).run() + exit(1) + + def blog_from_keyword(): """ Input blog keywords, research and write a factual blog.""" while True: diff --git a/requirements.txt b/requirements.txt index 05c92698..0fbe1e9a 100644 --- a/requirements.txt +++ b/requirements.txt @@ -26,3 +26,5 @@ prompt_toolkit ipython html2image lxml_html_clean +yfinance +pandas_ta