From 31bedbab6e99c2a5a41878ef2061219732f97a0a Mon Sep 17 00:00:00 2001 From: wraysu Date: Sat, 22 Jun 2024 10:00:14 +0800 Subject: [PATCH 1/2] commit --- .deployment | 2 ++ .vscode/settings.json | 35 +++++++++++++++++++++++++++++++++++ requirements.txt | 7 +++++++ 3 files changed, 44 insertions(+) create mode 100644 .deployment create mode 100644 .vscode/settings.json diff --git a/.deployment b/.deployment new file mode 100644 index 000000000..627833181 --- /dev/null +++ b/.deployment @@ -0,0 +1,2 @@ +[config] +SCM_DO_BUILD_DURING_DEPLOYMENT=true \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 000000000..2e5a3f600 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,35 @@ +{ + "appService.zipIgnorePattern": [ + "__pycache__{,/**}", + "*.py[cod]", + "*$py.class", + ".Python{,/**}", + "build{,/**}", + "develop-eggs{,/**}", + "dist{,/**}", + "downloads{,/**}", + "eggs{,/**}", + ".eggs{,/**}", + "lib{,/**}", + "lib64{,/**}", + "parts{,/**}", + "sdist{,/**}", + "var{,/**}", + "wheels{,/**}", + "share/python-wheels{,/**}", + "*.egg-info{,/**}", + ".installed.cfg", + "*.egg", + "MANIFEST", + ".env{,/**}", + ".venv{,/**}", + "env{,/**}", + "venv{,/**}", + "ENV{,/**}", + "env.bak{,/**}", + "venv.bak{,/**}", + ".vscode{,/**}" + ], + "appService.defaultWebAppToDeploy": "/subscriptions/e1826c46-f6e4-45a7-9fb8-ec9a47c20bf5/resourceGroups/NCDRAI/providers/Microsoft.Web/sites/NCDRAIBot", + "appService.deploySubpath": "." +} \ No newline at end of file diff --git a/requirements.txt b/requirements.txt index c785af01f..ebad036d0 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,10 @@ Flask==2.2.2 gunicorn Werkzeug==2.2.2 +line-bot-sdk==3.5.0 +fastapi +langchain +openai= +yfinance +pydantic +tiktoken \ No newline at end of file From 4884789693ab6c71ab117bb0e9b9c1ab3a64cc1c Mon Sep 17 00:00:00 2001 From: wraysu Date: Sat, 22 Jun 2024 10:31:50 +0800 Subject: [PATCH 2/2] commit --- app.json | 32 ++++++++++++++++++++++ app.py | 37 +++++++++++++++++++++++++ requirements.txt | 2 +- stock_peformace.py | 68 ++++++++++++++++++++++++++++++++++++++++++++++ stock_price.py | 30 ++++++++++++++++++++ yf_tool.py | 61 +++++++++++++++++++++++++++++++++++++++++ 6 files changed, 229 insertions(+), 1 deletion(-) create mode 100755 app.json create mode 100755 stock_peformace.py create mode 100755 stock_price.py create mode 100755 yf_tool.py diff --git a/app.json b/app.json new file mode 100755 index 000000000..e3689d400 --- /dev/null +++ b/app.json @@ -0,0 +1,32 @@ +{ + "name": "LINE Bot with Langchain", + "description": "Quick LINEBot with Langchain", + "repository": "https://github.com/kkdai/linebot-langchain", + "keywords": [ + "python", + "linebot", + "Sample" + ], + "buildpacks": [ + { + "url": "https://github.com/heroku/heroku-buildpack-python.git" + }, + { + "url": "heroku/python" + } + ], + "env": { + "OPENAI_API_KEY": { + "description": "OpenAI Access Token", + "required": true + }, + "ChannelAccessToken": { + "description": "Channel Access Token", + "required": true + }, + "ChannelSecret": { + "description": "Channel Secret", + "required": true + } + } +} \ No newline at end of file diff --git a/app.py b/app.py index 3d1808cf6..995c43157 100644 --- a/app.py +++ b/app.py @@ -1,4 +1,41 @@ import os +import sys + +import aiohttp + +from fastapi import Request, FastAPI, HTTPException + +from langchain.chat_models import ChatOpenAI +from langchain.agents import AgentType +from langchain.agents import initialize_agent + +from stock_price import StockPriceTool +from stock_peformace import StockPercentageChangeTool +from stock_peformace import StockGetBestPerformingTool + +from linebot import ( + AsyncLineBotApi, WebhookParser +) +from linebot.aiohttp_async_http_client import AiohttpAsyncHttpClient +from linebot.exceptions import ( + InvalidSignatureError +) +from linebot.models import ( + MessageEvent, TextMessage, TextSendMessage, +) + +from dotenv import load_dotenv, find_dotenv +_ = load_dotenv(find_dotenv()) # read local .env file + +# get channel_secret and channel_access_token from your environment variable +channel_secret = os.getenv('ChannelSecret', None) +channel_access_token = os.getenv('ChannelAccessToken', None) +if channel_secret is None: + print('Specify LINE_CHANNEL_SECRET as environment variable.') + sys.exit(1) +if channel_access_token is None: + print('Specify LINE_CHANNEL_ACCESS_TOKEN as environment variable.') + sys.exit(1) from flask import (Flask, redirect, render_template, request, send_from_directory, url_for) diff --git a/requirements.txt b/requirements.txt index ebad036d0..88d42ed39 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,7 +4,7 @@ Werkzeug==2.2.2 line-bot-sdk==3.5.0 fastapi langchain -openai= +openai yfinance pydantic tiktoken \ No newline at end of file diff --git a/stock_peformace.py b/stock_peformace.py new file mode 100755 index 000000000..a6d39bf41 --- /dev/null +++ b/stock_peformace.py @@ -0,0 +1,68 @@ +from typing import List +from langchain.tools import BaseTool +from typing import Optional, Type +from pydantic import BaseModel, Field +from yf_tool import get_price_change_percent, get_best_performing + + +class StockChangePercentageCheckInput(BaseModel): + """Input for Stock ticker check. for percentage check""" + + stockticker: str = Field(..., + description="Ticker symbol for stock or index") + days_ago: int = Field(..., description="Int number of days to look back") + + +class StockPercentageChangeTool(BaseTool): + name = "get_price_change_percent" + description = ( + "Useful for when you need to find out the percentage change " + "in a stock's value. " + "You should input the stock ticker used on the yfinance API " + "and also input the " + "number of days to check the change over" + ) + + def _run(self, stockticker: str, days_ago: int): + price_change_response = get_price_change_percent(stockticker, days_ago) + + return price_change_response + + def _arun(self, stockticker: str, days_ago: int): + raise NotImplementedError("This tool does not support async") + + args_schema: Optional[Type[BaseModel]] = StockChangePercentageCheckInput + + +# the best performing + +class StockBestPerformingInput(BaseModel): + """Input for Stock ticker check. for percentage check""" + + stocktickers: List[str] = Field(..., + description=( + "Ticker symbols for " + "stocks or indices" + )) # Close the parenthesis here + days_ago: int = Field(..., description="Int number of days to look back") + + +class StockGetBestPerformingTool(BaseTool): + name = "get_best_performing" + description = ( + "Useful for when you need to the performance of multiple " + "stocks over a period. " + "You should input a list of stock " + "tickers used on the yfinance API " + "and also input the number of days to check the change over" + ) + + def _run(self, stocktickers: List[str], days_ago: int): + price_change_response = get_best_performing(stocktickers, days_ago) + + return price_change_response + + def _arun(self, stockticker: List[str], days_ago: int): + raise NotImplementedError("This tool does not support async") + + args_schema: Optional[Type[BaseModel]] = StockBestPerformingInput diff --git a/stock_price.py b/stock_price.py new file mode 100755 index 000000000..4f3e4a347 --- /dev/null +++ b/stock_price.py @@ -0,0 +1,30 @@ +from langchain.tools import BaseTool +from typing import Optional, Type +from pydantic import BaseModel, Field +from yf_tool import get_stock_price + + +class StockPriceCheckInput(BaseModel): + """Input for Stock price check.""" + + stockticker: str = Field(..., + description="Ticker symbol for stock or index") + + +class StockPriceTool(BaseTool): + name = "get_stock_ticker_price" + description = ( + "Useful for when you need to find out the price of stock. " + "You should input the stock ticker used on the yfinance API" + ) + + def _run(self, stockticker: str): + # print("i'm running") + price_response = get_stock_price(stockticker) + + return price_response + + def _arun(self, stockticker: str): + raise NotImplementedError("This tool does not support async") + + args_schema: Optional[Type[BaseModel]] = StockPriceCheckInput diff --git a/yf_tool.py b/yf_tool.py new file mode 100755 index 000000000..30dadcdfe --- /dev/null +++ b/yf_tool.py @@ -0,0 +1,61 @@ +from datetime import datetime, timedelta +import yfinance as yf + + +def calculate_performance(symbol, days_ago): + ticker = yf.Ticker(symbol) + end_date = datetime.now() + start_date = end_date - timedelta(days=days_ago) + start_date = start_date.strftime('%Y-%m-%d') + end_date = end_date.strftime('%Y-%m-%d') + historical_data = ticker.history(start=start_date, end=end_date) + old_price = historical_data['Close'].iloc[0] + new_price = historical_data['Close'].iloc[-1] + percent_change = ((new_price - old_price) / old_price) * 100 + return round(percent_change, 2) + + +def get_best_performing(stocks, days_ago): + best_stock = None + best_performance = None + for stock in stocks: + try: + performance = calculate_performance(stock, days_ago) + if best_performance is None or performance > best_performance: + best_stock = stock + best_performance = performance + except Exception as e: + print(f"Could not calculate performance for {stock}: {e}") + return best_stock, best_performance + + +def get_stock_price(symbol): + ticker = yf.Ticker(symbol) + todays_data = ticker.history(period='1d') + return round(todays_data['Close'][0], 2) + + +def get_price_change_percent(symbol, days_ago): + ticker = yf.Ticker(symbol) + + # Get today's date + end_date = datetime.now() + + # Get the date N days ago + start_date = end_date - timedelta(days=days_ago) + + # Convert dates to string format that yfinance can accept + start_date = start_date.strftime('%Y-%m-%d') + end_date = end_date.strftime('%Y-%m-%d') + + # Get the historical data + historical_data = ticker.history(start=start_date, end=end_date) + + # Get the closing price N days ago and today's closing price + old_price = historical_data['Close'].iloc[0] + new_price = historical_data['Close'].iloc[-1] + + # Calculate the percentage change + percent_change = ((new_price - old_price) / old_price) * 100 + + return round(percent_change, 2)