├── .gitignore ├── CHANGELOG.md ├── LICENSE.md ├── README.md ├── aitradingprototypebacktester ├── __main__.py ├── config_loader.py ├── data_downloader.py ├── strategy │ ├── common_enums.py │ ├── successive_strategy.py │ └── tb_enums.py └── strategy_manager.py ├── config.yaml.example ├── requirements.txt ├── sentiment_data └── sentiment_data.csv ├── setup.py └── strategy_configuration ├── aggressive.yaml.example ├── conservative.yaml.example └── standard.yaml.example /.gitignore: -------------------------------------------------------------------------------- 1 | .python-version 2 | .env 3 | .venv 4 | __pycache__ 5 | output 6 | market_data 7 | *.yaml -------------------------------------------------------------------------------- /CHANGELOG.md: -------------------------------------------------------------------------------- 1 | # Change log 2 | 3 | ## v1.0.4 - 2025-01-07 4 | 5 | - Pump dependencies 6 | 7 | ## v1.0.3 - 2024-04-17 8 | 9 | - Pump dependencies 10 | 11 | ## v1.0.2 - 2024-02-26 12 | 13 | - Pump dependencies 14 | 15 | ## v1.0.1 - 2023-10-06 16 | 17 | - Pump dependencies 18 | 19 | ## v1.0.0 - 2023-08-30 20 | 21 | - Initial release 22 | -------------------------------------------------------------------------------- /LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) [2023] 4 | 5 | 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: 6 | 7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software. 8 | 9 | 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. -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # AI Trading Prototype Backtester 2 | 3 | This project is a backtester for the sentiment-analysis cryptocurrency trading strategy that pairs with the live trading bot, [ai-trading-prototype](https://github.com/binance/ai-trading-prototype/). This backtester is designed to run accurate simulations for a given date range on a chosen trading symbol. 4 | 5 | ![Diagram](../assets/diagram.png?raw=true) 6 | 7 | ## Disclaimer 8 | 9 | This trading bot backtester does not provide financial advice or endorse trading strategies. Users assume all risks, as past results from historical data do not guarantee future returns; the creators bear no responsibility for losses. Please seek guidance from financial experts before trading or investing. By using this project you accept these conditions. 10 | 11 | ## Features 12 | 13 | * A flexible trading strategy builder. 14 | * Customisable Backtesting strategy. 15 | * Automated data downloader using `data.binance.vision`. 16 | * Detailed backtest results and performance assessment, including HTML visualisations of all trades. 17 | 18 | ## Installation 19 | 20 | 1. Clone the repository 21 | ``` 22 | git clone https://github.com/binance/ai-trading-prototype-backtester 23 | ``` 24 | 2. Move into the cloned directory 25 | ``` 26 | cd ai-trading-prototype-backtester 27 | ``` 28 | 3. Install dependencies 29 | ``` 30 | pip install -r requirements.txt 31 | ``` 32 | 33 | ## Usage 34 | ### Configuration 35 | 36 | All configurations are stored in `config.yaml.example`. You can specify: 37 | * `symbol`: The trading symbol/ pair. Example: `ETHUSDT`. 38 | * `kline_interval`: The interval of the candlesticks. Valid intervals are: `1m, 3m, 5m, 15m, 30m, 1h, 2h, 4h, 6h, 8h, 12h` or `1d` 39 | * `start_date` and `end_date`: The range of dates to backtest on. Format: `YYYY-MM-DD`. 40 | * `sentiment_data`: The path to the sentiment data file. Default: `./sentiment_data/sentiment_data.csv`. 41 | * `start_balance`: The starting balance in quote currency (eg. USDT). Default: `100000`. 42 | * `order_size`: The order size in base currency (eg. BTC). Default: `0.01`. 43 | * `total_quantity_limit`: The maximum quantity of base currency (eg. BTC) that can be held at any given time. Default: `1`. 44 | * `commission`: The trading fee/commission ratio. Default: `0.002`. 45 | * `logging_level`: The logging detail level. Default: `INFO`. 46 | 47 | Please also ensure that the sentiment data file `sentiment_data.csv` is present and formatted as `"headline source","headline collected timestamp (ms)","headline published timestamp (ms)","headline","sentiment"`. 48 | 49 | #### Strategy Configuration Profiles 50 | 51 | Within the `strategy_configuration` directory, there are 3 extra configuration files. Each file corresponds to a different strategy/risk level (Aggressive, Conservative and Standard). These can be used to quickly test different parameters with varying degrees of risk. 52 | 53 | ### Run the backtester as module 54 | 55 | ``` 56 | python -m aitradingprototypebacktester 57 | ``` 58 | 59 | #### How it works 60 | 61 | * During the backtesting process, the backtester checks `/sentiment_data/sentiment_data.csv` for a published headline at each kline/candlestick interval. 62 | * If a headline was published during the current time-period, it reads the sentiment of the headline. 63 | * By default, the backtester uses the `successive_strategy` which is defined in `aitradingprototypebacktester/strategy/successive_strategy.py`. The details of how this strategy works is as follows: 64 | * If the sentiment was "bullish" (meaning it is expected that the price would increase) it will attempt a BUY order of size = `order_size` of base currency, so long as the `total_quantity_limit` (max. quantity that can be held at any given time) has not yet been reached. 65 | * If the sentiment was "bearish" (meaning that a fall in price is expected), it will attempt to SELL `order_size` quantity of the base currency, so long as the current base currency balance is > 0. 66 | * Once the backtest is complete, it will return a detailed table of results along with an HTML visualisation of all the buys and sells plotted against the trading symbol's price throughout the period. 67 | * An image, `backtest_result.png`, will also be generated. This is a static view of the HTML visualisation. 68 | * If there is a position still open at the end of the backtest, it will be closed at the open price of the last kline in the backtesting period. 69 | 70 | ## Backtest Results 71 | 72 | * As outlined in the `How it works` section above, the backtester will output several files as a result of each backtest: 73 | * `output/raw/backtest_result.txt` - A summary of the backtest. 74 | * `output/raw/backtest_trades.txt` - A detailed list of each individual trade executed during the backtest. 75 | * `output/visualisation/dynamic_report.html` - HTML visualisation of all the buys and sells plotted against the trading symbol's price throughout the period. 76 | * `output/visualisation/backtest_result.png` - A static view of the HTML visualisation (image below). 77 | 78 | ![Backtest Result](../assets/backtest_result.png?raw=true) -------------------------------------------------------------------------------- /aitradingprototypebacktester/__main__.py: -------------------------------------------------------------------------------- 1 | import logging 2 | import os 3 | 4 | import bokeh 5 | from backtesting import Backtest 6 | 7 | from aitradingprototypebacktester.config_loader import load_config 8 | from aitradingprototypebacktester.data_downloader import download_binance_data 9 | from aitradingprototypebacktester.strategy_manager import StrategyManager 10 | 11 | 12 | def initialise_config(config): 13 | """ 14 | Loads configuration values from config.yaml and returns neccessary variables 15 | """ 16 | symbol = config["symbol"] 17 | kline_interval = config["kline_interval"] 18 | start_date = config["start_date"] 19 | end_date = config["end_date"] 20 | start_balance = int(config["start_balance"]) 21 | commission = float(config["commission"]) 22 | logging_level = config["logging_level"] 23 | return ( 24 | symbol, 25 | kline_interval, 26 | start_date, 27 | end_date, 28 | start_balance, 29 | commission, 30 | logging_level, 31 | ) 32 | 33 | 34 | def create_directories(): 35 | """ 36 | Check if "output/visualisation" and "output/raw" directories exist, if not create them 37 | """ 38 | if not os.path.exists("output/visualisation"): 39 | os.makedirs("output/visualisation") 40 | if not os.path.exists("output/raw"): 41 | os.makedirs("output/raw") 42 | 43 | 44 | def write_results(results): 45 | """ 46 | Write the backtest results and individual trades to output/raw directory separate text files. 47 | """ 48 | create_directories() 49 | with open("output/raw/backtest_result.txt", "w") as file: 50 | file.write(str(results)) 51 | with open("output/raw/backtest_trades.txt", "w") as file: 52 | file.write(str(results._trades)) 53 | 54 | 55 | def convert_from_satoshi(results, bt): 56 | """ 57 | Convert the columns `Size`, `EntryPrice`, `ExitPrice`, `Open`, `High`, `Low`, `Close`, and `Volume` from satoshis back to their original values. 58 | 59 | Args: 60 | results (object): The `results` object that contains the trades data. 61 | bt (object): The `bt` object that contains the data for the strategy. 62 | 63 | Returns: 64 | tuple: A tuple containing the modified `results` object and the modified `bt` object. 65 | """ 66 | # Convert columns: Size, EntryPrice, ExitPrice, back from satoshis: 67 | results._trades = results._trades.assign( 68 | Size=results._trades.Size / 1e6, 69 | EntryPrice=results._trades.EntryPrice * 1e6, 70 | ExitPrice=results._trades.ExitPrice * 1e6, 71 | ) 72 | bt._data = bt._data.assign( 73 | Open=results._strategy._data._Data__df.Open * 1e6, 74 | High=results._strategy._data._Data__df.High * 1e6, 75 | Low=results._strategy._data._Data__df.Low * 1e6, 76 | Close=results._strategy._data._Data__df.Close * 1e6, 77 | Volume=results._strategy._data._Data__df.Volume / 1e6, 78 | ) 79 | return results, bt 80 | 81 | 82 | if __name__ == "__main__": 83 | """ 84 | - Loads configuration values from config.yaml 85 | - Downloads binance kline/candlestick data from data.binance.vision 86 | - Creates a Backtest instance with the trading data and trading strategy 87 | - Runs backtest, outputs results and creates html + png visualisation of results 88 | - Saves raw backtest results and individual trades 89 | """ 90 | config = load_config("config.yaml") 91 | ( 92 | symbol, 93 | kline_interval, 94 | start_date, 95 | end_date, 96 | start_balance, 97 | commission, 98 | logging_level, 99 | ) = initialise_config(config) 100 | logging.basicConfig(level=logging_level, format="%(message)s") # Initialise Logging 101 | 102 | kline_data = download_binance_data( 103 | symbol, kline_interval, start_date, end_date, logging_level 104 | ) 105 | kline_data = (kline_data / 1e6).assign( 106 | Volume=kline_data.Volume * 1e6 # Convert relevant columns to satoshis 107 | ) 108 | 109 | bt = Backtest( 110 | kline_data, 111 | StrategyManager, 112 | cash=start_balance, 113 | commission=commission, 114 | exclusive_orders=False, 115 | ) 116 | logging.info("Running Backtest...") 117 | 118 | results = bt.run() 119 | results, bt = convert_from_satoshi( 120 | results, bt 121 | ) # Convert relevant columns back from satoshis 122 | logging.info(results) 123 | 124 | write_results(results) 125 | plot = bt.plot(resample=False, filename="output/visualisation/dynamic_report.html") 126 | bokeh.io.export.export_png( 127 | plot, filename="output/visualisation/backtest_result.png" 128 | ) 129 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/config_loader.py: -------------------------------------------------------------------------------- 1 | import yaml 2 | 3 | 4 | def load_config(file_path: str) -> dict: 5 | """ 6 | Load configuration file and return the content as a dictionary. 7 | """ 8 | with open(file_path, "r") as file: 9 | config = yaml.safe_load(file) 10 | return config 11 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/data_downloader.py: -------------------------------------------------------------------------------- 1 | import datetime 2 | import logging 3 | import os 4 | from io import BytesIO 5 | from zipfile import ZipFile 6 | 7 | import pandas as pd 8 | import requests 9 | 10 | 11 | def get_kline_data(symbol, kline_interval, date_str): 12 | """ 13 | Fetches the binance data for given symbol and kline-interval for a particular day 14 | """ 15 | url = f"https://data.binance.vision/data/spot/daily/klines/{symbol.upper()}/{kline_interval}/{symbol.upper()}-{kline_interval}-{date_str}.zip" 16 | response = requests.get(url) 17 | return response 18 | 19 | 20 | def parse_data(response): 21 | """ 22 | Parses the fetched data and appends it into the list 'data' 23 | """ 24 | data = [] 25 | with ZipFile(BytesIO(response.content)) as zip_file: 26 | for file in zip_file.namelist(): 27 | with zip_file.open(file) as f: 28 | df = pd.read_csv(f, usecols=range(6)) 29 | df.columns = ["Timestamp", "Open", "High", "Low", "Close", "Volume"] 30 | data.append(df) 31 | return data 32 | 33 | 34 | def process_data(data): 35 | """ 36 | Processes the list into a DataFrame 37 | """ 38 | df = pd.concat(data) 39 | df.columns = ["Timestamp", "Open", "High", "Low", "Close", "Volume"] 40 | df["Timestamp"] = pd.to_datetime(df["Timestamp"], unit="ms") 41 | df.set_index("Timestamp", inplace=True) 42 | df.sort_index(ascending=True, inplace=True) 43 | return df 44 | 45 | 46 | def load_csv_data(file_path): 47 | """ 48 | Loads the DataFrame from the specified csv file. 49 | """ 50 | return pd.read_csv(file_path, index_col=[0]) 51 | 52 | 53 | def download_binance_data(symbol, kline_interval, start_date, end_date, logging_level): 54 | """ 55 | Downloads Binance data for a given symbol and date range. 56 | 57 | Args: 58 | symbol (str): The symbol to download data for. 59 | kline_interval (str): The interval of the kline data. 60 | start_date (datetime.date): The start date of the data to download. 61 | end_date (datetime.date): The end date of the data to download. 62 | """ 63 | logging.basicConfig(level=logging_level, format="%(message)s") # Initialise Logging 64 | market_data_path = f"market_data/{symbol.upper()}/{kline_interval}" 65 | 66 | # Check if the downloads directory exists, create it if it doesn't 67 | if not os.path.exists(market_data_path): 68 | os.makedirs(market_data_path) 69 | 70 | data = [] 71 | total_days = (end_date - start_date).days + 1 72 | downloaded_days = 0 73 | 74 | while start_date <= end_date: 75 | date_str = start_date.strftime("%Y-%m-%d") 76 | file_path = f"{market_data_path}/{date_str}.csv" 77 | 78 | # Check if data is already downloaded 79 | if os.path.isfile(file_path): 80 | df = load_csv_data(file_path) 81 | data.append(df) 82 | logging.info(f"Loaded data for {date_str} from disk.") 83 | downloaded_days += 1 84 | else: 85 | response = get_kline_data(symbol, kline_interval, date_str) 86 | 87 | if response.status_code == 200: 88 | data += parse_data(response) 89 | 90 | # Save processed data to csv file 91 | data[-1].to_csv(file_path) 92 | 93 | downloaded_days += 1 94 | logging.info( 95 | f"Downloaded and saved kline data for {date_str}\nProgress: {downloaded_days}/{total_days} days ({(downloaded_days/total_days)*100:.2f}%) downloaded.\n" 96 | ) 97 | else: 98 | logging.error( 99 | f"Failed to download kline data for date {date_str}. Status code: {response.status_code}" 100 | ) 101 | downloaded_days += 1 102 | pass 103 | 104 | start_date += datetime.timedelta(days=1) 105 | return process_data(data) 106 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/strategy/common_enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class Sentiment(Enum): 5 | BULLISH = "bullish" 6 | BEARISH = "bearish" 7 | UNKNOWN = "unknown" 8 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/strategy/successive_strategy.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | from aitradingprototypebacktester.strategy.common_enums import Sentiment 4 | from aitradingprototypebacktester.strategy.tb_enums import ( 5 | OrderAction, 6 | OrderSide, 7 | OrderType, 8 | ) 9 | 10 | from aitradingprototypebacktester.config_loader import load_config 11 | 12 | logger = logging.getLogger(__name__) 13 | 14 | 15 | class SuccessiveStrategy: 16 | """ 17 | Successive Strategy 18 | ------------------- 19 | Based on the received sentiment, the strategy will decide whether to place an order or not. 20 | The strategy logic is: 21 | - BUY order, when sentiment is `bullish` and `total_quantity_limit` won't be exceeded. 22 | - SELL order, when sentiment is `bearish` and we have `holding_quantity` to sell. 23 | - Skip order, when the above conditions are not met or when the sentiment is `unknown`/invalid. 24 | Note: 25 | - The strategy is designed to be used with a single trading pair. 26 | - `total_quantity_limit` - The upper limit on the total quantity of base asset that can be held via the accumulation of BUY orders at any given point. 27 | - `holding_quantity` - The total base asset quantity that is incremented or decremented by trades net quantity. 28 | """ 29 | 30 | def __init__(self, trading_specs: dict): 31 | self.symbol = trading_specs["symbol"] 32 | self.order_qty = trading_specs["order_quantity"] 33 | self.total_qty_limit = trading_specs["total_quantity_limit"] 34 | 35 | def order_strategy(self, sentiment: str, holding_quantity: float) -> dict: 36 | """ 37 | This method implements the strategy logic mentioned in the class docstring. 38 | If strategy is to place order, returns `OrderAction.POST_ORDER` with the order arguments. 39 | If strategy is to skip placing order, returns `OrderAction.SKIP_ORDER` with a reason. 40 | """ 41 | 42 | if sentiment == Sentiment.BULLISH.value: 43 | if (holding_quantity + self.order_qty) <= self.total_qty_limit: 44 | return { 45 | "action": OrderAction.POST_ORDER, 46 | "order": { 47 | "symbol": self.symbol, 48 | "side": OrderSide.BUY.value, 49 | "type": OrderType.MARKET.value, 50 | "quantity": self.order_qty, 51 | }, 52 | } 53 | else: 54 | return { 55 | "action": OrderAction.SKIP_ORDER, 56 | "reason": OrderAction.SKIP_ORDER.value.format( 57 | "the total quantity limit ({}) is, or would be, exceeded".format( 58 | self.total_qty_limit 59 | ) 60 | ), 61 | } 62 | 63 | elif sentiment == Sentiment.BEARISH.value: 64 | if holding_quantity - self.order_qty >= 0: 65 | return { 66 | "action": OrderAction.POST_ORDER, 67 | "order": { 68 | "symbol": self.symbol, 69 | "side": OrderSide.SELL.value, 70 | "type": OrderType.MARKET.value, 71 | "quantity": self.order_qty, 72 | }, 73 | } 74 | else: 75 | return { 76 | "action": OrderAction.SKIP_ORDER, 77 | "reason": OrderAction.SKIP_ORDER.value.format( 78 | "holding quantity {} is not enough to sell".format( 79 | holding_quantity 80 | ) 81 | ), 82 | } 83 | else: 84 | return { 85 | "action": OrderAction.SKIP_ORDER, 86 | "reason": OrderAction.SKIP_ORDER.value.format( 87 | "sentiment is {}".format(sentiment) 88 | ), 89 | } 90 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/strategy/tb_enums.py: -------------------------------------------------------------------------------- 1 | from enum import Enum 2 | 3 | 4 | class TradingError(Enum): 5 | NOTIONAL_FILTER = "Filter failure: NOTIONAL, the minimum quantity should be {}." 6 | 7 | 8 | class OrderAction(Enum): 9 | SKIP_ORDER = "Skip order, reason is '{}'." 10 | AVOID_ORDER = "Avoid placing unwanted order, reason is '{}'." 11 | POST_ORDER = "Post order." 12 | 13 | 14 | class OrderSide(Enum): 15 | BUY = "BUY" 16 | SELL = "SELL" 17 | 18 | 19 | class OrderType(Enum): 20 | MARKET = "MARKET" 21 | LIMIT = "LIMIT" 22 | -------------------------------------------------------------------------------- /aitradingprototypebacktester/strategy_manager.py: -------------------------------------------------------------------------------- 1 | import logging 2 | 3 | import pandas as pd 4 | from backtesting import Strategy 5 | 6 | from aitradingprototypebacktester.strategy.successive_strategy import SuccessiveStrategy 7 | from aitradingprototypebacktester.strategy.tb_enums import OrderAction, OrderSide 8 | 9 | from aitradingprototypebacktester.config_loader import load_config 10 | 11 | 12 | class StrategyManager(Strategy): 13 | """ 14 | Strategy Manager 15 | ---------------- 16 | This class inherits from the backtesting library's `Strategy` class and provides a mechanism to manage trading 17 | decisions based on sentiment data and a successive trading strategy. 18 | """ 19 | 20 | def init(self): 21 | """ 22 | Initializes the object by loading the configuration from the "config.yaml" file and setting up the logging level. 23 | """ 24 | config = load_config("config.yaml") 25 | logging_level = config["logging_level"] 26 | logging.basicConfig(level=logging_level, format="%(message)s") 27 | # Convert config["order_quantity"] and config["total_quantity_limit"] to satoshis 28 | self.order_quantity = config["order_quantity"] * 1e6 29 | self.total_quantity_limit = config["total_quantity_limit"] * 1e6 30 | self.sentiment_data = pd.read_csv( 31 | config["sentiment_data"], 32 | names=[ 33 | "source", 34 | "collected_timestamp", 35 | "published_timestamp", 36 | "headline", 37 | "sentiment", 38 | ], 39 | quotechar='"', 40 | skipinitialspace=True, 41 | ) 42 | 43 | # If the published_timestamp is in seconds (i.e., less than 10**10), convert it to Milliseconds. 44 | self.sentiment_data.loc[ 45 | self.sentiment_data["published_timestamp"] < 10**10, "published_timestamp" 46 | ] *= 1000 47 | 48 | # Set the published_timestamp as the index 49 | self.sentiment_data.set_index("published_timestamp", inplace=True) 50 | # Convert the ms timestamp to datetime format in order to be compatible with backtesting library 51 | self.sentiment_data.index = pd.to_datetime(self.sentiment_data.index, unit="ms") 52 | self.successive_strategy = SuccessiveStrategy( 53 | { 54 | "symbol": config["symbol"], 55 | "order_quantity": self.order_quantity, 56 | "total_quantity_limit": self.total_quantity_limit, 57 | } 58 | ) 59 | 60 | def next(self): 61 | """ 62 | Passes the sentiment to the trading strategy and executes trading decisions based on this strategy. 63 | 64 | This method is called for each time step in the backtesting process and makes trading decisions based on the 65 | sentiment data and the successive strategy. It determines whether to place a buy or sell order, or to skip the 66 | order for the current time step. 67 | """ 68 | kline_open_timestamp = pd.Timestamp(self.data.index[-1]).tz_localize(None) 69 | try: 70 | previous_kline_open_timestamp = pd.Timestamp( 71 | self.data.index[-2] 72 | ).tz_localize(None) 73 | sentiment = ( 74 | self.sentiment_data.loc[ 75 | (self.sentiment_data.index <= kline_open_timestamp) 76 | & (self.sentiment_data.index > previous_kline_open_timestamp) 77 | ] 78 | .tail(1)["sentiment"] 79 | .values[0] 80 | ) 81 | except: 82 | sentiment = "unknown" 83 | strategy_decision = self.successive_strategy.order_strategy( 84 | sentiment, self.position.size 85 | ) 86 | if strategy_decision["action"] == OrderAction.POST_ORDER: 87 | # Order is to be placed 88 | order = strategy_decision["order"] 89 | if order["side"] == OrderSide.BUY.value: 90 | # Place buy order for config-defined order quantity 91 | logging.info(f"Placing buy order at {kline_open_timestamp}") 92 | self.buy(size=order["quantity"]) 93 | elif order["side"] == OrderSide.SELL.value: 94 | # Place sell order for config-defined order quantity 95 | logging.info(f"Placing sell order at {kline_open_timestamp}") 96 | self.sell(size=order["quantity"]) 97 | elif strategy_decision["action"] == OrderAction.SKIP_ORDER: 98 | # No sentiment for this kline, skip order 99 | pass 100 | -------------------------------------------------------------------------------- /config.yaml.example: -------------------------------------------------------------------------------- 1 | symbol: BTCUSDT 2 | start_balance: 10000 3 | order_quantity: 0.01 4 | total_quantity_limit: 1 5 | commission: 0.002 6 | kline_interval: 1m 7 | start_date: 2023-05-25 8 | end_date: 2023-07-22 9 | sentiment_data: ./sentiment_data/sentiment_data.csv 10 | logging_level: INFO -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | Backtesting==0.3.3 2 | bokeh==3.4.1 3 | certifi>=2023.7.22 4 | charset-normalizer==3.3.2 5 | contourpy==1.2.1 6 | idna==3.7 7 | Jinja2==3.1.5 8 | MarkupSafe==2.1.5 9 | numpy>=1.26.0 10 | packaging==24.0 11 | pandas==2.2.2 12 | Pillow>=10.0.1 13 | python-dateutil==2.9.0 14 | pytz==2023.3 15 | PyYAML==6.0.1 16 | requests==2.32.0 17 | six==1.16.0 18 | tornado>=6.3.3 19 | typing_extensions==4.11.0 20 | tzdata==2024.1 21 | urllib3>=2.0.6 22 | xyzservices==2024.4.0 23 | -------------------------------------------------------------------------------- /sentiment_data/sentiment_data.csv: -------------------------------------------------------------------------------- 1 | "NewsAPI","1693286929493","1692804988000","Ex-OpenSea employee receives prison sentence for NFT insider trading","unknown" 2 | "NewsAPI","1693286929493","1691167372000","GameStop’s NFT wallet will shut down in November","unknown" 3 | "NewsAPI","1693286929493","1691417271000","PayPal launches PYUSD stablecoin backed by the US dollar","unknown" 4 | "NewsAPI","1693286929493","1692800040000","Former OpenSea Executive Sentenced to Three Months in Prison for Insider Trading","unknown" 5 | "NewsAPI","1693286929493","1692972600000","Dropbox Is Dropping Unlimited Storage, Blames Crypto Miners","unknown" 6 | "NewsAPI","1693286929493","1691429760000","PayPal Dives Into Crypto Madness With Dollar-Pegged Stablecoin","bullish" 7 | "NewsAPI","1693286929493","1691166003000","GameStop, citing “regulatory uncertainty,” winds down its crypto and NFT wallet","unknown" 8 | "NewsAPI","1693286929493","1690691820000","Kraken says investigating issue affecting Ethereum funding gateway - TradingView","unknown" 9 | "NewsAPI","1693286929493","1691889615000","Cryptoverse: Ethereum Upgrade to Unlock $33 Billion – Reuters.com - CryptoSaurus","bullish" 10 | "NewsAPI","1693286929493","1690710000000","Kraken says all systems operational after issue with Ethereum funding gateway - Reuters","bullish" 11 | "NewsAPI","1693286929493","1691416860000","PayPal Launches Stablecoin on Ethereum, Citing 'Shift Toward Digital Currencies'","unknown" 12 | "NewsAPI","1693286929493","1692811440000","Tornado Cash Founders Charged With Laundering More Than $1 Billion","unknown" 13 | "NewsAPI","1693286929493","1690900131000","Here's how to invest $100,000 right now, according to billionaire Mike Novogratz","bullish" 14 | "NewsAPI","1693286929493","1693034993000","New Cryptocurrency Launches to Invest in for August 2023","bullish" 15 | "NewsAPI","1693286929493","1692734456000","Cryptocurrency and Blockchain Security: Challenges and Solutions","bullish" 16 | "NewsAPI","1693286929493","1692414039000","NFT Revolution: Unveiling the Marketing Agencies Driving Global Adoption of Non-Fungible Tokens","bullish" 17 | "NewsAPI","1693286929493","1693034407000","10 Best Meme Coins to Buy in 2023","bullish" 18 | "NewsAPI","1693286929493","1690906857000","CoreWeave ‘came out of nowhere.’ Now it’s poised to make billions off of AI with its GPU cloud","bullish" 19 | "NewsAPI","1693286929493","1692982800000","Saga unveils Web3 tech that it hopes will enable the multiverse | The DeanBeat","bullish" 20 | "NewsAPI","1693286929493","1692030420000","Zynga unveils Sugartown as its first Web3 game","unknown" 21 | "NewsAPI","1693286929493","1691676016000","The World ID Orb and the Question of What Defines a Person","bullish" 22 | "NewsAPI","1693286929493","1691340125000","O.C. Actor Ben McKenzie's Book 'Easy Money' Exposes the Dark Side of Crypto","unknown" 23 | "NewsAPI","1693286929493","1690729180000","Worldcoin: A solution in search of its problem","unknown" 24 | "NewsAPI","1693286929493","1692812048000","Token prison sentence for first convicted NFT insider trader","bullish" 25 | "NewsAPI","1693286929493","1693166427000","The Top 10 Web3 Development Platforms","unknown" 26 | "NewsAPI","1693286929493","1692741623000","What Is Scalability in Blockchain? A Simple Explanation","bullish" 27 | "NewsAPI","1693286929493","1692142344000","Are GPU Prices Set to Rise in 2023?","unknown" 28 | "NewsAPI","1693286929493","1692113401000","What Is DRC-20? How Do You Mint DRC-20 Tokens?","unknown" 29 | "NewsAPI","1693286929493","1691323224000","How to Access Unstoppable Domains Websites in Your Browser","bullish" 30 | "NewsAPI","1693286929493","1692287184000","What Is a Crypto Airdrop and How Does It Work?","bullish" 31 | "NewsAPI","1693286929493","1693044925000","5 Reasons GameFi Will Never Take Off or Attract Real Gamers","unknown" 32 | "NewsAPI","1693286929493","1691377387000","Five Eyes nations detail dirty dozen most exploited vulnerabilities","unknown" 33 | "NewsAPI","1693286929493","1691884560000","Wait, is this Bruce Lee anime his family's making some kind of weird NFT thing?","unknown" 34 | "NewsAPI","1693286929493","1691500858000","PayPal Launches U.S. Dollar Stablecoin, PYUSD","bullish" 35 | "NewsAPI","1693286929493","1692014418000","8 Cryptocurrency Scams to Avoid","unknown" 36 | "NewsAPI","1693286929493","1690837740000","A YouTuber is accused of buying the world's largest black diamond with the proceeds of a $1 billion crypto fraud","unknown" 37 | "NewsAPI","1693286929493","1691067648000","CoreWeave, which provides cloud infrastructure for AI training, secures $2.3B loan","bullish" 38 | "NewsAPI","1693286929493","1692248261000","7+ Best NFT Art Generators for Creating Stunning Artwork in 2023","unknown" 39 | "NewsAPI","1693286929496","1692640812000","PayPal's new patent squeezes blockchain","unknown" 40 | "NewsAPI","1693286929496","1691485450000","PayPal's latest crypto play is a stablecoin pegged to the US dollar","unknown" 41 | "NewsAPI","1693286929496","1692813626000","2:00PM Water Cooler 8/23/2023","unknown" 42 | "NewsAPI","1693286929496","1691096710000","This Crypto Influencer Bought Some Sick Watches (And Then the SEC Charged Him With Fraud)","unknown" 43 | "NewsAPI","1693286929496","1690716642000","How To Use AI To Gain Ethereum Market Insights","bullish" 44 | "NewsAPI","1693286929496","1692560143000","Texas Judge Sides With The Treasury Against Tornado Cash In Crypto Case","bullish" 45 | "NewsAPI","1693286929496","1691785871000","Donald Trump Is Invested In Cryptocurrency","bullish" 46 | "NewsAPI","1693286929496","1691621124000","Leak Reveals BlackRock’s Game-Changing Bitcoin Plan That Could Cause Price Chaos For Ethereum, XRP And Crypto","bullish" 47 | "NewsAPI","1693286929496","1691839837000","$2.6 Trillion ‘Glitch’ Causes Crypto Price Chaos After XRP-Led Bitcoin And Ethereum Price Boom","bullish" 48 | "NewsAPI","1693286929496","1690893014000","Crypto Price Armageddon: Coinbase CEO Reveals A Stark Warning That Could Hit Bitcoin, Ethereum, BNB, XRP, Cardano, Dogecoin, Solana, Polygon, Tron And Litecoin","bearish" 49 | "NewsAPI","1693286929496","1692272739000","‘A Big Deal’—Crypto Braced For A ‘Critical’ Earthquake That Could Play Havoc With The Price Of Bitcoin, Ethereum And XRP","unknown" 50 | "NewsAPI","1693286929496","1691500551000","‘Very Exciting’—Huge $5 Trillion Bitcoin And Ethereum Price Prediction Issued By Legendary Investor After Shock Bombshell","bullish" 51 | "NewsAPI","1693286929496","1692098724000","‘Greatest Rug Pull Ever’—‘Shocking’ Fed Warning Heralds Bitcoin, Ethereum, XRP And Crypto Price Chaos","bullish" 52 | "NewsAPI","1693286929496","1692877530000","Surprise ‘Black Swan’ Has Primed Crypto For A $3 Trillion Bitcoin, Ethereum And XRP Price Bombshell","bullish" 53 | "NewsAPI","1693286929496","1692178200000","Layer 2 Wars Heat Up As Coinbase Launches Base","bullish" 54 | "NewsAPI","1693286929496","1692922026000","U.S. Government Goes After Ethereum Privacy Tool Founders With Indictment And Arrest","unknown" 55 | "NewsAPI","1693286929496","1690805464000","The ‘Most Important Thing’—‘Simultaneous’ $15.5 Trillion Crypto Earthquakes Are Heading For Bitcoin And Ethereum After XRP-Led BNB, Cardano, Dogecoin, Litecoin, Solana, Tron And Polygon Price Boom","bullish" 56 | "NewsAPI","1693286929496","1691235946000","China Just Made A ‘Significant’ Game-Changing Move That Could Be About To Hit The Price Of Bitcoin, Ethereum, BNB, XRP, Cardano, Dogecoin, Tron, Polygon And Solana","bullish" 57 | "NewsAPI","1693286929496","1692532224000","Crypto Now Braced For A $15.5 Trillion September Wall Street Earthquake After Shock Bitcoin, Ethereum, XRP And Crypto Price Crash","bearish" 58 | "NewsAPI","1693286929496","1691927143000","‘Monster Crypto Killer-App’—Crypto Suddenly Braced For A $2.8 Trillion PayPal Earthquake After Bitcoin, Ethereum, BNB, XRP, Cardano, Dogecoin, Litecoin, Solana, Tron And Shiba Inu Price Boom","bullish" 59 | "NewsAPI","1693286929496","1691149536000","‘The Next Logical Next Step’—Leak Reveals Elon Musk Could Be About To Blow Up The Bitcoin, Ethereum, XRP And Dogecoin Price","unknown" 60 | "NewsAPI","1693286929496","1693050346000","Leak Reveals Elon Musk, X And Wall Street Could Be About To Cause Bitcoin And Crypto Price Chaos With ‘PayPal Update’","unknown" 61 | "NewsAPI","1693286929496","1692711900000","‘Buyer Beware’—SEC Insider Issues ‘Disturbing’ Binance Withdrawal Warning Amid Crypto Price Collapse Fears That Could Crash Bitcoin, Ethereum, XRP And BNB","bearish" 62 | "NewsAPI","1693286929496","1692445542000","Stark BlackRock Warning As Sudden $1 Trillion Crypto Market Crash Tanks The Bitcoin, Ethereum, BNB, XRP, Cardano, Dogecoin, Litecoin, Solana, Tron And Shiba Inu Price","bearish" 63 | "NewsAPI","1693286929496","1691321710000","Elon Musk Declaration Sparks Wild Speculation He Could Be About To Blow Up The Bitcoin, Ethereum, XRP And Dogecoin Price","unknown" 64 | "NewsAPI","1693286929496","1690979417000","Crypto Now Braced For $6 Trillion Gold Earthquake in 2024 After XRP-Led Bitcoin And Ethereum Price Boom","bullish" 65 | "NewsAPI","1693286929496","1692228095000","Maker Protocol Revenue Surges To A Bi-Annual High Of $165 Million","unknown" 66 | "NewsAPI","1693286929496","1690732683000","BALD Impressive 40,000% Pump Is Almost Gone","bullish" 67 | "NewsAPI","1693286929496","1692814649000","Feds Charge Tornado Cash Founders In $1 Billion Alleged Crypto Laundering Scheme—Including Millions For North Korea","unknown" 68 | "NewsAPI","1693286929497","1692640669000","How Bitcoin Usage Could Skyrocket Thanks To Zero-Knowledge Proofs","bullish" 69 | "NewsAPI","1693286929497","1691944977000","How The PayPal Stablecoin Will Change The Crypto Conversation","bullish" 70 | "NewsAPI","1693286929497","1692577001000","Blockchain Privacy At The Mercy Of Bad Actors","unknown" 71 | "NewsAPI","1693286929497","1693146953000","Friend.tech Hype On Base Is Here: What Is It And How To Use It","unknown" 72 | "NewsAPI","1693286929497","1691751600000","Overhauling Cross-Chain Transactions In DeFi","bullish" 73 | "NewsAPI","1693286929497","1690717554000","Biden Rival RFK Jr. Just Issued A Shock Bitcoin Warning About A Looming $9 Trillion Crypto Price ‘Revolution’","bullish" 74 | "NewsAPI","1693286929497","1692596058000","4 Liquid Staking Startups That Are Unlocking Ethereum’s Potential","unknown" 75 | "NewsAPI","1693286929497","1692629994000","Blockchain Capital’s Bart Stephens Lost $6.3 Million In SIM-Swap Crypto Hack","unknown" 76 | "NewsAPI","1693286929497","1690884000000","Meet The Crypto ‘Gals’ Of The 2023 Forbes 50 Over 50","bullish" 77 | "NewsAPI","1693286929497","1691077503000","$1.5 Billion Withdrawn From DeFi Following Curve, BALD, And Base Hacks","unknown" 78 | "NewsAPI","1693286929497","1692871324000","Decoding 5 Crypto Acronyms: MPC, ZK, FHE, TEE & HSM","unknown" 79 | "NewsAPI","1693286929497","1691409079000","IRS Guidance Provides Long-Awaited Instructions For Paying Taxes On Crypto Staking Rewards","bullish" 80 | "NewsAPI","1693286929497","1692892144000","The DEA Accidentally Sent $50,000 Of Seized Cryptocurrency To A Scammer","unknown" 81 | "NewsAPI","1693286929497","1691068802000","Worldcoin’s Iris-Scan ID Proposition Is Best Considered With Eyes Wide Open","unknown" 82 | "NewsAPI","1693286929497","1692613800000","How Vivek Ramaswamy Became A Billionaire","unknown" 83 | "NewsAPI","1693286929497","1691701334000","Crypto Clubs Throwing Summer Parties: Inside The Friends With Benefits DAO Festival","bullish" 84 | "NewsAPI","1693286929497","1692892432000","Cold Or Hot Wallets: Where To Keep Our Shrinking Bitcoin Holdings?","unknown" 85 | "NewsAPI","1693286929497","1691577000000","30 Under 30 Local 2023: Puerto Rico","unknown" 86 | "NewsAPI","1693286929497","1690832028000","SEC Sues Crypto Founder For Using Funds On Luxury Purchases—Including A 555-Carat Black Diamond","unknown" 87 | "NewsAPI","1693286929497","1691703484000","Ethereum remains below $1900","unknown" 88 | "NewsAPI","1693286929497","1692902404000","Similarities Between Bitcoin and Ethereum","bullish" 89 | "NewsAPI","1693286929497","1692284889000","Cardano Founder Says Ethereum Can’t Change Its “Terrible Programming Model”","unknown" 90 | "NewsAPI","1693286929497","1691892246000","Cryptoverse: Ethereum Upgrade to Unlock $33 Billion","bullish" 91 | "NewsAPI","1693286929497","1692955924000","ARK Invest and 21Shares apply for Ethereum ETFs","unknown" 92 | "NewsAPI","1693286929497","1692810604000","Here’s What Could Trigger A Rebound For Ethereum","unknown" 93 | "NewsAPI","1693286929497","1691564064000","Ethereum’s Billion-Dollar Revolution: How LSDs Are Set to Change the Ethereum Ecosystem","unknown" 94 | "NewsAPI","1693286929497","1692458764000","Ethereum Testnet “Holesky” to Launch in September Ahead of Goerli Deprecation","unknown" 95 | "NewsAPI","1693286929497","1692265443000","$1.7M of Ethereum 'Stuck' in SHIB Layer-2 Network Shibarium","unknown" 96 | "NewsAPI","1693286929497","1693047604000","35% of ETH in Circulation Now Held by Just 10 Wallets, Here's What Triggered It","unknown" 97 | "HackerNews","1693286929497","1693052812000","Show HN: lazy-etherscan – TUI for Ethereum Blockchain Explorer Written in Rust","unknown" 98 | "HackerNews","1693286929497","1693001183000","Show HN: ERC-7432: Non-Fungible Token Roles","unknown" 99 | "HackerNews","1693286929497","1692971235000","Elixir Ethereum library using metaprogramming","unknown" 100 | "HackerNews","1693286929497","1692373344000","Show HN: Secure, on-chain, out-of-band swaps with Triswap","bullish" 101 | "HackerNews","1693286929497","1692210012000","Building an Ethereum Address Tracker","bullish" 102 | "HackerNews","1693286929497","1692104864000","An Ethereum WSS get-and-stay-current event listener in Python","unknown" 103 | "HackerNews","1693286929497","1692066579000","Bald Developer Sends $12M Back to Ethereum After Mess Up","unknown" 104 | "HackerNews","1693286929497","1691610636000","PayPal launches stablecoin on Ethereum – share price rallies","bullish" 105 | "HackerNews","1693286929497","1691605872000","Coinbase Ethereum L2 Launches with $142M Already on Its Network","bullish" 106 | "HackerNews","1693286929497","1691490017000","Store Files in the Ethereum Blockchain","unknown" 107 | "HackerNews","1693286929497","1691224652000","Could Ethereum help stop museums from hoarding stolen artifacts?","unknown" 108 | "HackerNews","1693286929497","1690915469000","Nimbus – A Lighter Ethereum Client (Nim)","unknown" 109 | "HackerNews","1693286929497","1690809121000","DeFi Exchange Curve Finance Confirms Various Ethereum Pools Hacked","unknown" 110 | "HackerNews","1693286929497","1690741967000","Ethereum Launches (2015)","unknown" -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | from setuptools import setup 2 | 3 | setup( 4 | name="aitradingprototypebacktester", 5 | version="1.0.4", 6 | description="AI Trading Prototype Backtester", 7 | author="Binance", 8 | packages=["aitradingprototypebacktester"], 9 | entry_points={ 10 | "console_scripts": [ 11 | "aitradingprototypebacktester = aitradingprototypebacktester.main:main", 12 | ], 13 | }, 14 | ) 15 | -------------------------------------------------------------------------------- /strategy_configuration/aggressive.yaml.example: -------------------------------------------------------------------------------- 1 | symbol: BTCUSDT 2 | start_balance: 100000 3 | order_quantity: 0.25 4 | total_quantity_limit: 1 5 | commission: 0.002 6 | kline_interval: 1m 7 | start_date: 2023-05-25 8 | end_date: 2023-07-22 9 | sentiment_data: ./sentiment_data/sentiment_data.csv 10 | logging_level: INFO -------------------------------------------------------------------------------- /strategy_configuration/conservative.yaml.example: -------------------------------------------------------------------------------- 1 | symbol: BTCUSDT 2 | start_balance: 100000 3 | order_quantity: 0.02 4 | total_quantity_limit: 0.2 5 | commission: 0.002 6 | kline_interval: 1m 7 | start_date: 2023-05-25 8 | end_date: 2023-07-22 9 | sentiment_data: ./sentiment_data/sentiment_data.csv 10 | logging_level: INFO -------------------------------------------------------------------------------- /strategy_configuration/standard.yaml.example: -------------------------------------------------------------------------------- 1 | symbol: BTCUSDT 2 | start_balance: 100000 3 | order_quantity: 0.035 4 | total_quantity_limit: 0.5 5 | commission: 0.002 6 | kline_interval: 1m 7 | start_date: 2023-05-25 8 | end_date: 2023-07-22 9 | sentiment_data: ./sentiment_data/sentiment_data.csv 10 | logging_level: INFO --------------------------------------------------------------------------------