├── Dockerfile ├── .github └── workflows │ └── docker-build.yaml ├── LICENSE ├── alpaca_bot_tester.py ├── agent.py ├── requirements.txt ├── lit.py ├── .gitignore ├── README.md ├── test.ipynb └── tools.py /Dockerfile: -------------------------------------------------------------------------------- 1 | FROM python:3.9-slim 2 | 3 | WORKDIR /app 4 | 5 | COPY . /app 6 | # COPY ./graph /app/graph 7 | # COPY ./table /app/table 8 | 9 | RUN pip3 install -r requirements.txt 10 | 11 | EXPOSE 8502 12 | 13 | ENTRYPOINT ["streamlit", "run", "lit.py", "--server.port=8502", "--server.address=0.0.0.0"] -------------------------------------------------------------------------------- /.github/workflows/docker-build.yaml: -------------------------------------------------------------------------------- 1 | name: Streamlit Docker Build and Push 2 | 3 | on: 4 | workflow_dispatch: 5 | push: 6 | branches: 7 | - main 8 | 9 | jobs: 10 | build: 11 | runs-on: ubuntu-latest 12 | 13 | steps: 14 | - name: Checkout code 15 | uses: actions/checkout@v2 16 | 17 | - name: Set up Docker Buildx 18 | uses: docker/setup-buildx-action@v1 19 | 20 | - name: Login to Docker Hub 21 | run: echo ${{ secrets.DOCKERHUB_ACCESS_TOKEN }} | docker login -u ${{ secrets.DOCKERHUB_USERNAME }} --password-stdin 22 | 23 | - name: Build and push Docker image 24 | run: | 25 | docker buildx create --use 26 | docker buildx build \ 27 | --file ./Dockerfile \ 28 | --tag jamesliangg/algoherence:latest \ 29 | --push . 30 | 31 | env: 32 | DOCKER_CLI_EXPERIMENTAL: enabled 33 | DOCKER_BUILDKIT: 1 -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2024 Ken Wu 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /alpaca_bot_tester.py: -------------------------------------------------------------------------------- 1 | import os 2 | import dotenv 3 | 4 | from alpaca.trading.client import TradingClient 5 | from alpaca.trading.requests import MarketOrderRequest, GetOrdersRequest 6 | from alpaca.trading.requests import LimitOrderRequest 7 | from alpaca.trading.enums import OrderSide, TimeInForce, QueryOrderStatus 8 | from alpaca.data import StockHistoricalDataClient, StockTradesRequest 9 | from datetime import datetime 10 | 11 | 12 | dotenv.load_dotenv() 13 | ALPACA_KEY = os.environ["ALPACA_KEY"] 14 | ALPACA_SECRET_KEY = os.environ["ALPACA_SECRET_KEY"] 15 | 16 | trading_cli = TradingClient(ALPACA_KEY, ALPACA_SECRET_KEY, paper=True) 17 | # data_cli = StockHistoricalDataClient("PKWP7PIJWJNRCM78Q38C", "imam9M5DEZafUD0OBPfvjhwpQbdbEErSubmj3ZNP") 18 | 19 | # print(trading_cli.get_account().account_number) 20 | # print(trading_cli.get_account().buying_power) 21 | 22 | def buy_shares(ticker: str, amount: int) -> str: 23 | # CREATING AN ORDER 24 | market_order_data = MarketOrderRequest( 25 | symbol=ticker, 26 | qty=amount, 27 | side=OrderSide.BUY, 28 | time_in_force=TimeInForce.DAY 29 | ) 30 | 31 | market_order = trading_cli.submit_order(order_data=market_order_data) 32 | 33 | print(market_order) 34 | print(market_order.status.name) 35 | 36 | 37 | def sell_shares(ticker: str, amount: int) -> str: 38 | # CREATING AN ORDER 39 | market_order_data = MarketOrderRequest( 40 | symbol=ticker, 41 | qty=amount, 42 | side=OrderSide.SELL, 43 | time_in_force=TimeInForce.DAY 44 | ) 45 | 46 | market_order = trading_cli.submit_order(order_data=market_order_data) 47 | 48 | print(market_order) 49 | print(market_order.status.name) 50 | 51 | 52 | buy_shares("SPY", 1) 53 | buy_shares("AAPL", 1) 54 | buy_shares("MSFT", 1) 55 | buy_shares("GME", 1) 56 | buy_shares("ENB", 1) 57 | sell_shares("NTDOY", 1) -------------------------------------------------------------------------------- /agent.py: -------------------------------------------------------------------------------- 1 | from langchain import hub 2 | from langchain.agents import AgentExecutor, create_react_agent 3 | from langchain_community.tools.tavily_search import TavilySearchResults 4 | from langchain_community.chat_models import ChatCohere 5 | from langchain.retrievers.tavily_search_api import TavilySearchAPIRetriever 6 | from langchain.retrievers import CohereRagRetriever 7 | from langchain_community.chat_models import ChatCohere 8 | from langchain_core.documents import Document 9 | from langchain.agents import create_structured_chat_agent 10 | from langchain.pydantic_v1 import BaseModel, Field 11 | from langchain.tools import BaseTool, StructuredTool, tool 12 | from tools import * 13 | import langchain 14 | from dotenv import load_dotenv 15 | import os 16 | 17 | import dotenv 18 | dotenv.load_dotenv() 19 | 20 | langchain.verbose=True 21 | 22 | COHERE_API_KEY = os.environ["COHERE_API_KEY"] 23 | 24 | class chatagent(): 25 | def __init__(self): 26 | return 27 | 28 | def query(self, message: str, cfg, memory) -> str: 29 | cohere = ChatCohere(model="command", temperature = 0, streaming=True, verbose=True) 30 | tools = [buy_stock, sell_stock, mean_reversion, rag] 31 | prompt = hub.pull("kenwu/react-json") 32 | 33 | agent = create_structured_chat_agent( 34 | cohere, 35 | tools, 36 | prompt 37 | ) 38 | 39 | # Create an agent executor by passing in the agent and tools 40 | agent_executor = AgentExecutor( 41 | agent=agent, 42 | tools=tools, 43 | verbose=True, 44 | memory=memory, 45 | max_iterations=5, 46 | handle_parsing_errors=True, 47 | return_intermediate_steps=True 48 | ) 49 | 50 | response = agent_executor.invoke({"input": message}, cfg, chat_history = []) 51 | return response -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | aiohttp==3.9.3 2 | aiosignal==1.3.1 3 | alpaca-py==0.16.0 4 | altair==5.2.0 5 | annotated-types==0.6.0 6 | anyio==4.3.0 7 | appdirs==1.4.4 8 | attrs==23.2.0 9 | backoff==2.2.1 10 | beautifulsoup4==4.12.3 11 | blinker==1.7.0 12 | cachetools==5.3.2 13 | certifi==2024.2.2 14 | charset-normalizer==3.3.2 15 | click==8.1.7 16 | cohere==4.50 17 | contourpy==1.2.0 18 | cycler==0.12.1 19 | dataclasses-json==0.6.4 20 | distro==1.9.0 21 | fastavro==1.9.4 22 | fonttools==4.49.0 23 | frozendict==2.4.0 24 | frozenlist==1.4.1 25 | gitdb==4.0.11 26 | GitPython==3.1.42 27 | h11==0.14.0 28 | html5lib==1.1 29 | httpcore==1.0.4 30 | httpx==0.27.0 31 | idna==3.6 32 | importlib-metadata==6.11.0 33 | Jinja2==3.1.3 34 | jsonpatch==1.33 35 | jsonpointer==2.4 36 | jsonschema==4.21.1 37 | jsonschema-specifications==2023.12.1 38 | kiwisolver==1.4.5 39 | langchain==0.1.9 40 | langchain-community==0.0.24 41 | langchain-core==0.1.26 42 | langchain-openai==0.0.7 43 | langchainhub==0.1.14 44 | langsmith==0.1.7 45 | lxml==5.1.0 46 | markdown-it-py==3.0.0 47 | MarkupSafe==2.1.5 48 | marshmallow==3.20.2 49 | matplotlib==3.8.3 50 | mdurl==0.1.2 51 | msgpack==1.0.7 52 | multidict==6.0.5 53 | multitasking==0.0.11 54 | mypy-extensions==1.0.0 55 | numpy==1.26.4 56 | openai==1.12.0 57 | orjson==3.9.15 58 | packaging==23.2 59 | pandas==2.2.1 60 | peewee==3.17.1 61 | pillow==10.2.0 62 | protobuf==4.25.3 63 | pyarrow==15.0.0 64 | pydantic==2.6.2 65 | pydantic_core==2.16.3 66 | pydeck==0.8.1b0 67 | Pygments==2.17.2 68 | pyparsing==3.1.1 69 | python-dateutil==2.8.2 70 | python-dotenv==1.0.1 71 | pytz==2024.1 72 | PyYAML==6.0.1 73 | referencing==0.33.0 74 | regex==2023.12.25 75 | requests==2.31.0 76 | rich==13.7.0 77 | rpds-py==0.18.0 78 | six==1.16.0 79 | smmap==5.0.1 80 | sniffio==1.3.0 81 | soupsieve==2.5 82 | SQLAlchemy==2.0.27 83 | sseclient-py==1.8.0 84 | streamlit==1.31.1 85 | tenacity==8.2.3 86 | tiktoken==0.6.0 87 | toml==0.10.2 88 | toolz==0.12.1 89 | tornado==6.4 90 | tqdm==4.66.2 91 | types-requests==2.31.0.20240218 92 | typing-inspect==0.9.0 93 | typing_extensions==4.9.0 94 | tzdata==2024.1 95 | tzlocal==5.2 96 | urllib3==2.2.1 97 | validators==0.22.0 98 | webencodings==0.5.1 99 | websockets==11.0.3 100 | yarl==1.9.4 101 | yfinance==0.2.36 102 | zipp==3.17.0 103 | -------------------------------------------------------------------------------- /lit.py: -------------------------------------------------------------------------------- 1 | from dotenv import load_dotenv 2 | import os 3 | import streamlit as st 4 | from langchain_community.callbacks import StreamlitCallbackHandler 5 | from langchain_community.chat_message_histories import StreamlitChatMessageHistory 6 | from langchain.memory import ConversationBufferMemory 7 | from langchain_core.runnables import RunnableConfig 8 | from agent import chatagent 9 | import pandas as pd 10 | import numpy as np 11 | import glob 12 | import warnings 13 | warnings.filterwarnings("ignore") 14 | 15 | load_dotenv() 16 | 17 | 18 | st.set_page_config(page_title="Algoherence", page_icon="🍮") 19 | st.title("Algoherence - Empowering Financial Literacy for All") 20 | 21 | msgs = StreamlitChatMessageHistory() 22 | memory = ConversationBufferMemory( 23 | chat_memory=msgs, return_messages=True, memory_key="chat_history", output_key="output" 24 | ) 25 | 26 | if len(msgs.messages) == 0: 27 | msgs.clear() 28 | msgs.add_ai_message("How can I help you?") 29 | st.session_state.steps = {} 30 | 31 | avatars = {"human": "🗿", "ai": "🍮"} 32 | for idx, msg in enumerate(msgs.messages): 33 | with st.chat_message(avatars[msg.type]): 34 | # Render intermediate steps if any were saved 35 | for step in st.session_state.steps.get(str(idx), []): 36 | if step[0].tool == "_Exception": 37 | continue 38 | with st.status(f"**{step[0].tool}**: {step[0].tool_input}", state="complete"): 39 | st.write(step[0].log) 40 | st.write(step[1]) 41 | st.write(msg.content) 42 | 43 | if prompt := st.chat_input(placeholder="Can you buy 10 shares of a stock that has low alpha value?"): 44 | st.chat_message("🗿").write(prompt) 45 | 46 | executor = chatagent() 47 | 48 | with st.chat_message("🍮"): 49 | st_cb = StreamlitCallbackHandler(st.container(), expand_new_thoughts=False) 50 | cfg = RunnableConfig() 51 | cfg["callbacks"] = [st_cb] 52 | response = executor.query(prompt, cfg, memory) 53 | st.write(response["output"]) 54 | st.session_state.steps[str(len(msgs.messages) - 1)] = response["intermediate_steps"] 55 | if os.path.isdir('./graph'): 56 | if not len(os.listdir('./graph')) == 0: 57 | print("Graph directory is not empty") 58 | image_file_path = glob.glob("./graph/*")[0] 59 | st.image(glob.glob(image_file_path)) 60 | os.remove(image_file_path) 61 | else: 62 | print("Graph directory is empty") 63 | if os.path.isdir('./table'): 64 | if not len(os.listdir('./table')) == 0: 65 | print("Table directory is not empty") 66 | csv_file_path = glob.glob("./table/*")[0] 67 | df = pd.read_csv(csv_file_path) 68 | st.dataframe(df) 69 | os.remove(csv_file_path) 70 | else: 71 | print("Table directory is empty") -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | share/python-wheels/ 24 | *.egg-info/ 25 | .installed.cfg 26 | *.egg 27 | MANIFEST 28 | 29 | # PyInstaller 30 | # Usually these files are written by a python script from a template 31 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 32 | *.manifest 33 | *.spec 34 | 35 | # Installer logs 36 | pip-log.txt 37 | pip-delete-this-directory.txt 38 | 39 | # Unit test / coverage reports 40 | htmlcov/ 41 | .tox/ 42 | .nox/ 43 | .coverage 44 | .coverage.* 45 | .cache 46 | nosetests.xml 47 | coverage.xml 48 | *.cover 49 | *.py,cover 50 | .hypothesis/ 51 | .pytest_cache/ 52 | cover/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | .pybuilder/ 76 | target/ 77 | 78 | # Jupyter Notebook 79 | .ipynb_checkpoints 80 | 81 | # IPython 82 | profile_default/ 83 | ipython_config.py 84 | 85 | # pyenv 86 | # For a library or package, you might want to ignore these files since the code is 87 | # intended to run in multiple environments; otherwise, check them in: 88 | # .python-version 89 | 90 | # pipenv 91 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 92 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 93 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 94 | # install all needed dependencies. 95 | #Pipfile.lock 96 | 97 | # poetry 98 | # Similar to Pipfile.lock, it is generally recommended to include poetry.lock in version control. 99 | # This is especially recommended for binary packages to ensure reproducibility, and is more 100 | # commonly ignored for libraries. 101 | # https://python-poetry.org/docs/basic-usage/#commit-your-poetrylock-file-to-version-control 102 | #poetry.lock 103 | 104 | # pdm 105 | # Similar to Pipfile.lock, it is generally recommended to include pdm.lock in version control. 106 | #pdm.lock 107 | # pdm stores project-wide configurations in .pdm.toml, but it is recommended to not include it 108 | # in version control. 109 | # https://pdm.fming.dev/#use-with-ide 110 | .pdm.toml 111 | 112 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow and github.com/pdm-project/pdm 113 | __pypackages__/ 114 | 115 | # Celery stuff 116 | celerybeat-schedule 117 | celerybeat.pid 118 | 119 | # SageMath parsed files 120 | *.sage.py 121 | 122 | # Environments 123 | .env 124 | .venv 125 | env/ 126 | venv/ 127 | ENV/ 128 | env.bak/ 129 | venv.bak/ 130 | 131 | # Spyder project settings 132 | .spyderproject 133 | .spyproject 134 | 135 | # Rope project settings 136 | .ropeproject 137 | 138 | # mkdocs documentation 139 | /site 140 | 141 | # mypy 142 | .mypy_cache/ 143 | .dmypy.json 144 | dmypy.json 145 | 146 | # Pyre type checker 147 | .pyre/ 148 | 149 | # pytype static type analyzer 150 | .pytype/ 151 | 152 | # Cython debug symbols 153 | cython_debug/ 154 | 155 | # PyCharm 156 | # JetBrains specific template is maintained in a separate JetBrains.gitignore that can 157 | # be found at https://github.com/github/gitignore/blob/main/Global/JetBrains.gitignore 158 | # and can be added to the global gitignore or merged into this file. For a more nuclear 159 | # option (not recommended) you can uncomment the following to ignore the entire idea folder. 160 | #.idea/ 161 | 162 | .env 163 | .idea -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Algoherence: A Leap Towards Accessible Financial Literacy 2 | ![Logo](https://github.com/KenWuqianghao/algoherence/assets/20444505/f33a248e-4876-41fb-b62b-8bcb767ab5db) 3 | 4 | ## Inspiration 5 | In a world where financial literacy is a gateway to empowerment but remains inaccessible to many, our project, Algoherence, emerges as a beacon of hope. Our journey began with a simple yet powerful vision: to democratize access to advanced financial instruments and trading knowledge. Recognizing the barriers of complexity and resource scarcity faced by the underprivileged, we embarked on creating a tool that speaks the universal language of accessibility. Algoherence is the culmination of this vision, a stock trading chatbot designed to bridge the gap between complex financial products and those who stand to benefit from them the most. 6 | 7 | Utilizing a robust tech stack comprising Python, Langchain, the Alpaca API, Cohere API, and Streamlit, we've crafted a solution that leverages the power of natural language processing to make financial literacy a reality for everyone. Algoherence is not just a tool; it's a movement towards inclusivity in financial education. 8 | 9 | ## What it does 10 | Algoherence is your financial ally, empowering you to navigate the world of stocks with ease and confidence. Through a simple conversational interface, users can engage in a variety of financial activities including buying and selling stocks, accessing rich analytical insights through the Mean Reversion algorithm, and obtaining reliable financial advice backed by the Retrieval-Augmented Generation (RAG) technology. Whether you're executing trades on the Alpaca paper trading platform or seeking wisdom on your next financial move, Algoherence ensures accuracy, reliability, and simplicity at every step. 11 | 12 | ## How we built it 13 | Our journey to build Algoherence was fueled by innovation and collaboration. At its core, the project leverages Langchain and Cohere API to breathe life into our agent, crafted meticulously in Python. The RAG component, powered by Cohere RAG and Langchain's Cohere RAG retriever API, stands as a testament to our commitment to reliable and informed financial guidance. The user experience, designed with Streamlit, ensures a seamless interaction that bridges the complexity of backend processes with the simplicity needed for widespread accessibility. Our structured agent, inspired by the ReAct cycle logic, mirrors the human process of thought, observation, and action, bringing a touch of humanity to the digital realm. 14 | 15 | ## Challenges we ran into 16 | Our journey was not without its hurdles. The creation of Algoherence demanded an intricate balance of promo engineering and fine-tuning, a challenge compounded by the need to navigate through a myriad of models, prompting structures, and integrations. The fusion of the backend's complex logic with Streamlit's frontend posed significant challenges, pushing us to rethink how we preserve the agent's thought process in a user-friendly interface. Despite these obstacles, our dedication to innovation and accessibility remained unwavering. 17 | 18 | ## Accomplishments that we're proud of 19 | Standing at the forefront of technological innovation, we take immense pride in being pioneers in utilizing Cohere's command model to build a functional agent. Our journey through the labyrinth of agent-based development has not only yielded Algoherence but has also laid the groundwork for future explorations in this domain. The knowledge and experience gained through this project have opened new horizons for us and the broader community engaged in creating accessible financial technologies. 20 | 21 | ## What we learned 22 | The development of Algoherence has been a profound learning experience, providing us with invaluable insights into the mechanics of agents and the intricacies of prompting engineering. Delving deep into the functionalities offered by Langchain, we've gained a comprehensive understanding of the ReAct cycle, equipping us with the knowledge to navigate and innovate within the realm of agent-based solutions. 23 | 24 | ## What's next for Algoherence 25 | Looking ahead, we envision Algoherence evolving into an even more powerful tool, one that not only enhances its current capabilities but also introduces personalized trading algorithms tailored to individual user preferences and history. Our commitment to reducing the frequency of hallucinated outputs and improving consistency marks the next chapter in our journey. Algoherence stands as a testament to our belief in the transformative power of technology to make financial literacy accessible to all, and we are excited to continue pushing the boundaries of what is possible. 26 | 27 | Join us in shaping the future of financial education, where accessibility, empowerment, and innovation converge to create a world where everyone has the opportunity to thrive financially. Algoherence is not just a project; it's a step towards a more inclusive and financially literate world. 28 | -------------------------------------------------------------------------------- /test.ipynb: -------------------------------------------------------------------------------- 1 | { 2 | "cells": [ 3 | { 4 | "cell_type": "code", 5 | "execution_count": 15, 6 | "metadata": {}, 7 | "outputs": [], 8 | "source": [ 9 | "from langchain import hub\n", 10 | "from langchain.agents import AgentExecutor, create_react_agent\n", 11 | "from langchain_community.tools.tavily_search import TavilySearchResults\n", 12 | "from langchain_community.chat_models import ChatCohere" 13 | ] 14 | }, 15 | { 16 | "cell_type": "code", 17 | "execution_count": 16, 18 | "metadata": {}, 19 | "outputs": [], 20 | "source": [ 21 | "from langchain.pydantic_v1 import BaseModel, Field\n", 22 | "from langchain.tools import BaseTool, StructuredTool, tool\n", 23 | "import langchain\n", 24 | "langchain.verbose=True" 25 | ] 26 | }, 27 | { 28 | "cell_type": "code", 29 | "execution_count": 17, 30 | "metadata": {}, 31 | "outputs": [ 32 | { 33 | "data": { 34 | "text/plain": [ 35 | "True" 36 | ] 37 | }, 38 | "execution_count": 17, 39 | "metadata": {}, 40 | "output_type": "execute_result" 41 | } 42 | ], 43 | "source": [ 44 | "from dotenv import load_dotenv\n", 45 | "import os\n", 46 | "\n", 47 | "import dotenv\n", 48 | "dotenv.load_dotenv()" 49 | ] 50 | }, 51 | { 52 | "cell_type": "code", 53 | "execution_count": 18, 54 | "metadata": {}, 55 | "outputs": [], 56 | "source": [ 57 | "COHERE_API_KEY = os.environ[\"COHERE_API_KEY\"]" 58 | ] 59 | }, 60 | { 61 | "cell_type": "code", 62 | "execution_count": 19, 63 | "metadata": {}, 64 | "outputs": [], 65 | "source": [ 66 | "cohere = ChatCohere(model=\"command\", temperature = 0, streaming=True, verbose=True)" 67 | ] 68 | }, 69 | { 70 | "cell_type": "code", 71 | "execution_count": 20, 72 | "metadata": {}, 73 | "outputs": [], 74 | "source": [ 75 | "from langchain.retrievers.tavily_search_api import TavilySearchAPIRetriever\n", 76 | "from langchain.retrievers import CohereRagRetriever\n", 77 | "from langchain_community.chat_models import ChatCohere\n", 78 | "from langchain_core.documents import Document" 79 | ] 80 | }, 81 | { 82 | "cell_type": "code", 83 | "execution_count": 21, 84 | "metadata": {}, 85 | "outputs": [], 86 | "source": [ 87 | "from tools import *" 88 | ] 89 | }, 90 | { 91 | "cell_type": "code", 92 | "execution_count": 22, 93 | "metadata": {}, 94 | "outputs": [], 95 | "source": [ 96 | "tools = [buy_stock, sell_stock, mean_reversion, rag]" 97 | ] 98 | }, 99 | { 100 | "cell_type": "code", 101 | "execution_count": 23, 102 | "metadata": {}, 103 | "outputs": [], 104 | "source": [ 105 | "from langchain.agents import create_structured_chat_agent" 106 | ] 107 | }, 108 | { 109 | "cell_type": "code", 110 | "execution_count": 24, 111 | "metadata": {}, 112 | "outputs": [], 113 | "source": [ 114 | "prompt = hub.pull(\"kenwu/react-json\")" 115 | ] 116 | }, 117 | { 118 | "cell_type": "code", 119 | "execution_count": 25, 120 | "metadata": {}, 121 | "outputs": [], 122 | "source": [ 123 | "# Construct the JSON agent\n", 124 | "agent = create_structured_chat_agent(cohere, tools, prompt)" 125 | ] 126 | }, 127 | { 128 | "cell_type": "code", 129 | "execution_count": 26, 130 | "metadata": {}, 131 | "outputs": [ 132 | { 133 | "name": "stdout", 134 | "output_type": "stream", 135 | "text": [ 136 | "\n", 137 | "\n", 138 | "\u001b[1m> Entering new AgentExecutor chain...\u001b[0m\n", 139 | "\u001b[32;1m\u001b[1;3mThought: The ticker symbol for Microsoft is \"MSFT\". We can buy 10 shares of MSFT using the buy_stock tool. \n", 140 | "\n", 141 | "Action:\n", 142 | "```\n", 143 | "{\n", 144 | " \"action\": \"buy_stock\",\n", 145 | " \"action_input\": {\n", 146 | " \"ticker\": \"MSFT\",\n", 147 | " \"amount\": 10\n", 148 | " }\n", 149 | "}\n", 150 | "```\u001b[0m\u001b[36;1m\u001b[1;3m\n", 151 | "Observation: 10 shares of MSFT is BOUGHT. ACTION COMPLETED\n", 152 | "\u001b[0m\u001b[32;1m\u001b[1;3mThought: The buy_stock tool was used to purchase 10 shares of MSFT. The output should reflect that the purchase has been completed. \n", 153 | "\n", 154 | "Action:\n", 155 | "```\n", 156 | "{\n", 157 | " \"action\": \"Final Answer\",\n", 158 | " \"action_input\": \"10 shares of MSFT have been purchased.\"\n", 159 | "}\n", 160 | "```\u001b[0m\n", 161 | "\n", 162 | "\u001b[1m> Finished chain.\u001b[0m\n" 163 | ] 164 | }, 165 | { 166 | "data": { 167 | "text/plain": [ 168 | "{'input': 'Buy 10 shares of MSFT\\n',\n", 169 | " 'output': '10 shares of MSFT have been purchased.'}" 170 | ] 171 | }, 172 | "execution_count": 26, 173 | "metadata": {}, 174 | "output_type": "execute_result" 175 | } 176 | ], 177 | "source": [ 178 | "agent_executor.invoke({\"input\": \"Buy 10 shares of MSFT\\n\"})" 179 | ] 180 | } 181 | ], 182 | "metadata": { 183 | "kernelspec": { 184 | "display_name": "algoherence", 185 | "language": "python", 186 | "name": "python3" 187 | }, 188 | "language_info": { 189 | "codemirror_mode": { 190 | "name": "ipython", 191 | "version": 3 192 | }, 193 | "file_extension": ".py", 194 | "mimetype": "text/x-python", 195 | "name": "python", 196 | "nbconvert_exporter": "python", 197 | "pygments_lexer": "ipython3", 198 | "version": "3.12.2" 199 | } 200 | }, 201 | "nbformat": 4, 202 | "nbformat_minor": 2 203 | } 204 | -------------------------------------------------------------------------------- /tools.py: -------------------------------------------------------------------------------- 1 | from langchain.retrievers.tavily_search_api import TavilySearchAPIRetriever 2 | from langchain.retrievers import CohereRagRetriever 3 | from langchain_community.chat_models import ChatCohere 4 | from langchain_core.documents import Document 5 | 6 | import os 7 | import dotenv 8 | 9 | from alpaca.trading.client import TradingClient 10 | from alpaca.trading.requests import MarketOrderRequest, GetOrdersRequest 11 | from alpaca.trading.requests import LimitOrderRequest 12 | from alpaca.trading.enums import OrderSide, TimeInForce, QueryOrderStatus 13 | from alpaca.data import StockHistoricalDataClient, StockTradesRequest 14 | from datetime import datetime 15 | 16 | import requests 17 | from langchain.pydantic_v1 import BaseModel, Field 18 | from langchain.tools import BaseTool, StructuredTool, tool 19 | from langchain_community.chat_models import ChatCohere 20 | from langchain_community.embeddings import CohereEmbeddings 21 | from langchain_community.vectorstores import Chroma 22 | from langchain.retrievers.document_compressors import CohereRerank 23 | from langchain.retrievers import ContextualCompressionRetriever 24 | from langchain_community.retrievers import CohereRagRetriever 25 | from langchain_core.messages import HumanMessage 26 | 27 | import pandas as pd 28 | import numpy as np 29 | import yfinance as yf 30 | import matplotlib.pyplot as plt 31 | import os 32 | 33 | dotenv.load_dotenv() 34 | ALPACA_KEY = os.environ["ALPACA_KEY"] 35 | ALPACA_SECRET_KEY = os.environ["ALPACA_SECRET_KEY"] 36 | 37 | trading_cli = TradingClient(ALPACA_KEY, ALPACA_SECRET_KEY, paper=True) 38 | # data_cli = StockHistoricalDataClient("PKWP7PIJWJNRCM78Q38C", "imam9M5DEZafUD0OBPfvjhwpQbdbEErSubmj3ZNP") 39 | 40 | @tool 41 | def rag(query: str): 42 | """ 43 | Dynamically provide relevant documents to improve Agent response when outside knowledge is required or useful. Essentially, useful for any non calculating queries. 44 | 45 | query: The query to search for in complete sentences 46 | """ 47 | 48 | rag = CohereRagRetriever(llm=ChatCohere(), verbose=True, connectors=[{"id": "web-search"}]) 49 | docs = rag.get_relevant_documents(query) 50 | 51 | answer = '\nObservation:' 52 | answer += docs[-1].page_content + '\n' 53 | 54 | citations = '\nCITATIONS\n' 55 | citations += '------------------------------\n' 56 | for doc in docs[0: len(docs) - 2]: 57 | citations += doc.metadata['title'] + ': ' + doc.metadata['url'] + '\n' 58 | citations += '\n' 59 | print(citations) 60 | 61 | return answer 62 | 63 | @tool 64 | def buy_stock(ticker: str, amount: int) -> int: 65 | """Buys a stock 66 | 67 | Args: 68 | ticker: Ticker of stock to buy 69 | amount: Amount of stock to buy 70 | """ 71 | 72 | # CREATING AN ORDER 73 | market_order_data = MarketOrderRequest( 74 | symbol=ticker, 75 | qty=amount, 76 | side=OrderSide.BUY, 77 | time_in_force=TimeInForce.DAY 78 | ) 79 | 80 | market_order = trading_cli.submit_order(order_data=market_order_data) 81 | 82 | if (market_order.status.name != "ACCEPTED"): 83 | return "\nObservation: Transaction FAILED. ACTION INCOMPLETE\n" 84 | 85 | return "\nObservation: {} shares of {} is BOUGHT. ACTION COMPLETED\n".format(amount, ticker) 86 | 87 | @tool 88 | def sell_stock(ticker: str, amount: int) -> int: 89 | """Sells a stock 90 | 91 | Args: 92 | ticker: Ticker of stock to sell 93 | amount: Amount of stock to sell 94 | """ 95 | 96 | try: 97 | # CREATING AN ORDER 98 | market_order_data = MarketOrderRequest( 99 | symbol=ticker, 100 | qty=amount, 101 | side=OrderSide.SELL, 102 | time_in_force=TimeInForce.DAY 103 | ) 104 | 105 | market_order = trading_cli.submit_order(order_data=market_order_data) 106 | 107 | if (market_order.status.name != "ACCEPTED"): 108 | return "\nObservation: Transaction FAILED. ACTION INCOMPLETE\n" 109 | 110 | return "\nObservation: {} shares of {} is SOLD. ACTION COMPLETED\n".format(amount, ticker) 111 | except: 112 | return "\nObservaton: ERROR: Wash error prohibited by Alpaca Trader\n" 113 | 114 | @tool 115 | def mean_reversion(ticker: str, shares: int, mean_frame: int = 20, backtest_frame: int = 365, investment_period:int = 1): 116 | """ 117 | Emulates profit of a specific stock using z-score mean reversion strategy. Do NOT use this unless explicitly mentioned. 118 | 119 | Args: 120 | ticker: Stock ticker 121 | shares: Number of shares to buy 122 | mean_frame: Time frame for calculating the mean 123 | backtest_frame: Time frame for backtesting 124 | investment_period: Future investment period in years for profit estimation 125 | """ 126 | # Fetch historical data 127 | start_date = '2018-08-01' 128 | end_date = '2024-02-24' 129 | stock_data = yf.download(ticker, start=start_date, end=end_date)['Close'] 130 | 131 | # Calculate the rolling mean and the z-scores 132 | stock_data = pd.DataFrame(stock_data) 133 | mean_data = stock_data['Close'].rolling(window=mean_frame).mean() 134 | stock_data['returns'] = stock_data['Close'].pct_change() 135 | stock_data['z_score'] = (stock_data['Close'] - mean_data) / stock_data['Close'].rolling(window=mean_frame).std() 136 | 137 | # Determine entry and exit signals from z-scores 138 | percentiles = [5, 10, 50, 90, 95] 139 | z_scores = stock_data['z_score'].dropna() 140 | percentile_values = np.percentile(z_scores, percentiles) 141 | buy_threshold = percentile_values[1] 142 | sell_threshold = percentile_values[3] 143 | stock_data['Signal'] = np.where(stock_data['z_score'] > sell_threshold, -1, np.where(stock_data['z_score'] < buy_threshold, 1, 0)) 144 | 145 | # Backtesting 146 | backtest_data = stock_data[-backtest_frame:].copy() 147 | backtest_data['Signal'] = backtest_data['Signal'].shift(1) 148 | backtest_data['StrategyReturn'] = backtest_data['Signal'] * backtest_data['returns'] 149 | 150 | # Backtesting 151 | backtest_data = stock_data[-backtest_frame:].copy() 152 | backtest_data['Signal'] = backtest_data['Signal'].shift(1) 153 | backtest_data['StrategyReturn'] = backtest_data['Signal'] * backtest_data['returns'] 154 | 155 | # Calculate cumulative returns for the strategy and buy-and-hold approach 156 | backtest_data['CumulativeStrategyReturn'] = (1 + backtest_data['StrategyReturn']).cumprod() 157 | backtest_data['CumulativeBuyHoldReturn'] = (1 + backtest_data['returns']).cumprod() 158 | 159 | # Plot the cumulative returns 160 | plt.figure(figsize=(10, 6)) 161 | plt.plot(backtest_data['CumulativeStrategyReturn'], label='Mean Reversion Strategy') 162 | plt.plot(backtest_data['CumulativeBuyHoldReturn'], label='Buy and Hold') 163 | plt.title(f"Cumulative Returns: {ticker}") 164 | plt.xlabel('Date') 165 | plt.ylabel('Cumulative Returns') 166 | plt.legend() 167 | plt.grid(True) 168 | 169 | graph_dir = 'graph' 170 | # Ensure the 'graph' directory exists 171 | graph_dir = 'graph' 172 | if not os.path.exists(graph_dir): 173 | os.makedirs(graph_dir) 174 | 175 | # Save the plot to the 'graph' directory 176 | plt.savefig(f"{graph_dir}/{ticker}_cumulative_returns.png") 177 | plt.close() # Close the plot 178 | 179 | 180 | # Ensure the 'table' directory exists 181 | table_dir = 'table' 182 | if not os.path.exists(table_dir): 183 | os.makedirs(table_dir) 184 | #calculate annual return 185 | annual_return = backtest_data['StrategyReturn'].mean() * 252 # Approximate trading days in a year 186 | 187 | # Estimate future profit 188 | estimated_annual_profit = annual_return * shares * stock_data['Close'].iloc[-1] # Estimate based on the last closing price 189 | estimated_future_profit = estimated_annual_profit * investment_period 190 | 191 | # Export the backtest data to CSV 192 | csv_filename = f"{table_dir}/{ticker}_mean_reversion_backtest.csv" 193 | backtest_data.to_csv(csv_filename) 194 | 195 | return(f"\nObservation: Estimated future profit for {ticker} over {investment_period} year(s) with {shares} shares: ${estimated_future_profit:.2f}\n") 196 | 197 | # # Example usage 198 | # ticker = input('Enter stock ticker: ') 199 | # shares = int(input('Number of shares to buy: ')) 200 | # mean_frame = int(input('Enter time frame for mean: ')) 201 | # backtest_frame = int(input('Enter time frame for backtest: ')) 202 | # estimate_future_profit(ticker, shares, mean_frame, backtest_frame) --------------------------------------------------------------------------------