Perplexity + Python to Build an AI Stock Alert Telegram Bot
- Nikhil Adithyan

- 2 days ago
- 8 min read
Integrating Perplexity AI for intelligent news filtering

Our previous article on developing a custom alerting mechanism using FMP Endpoints to retrieve data and Telegram for notifications is an excellent way to receive events of interest almost in real-time as they occur.
One of the biggest mistakes someone can make with alerts is getting excited about all the information they can receive, leading them to configure as many alerts as they can think of. This results in an enormous amount of information; their mobile just won’t stop vibrating with notifications. They become overwhelmed by this flood of information, which ultimately causes them to stop reading the messages and give up.
The solution to this issue — aside from the obvious options of not configuring so many alerts, limiting the number of stocks you monitor, or increasing your notification thresholds — is to introduce AI to filter the received information. This is especially useful for news, where there is no other way to automatically filter which news you receive based on its importance. In this article, we will demonstrate how to use the Perplexity API to filter the news obtained through the FMP Stock News API.
What to expect in this article
In this article, we will develop a Python script that will do the following:
Will get the news of a list of stocks
For each news item, we will send it to Perplexity’s API and ask for a sentiment score from -1 (negative) to 1 (positive)
Based on the sentiment score we receive, we will notify ourselves via Telegram only for news exceeding a predefined threshold.
Additionally, we will monitor the cost of API usage to ensure it does not exceed a specified limit.
How to set up Perplexity API
To set up the Perplexity API, click on your user icon at the bottom left of your screen, then select the API option. If you’ve never used this before, you’ll be prompted to create an API Group.

The logic of the group is useful for enterprise use and having different members for various APIs. If this is not necessary, you will probably only need one group. Also, note that Perplexity API does not offer a Free Tier, so you’ll need to add a payment method and purchase some credits. If you are a PRO user, you already receive $5 of free credits per month for API usage.
After that, you should select the “API Keys” option, which will lead you to a list of your previously created API keys. If you have never used it, this list will be empty. Next, select “Accept the Terms and Generate API Key”. You can choose a name for this API Key (or not, as in my case). The API key will then be created, and you can copy it to your clipboard.

Let’s Code
Now that we have our Perplexity API key, let’s start coding. As usual, we will define the imports and parameters
import requests
import os
import json
from datetime import date, timedelta
import schedule
import time
TELEGRAM_TOKEN = 'YOUR TELEGRAM TOKEN'
TELEGRAM_CHAT_ID = 'YOUR CHAT ID'
FMP_API_KEY = 'YOUR FMP TOKEN'
STOCKS_TO_CHECK = ['AAPL','MSFT','GOOGL']
PERPLEXITY_API_KEY = 'YOUR PERPLEXITY API KEY'
STOP_AT_COST = 1
NEWS_UPDATE_THRESHOLD = 0.7
total_cost = 0
Besides the obvious tokens needed for FMP, Telegram, and Perplexity, the remaining parameters are used for:
Your universe to check stocks should include the tickers you want to retrieve news for.
Stop At Cost is the variable that tracks the total cost for the specific run with perplexity and will stop the script if this amount is exceeded. Note that the cost is for the current run and does not remember the cost of previous usage.
The new threshold for updates is the sentiment score above which you will be notified about the news. This means that an article with a sentiment score greater than 0.7 or less than -0.7 (negative sentiment) will be sent to you via Telegram.
total_cost is the variable that will keep the total cost during the run
Now, let’s recall two important functions from the previous article. One is the logging system, which ensures we do not process the same news item repeatedly, and the other is the one that sends the message to Telegram.
def get_log(file_log_name):
if not os.path.exists(file_log_name):
with open(file_log_name, "w", encoding="utf-8") as f:
json.dump([], f, ensure_ascii=False, indent=2)
print(f"Created {file_log_name}")
return json.load(open(file_log_name, "r", encoding="utf-8"))
def send_telegram_message(message: str):
url = f"https://api.telegram.org/bot{TELEGRAM_TOKEN}/sendMessage"
payload = {
"chat_id": TELEGRAM_CHAT_ID,
"text": message,
"parse_mode": "Markdown"
}
response = requests.post(url, data=payload)
response.raise_for_status()
print("Message sent:", response.json())
The next step is to begin the Perplexity integration. We will create a function that takes the article text as input and returns the payload that should be sent to Perplexity. We developed this as a separate function because it is quite important, and we need to pause here to explain its purpose.
We will configure the Sonar model — this is the most basic model for perplexity and a good starting point for our article. When, and if, you encounter more complex needs, you should consider the advantages and disadvantages of each supported model and choose carefully.
The role system is the persona you want your model to adopt. In our case, it should behave like a financial analyst and have the know-how to assess the sentiment of news items.
Role user: This is the actual question. We are asking (1) to analyse the sentiment on a scale from -1 (negative) to 1 (positive), (2) also inquire about the time period during which that news is expected to have an impact, and (3) specify that the reply should be a JSON object instead of plain text to be read.
Response format: We ensure that the return will be a JSON object with a specific schema (note how you should set up enums to have a uniform reply through the API).
Temperature controls creativity versus precision; 0.7 offers slightly varied but still focused outputs. Lower values make it more deterministic. From 0, it is completely deterministic, up to 1.5 or 2.
Max Tokens limits the response length for efficiency, since only a small JSON object is expected
def get_perplexity_payload(text):
payload = {
"model": "sonar",
"messages": [
{
"role": "system",
"content": (
"You are a highly experienced financial analyst with deep expertise in interpreting financial news, "
"analyzing market trends, and providing precise insights. Your responses should be data-driven, concise, and professional, "
"focusing on clarity and actionable information relevant to financial contexts. "
"Always assess both the sentiment and the likely timeframe of market impact from the given news."
),
},
{
"role": "user",
"content": (
"Analyze the sentiment of the following stock news text with a score from -1 (very negative) to 1 (very positive), "
"and identify whether the potential effects of this news are likely to be immediate, short-term, mid-term, or long-term. "
"Do not perform any web searches. Return a JSON object in the following format: "
"{\"sentiment_score\": number, \"effect_duration\": string}." + text
),
},
],
"response_format": {
"type": "json_schema",
"json_schema": {
"schema": {
"type": "object",
"properties": {
"sentiment_score": {"type": "number"},
"effect_duration": {
"type": "string",
"enum": ["immediate", "short-term", "mid-term", "long-term"]
}
},
"required": ["sentiment_score", "effect_duration"]
}
}
},
"temperature": 0.7,
"max_tokens": 100,
}
return payload
Now we will prepare the function that will be responsible for getting the sentiment:
Later, you will see that the FMP Stock News API returns, among other things, the title and the text of the article, which we will pass to the perplexity payload along with the symbol.
The function will return the sentiment score, the effect duration, and the total usage (the cost) of the specific call.
def get_sentiment(symbol, title, text):
url_perplexity = "https://api.perplexity.ai/chat/completions"
headers_perplexity = {
"Authorization": f"Bearer {PERPLEXITY_API_KEY}",
"Content-Type": "application/json",
}
text_for_payload = f'For stock with symbol {symbol} I have an article with title: "{title}" and text: "{text}"'
payload = get_perplexity_payload(text_for_payload)
response = requests.post(url_perplexity, headers=headers_perplexity, json=payload)
response = response.json()
data = json.loads(response['choices'][0]['message']['content'])
print(f'I got sentiment score: {data["sentiment_score"]} and effect duration: {data["effect_duration"]} with cost: {response["usage"]["cost"]["total_cost"]} for {symbol} and article with title: {title} and text: {text}')
return data["sentiment_score"], data["effect_duration"], response['usage']['cost']['total_cost']
In the previous article, we had a function called get_stock_news responsible for getting the news for each stock. We will use it as is.
def get_stocks_news(days_to_check=5, limit=3):
today = date.today()
from_date = (today - timedelta(days=days_to_check)).strftime('%Y-%m-%d')
to_date = today.strftime('%Y-%m-%d')
print(f"Will get the stock news from {from_date} to {to_date}")
news_data = []
for stock in STOCKS_TO_CHECK:
url = f"https://financialmodelingprep.com/stable/news/stock"
querystring = {"apikey": FMP_API_KEY, "symbols": stock, "from": from_date, "to": to_date, "limit": limit}
response = requests.get(url, querystring)
data = response.json()
print(f"Got {len(data)} news for {stock}")
news_data = news_data + data
return news_data
Also, we will use the previous articles’ function run_and_notify_stock_news , however, we will make some small changes:
We will call the new function to get the sentiment
We will be filtering based on the sentiment threshold, so we don’t send any new articles about the stocks we monitor
We will be updating the global variable total_cost each time we call the perplexity API
And finally, we will perform a check to ensure that if the cost of a specific run exceeds the defined limit, the script will be stopped to prevent further costs.
def run_and_notify_stock_news():
global total_cost
file_log_name = 'stock_news_log.txt'
news = get_stocks_news()
news_log = get_log(file_log_name)
for news_item in news:
if news_item not in news_log:
try:
news_log.append(news_item)
sentiment_score, effect_duration, cost = get_sentiment(news_item['symbol'], news_item['title'], news_item['text'])
total_cost = total_cost + cost
if (sentiment_score > NEWS_UPDATE_THRESHOLD) | (sentiment_score < -NEWS_UPDATE_THRESHOLD):
if sentiment_score > NEWS_UPDATE_THRESHOLD:
message = f"🚀 Positive news with sentiment *{sentiment_score}* and *{effect_duration}* effect, for *{news_item['symbol']}:* {news_item['title']}. [link]({news_item['url']})"
elif sentiment_score < -NEWS_UPDATE_THRESHOLD:
message = f"⚠️ Negative news with sentiment *{sentiment_score}* and *{effect_duration}* effect, for *{news_item['symbol']}:* {news_item['title']}. [link]({news_item['url']})"
else:
message = 'No message to send'
send_telegram_message(message)
# Log the news so do not send it again
with open(file_log_name, "w", encoding="utf-8") as f:
json.dump(news_log, f, ensure_ascii=False, indent=2)
except Exception as e:
send_telegram_message(f"Error getting sentiment for {news_item['symbol']}: {e}")
#Stop if the cost exceeds the threshold
if total_cost >= STOP_AT_COST:
print(f"Stopping at cost: {total_cost}")
break
send_cost_notification()
At the end of our script, we will be adding the following:
Schedule the news to be analysed hourly at 00 minutes.
Schedule to receive a notification of the total cost so far every hour at 30 minutes past the hour. We can also use this as a heartbeat to check if your script is running. If we don’t receive any message for some time, it might mean that there are no important messages so far, or our script has stopped running for some reason. The latter can be checked if we did not receive the heartbeat either.
def send_cost_notification():
send_telegram_message(f"Total cost so far: {total_cost}")
def main():
send_telegram_message("Bot Started!")
schedule.every().hour.at(":00").do(run_and_notify_stock_news)
schedule.every().hour.at(":30").do(send_cost_notification)
try:
while True:
schedule.run_pending()
time.sleep(1)
except KeyboardInterrupt:
send_telegram_message(f"Bot Stopped with total cost so far {total_cost}")
except Exception as e:
send_telegram_message(f"Bot crashed with error: {e}. Total cost so far: {total_cost}")
if __name__ == "__main__":
run_and_notify_stock_news()
main()
Now we have our script ready to run. Below, you can see two cases for which we received notifications of important news.
You can see that Apple had some important positive news with new highs; however, Google received some bad news with OpenAI’s Browser forcing Google’s stock to decline…


Next Steps and Closure
There are many ways you can experiment with AI and its applications.
With the news, you can experiment with different prompts and models. What we demonstrated here is a simple prompt designed to capture the overall sentiment of the stock. You can become more explicit and, for example, modify it to try to predict if the stock’s price will peak and trough within the next three months. If the low price exceeds 10%, you can then receive a notification.
You can make your own predictions. For example, each weekend, send the stock prices along with some technical indicators, sector index, and fundamental data like P/E or ROE, and ask for perplexity to predict whether the stock will outperform its sector index.
You can send SEC filings or earnings transcripts as soon as they are published and receive summaries based on a custom prompt, so you don’t have to read (or listen) to the entire call.
The information is available, and the tools are there as well. It’s simply a matter of using them wisely. Developing AI prompts is a whole new realm in the industry. Find what suits your style and strategy. Ask the right questions to AI using the proper data, and you will be able to trade profitably with confidence.
I hope you enjoyed this article.



Comments