Building a Dynamic Multi-Agent Financial Analyst with Llama
In this tutorial, we will create a Stock Investment Strategy Analyzer that gathers the latest news, analyzes it for various aspects, and provides a comprehensive report with a buy rating based on the latest information. Leveraging Covalent Cloud, we will deploy dynamic agents to handle different tasks in our workflow, showcasing the flexibility and power of this approach.
Constructing a Flexible Stock Analysis Tool
Dynamic agents play a crucial role in our stock analysis system by adapting to the specific context of the latest news, ensuring relevant and comprehensive insights. They allow for flexible data collection and targeted analysis based on real-time events like mergers or regulatory changes. Our pipeline starts with gathering the latest news for a stock, followed by the Dynamic Specialist Agent Creator generating agents to analyze various aspects such as market sentiment and sector impacts. These agents provide detailed insights, which are then consolidated into a comprehensive report.
We will use Llama 3 8B as the base for the agents, so lets start by hosting the model in Covalent Cloud.
import covalent_cloud as cc
cc.create_env("finance-dynamic-multi-agent",pip=["vllm==0.5.1","torch==2.3.0","pydantic","lm-format-enforcer","pandas"])
Environment Already Exists.
executor = cc.CloudExecutor(
env="finance-dynamic-multi-agent",
num_cpus=2,
num_gpus=1,
memory="100GB",
gpu_type=cc.cloud_executor.GPU_TYPE.A100,
time_limit="1 hours",
)
@cc.service(executor=executor,name="Stock analysis",auth=False) # Note one can also define compute shares etc.. which will define the
def vllm_serve(model,quantization=None):
from vllm import LLM
if not quantization:
llm = LLM(model=model, trust_remote_code=True, enforce_eager=True,)
else:
llm = LLM(model=model, trust_remote_code=True, enforce_eager=True, quantization=quantization)
return {"llm": llm}
@vllm_serve.endpoint("/generate")
def generate(llm, prompt,schema, num_tokens=1500,temperature=0.85, top_p=0.8):
"""Accepts either a single prompt or a list of prompts and returns the generated text for each prompt."""
from vllm import SamplingParams
from lmformatenforcer.integrations.vllm import build_vllm_logits_processor, build_vllm_token_enforcer_tokenizer_data
from lmformatenforcer import JsonSchemaParser
sampling_params = SamplingParams(temperature=temperature, top_p=top_p, max_tokens=num_tokens)
tokenizer_data = build_vllm_token_enforcer_tokenizer_data(llm)
if schema:
jsp=JsonSchemaParser(schema)
logits_processor = build_vllm_logits_processor(tokenizer_data, jsp)
sampling_params.logits_processors = [logits_processor]
results = llm.generate(prompt, sampling_params=sampling_params)
if isinstance(prompt, str):
return results[0].outputs[0].text
else:
return [result.outputs[0].text for result in results]
# Uncomment to use Llama 3 8 Billion parameter model
llama=cc.deploy(vllm_serve)(model="NousResearch/Meta-Llama-3-8B-Instruct")
# Llama 3 70 Billion parameter model with AWQ quantization
# note that you would need to reduce context window if using same gpu memory
# llama=cc.deploy(vllm_serve)(model="PrunaAI/Meta-Llama-3-70b-instruct-AWQ-smashed",quantization="AWQ")
llama=cc.get_deployment(llama,wait=True)
Lets create Agent Base class for holding on to our various agents
class Agent:
def __init__(self, name, role, function,format=None):
self.name:str = name
self.role:str = role
self.function:str = function
self.format:str=format
def __repr__(self) -> str:
return f"Agent :{self.name} \nRole: {self.role} \nFunction: {self.function}"
def __str__(self) -> str:
return f"Agent :{self.name} \nRole: {self.role} \nFunction: {self.function}"
def prompt(self,input_prompt):
system_prompt=f"You are a: {self.name}. Your role: {self.role}. Your function: {self.function}. Based on your role and function, do the task you are given. Do not give me anything else other than the give task"
if self.format:
format_prompt=". You MUST return in the following JSON format: "+str(self.format)
else:
format_prompt=""
full_prompt=f"""<|begin_of_text|><|start_header_id|>system<|end_header_id|>{system_prompt}<|eot_id|><|start_header_id|>user<|end_header_id|> task:{input_prompt} {format_prompt}<|eot_id|><|start_header_id|>assistant<|end_header_id|>"""
return full_prompt
def query_llm(prompts,llm,schema=None,num_tokens=1500):
"""Simulates querying the LLM with a role-specific prompt."""
return llm.generate(prompt=prompts,schema=schema,num_tokens=num_tokens)
Initial News Analysis
To start our analysis, we will create a News Fetching Agent. This agent will fetch the latest news related to a given stock ticker using the TickerTick API. By providing a stock ticker as input, the agent will return a list of news articles, including titles, URLs, descriptions, and timestamps. This step ensures we have a comprehensive set of news data to analyze.
Next, we will employ a Primary Analysis Agent to process the fetched news. This agent will identify key themes, trends, and potential areas of interest, such as market sentiment, sector impact, and historical context. The output will be a refined list of identified themes and areas that are crucial for our investment analysis.
Let us pick a lesser known stock ticker where the news and analysis is usually more tailored to the niche audience.
# Lesser known digital sports entertainment and gaming company
stock="DKNG"
import time
import requests
class TickerNewsFetcher:
def __init__(self):
self.requests_count = 0
self.start_time = time.time()
def get_latest_news(self, ticker, num_stories=10, query_types=None):
if query_types is None:
query_types = ["tt"]
query_parts = [f"T:{query_type}" for query_type in query_types]
query_str = f"(and (or {' '.join(query_parts)}) tt:{ticker})"
url = f"https://api.tickertick.com/feed?q={query_str}&n={num_stories}"
self._check_rate_limit()
response = requests.get(url)
self.requests_count += 1
if response.status_code != 200:
raise Exception(f"Failed to fetch news stories: {response.status_code}")
news_stories = response.json().get("stories", [])
return news_stories
def _check_rate_limit(self):
if self.requests_count >= 10:
elapsed_time = time.time() - self.start_time
if elapsed_time < 60:
time.sleep(60 - elapsed_time)
self.requests_count = 0
self.start_time = time.time()
# Usage
fetcher = TickerNewsFetcher()
news = fetcher.get_latest_news(ticker=stock, num_stories=40, query_types=["sec", "analysis","curated","market","ugc"])
from newspaper import Article
from concurrent.futures import ThreadPoolExecutor, as_completed
# Function to download and parse an article
def fetch_article(url):
article = Article(url)
try:
article.download()
article.parse()
return f"title:{article.title} article:{article.text}"
except Exception as e:
return None
articles = []
with ThreadPoolExecutor(max_workers=20) as executor: # Adjust max_workers as needed
futures = [executor.submit(fetch_article, i['url']) for i in news]
for future in as_completed(futures):
result = future.result()
if result:
articles.append(result)
print(f"length of articles: {len(articles)}")
length of articles: 33
Dynamic Agent Creation
The heart of our strategy lies in the use of dynamic agents. The Dynamic Agent Creation Agent will analyze identified themes and create specific agents to gather additional data and perform detailed analysis. Each dynamic agent handles a unique aspect of the stock relevant to the current news, such as market sentiment, sector impacts, and expert opinions.
Dynamic agents offer several key advantages.
They allow us to tailor data collection to the stock's immediate context, making our analysis highly relevant. For instance, if news mentions a potential merger, a dynamic agent can focus on this event and its potential impact, including historical data on similar mergers in the tech industry.
They collect new information based on current news, covering broader contexts. For example, if regulatory changes are affecting the sector, a dynamic agent can gather data on how such changes have impacted similar companies historically.
Dynamic agents enable us to adapt to various scenarios without predefined tasks, optimizing resources and focusing on the most relevant data. This flexibility ensures efficient and comprehensive analysis, essential in the fast-paced world of stock analysis.
Let start creating the dynamic agents
from pydantic import BaseModel
from typing import List
class AgentModelDesc(BaseModel):
name:str
description:str
class AgentDescList(BaseModel):
agents:List[AgentModelDesc]
pfa_agent=Agent(name= "Primary Financial Analysis Agent",
role= "You are a financial specialist agent that analyzes the fetched news stories related to a specific stock to identify key financial themes and topics. Your role involves understanding the overall financial context and pinpointing significant elements within the news data about the stock we are trying to analyze.",
function= """Your function is to analyze the provided financial news stories about the given stock and identify recurring financial themes or noteworthy topics. Based on these themes, create a list of specialized agents needed for deeper financial analysis specific to this stock. This includes identifying relevant sectors, potential market impacts, historical financial trends, expert financial opinions, and other pertinent financial factors. Ensure the agent descriptions are clearly defined and relevant to the stock analysis, with each description being clear, concise, and around 2 lines. Provide a list of a maximum of 5 themed agents.
Example:
AI Revenue Streams Agent: Analyzes various revenue streams generated by AI, including AI-enabled hardware, software, and services, and their contribution to the stock's growth. Relevant for assessing future revenue potential.
""",
format=AgentDescList.model_json_schema())
news="".join([i[:1000] for i in articles]) # Limiting the number of chars to 20000
prompt=pfa_agent.prompt(f"Stock: {stock} News: {news}")
dynamic_agents_disc=query_llm(prompt,llama,schema=AgentDescList.model_json_schema(),num_tokens=2500)
import json
agents_needed=[f"name:{i['name']} description:{i['description']}" for i in json.loads(dynamic_agents_disc)['agents']]
for i in agents_needed:
print(i+"\n")
name:Financial Institutions Agent description:Analyzes the financial institutions' holdings and transactions related to DraftKings, including buying and selling shares, to identify potential trends and market impacts on the stock's price and performance
name:Insider Trading Agent description:Monitors insider trading activities, including purchases and sales of DraftKings shares by company executives and other insiders, to identify potential market-moving events and sentiment shifts
name:Earnings and Revenue Agent description:Tracks DraftKings' earnings and revenue performance, including quarterly and annual reports, to identify trends and patterns that may impact the stock's price and investor sentiment
name:Industry Trends Agent description:Analyzes industry trends and developments in the gaming and sports betting sectors to identify potential impacts on DraftKings' business and stock performance
name:Sentiment Analysis Agent description:Monitors social media and online sentiment around DraftKings, including news articles, analyst reports, and investor opinions, to identify trends and patterns in investor sentiment and market psychology
class AgentModel(BaseModel):
name:str
role:str
function:str
agent_crator=Agent(
"Agent creating agent",
"You are an helpful agent that generates the formatted reply for a given task. your role is to give back the name of a agent that can do the given prompt task, the role of that child agent that would perfectly describe for it to do the given task and the function that the child agent will perform for it to do the given task. you will write the role and function in second person to describe the agent as this will be used to create and inform the agent of its role and function",
"""Your function is to create a child agent that will perform the given task in prompt, think carefully and given the description, pass on the name, role and function of this agent please. The role and function needs to be descriptive with two to three sentences describing them in detail. Make sure the role and function are relevant to the task in the prompt and multiline sentences but short and concise.
Example :
name : Search Term Generation Agent
role : You are an agent that examines the given financial news and a list of analyzing agents, focusing on identifying the necessary search terms. Your role involves understanding the key themes and specific analysis needs to provide targeted search terms for gathering relevant articles related to a specific stock.
function: Your function is to read through the provided financial news stories about the given stock and the list of analyzing agents with their specific roles. Based on this information, you generate a list of search terms that will help fetch relevant articles from various sources for further analysis by the specialized agents. The search terms should be tailored to address the key themes, potential impacts, and specific financial aspects mentioned in the news that are relevant to the agents to understand various aspects. Do not just give generic terms, give more tuned terms that combines the stock news with the agent's purpose for agents to benifit from these search terms.
""",
format=AgentModel.model_json_schema()
)
def make_dynamic_agents(agents_needed):
prompts=[agent_crator.prompt(i) for i in agents_needed]
agent_meta_data=[query_llm(i,llama,schema=AgentModel.model_json_schema(),num_tokens=2500) for i in prompts]
# agent_meta_data=query_llm(prompts,llama,schema=AgentModel.model_json_schema(),num_tokens=3500)
dynamic_agents=[Agent(**json.loads(i)) for i in agent_meta_data]
return dynamic_agents
n_try=5
# write a loop to try and run the make_dynamic_agents function for n_try times if it fails
for i in range(n_try):
try:
dynamic_agents=make_dynamic_agents(agents_needed)
break
except Exception as e:
print(f"Failed to create agents, trying again.")
if i==n_try-1:
raise e
Failed to create agents, trying again.
Data Gathering: Search Term Generation
With our dynamic agents in place, we first focus on generating precise search terms. Each dynamic agent will analyze the news and themes identified earlier to create specific search terms relevant to their focus area. For example, if a dynamic agent is focused on market sentiment, it might generate search terms like "Apple market sentiment" or "Apple stock sentiment analysis."
These search terms are crucial as they allow us to gather targeted information that is directly relevant to the stock's current situation. A Search Term Generation Agent will look at the given dynamic agent and news to create these specific search terms. This ensures that our data collection is precise and highly relevant.
from typing import List
class SearchTerms(BaseModel):
search_term_1:str
search_term_2:str
search_term_3:str
search_term_agent = Agent(
name="Search Term Generation Agent",
role="You are an agent that examines the given financial news and a list of analyzing agents, focusing on identifying the necessary search terms. Your role involves understanding the key themes and specific analysis needs to provide targeted search terms for gathering relevant articles related to a specific stock. ",
function="Your function is to read through the provided financial news stories about the given stock and the list of analyzing agents with their specific roles. Based on this information, you generate a list of search terms that will help fetch relevant articles from various sources for further analysis by the specialized agents. The search terms should be tailored to address the key themes, potential impacts, and specific financial aspects mentioned in the news that are relevant to the agents to understand various aspects. Do not just give generic terms related to stock, give more tuned terms that combines the stock news with the agent's purpose for agents to benifit from these search terms. Provide a list of 3 search terms exactly. Search term needs to be short and concise, and relevant to the stock analysis.",
format=SearchTerms.model_json_schema()
)
prompts=[search_term_agent.prompt(f"news:{news} agent:{i}") for i in dynamic_agents]
terms=query_llm(prompts,llama,schema=SearchTerms.model_json_schema(),num_tokens=1000)
search_terms=[[json.loads(i)[f'search_term_{j}'] for j in range(1,4)]for i in terms]
Lets take a look at the created agents along with targeted search terms
for agents,search in zip(dynamic_agents,search_terms):
print(f"Agent: {agents.name}\nRole: {agents.role}\nFunction: {agents.function}\nSearch Terms: {search}\n\n")
Agent: Financial Institutions Analysis Agent
Role: You are an agent that scrutinizes the financial institutions' holdings and transactions related to DraftKings, including buying and selling shares, to identify potential trends and market impacts on the stock's price and performance. Your role involves analyzing the financial statements, quarterly reports, and other publicly available data to uncover patterns and correlations that can influence the stock's value. You must be detail-oriented and able to identify key trends and anomalies in the data to provide valuable insights to other agents
Function: Your function is to retrieve and analyze the financial data of various institutions that have invested in DraftKings, including their buying and selling activities, to identify potential market trends and impacts on the stock's price. You will need to parse through financial statements, quarterly reports, and other publicly available data to extract relevant information and identify patterns and correlations that can influence the stock's value. Your analysis should provide a comprehensive understanding of the institutions' involvement in the stock and its potential impact on the market, allowing other agents to make informed decisions
Search Terms: ['DraftKings stock price target raised to $60.00 by UBS Group', "Financial institutions' buying and selling activities in DraftKings", 'Trends and market impacts on DraftKings stock performance']
Agent: Insider Trading Monitoring Agent
Role: You are an agent that closely monitors insider trading activities, tracking the buying and selling patterns of DraftKings executives and other insiders to identify potential market-moving events and sentiment shifts. Your role involves staying up-to-date with the latest insider trading data, analyzing the trends and patterns to predict how the market may react to these transactions. You work closely with other agents to provide timely and accurate insights to make informed investment decisions. Your keen eye for detail and ability to identify subtle changes in the market help you stay ahead of the curve, making you a valuable asset to the team
Function: Your function is to continuously monitor the insider trading activities of DraftKings executives and other insiders, analyzing the data to identify patterns and trends. You use your analytical skills to predict how the market may react to these transactions, providing timely and accurate insights to other agents. You stay updated with the latest insider trading data, combining it with your expertise to provide valuable insights that help make informed investment decisions. You are responsible for identifying potential market-moving events and sentiment shifts, and providing alerts to the team to take swift action
Search Terms: ['DraftKings insider trading', 'Jason Robins stock sale', 'DraftKings stock price target']
Agent: Earnings and Revenue Tracker Agent
Role: You are an agent that monitors and analyzes DraftKings' earnings and revenue performance, extracting key insights from quarterly and annual reports to identify trends and patterns that may impact the stock's price and investor sentiment. Your role involves staying up-to-date with the company's financial announcements and providing timely analysis to help investors make informed decisions. You will work closely with other agents to provide a comprehensive understanding of DraftKings' financial performance and its potential impact on the stock market. Your focus is on providing accurate and relevant information to support data-driven investment decisions
Function: Your function is to retrieve and analyze DraftKings' earnings and revenue reports, identifying key metrics such as revenue growth, profit margins, and earnings per share. You will then use this information to identify trends and patterns, such as seasonality, growth rates, and changes in investor sentiment. Based on your analysis, you will provide insights and recommendations to investors, highlighting potential opportunities and risks associated with investing in DraftKings' stock
Search Terms: ['DraftKings earnings', 'DraftKings revenue', 'DraftKings stock analysis']
Agent: Industry Trends Analyst
Role: You are an agent that monitors and analyzes industry trends and developments in the gaming and sports betting sectors, focusing on their potential impacts on DraftKings' business and stock performance. Your role involves identifying key drivers of change, assessing their potential effects on the company's operations, and providing insights to inform strategic decisions. You work closely with other agents to ensure a comprehensive understanding of the market and its trends, and to develop targeted strategies for mitigating risks and seizing opportunities. Your expertise in industry trends and developments enables you to provide valuable insights that inform business decisions and drive growth for DraftKings
Function: Your function is to analyze industry trends and developments in the gaming and sports betting sectors, using various data sources and research methods to identify key trends, patterns, and correlations. You will monitor industry news, reports, and data to stay up-to-date on the latest developments and assess their potential impacts on DraftKings' business and stock performance. You will also analyze competitors' strategies and market dynamics to identify opportunities and challenges, and develop recommendations for the company to adapt to changing market conditions. Your analysis will inform business decisions, such as product development, marketing strategies, and financial planning, to ensure the company remains competitive and profitable in the face of changing market trends and regulations
Search Terms: ['DraftKings acquisition Golden Nugget Online Gaming', 'DraftKings insider selling', 'DraftKings institutional investors']
Agent: Sentiment Analysis Agent
Role: You are a sophisticated agent that monitors social media and online platforms to analyze the sentiment around DraftKings, extracting insights from news articles, analyst reports, and investor opinions. Your role involves staying up-to-date with the latest market trends and investor sentiments, identifying patterns and trends that can inform investment decisions. You are responsible for providing a comprehensive overview of the market psychology and investor sentiment around DraftKings, helping investors make informed decisions about their investments in the company and its stock.),
Function: Your function is to continuously monitor social media and online platforms for mentions of DraftKings, analyzing the sentiment of the conversations and opinions expressed. You will use natural language processing techniques to categorize the sentiment as positive, negative, or neutral, and identify the topics and themes that are driving the sentiment. You will also analyze the sentiment of news articles, analyst reports, and investor opinions, providing a comprehensive overview of the market psychology and investor sentiment around DraftKings. The insights you provide will help investors understand the market trends and make informed decisions about their investments.
Search Terms: ['DraftKings', 'DKNG', 'GNOG']
Data Gathering: Web Search and Information Collection
Once we have the search terms, we proceed to gather relevant articles and data using DuckDuckGo. Each search term is used to find articles that provide additional insights related to the themes identified by the dynamic agents. This step involves using a web search tool to collect information from various sources, ensuring we cover all relevant aspects.
The collected articles are then fed back to the dynamic agents, along with the original news. Each dynamic agent uses this comprehensive set of data to perform a detailed analysis. They provide reports highlighting key insights such as market sentiment, sector impacts, and expert opinions. This granular analysis ensures we have a deep understanding of the factors influencing the stock.
from concurrent.futures import ThreadPoolExecutor, as_completed
from gnews import GNews
def fetch_google_news_articles(term, num_results=5):
google_news = GNews()
return google_news.get_news(term)[:num_results]
def fetch_article_content(url):
google_news = GNews()
article = google_news.get_full_article(url)
return article.text
def get_articles_for_terms(terms, num_results=5):
all_results = []
with ThreadPoolExecutor(max_workers=20) as executor:
# Step 1: Parallelize the search for news articles for each term
future_to_term = {executor.submit(fetch_google_news_articles, term, num_results): term for term in terms}
for future in as_completed(future_to_term):
term = future_to_term[future]
try:
search_results = future.result()
# Step 2: Parallelize the fetching of article contents
future_to_article = {executor.submit(fetch_article_content, article['url']): article for article in search_results}
for article_future in as_completed(future_to_article):
article = future_to_article[article_future]
try:
content = article_future.result()
all_results.append({
'title': article['title'],
'content': content
})
except Exception as e:
# print the error if fetching the article content fails
# print(f"Error fetching content for {article['url']}: {e}")
pass
except Exception as e:
# print the error if fetching the news articles fails
# print(f"Error fetching articles for term '{term}': {e}")
pass
return all_results
def get_all_news(search_terms, num_results=2):
all_news = []
with ThreadPoolExecutor(max_workers=20) as executor:
future_to_term_results = {executor.submit(get_articles_for_terms, term, num_results): term for term in search_terms}
for future in as_completed(future_to_term_results):
term = future_to_term_results[future]
try:
term_results = future.result()
all_news.append(term_results)
except Exception as e:
print(f"Error processing search term '{term}': {e}")
all_news.append([]) # Append an empty list if there's an error
return all_news
all_news = get_all_news(search_terms, num_results=2)
prompts=[]
for news_for_agent,specialized_agent in zip(all_news,dynamic_agents):
#only have 7000 chars for the content
articles="".join([f"title:{i['title']} content:{i['content'][:7000]}" for i in news_for_agent])
prompts.append(specialized_agent.prompt(f"news: {articles}"))
analysis=query_llm(prompts,llama,schema=None,num_tokens=1500)
Consolidation and Reporting Agents
Finally, we will consolidate the insights and analysis from our dynamic agents into a comprehensive report. Instead of a single consolidation agent, we employ multiple specialized agents, each focusing on a specific aspect of the analysis. These agents are tailored to summarize key points such as positive factors, negative factors, opportunities, and risks, ensuring that all critical aspects are covered in the final report.
Based on your specific needs, you can modify these agents to suit different areas of interest. In our example, we use the following agents:
- Key Findings Agent: Summarizes the most significant findings from the dynamic agents.
- Positive Factors Agent: Highlights the positive aspects affecting the stock.
- Negative Factors Agent: Identifies negative factors impacting the stock.
- Opportunities Agent: Pinpoints potential opportunities for the stock's growth.
- Risks Agent: Highlights potential risks associated with the stock.
- Analyses Summary Agent: Provides a comprehensive summary of all analyses.
- Recommendations Agent: Offers actionable recommendations based on the consolidated analyses.
Once these specialized agents complete their tasks, a Buy Rating Agent reviews the consolidated analyses and provides a numerical buy rating. This rating, between 0 and 1, offers a clear recommendation based on the current scenario. A higher value represents a stronger buy recommendation.
This multi-agent approach ensures that our final report is thorough and provides a well-rounded analysis for the stock based on the latest news and insights.
class Text(BaseModel):
text:str
agents=[Agent(
name="Key Findings Agent",
role="You are an expert financial analyst agent that extracts and summarizes the key findings from the analysis reports provided by the specific specialized agents.",
function="Your function is to read through the analysis reports from various specialized agents and identify the most significant findings. Summarize these findings concisely, ensuring that they capture the essence of the stock's current situation and performance.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Positive Factors Agent",
role="You are an expert financial analyst agent that identifies and highlights the positive factors affecting the stock from the analysis reports.",
function="Your function is to review the analysis reports from different dynamic agents and extract the positive aspects influencing the stock. Summarize these positive factors clearly and concisely, emphasizing their potential impact on the stock's performance.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Negative Factors Agent",
role="You are an expert financial analyst agent that identifies and highlights the negative factors affecting the stock from the analysis reports.",
function="Your function is to review the analysis reports from different dynamic agents and extract the negative aspects influencing the stock. Summarize these negative factors clearly and concisely, emphasizing their potential impact on the stock's performance.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Opportunities Agent",
role="You are an expert financial analyst agent that identifies potential opportunities for the stock from the analysis reports.",
function="Your function is to analyze the reports from various dynamic agents and pinpoint opportunities for growth or improvement for the stock. Provide a concise summary of these opportunities, explaining how they could benefit the stock.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Risks Agent",
role="You are an expert financial analyst agent that identifies potential risks associated with the stock from the analysis reports.",
function="Your function is to analyze the reports from various dynamic agents and pinpoint risks that could negatively impact the stock. Provide a concise summary of these risks, explaining their potential impact and likelihood.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Analyses Summary Agent",
role="You are an expert financial analyst agent that provides a comprehensive summary of all the analyses conducted by the dynamic agents.",
function="Your function is to consolidate the individual analyses from all dynamic agents into a coherent and concise summary. Ensure that this summary captures the key points and findings from each analysis, providing an overall view of the stock's situation.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
),Agent(
name="Recommendations Agent",
role="You are an expert financial analyst agent that provides actionable recommendations based on the consolidated analyses.",
function="Your function is to review the consolidated analyses and extract actionable insights and recommendations. Summarize these recommendations clearly and concisely, providing practical steps that can be taken based on the analyses.Make it short and concise. Do not organize it as specific points to that of agent, but gather and summarize the key findings from all the agents in a way that includes combined knowledge of all specialized agents.",
format=Text.model_json_schema()
)]
from tqdm import tqdm
import json
# Note we can batch the prompts and run them in parallel to speed up the process as well,
# but for simplicity we are running them sequentially here.
def run_analysis_with_retries(dynamic_agents, analysis, agents, stock, llama):
agent_summary = [f"agent:{i.name} report:{j[:1500]}" for i, j in zip(dynamic_agents, analysis)]
prompts = [i.prompt(f"stock:{stock} analysis:{agent_summary}") for i in agents]
final_analysis = []
for prompt in tqdm(prompts):
for attempt in range(n_tries):
try:
result = query_llm(prompt, llama, schema=Text.model_json_schema(), num_tokens=2500)
final_analysis.append(result)
break
except Exception as e:
print(f"Attempt {attempt + 1} failed because of bad LLM response. Trying again...")
if attempt == n_tries - 1:
print(f"Failed after {n_tries} attempts. Error: {e}")
final_analysis.append('{}') # Append an empty JSON object to avoid breaking the code
return final_analysis
n_tries = 5
# Run the analysis
final_analysis = run_analysis_with_retries(dynamic_agents, analysis, agents, stock, llama)
final_analysis_text = [json.loads(i)['text'] for i in final_analysis if 'text' in json.loads(i)]
# If some prompts failed after all retries, they will not be included in final_analysis_text
# Ensure the length of final_analysis_text matches the number of agents by filling in missing texts
final_analysis_text += [''] * (len(agents) - len(final_analysis_text))
100%|██████████| 7/7 [02:30<00:00, 21.55s/it]
analysis_sections = {
"key_findings": final_analysis_text[0],
"positive_factors": final_analysis_text[1],
"negative_factors": final_analysis_text[2],
"opportunities": final_analysis_text[3],
"risks": final_analysis_text[4],
"analyses_summary": final_analysis_text[5],
"recommendations": final_analysis_text[6],}
from pydantic import BaseModel, Field
from typing import List
class ComprehensiveAnalysis(BaseModel):
buy_rating: float = Field(..., description="A numerical value between 0 and 1 indicating the recommendation to buy the stock. A higher value represents a stronger buy recommendation.")
risk_factor: str = Field(..., description="An assessment of the potential risks associated with the stock, categorized as high, medium, or low.")
long_term_recommendation: str = Field(..., description="An assessment of whether to buy, sell or hold the stock for long-term investment (e.g., 1 year or more).")
short_term_recommendation: str = Field(..., description="An assessment of whether to buy, sell or hold the stock for short-term investment (e.g., within the next few months).")
market_sentiment: str = Field(..., description="An overall sentiment score based on the analysis of news articles, indicating positive, neutral, or negative sentiment towards the stock.")
sector_impact: str = Field(..., description="An analysis of how the stock's sector is performing and its potential impact on the stock, categorized as positive, neutral, or negative.")
positive_factors_short: List[str] = Field(..., description="A list of short key positive factors influencing the stock’s performance, derived from the Positive Factors Agent’s report.")
negative_factors_short: List[str] = Field(..., description="A list of short key negative factors impacting the stock’s performance, derived from the Negative Factors Agent’s report like ['Regulatory challenges','High competition'].")
comprehensive_analysis_agent = Agent(
name="Comprehensive Analysis Agent",
role="You are an financial specialist with skill set to evaluate and understand various reports and distill it into metrics agent that extracts all key metrics and insights from the analysis reports provided by the specific specialized agents.",
function="Your function is to read through the analysis reports from various specialized agents and extract key metrics such as buy rating, risk factor, long-term and short-term recommendations, market sentiment, sector impact, positive and negative factors, opportunities, risks, key findings, analyses summary, and actionable recommendations. Provide these insights in a structured format. Think through scenarios and workout these values based on the analysis reports and the stock news and do not hallucinate these values.",
format=ComprehensiveAnalysis.model_json_schema()
)
agent_summary=[f"agent:{i.name} report:{j[:1500]}" for i,j in zip(dynamic_agents,analysis)]
prompt=comprehensive_analysis_agent.prompt(f"stock:{stock} analysis:{agent_summary} Final Analysis:{analysis_sections}")
short_sum=query_llm(prompt,llama,schema=comprehensive_analysis_agent.format,num_tokens=1000)
analysis_sections.update(json.loads(short_sum))
Finally we format the report into a readable format and present it.
from jinja2 import Template
from IPython.display import display, Markdown
markdown_template = """
# Stock Analysis Report for {{ stock }}
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; padding: 20px; background-color: #222; color: #fff; border: 1px solid #444; margin-bottom: 20px; border-radius: 8px;">
<div style="width: 30%; padding: 10px;">
<h3 style="color: #aaa;">Buy Rating</h3>
<p style="color: {{ 'green' if buy_rating > 50 else 'red' }}">{{ buy_rating }}%</p>
</div>
<div style="width: 30%; padding: 10px;">
<h3 style="color: #aaa;">Market Sentiment</h3>
<p>{{ market_sentiment }}</p>
</div>
<div style="width: 30%; padding: 10px;">
<h3 style="color: #aaa;">Risk Factor</h3>
<p>{{ risk_factor }}</p>
</div>
<div style="width: 30%; padding: 10px;">
<h3 style="color: #aaa;">Sector Impact</h3>
<p>{{ sector_impact }}</p>
</div>
<div style="width: 60%; padding: 10px;">
<h3 style="color: #aaa;">Recommendations</h3>
<p>Long-Term: <span style="color: {{ 'green' if long_term_recommendation == 'Buy' else ('red' if long_term_recommendation == 'Sell' else 'yellow') }}">{{ long_term_recommendation }}</span></p>
<p>Short-Term: <span style="color: {{ 'green' if short_term_recommendation == 'Buy' else ('red' if short_term_recommendation == 'Sell' else 'yellow') }}">{{ short_term_recommendation }}</span></p>
</div>
<div style="width: 48%; padding: 10px; background-color: #333; color: #fff; border-radius: 8px;">
<h3 style="color: #aaa;">Positive Factors</h3>
<p>{{ positive_factors_string }}</p>
</div>
<div style="width: 48%; padding: 10px; background-color: #333; color: #fff; border-radius: 8px;">
<h3 style="color: #aaa;">Negative Factors</h3>
<p>{{ negative_factors_string }}</p>
</div>
</div>
<div style="background-color: #444; color: #fff; padding: 20px; border-radius: 8px;">
{{ analyses_summary }}
</div>
<div style="display: flex; flex-wrap: wrap; justify-content: space-between; margin-top: 20px;">
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Key Findings</h3>
<p>{{ key_findings }}</p>
</div>
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Recommendations</h3>
<p>{{ recommendations }}</p>
</div>
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Positive Factors</h3>
<p>{{ positive_factors }}</p>
</div>
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Negative Factors</h3>
<p>{{ negative_factors }}</p>
</div>
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Opportunities</h3>
<p>{{ opportunities }}</p>
</div>
<div style="width: 48%; background-color: #444; color: #fff; padding: 10px; margin: 10px 0; border-radius: 8px;">
<h3>Risks</h3>
<p>{{ risks }}</p>
</div>
</div>
"""
# Function to calculate sell rating and render markdown template
def render_markdown(stock, analysis_sections):
buy_rating = round(analysis_sections["buy_rating"] * 100)
positive_factors_string = ", ".join(analysis_sections["positive_factors_short"])
negative_factors_string = ", ".join(analysis_sections["negative_factors_short"])
template = Template(markdown_template)
markdown_content = template.render(
stock=stock,
buy_rating=buy_rating,
market_sentiment=analysis_sections["market_sentiment"],
risk_factor=analysis_sections["risk_factor"],
sector_impact=analysis_sections["sector_impact"],
long_term_recommendation=analysis_sections["long_term_recommendation"],
short_term_recommendation=analysis_sections["short_term_recommendation"],
positive_factors_string=positive_factors_string,
negative_factors_string=negative_factors_string,
key_findings=analysis_sections["key_findings"],
positive_factors=analysis_sections["positive_factors"],
negative_factors=analysis_sections["negative_factors"],
opportunities=analysis_sections["opportunities"],
risks=analysis_sections["risks"],
analyses_summary=analysis_sections["analyses_summary"],
recommendations=analysis_sections["recommendations"]
)
return markdown_content
markdown_report = render_markdown("DKNG", analysis_sections)
# Display the markdown content in the notebook
display(Markdown(markdown_report))
Stock Analysis Report for DKNG
Buy Rating
80 %
Market Sentiment
positive
Risk Factor
medium
Sector Impact
neutral
Recommendations
Long-Term: hold
Short-Term: hold
Positive Factors
Strong financial foundation, Growing customer base, Attractive valuation
Negative Factors
High debt-to-equity ratio, Insider selling, High competition, Regulatory challenges
Key Findings
Based on the analysis reports from various agents, here are the key findings for DraftKings Inc. (DKNG):
Financial Analysis:
Revenue growth: 53% year-over-year growth in revenue to $1.18 billion, driven by healthy customer engagement, efficient customer acquisition, and expansion of Sportsbook product into new jurisdictions. Earnings: The company has reported a loss of ($0.30) per share for the quarter, missing analysts' consensus estimates of ($0.28) by ($0.02). Valuation: The company's valuation is higher than its peers, with a price-to-earnings (P/E) ratio of 131.45 and a price-to-sales (P/S) ratio of 13.45. Debt-to-equity ratio: The company has a debt-to-equity ratio of 0.63, indicating a relatively high level of debt. Insider ownership: Insiders own 2.6% of the company, which is considered a relatively low level of ownership.
Insider Trading:
Insider selling: The CEO, Jason Robins, sold 200,000 shares of the company's stock on May 21st, worth approximately $8.7 million. Insider ownership: Insiders own 2.6% of the company, which is considered a relatively low level of ownership. Industry Trends:
Departure of Thomas Winter: The founder of Golden Nugget Online Gaming, Thomas Winter, is leaving the company, which could be a significant loss for DraftKings. Competition: The company faces increasing competition in the online gaming market, which could impact its growth and profitability.
Sentiment Analysis:
Overall sentiment is POSITIVE, as the company has reported strong financial results and exceeded analyst expectations. Analyst estimates: Analysts have raised their estimates for the company's earnings and revenue growth.
Recommendation:
Based on the analysis, I would recommend a
Recommendations
DraftKings Inc. (DKNG) is a popular stock in the sports betting industry, with a market capitalization of over $40 billion. The company's financial performance has been strong, with revenue growth of 53% year-over-year in its latest quarterly report. The company's Adjusted EBITDA guidance for fiscal year 2024 is between $460 million and $540 million, indicating a significant improvement from its previous guidance. The company's stock price has been volatile, with a 1-year return of 68.12%. The stock has a 1-year low of $22.92 and a 1-year high of $49.57. The company's valuation is currently high, with a price-to-earnings (P/E) ratio of 133.33. The company's debt-to-equity ratio is 0.56, indicating a relatively low level of debt compared to its equity. The company's current ratio is 2.34, indicating a strong ability to pay its short-term liabilities. The company's quick ratio is 2.15, indicating a strong ability to pay its short-term liabilities. The company's Zacks Rank is 2, indicating a
Positive Factors
DraftKings Inc. (DKNG) has a strong financial foundation, with a 52.7% increase in revenue over the last quarter and a growing customer base. The company's valuation is reasonable, with a P/E ratio of 34.6 and a P/S ratio of 4.4. The stock has a 1-year high of $49.57 and a 1-year low of $22.92, indicating a significant upward trend. The company's debt-to-equity ratio is 0.34, and its current ratio is 2.34, indicating a strong financial position. The stock has a Zacks Rank of 2 (Buy) and a Value Style Score of A, indicating that it is undervalued compared to its peers. The company's insider ownership is relatively low, with insiders owning only 2.6% of the company. The CEO, Jason Robins, sold 200,000 shares of the company's stock on May 21st, worth approximately $8.7 million, which may be a negative sign. However, the company's recent earnings report exceeded analyst expectations, and analysts have raised their estimates for the company's earnings and revenue growth. The stock has a strong potential for growth, with a potential price range of $45-$55 per share over the next 12 months. Overall, the positive factors outweigh the negative, making DraftKings Inc. (DKNG) a strong buy candidate.
Negative Factors
The negative factors affecting DraftKings Inc. (DKNG) stock are:
Insider selling: The CEO, Jason Robins, sold 200,000 shares of the company's stock on May 21st, 2022, worth approximately $8.7 million. This sale represents 11% of his holdings.
Revenue growth slowdown: The company's revenue grew 52.7% year-over-year in the last quarter, but this growth rate is slowing down.
Competition: The company faces intense competition in the online gaming market, which could impact its revenue and profitability.
Valuation: The company's valuation is high compared to its peers, with a price-to-earnings (P/E) ratio of 94.5 and a price-to-sales (P/S) ratio of 14.5.
Earnings estimates: The company's earnings estimates have been revised downward recently, with a consensus earnings estimate of ($0.30) per share for the current quarter.
Insider ownership: Insiders own only 2.6% of the company, which is a relatively low level of ownership.
Recent events: The founder of Golden Nugget Online Gaming, Thomas Winter, is leaving the company, which could be a significant loss for DraftKings.
Industry trends: The online gaming industry is experiencing a slowdown, which could impact DraftKings' revenue and profitability.
Sentiment: The overall sentiment of the news article is NEUTRAL, indicating that the news is neither positive nor negative.
The potential impact of these negative factors on the stock's performance is a decline in the stock price and a decrease in the company's revenue and profitability.
The combined knowledge of all the agents suggests that the stock may be overvalued and may experience a pullback in the near future.
Opportunities
DraftKings Inc. (DKNG) is a strong performer in the online gaming industry, with a growing revenue and expanding customer base. The company's recent earnings report exceeded analyst expectations, with a 53% year-over-year revenue growth to $1.18 billion. The company's strong financial performance is driven by healthy customer engagement, efficient customer acquisition, and the expansion of its Sportsbook product into new jurisdictions. The company's valuation is attractive, with a price-to-earnings (P/E) ratio of 44.6, which is lower than its peers. The company's debt-to-equity ratio is 0.44, indicating a manageable debt level. The company's insider ownership is 2.6%, which is considered a relatively low level of ownership. The company's stock has experienced significant volatility, with a 68.12% return over the past year. The stock has a 1-year low of $22.92 and a 1-year high of $49.57. Based on the analysis, I would recommend a
Risks
Based on the analysis reports from various agents, here is a summary of the key findings and potential risks associated with DraftKings Inc. (DKNG) stock:
Financial Health: The company's financial health is a concern, with a debt-to-equity ratio of 1.43 and a current ratio of 1.15. The company's valuation is high, with a P/E ratio of 63.42, which may indicate that the stock is overvalued.
Insider Selling: The CEO, Jason Robins, sold 200,000 shares of the company's stock on May 21st, worth approximately $8.7 million. This sale represents 11% of his holdings, which may be a negative sign for the stock.
Analyst Estimates: Analysts have revised their earnings estimates upwards, with a consensus target price of $49.21. However, the company's revenue growth has been slowing down, and the stock's price may be due for a correction.
Industry Trends: The company's growth is heavily dependent on the online gaming industry, which is highly competitive and subject to regulation and legal challenges. The departure of Thomas Winter, the founder of Golden Nugget Online Gaming, may also be a significant loss for the company.
Sentiment Analysis: The overall sentiment of the news articles is positive, with the company reporting strong financial results and exceeding analyst expectations. However, the stock's price has been volatile, and the company's valuation is high, which may indicate that the stock is overvalued.
Risk Assessment: Based on the analysis, the potential risks associated with DraftKings Inc. (DKNG) stock are:
High debt-to-equity ratio, which may indicate financial instability Insider selling, which may be a negative sign for the stock High valuation, which may indicate that the stock is overvalued Dependence on the online gaming industry, which is highly competitive and subject to regulation and legal challenges Potential for a correction in the stock price due to slowing revenue growth Recommendation: Based on the analysis, I would recommend a 'Hold' rating for DraftKings Inc. (DKNG) stock, with a potential price range of $40-$50 per share over the next 12 months.
Lets save the report to a HTML file
# ! pip install markdown2
import markdown2
def save_markdown_as_html(markdown_report, file_name="stock_analysis_report.html"):
# Convert the markdown to HTML
html_content = markdown2.markdown(markdown_report)
# Save the HTML content to a file
with open(file_name, "w") as html_file:
html_file.write(html_content)
save_markdown_as_html(markdown_report, f"{stock}_analysis_report.html")
Danger Zone!
Run the cell below to tear down the deployment and release all its resources. (You can also do this from the Covalent Cloud UI.)
# import covalent_cloud as cc
# llama = cc.get_deployment("6650d66ef7d37dbf2a468ba6")
llama.teardown()
Congratulations! You've successfully built a Stock Investment Strategy Analyzer using Covalent Cloud. This powerful system gathers the latest news, analyzes various aspects, and generates detailed reports with actionable insights and a buy rating. By utilizing dynamic agents, we created a flexible and adaptive workflow that ensures our analysis is always relevant and comprehensive.
This tutorial has shown how Covalent Cloud can handle complex workflows effortlessly, highlighting the advantages of dynamic agents in financial analysis. While this is just one example, the possibilities for improvement and customization are endless. You can fine-tune certain models, add more dynamic steps, implement dedicated parallelization to speed up processes, improve the retry logic to make sure LLM gives out the correct response, adding in guardrails and explore many other enhancements to tailor the analyzer to your specific needs.