diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..7ea0219 --- /dev/null +++ b/.env.example @@ -0,0 +1,4 @@ +POLYGON_WALLET_PRIVATE_KEY="" +OPENAI_API_KEY="" +TAVILY_API_KEY="" +NEWSAPI_API_KEY="" diff --git a/.github/workflows/dependency-review.yml b/.github/workflows/dependency-review.yml new file mode 100644 index 0000000..d19e21b --- /dev/null +++ b/.github/workflows/dependency-review.yml @@ -0,0 +1,39 @@ +# Dependency Review Action +# +# This Action will scan dependency manifest files that change as part of a Pull Request, +# surfacing known-vulnerable versions of the packages declared or updated in the PR. +# Once installed, if the workflow run is marked as required, PRs introducing known-vulnerable +# packages will be blocked from merging. +# +# Source repository: https://github.com/actions/dependency-review-action +# Public documentation: https://docs.github.com/en/code-security/supply-chain-security/understanding-your-software-supply-chain/about-dependency-review#dependency-review-enforcement +name: 'Dependency review' +on: + pull_request: + branches: [ "main" ] + +# If using a dependency submission action in this workflow this permission will need to be set to: +# +# permissions: +# contents: write +# +# https://docs.github.com/en/enterprise-cloud@latest/code-security/supply-chain-security/understanding-your-software-supply-chain/using-the-dependency-submission-api +permissions: + contents: read + # Write permissions for pull-requests are required for using the `comment-summary-in-pr` option, comment out if you aren't using this option + pull-requests: write + +jobs: + dependency-review: + runs-on: ubuntu-latest + steps: + - name: 'Checkout repository' + uses: actions/checkout@v4 + - name: 'Dependency Review' + uses: actions/dependency-review-action@v4 + # Commonly enabled options, see https://github.com/actions/dependency-review-action#configuration-options for all available options. + with: + comment-summary-in-pr: always + # fail-on-severity: moderate + # deny-licenses: GPL-1.0-or-later, LGPL-2.0-or-later + # retry-on-snapshot-warnings: true diff --git a/.github/workflows/docker-image.yml b/.github/workflows/docker-image.yml new file mode 100644 index 0000000..5f4e36d --- /dev/null +++ b/.github/workflows/docker-image.yml @@ -0,0 +1,18 @@ +name: Docker Image CI + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +jobs: + + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Build the Docker image + run: docker build . --file Dockerfile --tag polymarket-agents:$(date +%s) diff --git a/.github/workflows/greetings.yml b/.github/workflows/greetings.yml new file mode 100644 index 0000000..2a0d068 --- /dev/null +++ b/.github/workflows/greetings.yml @@ -0,0 +1,16 @@ +name: Greetings + +on: [pull_request_target, issues] + +jobs: + greeting: + runs-on: ubuntu-latest + permissions: + issues: write + pull-requests: write + steps: + - uses: actions/first-interaction@v1 + with: + repo-token: ${{ secrets.GITHUB_TOKEN }} + issue-message: "Welcome to Polymarket Agents. Thank you for filing your first issue." + pr-message: "Welcome to Polymarket Agents. Thank you for creating your first PR. Cheers!" diff --git a/.github/workflows/python-app.yml b/.github/workflows/python-app.yml new file mode 100644 index 0000000..fc8704f --- /dev/null +++ b/.github/workflows/python-app.yml @@ -0,0 +1,36 @@ +# This workflow will install Python dependencies, run tests and lint with a single version of Python +# For more information see: https://docs.github.com/en/actions/automating-builds-and-tests/building-and-testing-python + +name: Python application + +on: + push: + branches: [ "main" ] + pull_request: + branches: [ "main" ] + +permissions: + contents: read + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v4 + - name: Set up Python 3.9 + uses: actions/setup-python@v3 + with: + python-version: "3.9" + - name: Install dependencies + run: | + python -m pip install --upgrade pip + pip install black pytest + if [ -f requirements.txt ]; then pip install -r requirements.txt; fi + - name: Lint with black + run: | + black . + - name: Test with unittest + run: | + python -m unittest discover diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..c9105b3 --- /dev/null +++ b/.gitignore @@ -0,0 +1,29 @@ +.venv +.env +.idea +.ipynb_checkpoints +.mypy_cache +.vscode +__pycache__ +.pytest_cache +htmlcov +dist +site +.coverage +coverage.xml +.netlify +test.db +log.txt +Pipfile.lock +env3.* +env +docs_build +site_build +venv +docs.zip +archive.zip +*~ +.*.sw? +.cache +.DS_Store +local_db* diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml new file mode 100644 index 0000000..daa67e6 --- /dev/null +++ b/.pre-commit-config.yaml @@ -0,0 +1,6 @@ +repos: +- repo: https://github.com/psf/black + rev: 24.4.2 + hooks: + - id: black + language_version: python3.9 diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md new file mode 100644 index 0000000..2aeddcd --- /dev/null +++ b/CONTRIBUTING.md @@ -0,0 +1,120 @@ +# Introduction + +### Welcome and Thanks! + +> First off, thank you for considering contributing to our Decentralized AI Agent for Polymarket! It's contributors like you that help make this project a powerful tool for prediction markets. + +### Why Follow These Guidelines? + +> Following these guidelines shows that you respect the time and effort of the developers working on this project. In return, we’ll address your contributions efficiently, review your changes, and help you finalize your pull requests. + +### Types of Contributions We Welcome + +We value all kinds of contributions! Whether it’s improving our documentation, triaging bugs, or enhancing our algorithms, your contributions are greatly appreciated. + +> This project is open source, and we’re thrilled to receive contributions from the community. You can help by writing tutorials, reporting bugs, suggesting new features, or contributing code to improve our AI agent. + +### Contributions We Do Not Seek + +To streamline the process, please avoid these types of contributions: + +> Please do not use the issue tracker for general support questions. For help, consider joining our the Polymarket Discord or referring to the [Polymarket API documentation](https://polymarket.com/docs). + +# Ground Rules + +### Setting Expectations + +We ask that all contributors adhere to the following standards: + +> Responsibilities +> * Ensure compatibility with multiple environments where our AI agent operates. +> * Create issues for significant changes and gather community feedback. +> * Keep changes modular and focused. +> * Foster an inclusive environment and welcome contributors from diverse backgrounds. + +# Your First Contribution + +### Getting Started + +New to contributing? Here’s how you can start: + +> Begin by exploring our beginner-friendly issues, which are tagged as [good first issue](#). These are smaller tasks that are great for newcomers. + +### Bonus: Helpful Resources + +Here are some tutorials to guide you through your first open source contribution: + +> New to open source? Check out [How to Contribute to an Open Source Project on GitHub](https://egghead.io/series/how-to-contribute-to-an-open-source-project-on-github). + +### Next Steps? + +> At this point, you're ready to make your changes! Feel free to ask for help; everyone is a beginner at first :smile_cat: +> +> If a maintainer asks you to "rebase" your PR, they're saying that a lot of code has changed, and that you need to update your branch so it's easier to merge. + +# Getting Started + +### Submitting Your Contribution + +Follow these steps to submit your contributions: + +* Fork the repository and make changes in your fork. +* Ensure your code follows our [style guide](#) and passes all tests. +* Sign the Contributor License Agreement (CLA), if required. +* Submit a pull request with a detailed description of your changes. + +> For significant changes: +> 1. Fork the repo and make your changes. +> 2. Ensure your changes align with the project’s coding standards. +> 3. Sign the Contributor License Agreement if applicable. +> 4. Open a pull request and describe the changes you’ve made. + +### For Small or “Obvious” Fixes + +For minor fixes, such as typos or documentation improvements, you can: + +> Submit a simple patch without a CLA if the change does not involve significant new functionality. Examples include: +> * Typographical corrections +> * Simple formatting changes +> * Non-functional code adjustments + +# How to Report a Bug + +### Security Disclosures + +If you discover a security issue, please report it privately: + +> If you find a security vulnerability, do NOT open an issue. Instead, email Polymarket directly. + +### Filing a Bug Report + +When reporting a bug, please provide the following details: + +> When filing a bug, include: +> 1. The version of the AI agent you are using. +> 2. Your operating system and environment. +> 3. Steps to reproduce the issue. +> 4. Expected behavior. +> 5. Actual behavior observed. + +# How to Suggest a Feature or Enhancement + +### Project Goals and Philosophy + +Before suggesting a feature, understand our project goals: + +> Our AI agent aims to leverage decentralized mechanisms to provide accurate predictions within Polymarket's markets. We are focused on enhancing market analysis and prediction accuracy. + +### Suggesting a Feature + +If you have an idea for a new feature: + +> Open an issue on GitHub with a detailed description of the feature, including its purpose, benefits, and any proposed implementation details. + +# Code Review Process + +### Review and Acceptance + +Our review process is as follows: + +> The core team must approve the PR. diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 0000000..72a12bc --- /dev/null +++ b/Dockerfile @@ -0,0 +1,6 @@ +FROM python:3.9 + +COPY . /home +WORKDIR /home + +RUN pip3 install -r requirements.txt diff --git a/EXAMPLE.md b/EXAMPLE.md new file mode 100644 index 0000000..a073076 --- /dev/null +++ b/EXAMPLE.md @@ -0,0 +1,125 @@ +(.venv) $ python -i application/trade.py + +1. FOUND 27 EVENTS + +... prompting ... You are an AI assistant for analyzing prediction markets. + You will be provided with json output for api data from Polymarket. + Polymarket is an online prediction market that lets users Bet on the outcome of future events in a wide range of topics, like sports, politics, and pop culture. + Get accurate real-time probabilities of the events that matter most to you. + + Filter these events for the ones you will be best at trading on profitably. + + + +2. FILTERED 4 EVENTS +https://gamma-api.polymarket.com/markets/500715 +https://gamma-api.polymarket.com/markets/500716 +https://gamma-api.polymarket.com/markets/500717 +https://gamma-api.polymarket.com/markets/500344 +https://gamma-api.polymarket.com/markets/500924 +https://gamma-api.polymarket.com/markets/500925 + +3. FOUND 6 MARKETS + + +... prompting ... You are an AI assistant for analyzing prediction markets. + You will be provided with json output for api data from Polymarket. + Polymarket is an online prediction market that lets users Bet on the outcome of future events in a wide range of topics, like sports, politics, and pop culture. + Get accurate real-time probabilities of the events that matter most to you. + + Filter these markets for the ones you will be best at trading on profitably. + + + +4. FILTERED 4 MARKETS + +... prompting ... + You are a Superforecaster tasked with correctly predicting the likelihood of events. + Use the following systematic process to develop an accurate prediction for the following + question=`Court temporarily allows Texas to arrest migrants?` and description=`On March 20 a 3-panel judge heard arguments on whether Texas should temporarily be permitted to enforce its immigration law which allows state officials to arrest people they suspect of entering the country illegally. + +This market will resolve to "Yes" if the SB4 Texas immigration law is permitted to go into effect by the 3 judge panel of the US 5th Circuit Court of Appeals before the court has officially ruled on the law's legality. Otherwise this market will resolve to "No". + +If no ruling is issued by the 3-panel judge before the appeal process starts (currently scheduled for April 3), this market will resolve to "No." + +The primary resolution source for this market will be official information from the US government, however a consensus of credible reporting will also be used.` combination. + + Here are the key steps to use in your analysis: + + 1. Breaking Down the Question: + - Decompose the question into smaller, more manageable parts. + - Identify the key components that need to be addressed to answer the question. + 2. Gathering Information: + - Seek out diverse sources of information. + - Look for both quantitative data and qualitative insights. + - Stay updated on relevant news and expert analyses. + 3. Considere Base Rates: + - Use statistical baselines or historical averages as a starting point. + - Compare the current situation to similar past events to establish a benchmark probability. + 4. Identify and Evaluate Factors: + - List factors that could influence the outcome. + - Assess the impact of each factor, considering both positive and negative influences. + - Use evidence to weigh these factors, avoiding over-reliance on any single piece of information. + 5. Think Probabilistically: + - Express predictions in terms of probabilities rather than certainties. + - Assign likelihoods to different outcomes and avoid binary thinking. + - Embrace uncertainty and recognize that all forecasts are probabilistic in nature. + + Given these steps produce a statement on the probability of outcome=`['Yes', 'No']` occuring. + + Give your response in the following format: + + I believe Court temporarily allows Texas to arrest migrants? has a likelihood `` for outcome of ``. + + +result: I believe Court temporarily allows Texas to arrest migrants? has a likelihood `0.3` for outcome of `Yes`. + +... prompting ... You are an AI assistant for analyzing prediction markets. + You will be provided with json output for api data from Polymarket. + Polymarket is an online prediction market that lets users Bet on the outcome of future events in a wide range of topics, like sports, politics, and pop culture. + Get accurate real-time probabilities of the events that matter most to you. + + Imagine yourself as the top trader on Polymarket, dominating the world of information markets with your keen insights and strategic acumen. You have an extraordinary ability to analyze and interpret data from diverse sources, turning complex information into profitable trading opportunities. + You excel in predicting the outcomes of global events, from political elections to economic developments, using a combination of data analysis and intuition. Your deep understanding of probability and statistics allows you to assess market sentiment and make informed decisions quickly. + Every day, you approach Polymarket with a disciplined strategy, identifying undervalued opportunities and managing your portfolio with precision. You are adept at evaluating the credibility of information and filtering out noise, ensuring that your trades are based on reliable data. + Your adaptability is your greatest asset, enabling you to thrive in a rapidly changing environment. You leverage cutting-edge technology and tools to gain an edge over other traders, constantly seeking innovative ways to enhance your strategies. + In your journey on Polymarket, you are committed to continuous learning, staying informed about the latest trends and developments in various sectors. Your emotional intelligence empowers you to remain composed under pressure, making rational decisions even when the stakes are high. + Visualize yourself consistently achieving outstanding returns, earning recognition as the top trader on Polymarket. You inspire others with your success, setting new standards of excellence in the world of information markets. + + + + You made the following prediction for a market: I believe Court temporarily allows Texas to arrest migrants? has a likelihood `0.3` for outcome of `Yes`. + + The current outcomes $['Yes', 'No'] prices are: $['0.17', '0.83'] + + Given your prediction, respond with a genius trade in the format: + ` + price:'price_on_the_orderbook', + size:'percentage_of_total_funds', + side: BUY or SELL, + ` + + Your trade should approximate price using the likelihood in your prediction. + + Example response: + + RESPONSE``` + price:0.5, + size:0.1, + side:BUY, + ``` + + + +result: ``` + price:0.3, + size:0.2, + side: BUY, + ``` + +5. CALCULATED TRADE ``` + price:0.3, + size:0.2, + side: BUY, + ``` + diff --git a/LICENSE.md b/LICENSE.md new file mode 100644 index 0000000..a1c04a5 --- /dev/null +++ b/LICENSE.md @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2024 Polymarket + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/README.md b/README.md new file mode 100644 index 0000000..5193a0d --- /dev/null +++ b/README.md @@ -0,0 +1,167 @@ +# Polymarket Agents + +Polymarket Agents is a developer framework and set of utilities for building AI agents for Polymarket. + +This code is free and publicly available under MIT License open source license ([terms of service](#terms-of-service))! + +## Features + +- Integration with Polymarket API +- AI agent utilities for prediction markets +- Local and remote RAG (Retrieval-Augmented Generation) support +- Data sourcing from betting services, news providers, and web search +- Comphrehensive LLM tools for prompt engineering + +# Getting started + +This repo is inteded for use with Python 3.9 + +1. Clone the repository + + ``` + git clone https://github.com/{username}/polymarket-agents.git + cd polymarket-agents + ``` + +2. Create the virtual environment + + ``` + virtualenv --python=python3.9 .venv + ``` + +3. Activate the virtual environment + + - On Windows: + + ``` + .venv\Scripts\activate + ``` + + - On macOS and Linux: + + ``` + source .venv/bin/activate + ``` + +4. Install the required dependencies: + + ``` + pip install -r requirements.txt + ``` + +5. Set up your environment variables: + + - Create a `.env` file in the project root directory + + ``` + cp .env.example .env + ``` + + - Add the following environment variables: + + ``` + POLYGON_WALLET_PRIVATE_KEY="" + OPENAI_API_KEY="" + ``` + +6. Load your wallet with USDC. + +7. Trade! + + ``` + python application/trade.py + ``` + +8. Note: If running the command outside of docker, please set the following env var: + + ``` + export PYTHONPATH="." + ``` + + If running with docker is preferred, we provide the following scripts: + + ``` + ./scripts/bash/build-docker.sh + ./scripts/bash/run-docker-dev.sh + ``` + +## Architecture + +The Polymarket Agents architecture features modular components that can be maintained and extended by individual community members. + +### Connectors + +Polymarket Agents connectors standardize data sources and order types. + +- `Polymarket.py`: defines a Polymarket class that interacts with the Polymarket API to retrieve and manage market and event data, and to execute orders on the Polymarket DEX. It includes methods for API key initialization, market and event data retrieval, and trade execution. The file also provides utility functions for building and signing orders, as well as examples for testing API interactions. + +- `Objects.py`: data models using Pydantic; representations for trades, markets, events, and related entities. + +- `Chroma.py`: chroma DB for vectorizing news sources and other API data. Developers are able to add their own vector database implementations. + +- `Gamma.py`: defines `GammaMarketClient` class, which interfaces with the Polymarket Gamma API to fetch and parse market and event metadata. Methods to retrieve current and tradable markets, as well as defined information on specific markets and events. + +### Scripts + +Files for managing your local environment, server set-up to run the application remotely, and cli for end-user commands. + +`cli.py` is the primary user interface for the repo. Users can run various commands to interact with the Polymarket API, retrieve relevant news articles, query local data, send data/prompts to LLMs, and execute trades in Polymarkets. + +Commands should follow this format: + +`python scripts/python/cli.py command_name [attribute value] [attribute value]` + +Example: + +`get_all_markets` +Retrieve and display a list of markets from Polymarket, sorted by volume. + + ``` + python scripts/python/cli.py get_all_markets --limit --sort-by + ``` + +- limit: The number of markets to retrieve (default: 5). +- sort_by: The sorting criterion, either volume (default) or another valid attribute. + +# Contributing + +If you would like to contribute to this project, please follow these steps: + +1. Fork the repository. +2. Create a new branch. +3. Make your changes. +4. Submit a pull request. + +Please run pre-commit hooks before making contributions. To initialize them: + + ``` + pre-commit install + ``` + +# Related Repos + +- [py-clob-client](https://github.com/Polymarket/py-clob-client): Python client for the Polymarket CLOB +- [python-order-utils](https://github.com/Polymarket/python-order-utils): Python utilities to generate and sign orders from Polymarket's CLOB +- [Polymarket CLOB client](https://github.com/Polymarket/clob-client): Typescript client for Polymarket CLOB +- [Langchain](https://github.com/langchain-ai/langchain): Utility for building context-aware reasoning applications +- [Chroma](https://docs.trychroma.com/getting-started): Chroma is an AI-native open-source vector database + +# Prediction markets reading + +- Prediction Markets: Bottlenecks and the Next Major Unlocks, Mikey 0x: https://mirror.xyz/1kx.eth/jnQhA56Kx9p3RODKiGzqzHGGEODpbskivUUNdd7hwh0 +- The promise and challenges of crypto + AI applications, Vitalik Buterin: https://vitalik.eth.limo/general/2024/01/30/cryptoai.html +- Superforecasting: How to Upgrade Your Company's Judgement, Schoemaker and Tetlock: https://hbr.org/2016/05/superforecasting-how-to-upgrade-your-companys-judgment + +# License + +This project is licensed under the MIT License. See the [LICENSE](https://github.com/Polymarket/agents/blob/main/LICENSE.md) file for details. + +# Contact + +For any questions or inquiries, please contact liam@polymarket.com or reach out at www.greenestreet.xyz + +Enjoy using the CLI application! If you encounter any issues, feel free to open an issue on the repository. + +# Terms of Service + +[Terms of Service](https://polymarket.com/tos) prohibit US persons and persons from certain other jurisdictions from trading on Polymarket (via UI & API and including agents developed by persons in restricted jurisdictions), although data and information is viewable globally. \ No newline at end of file diff --git a/application/creator.py b/application/creator.py new file mode 100644 index 0000000..47839e6 --- /dev/null +++ b/application/creator.py @@ -0,0 +1,56 @@ +from application.executor import Executor as Agent +from connectors.gamma import GammaMarketClient as Gamma +from connectors.polymarket import Polymarket + +import time + + +class Creator: + def __init__(self): + self.polymarket = Polymarket() + self.gamma = Gamma() + self.agent = Agent() + + def one_best_market(self): + """ + + one_best_trade is a strategy that evaluates all events, markets, and orderbooks + + leverages all available information sources accessible to the autonomous agent + + then executes that trade without any human intervention + + """ + try: + events = self.polymarket.get_all_tradeable_events() + print(f"1. FOUND {len(events)} EVENTS") + + filtered_events = self.agent.filter_events_with_rag(events) + print(f"2. FILTERED {len(filtered_events)} EVENTS") + + markets = self.agent.map_filtered_events_to_markets(filtered_events) + print() + print(f"3. FOUND {len(markets)} MARKETS") + + print() + filtered_markets = self.agent.filter_markets(markets) + print(f"4. FILTERED {len(filtered_markets)} MARKETS") + + best_market = self.agent.source_best_market_to_create(filtered_markets) + print(f"5. IDEA FOR NEW MARKET {best_market}") + return best_market + + except Exception as e: + print(f"Error {e} \n \n Retrying") + self.one_best_market() + + def maintain_positions(self): + pass + + def incentive_farm(self): + pass + + +if __name__ == "__main__": + c = Creator() + c.one_best_market() diff --git a/application/cron.py b/application/cron.py new file mode 100644 index 0000000..e64b192 --- /dev/null +++ b/application/cron.py @@ -0,0 +1,23 @@ +import time +from application.trade import Trader + +from scheduler import Scheduler +from scheduler.trigger import Monday + + +class Scheduler: + def __init__(self) -> None: + self.trader = Trader() + self.schedule = Scheduler() + + def start(self) -> None: + while True: + self.schedule.exec_jobs() + time.sleep(1) + + +class TradingAgent(Scheduler): + def __init__(self) -> None: + super() + self.trader = Trader() + self.weekly(Monday(), self.trader.one_best_trade) diff --git a/application/executor.py b/application/executor.py new file mode 100644 index 0000000..a821727 --- /dev/null +++ b/application/executor.py @@ -0,0 +1,132 @@ +import os +import json +import pdb +import ast +import time +import re + +from dotenv import load_dotenv +from langchain_core.messages import HumanMessage, SystemMessage +from langchain_openai import ChatOpenAI + +from connectors.gamma import GammaMarketClient as Gamma +from connectors.chroma import PolymarketRAG as Chroma +from connectors.objects import SimpleEvent, SimpleMarket +from application.prompts import Prompter +from connectors.polymarket import Polymarket + + +class Executor: + def __init__(self) -> None: + load_dotenv() + self.prompter = Prompter() + self.openai_api_key = os.getenv("OPENAI_API_KEY") + self.llm = ChatOpenAI( + model="gpt-3.5-turbo", + temperature=0, + ) + self.gamma = Gamma() + self.chroma = Chroma() + self.polymarket = Polymarket() + + def get_llm_response(self, user_input: str) -> str: + system_message = SystemMessage(content=str(self.prompter.market_analyst())) + human_message = HumanMessage(content=user_input) + messages = [system_message, human_message] + result = self.llm.invoke(messages) + return result.content + + def get_superforecast( + self, event_title: str, market_question: str, outcome: str + ) -> str: + messages = self.prompter.superforecaster( + event_title=event_title, market_question=market_question, outcome=outcome + ) + result = self.llm.invoke(messages) + return result.content + + def get_polymarket_llm(self, user_input: str) -> str: + data1 = self.gamma.get_current_events() + data2 = self.gamma.get_current_markets() + system_message = SystemMessage( + content=str(self.prompter.prompts_polymarket(data1=data1, data2=data2)) + ) + human_message = HumanMessage(content=user_input) + messages = [system_message, human_message] + result = self.llm.invoke(messages) + return result.content + + def filter_events(self, events: "list[SimpleEvent]") -> str: + prompt = self.prompter.filter_events(events) + result = self.llm.invoke(prompt) + return result.content + + def filter_events_with_rag(self, events: "list[SimpleEvent]") -> str: + prompt = self.prompter.filter_events() + print() + print("... prompting ... ", prompt) + print() + return self.chroma.events(events, prompt) + + def map_filtered_events_to_markets( + self, filtered_events: "list[SimpleEvent]" + ) -> "list[SimpleMarket]": + markets = [] + for e in filtered_events: + data = json.loads(e[0].json()) + market_ids = data["metadata"]["markets"].split(",") + for market_id in market_ids: + market_data = self.gamma.get_market(market_id) + formatted_market_data = self.polymarket.map_api_to_market(market_data) + markets.append(formatted_market_data) + return markets + + def filter_markets(self, markets) -> "list[tuple]": + prompt = self.prompter.filter_markets() + print() + print("... prompting ... ", prompt) + print() + return self.chroma.markets(markets, prompt) + + def source_best_trade(self, market_object) -> str: + market_document = market_object[0].dict() + market = market_document["metadata"] + outcome_prices = ast.literal_eval(market["outcome_prices"]) + outcomes = ast.literal_eval(market["outcomes"]) + question = market["question"] + description = market_document["page_content"] + + prompt = self.prompter.superforecaster(question, description, outcomes) + print() + print("... prompting ... ", prompt) + print() + result = self.llm.invoke(prompt) + content = result.content + + print("result: ", content) + print() + prompt = self.prompter.one_best_trade(content, outcomes, outcome_prices) + print("... prompting ... ", prompt) + print() + result = self.llm.invoke(prompt) + content = result.content + + print("result: ", content) + print() + return content + + def format_trade_prompt_for_execution(self, best_trade: str) -> float: + data = best_trade.split(",") + # price = re.findall("\d+\.\d+", data[0])[0] + size = re.findall("\d+\.\d+", data[1])[0] + usdc_balance = self.polymarket.get_usdc_balance() + return float(size) * usdc_balance + + def source_best_market_to_create(self, filtered_markets) -> str: + prompt = self.prompter.create_new_market(filtered_markets) + print() + print("... prompting ... ", prompt) + print() + result = self.llm.invoke(prompt) + content = result.content + return content diff --git a/application/prompts.py b/application/prompts.py new file mode 100644 index 0000000..a993ee1 --- /dev/null +++ b/application/prompts.py @@ -0,0 +1,246 @@ +from typing import List +from datetime import datetime + + +class Prompter: + + def generate_simple_ai_trader(market_description: str, relevant_info: str) -> str: + return f""" + + You are a trader. + + Here is a market description: {market_description}. + + Here is relevant information: {relevant_info}. + + Do you buy or sell? How much? + """ + + def market_analyst(self) -> str: + return f""" + You are a market analyst that takes a description of an event and produces a market forecast. + Assign a probability estimate to the event occurring described by the user + """ + + def sentiment_analyzer(question: str, outcome: str) -> float: + return f""" + You are a political scientist trained in media analysis. + You are given a question: {question}. + and an outcome of yes or no: {outcome}. + + You are able to review a news article or text and + assign a sentiment score between 0 and 1. + + """ + + def prompts_polymarket( + data1: str, data2: str, market_question: str, outcome: str + ) -> str: + current_market_data = str(data1) + current_event_data = str(data2) + return f""" + You are an AI assistant for users of a prediction market called Polymarket. + Users want to place bets based on their beliefs of market outcomes such as political or sports events. + + Here is data for current Polymarket markets {current_market_data} and + current Polymarket events {current_event_data}. + + Help users identify markets to trade based on their interests or queries. + Provide specific information for markets including probabilities of outcomes. + Give your response in the following format: + + I believe {market_question} has a likelihood {float} for outcome of {outcome}. + """ + + def prompts_polymarket(data1: str, data2: str, user_input: str) -> str: + current_market_data = str(data1) + current_event_data = str(data2) + return f""" + You are an AI assistant for users of a prediction market called Polymarket. + Users want to place bets based on their beliefs of market outcomes such as political or sports events. + + Here is data for current Polymarket markets {current_market_data} and + current Polymarket events {current_event_data}. + Help users identify markets to trade based on their interests or queries. + Provide specific information for markets including probabilities of outcomes. + + """ + + def routing(system_message: str) -> str: + return f"""You are an expert at routing a user question to the appropriate data source. """ + + def multiquery(question: str) -> str: + return f""" + You're an AI assistant. Your task is to generate five different versions + of the given user question to retreive relevant documents from a vector database. By generating + multiple perspectives on the user question, your goal is to help the user overcome some of the limitations + of the distance-based similarity search. + Provide these alternative questions separated by newlines. Original question: {question} + + """ + + def read_polymarket(self) -> str: + return f""" + You are an prediction market analyst. + """ + + def polymarket_analyst_api(self) -> str: + return f"""You are an AI assistant for analyzing prediction markets. + You will be provided with json output for api data from Polymarket. + Polymarket is an online prediction market that lets users Bet on the outcome of future events in a wide range of topics, like sports, politics, and pop culture. + Get accurate real-time probabilities of the events that matter most to you. """ + + def filter_events(self) -> str: + return ( + self.polymarket_analyst_api() + + f""" + + Filter these events for the ones you will be best at trading on profitably. + + """ + ) + + def filter_markets(self) -> str: + return ( + self.polymarket_analyst_api() + + f""" + + Filter these markets for the ones you will be best at trading on profitably. + + """ + ) + + def superforecaster(self, question: str, description: str, outcome: str) -> str: + return f""" + You are a Superforecaster tasked with correctly predicting the likelihood of events. + Use the following systematic process to develop an accurate prediction for the following + question=`{question}` and description=`{description}` combination. + + Here are the key steps to use in your analysis: + + 1. Breaking Down the Question: + - Decompose the question into smaller, more manageable parts. + - Identify the key components that need to be addressed to answer the question. + 2. Gathering Information: + - Seek out diverse sources of information. + - Look for both quantitative data and qualitative insights. + - Stay updated on relevant news and expert analyses. + 3. Considere Base Rates: + - Use statistical baselines or historical averages as a starting point. + - Compare the current situation to similar past events to establish a benchmark probability. + 4. Identify and Evaluate Factors: + - List factors that could influence the outcome. + - Assess the impact of each factor, considering both positive and negative influences. + - Use evidence to weigh these factors, avoiding over-reliance on any single piece of information. + 5. Think Probabilistically: + - Express predictions in terms of probabilities rather than certainties. + - Assign likelihoods to different outcomes and avoid binary thinking. + - Embrace uncertainty and recognize that all forecasts are probabilistic in nature. + + Given these steps produce a statement on the probability of outcome=`{outcome}` occuring. + + Give your response in the following format: + + I believe {question} has a likelihood `{float}` for outcome of `{str}`. + """ + + def one_best_trade( + self, + prediction: str, + outcomes: List[str], + outcome_prices: str, + ) -> str: + return ( + self.polymarket_analyst_api() + + f""" + + Imagine yourself as the top trader on Polymarket, dominating the world of information markets with your keen insights and strategic acumen. You have an extraordinary ability to analyze and interpret data from diverse sources, turning complex information into profitable trading opportunities. + You excel in predicting the outcomes of global events, from political elections to economic developments, using a combination of data analysis and intuition. Your deep understanding of probability and statistics allows you to assess market sentiment and make informed decisions quickly. + Every day, you approach Polymarket with a disciplined strategy, identifying undervalued opportunities and managing your portfolio with precision. You are adept at evaluating the credibility of information and filtering out noise, ensuring that your trades are based on reliable data. + Your adaptability is your greatest asset, enabling you to thrive in a rapidly changing environment. You leverage cutting-edge technology and tools to gain an edge over other traders, constantly seeking innovative ways to enhance your strategies. + In your journey on Polymarket, you are committed to continuous learning, staying informed about the latest trends and developments in various sectors. Your emotional intelligence empowers you to remain composed under pressure, making rational decisions even when the stakes are high. + Visualize yourself consistently achieving outstanding returns, earning recognition as the top trader on Polymarket. You inspire others with your success, setting new standards of excellence in the world of information markets. + + """ + + f""" + + You made the following prediction for a market: {prediction} + + The current outcomes ${outcomes} prices are: ${outcome_prices} + + Given your prediction, respond with a genius trade in the format: + ` + price:'price_on_the_orderbook', + size:'percentage_of_total_funds', + side: BUY or SELL, + ` + + Your trade should approximate price using the likelihood in your prediction. + + Example response: + + RESPONSE``` + price:0.5, + size:0.1, + side:BUY, + ``` + + """ + ) + + def format_price_from_one_best_trade_output(self, output: str) -> str: + return f""" + + You will be given an input such as: + + ` + price:0.5, + size:0.1, + side:BUY, + ` + + Please extract only the value associated with price. + In this case, you would return "0.5". + + Only return the number after price: + + """ + + def format_size_from_one_best_trade_output(self, output: str) -> str: + return f""" + + You will be given an input such as: + + ` + price:0.5, + size:0.1, + side:BUY, + ` + + Please extract only the value associated with price. + In this case, you would return "0.1". + + Only return the number after size: + + """ + + def create_new_market(self, filtered_markets: str) -> str: + return f""" + {filtered_markets} + + Invent an information market similar to these markets that ends in the future, + at least 6 months after today, which is: {datetime.today().strftime('%Y-%m-%d')}, + so this date plus 6 months at least. + + Output your format in: + + Question: "..."? + Outcomes: A or B + + With ... filled in and A or B options being the potential results. + For example: + + Question: "Will Kamala win" + Outcomes: Yes or No + + """ diff --git a/application/trade.py b/application/trade.py new file mode 100644 index 0000000..0d1876d --- /dev/null +++ b/application/trade.py @@ -0,0 +1,76 @@ +from application.executor import Executor as Agent +from connectors.gamma import GammaMarketClient as Gamma +from connectors.polymarket import Polymarket + +import shutil + + +class Trader: + def __init__(self): + self.polymarket = Polymarket() + self.gamma = Gamma() + self.agent = Agent() + + def pre_trade_logic(self) -> None: + self.clear_local_dbs() + + def clear_local_dbs(self) -> None: + try: + shutil.rmtree("local_db_events") + except: + pass + try: + shutil.rmtree("local_db_markets") + except: + pass + + def one_best_trade(self) -> None: + """ + + one_best_trade is a strategy that evaluates all events, markets, and orderbooks + + leverages all available information sources accessible to the autonomous agent + + then executes that trade without any human intervention + + """ + try: + self.pre_trade_logic() + + events = self.polymarket.get_all_tradeable_events() + print(f"1. FOUND {len(events)} EVENTS") + + filtered_events = self.agent.filter_events_with_rag(events) + print(f"2. FILTERED {len(filtered_events)} EVENTS") + + markets = self.agent.map_filtered_events_to_markets(filtered_events) + print() + print(f"3. FOUND {len(markets)} MARKETS") + + print() + filtered_markets = self.agent.filter_markets(markets) + print(f"4. FILTERED {len(filtered_markets)} MARKETS") + + market = filtered_markets[0] + best_trade = self.agent.source_best_trade(market) + print(f"5. CALCULATED TRADE {best_trade}") + + amount = self.agent.format_trade_prompt_for_execution(best_trade) + # Please refer to TOS before uncommenting: polymarket.com/tos + # trade = self.polymarket.execute_market_order(market, amount) + # print(f"6. TRADED {trade}") + + except Exception as e: + print(f"Error {e} \n \n Retrying") + self.one_best_trade() + + def maintain_positions(self): + pass + + def incentive_farm(self): + pass + + +if __name__ == "__main__": + t = Trader() + t.one_best_trade() diff --git a/application/utils.py b/application/utils.py new file mode 100644 index 0000000..e397ddb --- /dev/null +++ b/application/utils.py @@ -0,0 +1,59 @@ +import json + + +def parse_camel_case(key) -> str: + output = "" + for char in key: + if char.isupper(): + output += " " + output += char.lower() + else: + output += char + return output + + +def preprocess_market_object(market_object: dict) -> dict: + description = market_object["description"] + + for k, v in market_object.items(): + if k == "description": + continue + if isinstance(v, bool): + description += ( + f' This market is{" not" if not v else ""} {parse_camel_case(k)}.' + ) + + if k in ["volume", "liquidity"]: + description += f" This market has a current {k} of {v}." + print("\n\ndescription:", description) + + market_object["description"] = description + + return market_object + + +def preprocess_local_json(file_path: str, preprocessor_function: function) -> None: + with open(file_path, "r+") as open_file: + data = json.load(open_file) + + output = [] + for obj in data: + preprocessed_json = preprocessor_function(obj) + output.append(preprocessed_json) + + split_path = file_path.split(".") + new_file_path = split_path[0] + "_preprocessed." + split_path[1] + with open(new_file_path, "w+") as output_file: + json.dump(output, output_file) + + +def metadata_func(record: dict, metadata: dict) -> dict: + print("record:", record) + print("meta:", metadata) + for k, v in record.items(): + metadata[k] = v + + del metadata["description"] + del metadata["events"] + + return metadata diff --git a/connectors/chroma.py b/connectors/chroma.py new file mode 100644 index 0000000..905be1b --- /dev/null +++ b/connectors/chroma.py @@ -0,0 +1,129 @@ +import json +import os +import time +import pdb + +from langchain_openai import OpenAIEmbeddings +from langchain_community.document_loaders import JSONLoader +from langchain_community.vectorstores.chroma import Chroma + +from connectors.gamma import GammaMarketClient +from connectors.objects import SimpleEvent +from connectors.objects import SimpleMarket + + +class PolymarketRAG: + def __init__(self, local_db_directory=None, embedding_function=None) -> None: + self.gamma_client = GammaMarketClient() + self.local_db_directory = local_db_directory + self.embedding_function = embedding_function + + def load_json_from_local( + self, json_file_path=None, vector_db_directory="./local_db" + ) -> None: + loader = JSONLoader( + file_path=json_file_path, jq_schema=".[].description", text_content=False + ) + loaded_docs = loader.load() + + embedding_function = OpenAIEmbeddings(model="text-embedding-3-small") + Chroma.from_documents( + loaded_docs, embedding_function, persist_directory=vector_db_directory + ) + + def create_local_markets_rag(self, local_directory="./local_db") -> None: + all_markets = self.gamma_client.get_all_current_markets() + + if not os.path.isdir(local_directory): + os.mkdir(local_directory) + + local_file_path = f"{local_directory}/all-current-markets_{time.time()}.json" + + with open(local_file_path, "w+") as output_file: + json.dump(all_markets, output_file) + + self.load_json_from_local( + json_file_path=local_file_path, vector_db_directory=local_directory + ) + + def query_local_markets_rag( + self, local_directory=None, query=None + ) -> "list[tuple]": + embedding_function = OpenAIEmbeddings(model="text-embedding-3-small") + local_db = Chroma( + persist_directory=local_directory, embedding_function=embedding_function + ) + response_docs = local_db.similarity_search_with_score(query=query) + return response_docs + + def events(self, events: "list[SimpleEvent]", prompt: str) -> "list[tuple]": + # create local json file + local_events_directory: str = "./local_db_events" + if not os.path.isdir(local_events_directory): + os.mkdir(local_events_directory) + local_file_path = f"{local_events_directory}/events.json" + dict_events = [x.dict() for x in events] + with open(local_file_path, "w+") as output_file: + json.dump(dict_events, output_file) + + # create vector db + def metadata_func(record: dict, metadata: dict) -> dict: + + metadata["id"] = record.get("id") + metadata["markets"] = record.get("markets") + + return metadata + + loader = JSONLoader( + file_path=local_file_path, + jq_schema=".[]", + content_key="description", + text_content=False, + metadata_func=metadata_func, + ) + loaded_docs = loader.load() + embedding_function = OpenAIEmbeddings(model="text-embedding-3-small") + vector_db_directory = f"{local_events_directory}/chroma" + local_db = Chroma.from_documents( + loaded_docs, embedding_function, persist_directory=vector_db_directory + ) + + # query + return local_db.similarity_search_with_score(query=prompt) + + def markets(self, markets: "list[SimpleMarket]", prompt: str) -> "list[tuple]": + # create local json file + local_events_directory: str = "./local_db_markets" + if not os.path.isdir(local_events_directory): + os.mkdir(local_events_directory) + local_file_path = f"{local_events_directory}/markets.json" + with open(local_file_path, "w+") as output_file: + json.dump(markets, output_file) + + # create vector db + def metadata_func(record: dict, metadata: dict) -> dict: + + metadata["id"] = record.get("id") + metadata["outcomes"] = record.get("outcomes") + metadata["outcome_prices"] = record.get("outcome_prices") + metadata["question"] = record.get("question") + metadata["clob_token_ids"] = record.get("clob_token_ids") + + return metadata + + loader = JSONLoader( + file_path=local_file_path, + jq_schema=".[]", + content_key="description", + text_content=False, + metadata_func=metadata_func, + ) + loaded_docs = loader.load() + embedding_function = OpenAIEmbeddings(model="text-embedding-3-small") + vector_db_directory = f"{local_events_directory}/chroma" + local_db = Chroma.from_documents( + loaded_docs, embedding_function, persist_directory=vector_db_directory + ) + + # query + return local_db.similarity_search_with_score(query=prompt) diff --git a/connectors/gamma.py b/connectors/gamma.py new file mode 100644 index 0000000..d05785a --- /dev/null +++ b/connectors/gamma.py @@ -0,0 +1,191 @@ +import httpx +import json + +from connectors.objects import Market, PolymarketEvent +from connectors.objects import ClobReward +from connectors.objects import Tag + +from connectors.polymarket import Polymarket + + +class GammaMarketClient: + def __init__(self): + self.gamma_url = "https://gamma-api.polymarket.com" + self.gamma_markets_endpoint = self.gamma_url + "/markets" + self.gamma_events_endpoint = self.gamma_url + "/events" + + def parse_pydantic_market(self, market_object: dict) -> Market: + try: + if "clobRewards" in market_object: + clob_rewards: list[ClobReward] = [] + for clob_rewards_obj in market_object["clobRewards"]: + clob_rewards.append(ClobReward(**clob_rewards_obj)) + market_object["clobRewards"] = clob_rewards + + if "events" in market_object: + events: list[PolymarketEvent] = [] + for market_event_obj in market_object["events"]: + events.append(self.parse_nested_event(market_event_obj)) + market_object["events"] = events + + # These two fields below are returned as stringified lists from the api + if "outcomePrices" in market_object: + market_object["outcomePrices"] = json.loads( + market_object["outcomePrices"] + ) + if "clobTokenIds" in market_object: + market_object["clobTokenIds"] = json.loads( + market_object["clobTokenIds"] + ) + + return Market(**market_object) + except Exception as err: + print(f"[parse_market] Caught exception: {err}") + print("exception while handling object:", market_object) + + # Event parser for events nested under a markets api response + def parse_nested_event(self, event_object: dict()) -> PolymarketEvent: + print("[parse_nested_event] called with:", event_object) + try: + if "tags" in event_object: + print("tags here", event_object["tags"]) + tags: list[Tag] = [] + for tag in event_object["tags"]: + tags.append(Tag(**tag)) + event_object["tags"] = tags + + return PolymarketEvent(**event_object) + except Exception as err: + print(f"[parse_event] Caught exception: {err}") + print("\n", event_object) + + def parse_pydantic_event(self, event_object: dict) -> PolymarketEvent: + try: + if "tags" in event_object: + print("tags here", event_object["tags"]) + tags: list[Tag] = [] + for tag in event_object["tags"]: + tags.append(Tag(**tag)) + event_object["tags"] = tags + return PolymarketEvent(**event_object) + except Exception as err: + print(f"[parse_event] Caught exception: {err}") + + def get_markets( + self, querystring_params={}, parse_pydantic=False, local_file_path=None + ) -> "list[Market]": + if parse_pydantic and local_file_path is not None: + raise Exception( + 'Cannot use "parse_pydantic" and "local_file" params simultaneously.' + ) + + response = httpx.get(self.gamma_markets_endpoint, params=querystring_params) + if response.status_code == 200: + data = response.json() + if local_file_path is not None: + with open(local_file_path, "w+") as out_file: + json.dump(data, out_file) + elif not parse_pydantic: + return data + else: + markets: list[Market] = [] + for market_object in data: + markets.append(self.parse_pydantic_market(market_object)) + return markets + else: + print(f"Error response returned from api: HTTP {response.status_code}") + raise Exception() + + def get_events( + self, querystring_params={}, parse_pydantic=False, local_file_path=None + ) -> "list[PolymarketEvent]": + if parse_pydantic and local_file_path is not None: + raise Exception( + 'Cannot use "parse_pydantic" and "local_file" params simultaneously.' + ) + + response = httpx.get(self.gamma_events_endpoint, params=querystring_params) + if response.status_code == 200: + data = response.json() + if local_file_path is not None: + with open(local_file_path, "w+") as out_file: + json.dump(data, out_file) + elif not parse_pydantic: + return data + else: + events: list[PolymarketEvent] = [] + for market_event_obj in data: + events.append(self.parse_event(market_event_obj)) + return events + else: + raise Exception() + + def get_all_markets(self, limit=2) -> "list[Market]": + return self.get_markets(querystring_params={"limit": limit}) + + def get_all_events(self, limit=2) -> "list[PolymarketEvent]": + return self.get_events(querystring_params={"limit": limit}) + + def get_current_markets(self, limit=4) -> "list[Market]": + return self.get_markets( + querystring_params={ + "active": True, + "closed": False, + "archived": False, + "limit": limit, + } + ) + + def get_all_current_markets(self, limit=100) -> "list[Market]": + offset = 0 + all_markets = [] + while True: + params = { + "active": True, + "closed": False, + "archived": False, + "limit": limit, + "offset": offset, + } + market_batch = self.get_markets(querystring_params=params) + all_markets.extend(market_batch) + + if len(market_batch) < limit: + break + offset += limit + + return all_markets + + def get_current_events(self, limit=4) -> "list[PolymarketEvent]": + return self.get_events( + querystring_params={ + "active": True, + "closed": False, + "archived": False, + "limit": limit, + } + ) + + def get_clob_tradable_markets(self, limit=2) -> "list[Market]": + return self.get_markets( + querystring_params={ + "active": True, + "closed": False, + "archived": False, + "limit": limit, + "enableOrderBook": True, + } + ) + + def get_market(self, market_id: int) -> dict(): + url = self.gamma_markets_endpoint + "/" + str(market_id) + print(url) + response = httpx.get(url) + return response.json() + + +if __name__ == "__main__": + gamma = GammaMarketClient() + market = gamma.get_market("253123") + poly = Polymarket() + object = poly.map_api_to_market(market) diff --git a/connectors/news.py b/connectors/news.py new file mode 100644 index 0000000..e520549 --- /dev/null +++ b/connectors/news.py @@ -0,0 +1,81 @@ +from datetime import datetime +import os + +from newsapi import NewsApiClient + +from connectors.objects import Article + + +class News: + def __init__(self) -> None: + self.configs = { + "language": "en", + "country": "us", + "top_headlines": "https://newsapi.org/v2/top-headlines?country=us&apiKey=", + "base_url": "https://newsapi.org/v2/", + } + + self.categories = { + "business", + "entertainment", + "general", + "health", + "science", + "sports", + "technology", + } + + self.API = NewsApiClient(os.getenv("NEWSAPI_API_KEY")) + + def get_articles_for_cli_keywords(self, keywords) -> "list[Article]": + query_words = keywords.split(",") + all_articles = self.get_articles_for_options(query_words) + article_objects: list[Article] = [] + for _, articles in all_articles.items(): + for article in articles: + article_objects.append(Article(**article)) + return article_objects + + def get_top_articles_for_market(self, market_object: dict) -> "list[Article]": + return self.API.get_top_headlines( + language="en", country="usa", q=market_object["description"] + ) + + def get_articles_for_options( + self, + market_options: "list[str]", + date_start: datetime = None, + date_end: datetime = None, + ) -> "list[Article]": + + all_articles = {} + # Default to top articles if no start and end dates are given for search + if not date_start and not date_end: + for option in market_options: + response_dict = self.API.get_top_headlines( + q=option.strip(), + language=self.configs["language"], + country=self.configs["country"], + ) + articles = response_dict["articles"] + all_articles[option] = articles + else: + for option in market_options: + response_dict = self.API.get_everything( + q=option.strip(), + language=self.configs["language"], + country=self.configs["country"], + from_param=date_start, + to=date_end, + ) + articles = response_dict["articles"] + all_articles[option] = articles + + return all_articles + + def get_category(self, market_object: dict) -> str: + news_category = "general" + market_category = market_object["category"] + if market_category in self.categories: + news_category = market_category + return news_category diff --git a/connectors/objects.py b/connectors/objects.py new file mode 100644 index 0000000..e5c71fe --- /dev/null +++ b/connectors/objects.py @@ -0,0 +1,228 @@ +from __future__ import annotations +from typing import Optional, Union +from pydantic import BaseModel + + +class Trade(BaseModel): + id: int + taker_order_id: str + market: str + asset_id: str + side: str + size: str + fee_rate_bps: str + price: str + status: str + match_time: str + last_update: str + outcome: str + maker_address: str + owner: str + transaction_hash: str + bucket_index: str + maker_orders: list[str] + type: str + + +class SimpleMarket(BaseModel): + id: int + question: str + # start: str + end: str + description: str + active: bool + deployed: bool + funded: bool + # orderMinSize: float + # orderPriceMinTickSize: float + rewardsMinSize: float + rewardsMaxSpread: float + volume: float + spread: float + outcomes: str + outcome_prices: str + clob_token_ids: Optional[str] + + +class ClobReward(BaseModel): + id: str # returned as string in api but really an int? + conditionId: str + assetAddress: str + rewardsAmount: float # only seen 0 but could be float? + rewardsDailyRate: int # only seen ints but could be float? + startDate: str # yyyy-mm-dd formatted date string + endDate: str # yyyy-mm-dd formatted date string + + +class Tag(BaseModel): + id: str + label: Optional[str] = None + slug: Optional[str] = None + forceShow: Optional[bool] = None # missing from current events data + createdAt: Optional[str] = None # missing from events data + updatedAt: Optional[str] = None # missing from current events data + _sync: Optional[bool] = None + + +class PolymarketEvent(BaseModel): + id: str # "11421" + ticker: Optional[str] = None + slug: Optional[str] = None + title: Optional[str] = None + startDate: Optional[str] = None + creationDate: Optional[str] = ( + None # fine in market event but missing from events response + ) + endDate: Optional[str] = None + image: Optional[str] = None + icon: Optional[str] = None + active: Optional[bool] = None + closed: Optional[bool] = None + archived: Optional[bool] = None + new: Optional[bool] = None + featured: Optional[bool] = None + restricted: Optional[bool] = None + liquidity: Optional[float] = None + volume: Optional[float] = None + reviewStatus: Optional[str] = None + createdAt: Optional[str] = None # 2024-07-08T01:06:23.982796Z, + updatedAt: Optional[str] = None # 2024-07-15T17:12:48.601056Z, + competitive: Optional[float] = None + volume24hr: Optional[float] = None + enableOrderBook: Optional[bool] = None + liquidityClob: Optional[float] = None + _sync: Optional[bool] = None + commentCount: Optional[int] = None + # markets: list[str, 'Market'] # forward reference Market defined below - TODO: double check this works as intended + markets: Optional[list[Market]] = None + tags: Optional[list[Tag]] = None + cyom: Optional[bool] = None + showAllOutcomes: Optional[bool] = None + showMarketImages: Optional[bool] = None + + +class Market(BaseModel): + id: int + question: Optional[str] = None + conditionId: Optional[str] = None + slug: Optional[str] = None + resolutionSource: Optional[str] = None + endDate: Optional[str] = None + liquidity: Optional[float] = None + startDate: Optional[str] = None + image: Optional[str] = None + icon: Optional[str] = None + description: Optional[str] = None + outcome: Optional[list] = None + outcomePrices: Optional[list] = None + volume: Optional[float] = None + active: Optional[bool] = None + closed: Optional[bool] = None + marketMakerAddress: Optional[str] = None + createdAt: Optional[str] = None # date type worth enforcing for dates? + updatedAt: Optional[str] = None + new: Optional[bool] = None + featured: Optional[bool] = None + submitted_by: Optional[str] = None + archived: Optional[bool] = None + resolvedBy: Optional[str] = None + restricted: Optional[bool] = None + groupItemTitle: Optional[str] = None + groupItemThreshold: Optional[int] = None + questionID: Optional[str] = None + enableOrderBook: Optional[bool] = None + orderPriceMinTickSize: Optional[float] = None + orderMinSize: Optional[int] = None + volumeNum: Optional[float] = None + liquidityNum: Optional[float] = None + endDateIso: Optional[str] = None # iso format date = None + startDateIso: Optional[str] = None + hasReviewedDates: Optional[bool] = None + volume24hr: Optional[float] = None + clobTokenIds: Optional[list] = None + umaBond: Optional[int] = None # returned as string from api? + umaReward: Optional[int] = None # returned as string from api? + volume24hrClob: Optional[float] = None + volumeClob: Optional[float] = None + liquidityClob: Optional[float] = None + acceptingOrders: Optional[bool] = None + negRisk: Optional[bool] = None + commentCount: Optional[int] = None + _sync: Optional[bool] = None + events: Optional[list[PolymarketEvent]] = None + ready: Optional[bool] = None + deployed: Optional[bool] = None + funded: Optional[bool] = None + deployedTimestamp: Optional[str] = None # utc z datetime string + acceptingOrdersTimestamp: Optional[str] = None # utc z datetime string, + cyom: Optional[bool] = None + competitive: Optional[float] = None + pagerDutyNotificationEnabled: Optional[bool] = None + reviewStatus: Optional[str] = None # deployed, draft, etc. + approved: Optional[bool] = None + clobRewards: Optional[list[ClobReward]] = None + rewardsMinSize: Optional[int] = ( + None # would make sense to allow float but we'll see + ) + rewardsMaxSpread: Optional[float] = None + spread: Optional[float] = None + + +class ComplexMarket(BaseModel): + id: int + condition_id: str + question_id: str + tokens: Union[str, str] + rewards: str + minimum_order_size: str + minimum_tick_size: str + description: str + category: str + end_date_iso: str + game_start_time: str + question: str + market_slug: str + min_incentive_size: str + max_incentive_spread: str + active: bool + closed: bool + seconds_delay: int + icon: str + fpmm: str + name: str + description: Union[str, None] = None + price: float + tax: Union[float, None] = None + + +class SimpleEvent(BaseModel): + id: int + ticker: str + slug: str + title: str + description: str + end: str + active: bool + closed: bool + archived: bool + restricted: bool + new: bool + featured: bool + restricted: bool + markets: str + + +class Source(BaseModel): + id: Optional[str] + name: Optional[str] + + +class Article(BaseModel): + source: Optional[Source] + author: Optional[str] + title: Optional[str] + description: Optional[str] + url: Optional[str] + urlToImage: Optional[str] + publishedAt: Optional[str] + content: Optional[str] diff --git a/connectors/polymarket.py b/connectors/polymarket.py new file mode 100644 index 0000000..34c1f47 --- /dev/null +++ b/connectors/polymarket.py @@ -0,0 +1,476 @@ +# core polymarket api +# https://github.com/Polymarket/py-clob-client/tree/main/examples + +import os +import pdb +import time +import ast +import requests + +from dotenv import load_dotenv + +from web3 import Web3 +from web3.constants import MAX_INT +from web3.middleware import geth_poa_middleware + +import httpx +from py_clob_client.client import ClobClient +from py_clob_client.clob_types import ApiCreds +from py_clob_client.constants import AMOY, POLYGON +from py_order_utils.builders import OrderBuilder +from py_order_utils.model import OrderData +from py_order_utils.signer import Signer +from py_clob_client.clob_types import ( + OrderArgs, + MarketOrderArgs, + OrderType, + OrderBookSummary, +) +from py_clob_client.order_builder.constants import BUY + +from connectors.objects import SimpleMarket, SimpleEvent + +load_dotenv() + + +class Polymarket: + def __init__(self) -> None: + self.gamma_url = "https://gamma-api.polymarket.com" + self.gamma_markets_endpoint = self.gamma_url + "/markets" + self.gamma_events_endpoint = self.gamma_url + "/events" + + self.clob_url = "https://clob.polymarket.com" + self.clob_auth_endpoint = self.clob_url + "/auth/api-key" + + self.chain_id = 137 # POLYGON + self.private_key = os.getenv("POLYGON_WALLET_PRIVATE_KEY") + self.polygon_rpc = "https://polygon-rpc.com" + self.w3 = Web3(Web3.HTTPProvider(self.polygon_rpc)) + + self.exchange_address = "0x4bfb41d5b3570defd03c39a9a4d8de6bd8b8982e" + self.neg_risk_exchange_address = "0xC5d563A36AE78145C45a50134d48A1215220f80a" + + self.erc20_approve = """[{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"owner","type":"address"},{"indexed":true,"internalType":"address","name":"spender","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Approval","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"authorizer","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"AuthorizationCanceled","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"authorizer","type":"address"},{"indexed":true,"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"AuthorizationUsed","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"Blacklisted","type":"event"},{"anonymous":false,"inputs":[{"indexed":false,"internalType":"address","name":"userAddress","type":"address"},{"indexed":false,"internalType":"address payable","name":"relayerAddress","type":"address"},{"indexed":false,"internalType":"bytes","name":"functionSignature","type":"bytes"}],"name":"MetaTransactionExecuted","type":"event"},{"anonymous":false,"inputs":[],"name":"Pause","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"newRescuer","type":"address"}],"name":"RescuerChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"previousAdminRole","type":"bytes32"},{"indexed":true,"internalType":"bytes32","name":"newAdminRole","type":"bytes32"}],"name":"RoleAdminChanged","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleGranted","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"bytes32","name":"role","type":"bytes32"},{"indexed":true,"internalType":"address","name":"account","type":"address"},{"indexed":true,"internalType":"address","name":"sender","type":"address"}],"name":"RoleRevoked","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"from","type":"address"},{"indexed":true,"internalType":"address","name":"to","type":"address"},{"indexed":false,"internalType":"uint256","name":"value","type":"uint256"}],"name":"Transfer","type":"event"},{"anonymous":false,"inputs":[{"indexed":true,"internalType":"address","name":"account","type":"address"}],"name":"UnBlacklisted","type":"event"},{"anonymous":false,"inputs":[],"name":"Unpause","type":"event"},{"inputs":[],"name":"APPROVE_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"BLACKLISTER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"CANCEL_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DECREASE_ALLOWANCE_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEFAULT_ADMIN_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DEPOSITOR_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"DOMAIN_SEPARATOR","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"EIP712_VERSION","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"INCREASE_ALLOWANCE_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"META_TRANSACTION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PAUSER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"PERMIT_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"RESCUER_ROLE","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"TRANSFER_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"WITHDRAW_WITH_AUTHORIZATION_TYPEHASH","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"}],"name":"allowance","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"approve","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"approveWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"authorizer","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"}],"name":"authorizationState","outputs":[{"internalType":"enum GasAbstraction.AuthorizationState","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"balanceOf","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"blacklist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"blacklisters","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"authorizer","type":"address"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"cancelAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"decimals","outputs":[{"internalType":"uint8","name":"","type":"uint8"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"subtractedValue","type":"uint256"}],"name":"decreaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"decrement","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"decreaseAllowanceWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"user","type":"address"},{"internalType":"bytes","name":"depositData","type":"bytes"}],"name":"deposit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"userAddress","type":"address"},{"internalType":"bytes","name":"functionSignature","type":"bytes"},{"internalType":"bytes32","name":"sigR","type":"bytes32"},{"internalType":"bytes32","name":"sigS","type":"bytes32"},{"internalType":"uint8","name":"sigV","type":"uint8"}],"name":"executeMetaTransaction","outputs":[{"internalType":"bytes","name":"","type":"bytes"}],"stateMutability":"payable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleAdmin","outputs":[{"internalType":"bytes32","name":"","type":"bytes32"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"uint256","name":"index","type":"uint256"}],"name":"getRoleMember","outputs":[{"internalType":"address","name":"","type":"address"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"}],"name":"getRoleMemberCount","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"grantRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"hasRole","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"addedValue","type":"uint256"}],"name":"increaseAllowance","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"increment","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"increaseAllowanceWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newName","type":"string"},{"internalType":"string","name":"newSymbol","type":"string"},{"internalType":"uint8","name":"newDecimals","type":"uint8"},{"internalType":"address","name":"childChainManager","type":"address"}],"name":"initialize","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"initialized","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"isBlacklisted","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"name","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"}],"name":"nonces","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"paused","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"pausers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"address","name":"spender","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"deadline","type":"uint256"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"permit","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"renounceRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"contract IERC20","name":"tokenContract","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"rescueERC20","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"rescuers","outputs":[{"internalType":"address[]","name":"","type":"address[]"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"bytes32","name":"role","type":"bytes32"},{"internalType":"address","name":"account","type":"address"}],"name":"revokeRole","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"symbol","outputs":[{"internalType":"string","name":"","type":"string"}],"stateMutability":"view","type":"function"},{"inputs":[],"name":"totalSupply","outputs":[{"internalType":"uint256","name":"","type":"uint256"}],"stateMutability":"view","type":"function"},{"inputs":[{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transfer","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"sender","type":"address"},{"internalType":"address","name":"recipient","type":"address"},{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"transferFrom","outputs":[{"internalType":"bool","name":"","type":"bool"}],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"from","type":"address"},{"internalType":"address","name":"to","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"transferWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"account","type":"address"}],"name":"unBlacklist","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[],"name":"unpause","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"string","name":"newName","type":"string"},{"internalType":"string","name":"newSymbol","type":"string"}],"name":"updateMetadata","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"uint256","name":"amount","type":"uint256"}],"name":"withdraw","outputs":[],"stateMutability":"nonpayable","type":"function"},{"inputs":[{"internalType":"address","name":"owner","type":"address"},{"internalType":"uint256","name":"value","type":"uint256"},{"internalType":"uint256","name":"validAfter","type":"uint256"},{"internalType":"uint256","name":"validBefore","type":"uint256"},{"internalType":"bytes32","name":"nonce","type":"bytes32"},{"internalType":"uint8","name":"v","type":"uint8"},{"internalType":"bytes32","name":"r","type":"bytes32"},{"internalType":"bytes32","name":"s","type":"bytes32"}],"name":"withdrawWithAuthorization","outputs":[],"stateMutability":"nonpayable","type":"function"}]""" + self.erc1155_set_approval = """[{"inputs": [{ "internalType": "address", "name": "operator", "type": "address" },{ "internalType": "bool", "name": "approved", "type": "bool" }],"name": "setApprovalForAll","outputs": [],"stateMutability": "nonpayable","type": "function"}]""" + + self.usdc_address = "0x2791Bca1f2de4661ED88A30C99A7a9449Aa84174" + self.ctf_address = "0x4D97DCd97eC945f40cF65F87097ACe5EA0476045" + + self.web3 = Web3(Web3.HTTPProvider(self.polygon_rpc)) + self.web3.middleware_onion.inject(geth_poa_middleware, layer=0) + + self.usdc = self.web3.eth.contract( + address=self.usdc_address, abi=self.erc20_approve + ) + self.ctf = self.web3.eth.contract( + address=self.ctf_address, abi=self.erc1155_set_approval + ) + + self._init_api_keys() + self._init_approvals(False) + + def _init_api_keys(self) -> None: + self.client = ClobClient( + self.clob_url, key=self.private_key, chain_id=self.chain_id + ) + self.credentials = self.client.create_or_derive_api_creds() + self.client.set_api_creds(self.credentials) + # print(self.credentials) + + def _init_approvals(self, run: bool = False) -> None: + if not run: + return + + priv_key = self.private_key + pub_key = self.get_address_for_private_key() + chain_id = self.chain_id + web3 = self.web3 + nonce = web3.eth.get_transaction_count(pub_key) + usdc = self.usdc + ctf = self.ctf + + # CTF Exchange + raw_usdc_approve_txn = usdc.functions.approve( + "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", int(MAX_INT, 0) + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_usdc_approve_tx = web3.eth.account.sign_transaction( + raw_usdc_approve_txn, private_key=priv_key + ) + send_usdc_approve_tx = web3.eth.send_raw_transaction( + signed_usdc_approve_tx.raw_transaction + ) + usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_usdc_approve_tx, 600 + ) + print(usdc_approve_tx_receipt) + + nonce = web3.eth.get_transaction_count(pub_key) + + raw_ctf_approval_txn = ctf.functions.setApprovalForAll( + "0x4bFb41d5B3570DeFd03C39a9A4D8dE6Bd8B8982E", True + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_ctf_approval_tx = web3.eth.account.sign_transaction( + raw_ctf_approval_txn, private_key=priv_key + ) + send_ctf_approval_tx = web3.eth.send_raw_transaction( + signed_ctf_approval_tx.raw_transaction + ) + ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_ctf_approval_tx, 600 + ) + print(ctf_approval_tx_receipt) + + nonce = web3.eth.get_transaction_count(pub_key) + + # Neg Risk CTF Exchange + raw_usdc_approve_txn = usdc.functions.approve( + "0xC5d563A36AE78145C45a50134d48A1215220f80a", int(MAX_INT, 0) + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_usdc_approve_tx = web3.eth.account.sign_transaction( + raw_usdc_approve_txn, private_key=priv_key + ) + send_usdc_approve_tx = web3.eth.send_raw_transaction( + signed_usdc_approve_tx.raw_transaction + ) + usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_usdc_approve_tx, 600 + ) + print(usdc_approve_tx_receipt) + + nonce = web3.eth.get_transaction_count(pub_key) + + raw_ctf_approval_txn = ctf.functions.setApprovalForAll( + "0xC5d563A36AE78145C45a50134d48A1215220f80a", True + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_ctf_approval_tx = web3.eth.account.sign_transaction( + raw_ctf_approval_txn, private_key=priv_key + ) + send_ctf_approval_tx = web3.eth.send_raw_transaction( + signed_ctf_approval_tx.raw_transaction + ) + ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_ctf_approval_tx, 600 + ) + print(ctf_approval_tx_receipt) + + nonce = web3.eth.get_transaction_count(pub_key) + + # Neg Risk Adapter + raw_usdc_approve_txn = usdc.functions.approve( + "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", int(MAX_INT, 0) + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_usdc_approve_tx = web3.eth.account.sign_transaction( + raw_usdc_approve_txn, private_key=priv_key + ) + send_usdc_approve_tx = web3.eth.send_raw_transaction( + signed_usdc_approve_tx.raw_transaction + ) + usdc_approve_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_usdc_approve_tx, 600 + ) + print(usdc_approve_tx_receipt) + + nonce = web3.eth.get_transaction_count(pub_key) + + raw_ctf_approval_txn = ctf.functions.setApprovalForAll( + "0xd91E80cF2E7be2e162c6513ceD06f1dD0dA35296", True + ).build_transaction({"chainId": chain_id, "from": pub_key, "nonce": nonce}) + signed_ctf_approval_tx = web3.eth.account.sign_transaction( + raw_ctf_approval_txn, private_key=priv_key + ) + send_ctf_approval_tx = web3.eth.send_raw_transaction( + signed_ctf_approval_tx.raw_transaction + ) + ctf_approval_tx_receipt = web3.eth.wait_for_transaction_receipt( + send_ctf_approval_tx, 600 + ) + print(ctf_approval_tx_receipt) + + def get_all_markets(self) -> "list[SimpleMarket]": + markets = [] + res = httpx.get(self.gamma_markets_endpoint) + if res.status_code == 200: + for market in res.json(): + try: + market_data = self.map_api_to_market(market) + markets.append(SimpleMarket(**market_data)) + except: + pass + return markets + + def filter_markets_for_trading(self, markets: "list[SimpleMarket]"): + tradeable_markets = [] + for market in markets: + if market.active and market.deployed: + tradeable_markets.append(market) + return tradeable_markets + + def get_market(self, token_id: str) -> SimpleMarket: + params = {"clob_token_ids": token_id} + res = httpx.get(self.gamma_markets_endpoint, params=params) + if res.status_code == 200: + data = res.json() + market = data[0] + return self.map_api_to_market(market, token_id) + + def map_api_to_market(self, market, token_id: str = "") -> SimpleMarket: + market = { + "id": int(market["id"]), + "question": market["question"], + "end": market["endDate"], + "description": market["description"], + "active": market["active"], + # "deployed": market["deployed"], + "funded": market["funded"], + "rewardsMinSize": float(market["rewardsMinSize"]), + "rewardsMaxSpread": float(market["rewardsMaxSpread"]), + # "volume": float(market["volume"]), + "spread": float(market["spread"]), + "outcomes": str(market["outcomes"]), + "outcome_prices": str(market["outcomePrices"]), + "clob_token_ids": str(market["clobTokenIds"]), + } + if token_id: + market["clob_token_ids"] = token_id + return market + + def get_all_events(self) -> "list[SimpleEvent]": + events = [] + res = httpx.get(self.gamma_events_endpoint) + if res.status_code == 200: + for event in res.json(): + try: + event_data = self.map_api_to_event(event) + events.append(SimpleEvent(**event_data)) + except: + pass + return events + + def map_api_to_event(self, event) -> SimpleEvent: + return { + "id": int(event["id"]), + "ticker": event["ticker"], + "slug": event["slug"], + "title": event["title"], + "description": event["description"], + "active": event["active"], + "closed": event["closed"], + "archived": event["archived"], + "new": event["new"], + "featured": event["featured"], + "restricted": event["restricted"], + "end": event["endDate"], + "markets": ",".join([x["id"] for x in event["markets"]]), + } + + def filter_events_for_trading( + self, events: "list[SimpleEvent]" + ) -> "list[SimpleEvent]": + tradeable_events = [] + for event in events: + if ( + event.active + and not event.restricted + and not event.archived + and not event.closed + ): + tradeable_events.append(event) + return tradeable_events + + def get_all_tradeable_events(self) -> "list[SimpleEvent]": + all_events = self.get_all_events() + return self.filter_events_for_trading(all_events) + + def get_sampling_simplified_markets(self) -> "list[SimpleEvent]": + markets = [] + raw_sampling_simplified_markets = self.client.get_sampling_simplified_markets() + for raw_market in raw_sampling_simplified_markets["data"]: + token_one_id = raw_market["tokens"][0]["token_id"] + market = self.get_market(token_one_id) + markets.append(market) + return markets + + def get_orderbook(self, token_id: str) -> OrderBookSummary: + return self.client.get_order_book(token_id) + + def get_orderbook_price(self, token_id: str) -> float: + return float(self.client.get_price(token_id)) + + def get_address_for_private_key(self): + account = self.w3.eth.account.from_key(str(self.private_key)) + return account.address + + def build_order( + self, + market_token: str, + amount: float, + nonce: str = str(round(time.time())), # for cancellations + side: str = "BUY", + expiration: str = "0", # timestamp after which order expires + ): + signer = Signer(self.private_key) + builder = OrderBuilder(self.exchange_address, self.chain_id, signer) + + buy = side == "BUY" + side = 0 if buy else 1 + maker_amount = amount if buy else 0 + taker_amount = amount if not buy else 0 + order_data = OrderData( + maker=self.get_address_for_private_key(), + tokenId=market_token, + makerAmount=maker_amount, + takerAmount=taker_amount, + feeRateBps="1", + nonce=nonce, + side=side, + expiration=expiration, + ) + order = builder.build_signed_order(order_data) + return order + + def execute_order(self, price, size, side, token_id) -> str: + return self.client.create_and_post_order( + OrderArgs(price=price, size=size, side=side, token_id=token_id) + ) + + def execute_market_order(self, market, amount) -> str: + token_id = ast.literal_eval(market[0].dict()["metadata"]["clob_token_ids"])[1] + order_args = MarketOrderArgs( + token_id=token_id, + amount=amount, + ) + signed_order = self.client.create_market_order(order_args) + print("Execute market order... signed_order ", signed_order) + resp = self.client.post_order(signed_order, orderType=OrderType.FOK) + print(resp) + print("Done!") + return resp + + def get_usdc_balance(self) -> float: + balance_res = self.usdc.functions.balanceOf( + self.get_address_for_private_key() + ).call() + return float(balance_res / 10e5) + + +def test(): + host = "https://clob.polymarket.com" + key = os.getenv("POLYGON_WALLET_PRIVATE_KEY") + print(key) + chain_id = POLYGON + + # Create CLOB client and get/set API credentials + client = ClobClient(host, key=key, chain_id=chain_id) + client.set_api_creds(client.create_or_derive_api_creds()) + + creds = ApiCreds( + api_key=os.getenv("CLOB_API_KEY"), + api_secret=os.getenv("CLOB_SECRET"), + api_passphrase=os.getenv("CLOB_PASS_PHRASE"), + ) + chain_id = AMOY + client = ClobClient(host, key=key, chain_id=chain_id, creds=creds) + + print(client.get_markets()) + print(client.get_simplified_markets()) + print(client.get_sampling_markets()) + print(client.get_sampling_simplified_markets()) + print(client.get_market("condition_id")) + + print("Done!") + + +def gamma(): + url = "https://gamma-com" + markets_url = url + "/markets" + res = httpx.get(markets_url) + code = res.status_code + if code == 200: + markets: list[SimpleMarket] = [] + data = res.json() + for market in data: + try: + market_data = { + "id": int(market["id"]), + "question": market["question"], + # "start": market['startDate'], + "end": market["endDate"], + "description": market["description"], + "active": market["active"], + "deployed": market["deployed"], + "funded": market["funded"], + # "orderMinSize": float(market['orderMinSize']) if market['orderMinSize'] else 0, + # "orderPriceMinTickSize": float(market['orderPriceMinTickSize']), + "rewardsMinSize": float(market["rewardsMinSize"]), + "rewardsMaxSpread": float(market["rewardsMaxSpread"]), + "volume": float(market["volume"]), + "spread": float(market["spread"]), + "outcome_a": str(market["outcomes"][0]), + "outcome_b": str(market["outcomes"][1]), + "outcome_a_price": str(market["outcomePrices"][0]), + "outcome_b_price": str(market["outcomePrices"][1]), + } + markets.append(SimpleMarket(**market_data)) + except Exception as err: + print(f"error {err} for market {id}") + pdb.set_trace() + else: + raise Exception() + + +def main(): + # auth() + # test() + # gamma() + print(Polymarket().get_all_events()) + + +if __name__ == "__main__": + load_dotenv() + + p = Polymarket() + + # k = p.get_api_key() + # m = p.get_sampling_simplified_markets() + + # print(m) + # m = p.get_market('11015470973684177829729219287262166995141465048508201953575582100565462316088') + + # t = m[0]['token_id'] + # o = p.get_orderbook(t) + # pdb.set_trace() + + """ + + (Pdb) pprint(o) + OrderBookSummary( + market='0x26ee82bee2493a302d21283cb578f7e2fff2dd15743854f53034d12420863b55', + asset_id='11015470973684177829729219287262166995141465048508201953575582100565462316088', + bids=[OrderSummary(price='0.01', size='600005'), OrderSummary(price='0.02', size='200000'), ... + asks=[OrderSummary(price='0.99', size='100000'), OrderSummary(price='0.98', size='200000'), ... + ) + + """ + + # https://polygon-rpc.com + + test_market_token_id = ( + "101669189743438912873361127612589311253202068943959811456820079057046819967115" + ) + test_market_data = p.get_market(test_market_token_id) + + # test_size = 0.0001 + test_size = 1 + test_side = BUY + test_price = float(ast.literal_eval(test_market_data["outcome_prices"])[0]) + + # order = p.execute_order( + # test_price, + # test_size, + # test_side, + # test_market_token_id, + # ) + + # order = p.execute_market_order(test_price, test_market_token_id) + + balance = p.get_usdc_balance() diff --git a/connectors/recorder.py b/connectors/recorder.py new file mode 100644 index 0000000..199cef7 --- /dev/null +++ b/connectors/recorder.py @@ -0,0 +1 @@ +# record the trades placed here diff --git a/connectors/search.py b/connectors/search.py new file mode 100644 index 0000000..0e1a2fe --- /dev/null +++ b/connectors/search.py @@ -0,0 +1,16 @@ +import os + +from dotenv import load_dotenv +from tavily import TavilyClient + +load_dotenv() + +openai_api_key = os.getenv("OPEN_API_KEY") +tavily_api_key = os.getenv("TAVILY_API_KEY") + +# Step 1. Instantiating your TavilyClient +tavily_client = TavilyClient(api_key=tavily_api_key) + +# Step 2. Executing a context search query +context = tavily_client.get_search_context(query="Will Biden drop out of the race?") +# Step 3. That's it! You now have a context string that you can feed directly into your RAG Application diff --git a/requirements.txt b/requirements.txt new file mode 100644 index 0000000..b096c0a --- /dev/null +++ b/requirements.txt @@ -0,0 +1,170 @@ +aiohappyeyeballs==2.3.4 +aiohttp==3.10.0 +aiosignal==1.3.1 +annotated-types==0.7.0 +anyio==4.4.0 +asgiref==3.8.1 +asttokens==2.4.1 +async-timeout==4.0.3 +attrs==23.2.0 +backoff==2.2.1 +bcrypt==4.2.0 +bitarray==2.9.2 +build==1.2.1 +cachetools==5.4.0 +certifi==2024.7.4 +cfgv==3.4.0 +charset-normalizer==3.3.2 +chroma-hnswlib==0.7.6 +chromadb==0.5.5 +ckzg==1.0.2 +click==8.1.7 +coloredlogs==15.0.1 +cytoolz==0.12.3 +dataclasses-json==0.6.7 +Deprecated==1.2.14 +devtools==0.12.2 +distlib==0.3.8 +distro==1.9.0 +dnspython==2.6.1 +eip712-structs==1.1.0 +email_validator==2.2.0 +eth-account==0.13.1 +eth-hash==0.7.0 +eth-keyfile==0.8.1 +eth-keys==0.5.1 +eth-rlp==2.1.0 +eth-typing==4.4.0 +eth-utils==4.1.1 +eth_abi==5.1.0 +exceptiongroup==1.2.2 +executing==2.0.1 +fastapi==0.111.0 +fastapi-cli==0.0.4 +filelock==3.15.4 +flatbuffers==24.3.25 +frozenlist==1.4.1 +fsspec==2024.6.1 +google-auth==2.32.0 +googleapis-common-protos==1.63.2 +grpcio==1.65.2 +h11==0.14.0 +hexbytes==1.2.1 +httpcore==1.0.5 +httptools==0.6.1 +httpx==0.27.0 +huggingface-hub==0.24.5 +humanfriendly==10.0 +identify==2.6.0 +idna==3.7 +importlib_metadata==8.0.0 +importlib_resources==6.4.0 +iniconfig==2.0.0 +Jinja2==3.1.4 +jq==1.7.0 +jsonpatch==1.33 +jsonpointer==3.0.0 +jsonschema==4.23.0 +jsonschema-specifications==2023.12.1 +kubernetes==30.1.0 +langchain==0.2.11 +langchain-chroma==0.1.2 +langchain-community==0.2.10 +langchain-core==0.2.26 +langchain-openai==0.1.19 +langchain-text-splitters==0.2.2 +langchainhub==0.1.20 +langgraph==0.1.17 +langsmith==0.1.94 +lru-dict==1.3.0 +markdown-it-py==3.0.0 +MarkupSafe==2.1.5 +marshmallow==3.21.3 +mdurl==0.1.2 +mmh3==4.1.0 +monotonic==1.6 +mpmath==1.3.0 +multidict==6.0.5 +mypy-extensions==1.0.0 +newsapi-python==0.2.7 +nodeenv==1.9.1 +numpy==1.26.4 +oauthlib==3.2.2 +onnxruntime==1.18.1 +openai==1.37.1 +opentelemetry-api==1.26.0 +opentelemetry-exporter-otlp-proto-common==1.26.0 +opentelemetry-exporter-otlp-proto-grpc==1.26.0 +opentelemetry-instrumentation==0.47b0 +opentelemetry-instrumentation-asgi==0.47b0 +opentelemetry-instrumentation-fastapi==0.47b0 +opentelemetry-proto==1.26.0 +opentelemetry-sdk==1.26.0 +opentelemetry-semantic-conventions==0.47b0 +opentelemetry-util-http==0.47b0 +orjson==3.10.6 +overrides==7.7.0 +packaging==24.1 +parsimonious==0.10.0 +platformdirs==4.2.2 +pluggy==1.5.0 +poly_eip712_structs==0.0.1 +posthog==3.5.0 +pre-commit==3.8.0 +protobuf==4.25.4 +py_clob_client==0.17.5 +py_order_utils==0.3.2 +pyasn1==0.6.0 +pyasn1_modules==0.4.0 +pycryptodome==3.20.0 +pydantic==2.8.2 +pydantic_core==2.20.1 +Pygments==2.18.0 +PyPika==0.48.9 +pyproject_hooks==1.1.0 +pysha3==1.0.2 +pytest==8.3.2 +python-dateutil==2.9.0.post0 +python-dotenv==1.0.1 +python-multipart==0.0.9 +pyunormalize==15.1.0 +PyYAML==6.0.1 +referencing==0.35.1 +regex==2024.7.24 +requests==2.32.3 +requests-oauthlib==2.0.0 +rich==13.7.1 +rlp==4.0.1 +rpds-py==0.19.1 +rsa==4.9 +scheduler==0.8.7 +shellingham==1.5.4 +six==1.16.0 +sniffio==1.3.1 +SQLAlchemy==2.0.31 +starlette==0.37.2 +sympy==1.13.1 +tavily-python==0.3.5 +tenacity==8.5.0 +tiktoken==0.7.0 +tokenizers==0.19.1 +tomli==2.0.1 +toolz==0.12.1 +tqdm==4.66.4 +typeguard==4.3.0 +typer==0.12.3 +types-requests==2.32.0.20240712 +typing-inspect==0.9.0 +typing_extensions==4.12.2 +ujson==5.10.0 +urllib3==2.2.2 +uvicorn==0.30.3 +uvloop==0.19.0 +virtualenv==20.26.3 +watchfiles==0.22.0 +web3==6.11.0 +websocket-client==1.8.0 +websockets==12.0 +wrapt==1.16.0 +yarl==1.9.4 +zipp==3.19.2 diff --git a/scripts/bash/build-docker.sh b/scripts/bash/build-docker.sh new file mode 100755 index 0000000..75d309e --- /dev/null +++ b/scripts/bash/build-docker.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker build -f Dockerfile --tag polymarket-agents:latest . diff --git a/scripts/bash/install.sh b/scripts/bash/install.sh new file mode 100755 index 0000000..f6a0d65 --- /dev/null +++ b/scripts/bash/install.sh @@ -0,0 +1 @@ +pip install -r requirements.txt diff --git a/scripts/bash/run-docker-dev.sh b/scripts/bash/run-docker-dev.sh new file mode 100755 index 0000000..f2a19cc --- /dev/null +++ b/scripts/bash/run-docker-dev.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run --rm -it -v $(pwd):/home polymarket-agents:latest bash diff --git a/scripts/bash/run-docker.sh b/scripts/bash/run-docker.sh new file mode 100755 index 0000000..2aa0140 --- /dev/null +++ b/scripts/bash/run-docker.sh @@ -0,0 +1,2 @@ +#!/bin/bash +docker run --rm -it polymarket-agents:latest diff --git a/scripts/bash/start-dev.sh b/scripts/bash/start-dev.sh new file mode 100755 index 0000000..cd2fd18 --- /dev/null +++ b/scripts/bash/start-dev.sh @@ -0,0 +1,2 @@ +python setup.py +fastapi dev server.py diff --git a/scripts/python/cli.py b/scripts/python/cli.py new file mode 100644 index 0000000..1cae4f9 --- /dev/null +++ b/scripts/python/cli.py @@ -0,0 +1,125 @@ +import typer +from devtools import pprint + +from connectors.polymarket import Polymarket +from application import executor, prompts +from connectors.chroma import PolymarketRAG +from connectors.news import News +from application.trade import Trader +from application.creator import Creator + +app = typer.Typer() +polymarket = Polymarket() +newsapi_client = News() +polymarket_rag = PolymarketRAG() + + +@app.command() +def get_all_markets(limit: int = 5, sort_by: str = "volume") -> None: + """ + Query Polymarket's markets + """ + print(f"limit: int = {limit}, sort_by: str = {sort_by}") + markets = polymarket.get_all_markets() + markets = polymarket.filter_markets_for_trading(markets) + if sort_by == "volume": + markets = sorted(markets, key=lambda x: x.volume, reverse=True) + markets = markets[:limit] + pprint(markets) + + +@app.command() +def get_relevant_news(keywords: str) -> None: + """ + Use NewsAPI to query the internet + """ + articles = newsapi_client.get_articles_for_cli_keywords(keywords) + pprint(articles) + + +@app.command() +def get_all_events(limit: int = 5, sort_by: str = "number_of_markets") -> None: + """ + Query Polymarket's events + """ + print(f"limit: int = {limit}, sort_by: str = {sort_by}") + events = polymarket.get_all_events() + events = polymarket.filter_events_for_trading(events) + if sort_by == "number_of_markets": + events = sorted(events, key=lambda x: len(x.markets), reverse=True) + events = events[:limit] + pprint(events) + + +@app.command() +def create_local_markets_rag(local_directory: str) -> None: + """ + Create a local markets database for RAG + """ + polymarket_rag.create_local_markets_rag(local_directory=local_directory) + + +@app.command() +def query_local_markets_rag(vector_db_directory: str, query: str) -> None: + """ + RAG over a local database of Polymarket's events + """ + response = polymarket_rag.query_local_markets_rag( + local_directory=vector_db_directory, query=query + ) + pprint(response) + + +@app.command() +def ask_superforecaster(event_title: str, market_question: str, outcome: str) -> None: + """ + Ask a superforecaster about a trade + """ + print( + f"event: str = {event_title}, question: str = {market_question}, outcome (usually yes or no): str = {outcome}" + ) + response = executor.get_superforecast( + event_title=event_title, market_question=market_question, outcome=outcome + ) + print(f"Response:{response}") + + +@app.command() +def create_market() -> None: + """ + Format a request to create a market on Polymarket + """ + c = Creator() + market_description = c.one_best_market() + print(f"market_description: str = {market_description}") + + +@app.command() +def ask_llm(user_input: str) -> None: + """ + Ask a question to the LLM and get a response. + """ + response = executor.get_llm_response(user_input) + print(f"LLM Response: {response}") + + +@app.command() +def ask_polymarket_llm(user_input: str) -> None: + """ + What types of markets do you want trade? + """ + response = executor.get_polymarket_llm(user_input=user_input) + print(f"LLM + current markets&events response: {response}") + + +@app.command() +def run_autonomous_trader() -> None: + """ + Let an autonomous system trade for you. + """ + trader = Trader() + trader.one_best_trade() + + +if __name__ == "__main__": + app() diff --git a/scripts/python/server.py b/scripts/python/server.py new file mode 100644 index 0000000..46743b7 --- /dev/null +++ b/scripts/python/server.py @@ -0,0 +1,27 @@ +from typing import Union +from fastapi import FastAPI + +app = FastAPI() + + +@app.get("/") +def read_root(): + return {"Hello": "World"} + + +@app.get("/items/{item_id}") +def read_item(item_id: int, q: Union[str, None] = None): + return {"item_id": item_id, "q": q} + + +@app.get("/trades/{trade_id}") +def read_trade(trade_id: int, q: Union[str, None] = None): + return {"trade_id": trade_id, "q": q} + + +@app.get("/markets/{market_id}") +def read_market(market_id: int, q: Union[str, None] = None): + return {"market_id": market_id, "q": q} + + +# post new prompt diff --git a/scripts/python/setup.py b/scripts/python/setup.py new file mode 100644 index 0000000..1322cc0 --- /dev/null +++ b/scripts/python/setup.py @@ -0,0 +1,4 @@ +import os +from dotenv import load_dotenv + +load_dotenv() diff --git a/test/test.py b/test/test.py new file mode 100644 index 0000000..bf48558 --- /dev/null +++ b/test/test.py @@ -0,0 +1,30 @@ +""" +% python test/test.py +... +---------------------------------------------------------------------- +Ran 3 tests in 0.000s + +OK +""" + +import unittest + + +class TestStringMethods(unittest.TestCase): + def test_upper(self): + self.assertEqual("foo".upper(), "FOO") + + def test_isupper(self): + self.assertTrue("FOO".isupper()) + self.assertFalse("Foo".isupper()) + + def test_split(self): + s = "hello world" + self.assertEqual(s.split(), ["hello", "world"]) + # check that s.split fails when the separator is not a string + with self.assertRaises(TypeError): + s.split(2) + + +if __name__ == "__main__": + unittest.main()