├── configuration.json.template ├── .gitignore ├── README.md ├── strategy.py ├── main.py ├── botter.py ├── once.py └── binance.py /configuration.json.template: -------------------------------------------------------------------------------- 1 | { 2 | "APIKEY": "", 3 | "SECRET": "", 4 | "SYMBOL": "ETH/BTC", 5 | "LOG_LEVEL": "INFO", 6 | "INTERVAL": 60, 7 | "TIMEFRAME": "1h", 8 | "SMA": 17, 9 | "LMA": 22, 10 | "QTY": 100 11 | } 12 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files for more about ignoring files. 2 | # 3 | # If you find yourself ignoring temporary files generated by your text editor 4 | # or operating system, you probably want to add a global ignore instead: 5 | # git config --global core.excludesfile '~/.gitignore_global' 6 | 7 | # Ignore build files. 8 | configuration.json 9 | crypto.py 10 | __pycache__/* 11 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Sample Bot program 2 | 3 | CCXT + Binance python program. 4 | 5 | ## setup steps 6 | 7 | 1. Copy the template `configuration.json.template` and rename it to `configuration.json`. You need to configure API key and Secret in the file. 8 | 2. Prepare `strategy.py` with your own trading strategy. 9 | 3. Modify `configuration.json` with your own parameters. 10 | 4. Run a bot program as below 11 | 12 | once.py is just a one-time runner for your testing purpose. 13 | ```sh 14 | $ python main.py configuration.json strategy.py 15 | $ python once.py configuration.json strategy.py 16 | ``` 17 | -------------------------------------------------------------------------------- /strategy.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import time 3 | import talib 4 | from datetime import datetime as dt, timezone as tz, timedelta as delta 5 | 6 | import pandas as pd 7 | from botter import Botter 8 | 9 | class Strategy: 10 | 11 | def __init__(self, Botter): 12 | self._exchange = Botter._exchange 13 | self._logger = Botter._logger 14 | self._config = Botter._config 15 | 16 | def run(self, ticker, orderbook, position, balance, candle): 17 | # here's your logic 18 | df = candle 19 | print(df) 20 | 21 | order = self._exchange.market_order('buy', qty) 22 | order = self._exchange.market_order('sell', qty) 23 | 24 | -------------------------------------------------------------------------------- /main.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import ccxt 4 | import time 5 | import json 6 | import talib 7 | 8 | import logging 9 | from logging import getLogger, StreamHandler, Formatter 10 | from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler 11 | 12 | from importlib import machinery 13 | from datetime import datetime, timedelta, timezone 14 | import calendar 15 | 16 | from binance import Binance 17 | sys.dont_write_bytecode = True 18 | args = sys.argv 19 | 20 | class Botter: 21 | 22 | def __init__(self, args): 23 | self._tz = timezone.utc # utc timezone 24 | self._ts = datetime.now(self._tz).timestamp() 25 | 26 | if len(args) != 3: 27 | print("number of args does not match") 28 | exit() 29 | 30 | with open(args[1], "r") as f: 31 | jsonData = json.load(f) 32 | self._config = jsonData 33 | print('Configuration file {} loaded'.format(args[1])) 34 | print(json.dumps(self._config, sort_keys=True, indent=4)) 35 | 36 | # logging configuration 37 | logger = getLogger(__name__) 38 | logger.setLevel(logging.DEBUG) 39 | 40 | # create file handler 41 | fh = logging.FileHandler('yourfilename.log') 42 | fh.setLevel(logging.DEBUG) 43 | fh_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 44 | fh.setFormatter(fh_formatter) 45 | 46 | # create console handler 47 | ch = logging.StreamHandler() 48 | ch.setLevel(logging.INFO) 49 | ch_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 50 | ch.setFormatter(ch_formatter) 51 | 52 | # add the handlers to the logger 53 | logger.addHandler(fh) 54 | logger.addHandler(ch) 55 | self._logger = logger 56 | 57 | if "LOG_LEVEL" in self._config: 58 | if self._config["LOG_LEVEL"] in [ 59 | "CRITICAL", 60 | "ERROR", 61 | "WARNING", 62 | "INFO", 63 | "DEBUG", 64 | ]: 65 | self._logger.setLevel(eval("logging." + self._config["LOG_LEVEL"])) 66 | 67 | self._exchange = Binance( 68 | symbol = self._config["SYMBOL"], 69 | apiKey = self._config["APIKEY"], 70 | secret = self._config["SECRET"], 71 | logger = self._logger 72 | ) 73 | 74 | # load your strategy 75 | module = machinery.SourceFileLoader("Strategy", args[2]).load_module() 76 | self._Strategy = module.Strategy(self) 77 | 78 | message = "Botter initialized with {}".format(args[1]) 79 | self._logger.info(message) 80 | 81 | if __name__ == "__main__": 82 | tz = timezone.utc 83 | 84 | def start(): 85 | bot = Botter(args=args) 86 | while True: 87 | try: 88 | run(Bot=bot) 89 | except KeyboardInterrupt: 90 | bot._logger.info("Keyboard Interruption detected, exit the program") 91 | if len(bot._exchange.open_orders()): 92 | bot._exchange.cancel_orders() 93 | bot._logger.info("Cancelled the existing orders, please confirm") 94 | time.sleep(3) 95 | exit() 96 | except Exception as e: 97 | bot._logger.info("Unknown error occurred, exit the program") 98 | if len(bot._exchange.open_orders()): 99 | bot._exchange.cancel_orders() 100 | bot._logger.info("Cancelled the existing orders, please confirm") 101 | time.sleep(3) 102 | exit() 103 | 104 | def run(Bot): 105 | Bot._ts = datetime.now(tz).timestamp() 106 | balance, candle, orderbook, position, ticker = None, None, None, None, None 107 | 108 | current = time.time() 109 | try: 110 | candle = Bot._exchange.ohlcv(symbol=Bot._config["SYMBOL"], timeframe=Bot._config["TIMEFRAME"]) 111 | balance = Bot._exchange.balance() 112 | ticker = Bot._exchange.ticker(symbol=Bot._config["SYMBOL"]) 113 | orderbook = Bot._exchange.orderbook(symbol=Bot._config["SYMBOL"]) 114 | position = Bot._exchange.position() 115 | 116 | except Exception as e: 117 | Bot._logger.error("Bot.run() raised an exception: {}".format(e)) 118 | elapsed_time = time.time() - current 119 | if elapsed_time > Bot._config["INTERVAL"]: 120 | Bot._logger.warning( 121 | "elapsed_time={0} over interval time={1}".format( 122 | elapsed_time, Bot._config["INTERVAL"] 123 | ) 124 | ) 125 | 126 | # Trade logic 127 | Bot._Strategy.run(ticker, orderbook, position, balance, candle) 128 | 129 | elapsed_time = time.time() - current 130 | interval = Bot._config["INTERVAL"] 131 | if interval - elapsed_time > 0: 132 | time.sleep(interval - elapsed_time) 133 | else: 134 | time.sleep(1) 135 | Bot._logger.warning( 136 | "elapsed_time={0} over interval time={1}".format( 137 | elapsed_time, interval 138 | ) 139 | ) 140 | 141 | bot = Botter(args=args) 142 | run(Bot=bot) 143 | del bot 144 | -------------------------------------------------------------------------------- /botter.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import ccxt 4 | import time 5 | import json 6 | 7 | import logging 8 | from logging import getLogger, StreamHandler, Formatter 9 | from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler 10 | 11 | from datetime import datetime, timedelta, timezone 12 | import calendar 13 | 14 | from crypto import CryptoBot 15 | args = sys.argv 16 | 17 | class Botter: 18 | 19 | def __init__(self, args): 20 | self._tz = timezone.utc # utc timezone 21 | self._ts = datetime.now(self._tz).timestamp() 22 | 23 | if len(args) != 3: 24 | print("number of args does not match") 25 | exit() 26 | 27 | with open(args[2], "r") as f: 28 | jsonData = json.load(f) 29 | self._config = jsonData 30 | print('Configuration file {} loaded'.format(args[1])) 31 | print(json.dumps(self._config, sort_keys=True, indent=4)) 32 | 33 | # logging configuration 34 | logger = getLogger(__name__) 35 | logger.setLevel(logging.DEBUG) 36 | 37 | # create file handler 38 | fh = logging.FileHandler('yourfilename.log') 39 | fh.setLevel(logging.DEBUG) 40 | fh_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 41 | fh.setFormatter(fh_formatter) 42 | 43 | # create console handler 44 | ch = logging.StreamHandler() 45 | ch.setLevel(logging.INFO) 46 | ch_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 47 | ch.setFormatter(ch_formatter) 48 | 49 | # add the handlers to the logger 50 | logger.addHandler(fh) 51 | logger.addHandler(ch) 52 | self._logger = logger 53 | 54 | if "LOG_LEVEL" in self._config: 55 | if self._config["LOG_LEVEL"] in [ 56 | "CRITICAL", 57 | "ERROR", 58 | "WARNING", 59 | "INFO", 60 | "DEBUG", 61 | ]: 62 | self._logger.setLevel(eval("logging." + self._config["LOG_LEVEL"])) 63 | 64 | self._exchange = CryptoBot( 65 | symbol = self._config["SYMBOL"], 66 | apiKey = self._config["APIKEY"], 67 | secret = self._config["SECRET"], 68 | logger = self._logger 69 | ) 70 | 71 | # load strategy and generate a class 72 | module = machinery.SourceFileLoader("Strategy", args[1]).load_module() 73 | self._Strategy = module.Strategy(self) 74 | 75 | message = "Botter initialized with CryptoBot={}, Config={}".format(args[1], args[2]) 76 | self._logger.info(message) 77 | 78 | if __name__ == "__main__": 79 | tz = timezone.utc 80 | 81 | def start(): 82 | bot = Botter(args=args) 83 | while True: 84 | try: 85 | run(Bot=bot) 86 | except KeyboardInterrupt: 87 | bot._logger.info("Keyboard Interruption detected, exit the program") 88 | if len(bot._exchange.open_orders()): 89 | bot._exchange.cancel_orders() 90 | bot._logger.info("Cancelled the existing orders, please confirm") 91 | time.sleep(3) 92 | exit() 93 | except Exception as e: 94 | bot._logger.info("Unknown error occurred, exit the program") 95 | if len(bot._exchange.open_orders()): 96 | bot._exchange.cancel_orders() 97 | bot._logger.info("Cancelled the existing orders, please confirm") 98 | time.sleep(3) 99 | exit() 100 | 101 | def run(Bot): 102 | while True: 103 | Bot._ts = datetime.now(tz).timestamp() 104 | balance, candle, orderbook, position, ticker = None, None, None, None, None 105 | 106 | current = time.time() 107 | try: 108 | candle = Bot._exchange.ohlcv(symbol=Bot._config["SYMBOL"], timeframe=Bot._config["TIMEFRAME"]) 109 | balance = Bot._exchange.balance() 110 | ticker = Bot._exchange.ticker(symbol=Bot._config["SYMBOL"]) 111 | orderbook = Bot._exchange.orderbook(symbol=Bot._config["SYMBOL"]) 112 | position = Bot._exchange.position() 113 | 114 | except Exception as e: 115 | Bot._logger.error("Bot.run() raised an exception: {}".format(e)) 116 | elapsed_time = time.time() - current 117 | if elapsed_time > Bot._config["INTERVAL"]: 118 | Bot._logger.warning( 119 | "elapsed_time={0} over interval time={1}".format( 120 | elapsed_time, Bot._config["INTERVAL"] 121 | ) 122 | ) 123 | continue 124 | 125 | # invoke the loaded strategy 126 | Bot._Strategy.run(ticker, orderbook, position, balance, candle) 127 | 128 | elapsed_time = time.time() - current 129 | interval = Bot._config["INTERVAL"] 130 | if interval - elapsed_time > 0: 131 | time.sleep(interval - elapsed_time) 132 | else: 133 | time.sleep(1) 134 | Bot._logger.warning( 135 | "elapsed_time={0} over interval time={1}".format( 136 | elapsed_time, interval 137 | ) 138 | ) 139 | start() 140 | 141 | -------------------------------------------------------------------------------- /once.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import sys 3 | import ccxt 4 | import time 5 | import json 6 | import talib 7 | 8 | import logging 9 | from logging import getLogger, StreamHandler, Formatter 10 | from logging.handlers import TimedRotatingFileHandler, RotatingFileHandler 11 | 12 | from importlib import machinery 13 | from datetime import datetime, timedelta, timezone 14 | import calendar 15 | 16 | from binance import Binance 17 | sys.dont_write_bytecode = True 18 | args = sys.argv 19 | 20 | class Botter: 21 | 22 | def __init__(self, args): 23 | self._tz = timezone.utc # utc timezone 24 | self._ts = datetime.now(self._tz).timestamp() 25 | 26 | if len(args) != 3: 27 | print("number of args does not match") 28 | exit() 29 | 30 | with open(args[1], "r") as f: 31 | jsonData = json.load(f) 32 | self._config = jsonData 33 | print('Configuration file {} loaded'.format(args[1])) 34 | print(json.dumps(self._config, sort_keys=True, indent=4)) 35 | 36 | # logging configuration 37 | logger = getLogger(__name__) 38 | logger.setLevel(logging.DEBUG) 39 | 40 | # create file handler 41 | fh = logging.FileHandler('yourfilename.log') 42 | fh.setLevel(logging.DEBUG) 43 | fh_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 44 | fh.setFormatter(fh_formatter) 45 | 46 | # create console handler 47 | ch = logging.StreamHandler() 48 | ch.setLevel(logging.INFO) 49 | ch_formatter = logging.Formatter('%(levelname)s : %(asctime)s : %(message)s') 50 | ch.setFormatter(ch_formatter) 51 | 52 | # add the handlers to the logger 53 | logger.addHandler(fh) 54 | logger.addHandler(ch) 55 | self._logger = logger 56 | 57 | if "LOG_LEVEL" in self._config: 58 | if self._config["LOG_LEVEL"] in [ 59 | "CRITICAL", 60 | "ERROR", 61 | "WARNING", 62 | "INFO", 63 | "DEBUG", 64 | ]: 65 | self._logger.setLevel(eval("logging." + self._config["LOG_LEVEL"])) 66 | 67 | self._exchange = Binance( 68 | symbol = self._config["SYMBOL"], 69 | apiKey = self._config["APIKEY"], 70 | secret = self._config["SECRET"], 71 | logger = self._logger 72 | ) 73 | 74 | # load your strategy 75 | module = machinery.SourceFileLoader("Strategy", args[2]).load_module() 76 | self._Strategy = module.Strategy(self) 77 | 78 | message = "Botter initialized with {}".format(args[1]) 79 | self._logger.info(message) 80 | 81 | if __name__ == "__main__": 82 | tz = timezone.utc 83 | 84 | def start(): 85 | bot = Botter(args=args) 86 | while True: 87 | try: 88 | run(Bot=bot) 89 | except KeyboardInterrupt: 90 | bot._logger.info("Keyboard Interruption detected, exit the program") 91 | if len(bot._exchange.open_orders()): 92 | bot._exchange.cancel_orders() 93 | bot._logger.info("Cancelled the existing orders, please confirm") 94 | time.sleep(3) 95 | exit() 96 | except Exception as e: 97 | bot._logger.info("Unknown error occurred, exit the program") 98 | if len(bot._exchange.open_orders()): 99 | bot._exchange.cancel_orders() 100 | bot._logger.info("Cancelled the existing orders, please confirm") 101 | time.sleep(3) 102 | exit() 103 | 104 | def run(Bot): 105 | while True: 106 | Bot._ts = datetime.now(tz).timestamp() 107 | balance, candle, orderbook, position, ticker = None, None, None, None, None 108 | 109 | current = time.time() 110 | try: 111 | candle = Bot._exchange.ohlcv(symbol=Bot._config["SYMBOL"], timeframe=Bot._config["TIMEFRAME"]) 112 | balance = Bot._exchange.balance() 113 | ticker = Bot._exchange.ticker(symbol=Bot._config["SYMBOL"]) 114 | orderbook = Bot._exchange.orderbook(symbol=Bot._config["SYMBOL"]) 115 | position = Bot._exchange.position() 116 | 117 | except Exception as e: 118 | Bot._logger.error("Bot.run() raised an exception: {}".format(e)) 119 | elapsed_time = time.time() - current 120 | if elapsed_time > Bot._config["INTERVAL"]: 121 | Bot._logger.warning( 122 | "elapsed_time={0} over interval time={1}".format( 123 | elapsed_time, Bot._config["INTERVAL"] 124 | ) 125 | ) 126 | continue 127 | 128 | # Trade logic 129 | Bot._Strategy.run(ticker, orderbook, position, balance, candle) 130 | 131 | elapsed_time = time.time() - current 132 | interval = Bot._config["INTERVAL"] 133 | if interval - elapsed_time > 0: 134 | time.sleep(interval - elapsed_time) 135 | else: 136 | time.sleep(1) 137 | Bot._logger.warning( 138 | "elapsed_time={0} over interval time={1}".format( 139 | elapsed_time, interval 140 | ) 141 | ) 142 | 143 | start() 144 | -------------------------------------------------------------------------------- /binance.py: -------------------------------------------------------------------------------- 1 | # -*- coding: utf-8 -*- 2 | import ccxt 3 | import time 4 | import math 5 | import json 6 | 7 | from datetime import datetime 8 | import calendar 9 | import logging 10 | 11 | import pandas as pd 12 | pd.options.mode.chained_assignment = None 13 | 14 | class Binance: 15 | 16 | SYMBOL = "ETH/BTC" # default symbol 17 | 18 | def __init__(self, symbol=SYMBOL, apiKey=None, secret=None, logger=None): 19 | self._exchange = ccxt.binance({"apiKey": apiKey, "secret": secret}) 20 | self._symbol = symbol 21 | self._logger = logger if logger is not None else logging.getLogger(__name__) 22 | self._logger.info("class Binance initialized") 23 | 24 | def __del__(self): 25 | self._logger.info("class Binance deleted") 26 | 27 | def __get_error(self, e): 28 | ret = {"error": {"message": "{}".format(e), "name": "Binance.__get_error"}} 29 | return ret 30 | 31 | def ceil(self, price): 32 | return math.ceil(price * 2) / 2 33 | 34 | def floor(self, price): 35 | return math.floor(price * 2) / 2 36 | 37 | def open_orders(self, symbol=SYMBOL): 38 | 39 | orders = None 40 | 41 | try: 42 | orders = self._exchange.fetch_open_orders(symbol) 43 | self._logger.debug("- open orders={}".format(orders)) 44 | except Exception as e: 45 | self._logger.error("- open orders: exception={}".format(e)) 46 | orders = self.__get_error(e) 47 | 48 | return orders 49 | 50 | def limit_order(self, side, price, size): 51 | 52 | order = None 53 | order_id = str(time.time() * 1000) 54 | 55 | try: 56 | order = self._exchange.create_order( 57 | symbol=self._symbol, 58 | type="limit", 59 | side=side, 60 | amount=size, 61 | price=price, 62 | params={"newClientOrderId": "{}_limit_{}".format(order_id, side)}, 63 | ) 64 | self._logger.debug("- limit order={}".format(order)) 65 | except Exception as e: 66 | self._logger.error("- limit order: exception={}".format(e)) 67 | order = self.__get_error(e) 68 | 69 | return order 70 | 71 | def market_order(self, side, size): 72 | 73 | order = None 74 | order_id = str(round(time.time() * 1000)) 75 | 76 | try: 77 | order = self._exchange.create_order( 78 | symbol=self._symbol, 79 | type="market", 80 | side=side, 81 | amount=size, 82 | params={"newClientOrderId": "{}_limit_{}".format(order_id, side)}, 83 | ) 84 | self._logger.debug("- market order={}".format(order)) 85 | except Exception as e: 86 | self._logger.error("- market order: exception={}".format(e)) 87 | order = self.__get_error(e) 88 | 89 | return order 90 | 91 | def cancel_order(self, orderId): 92 | 93 | order = None 94 | 95 | try: 96 | order = self._exchange.cancel_order(symbol=self._symbol, id=orderId) 97 | self._logger.debug("- cancel order={}".format(order)) 98 | except Exception as e: 99 | self._logger.error("- cancel order: exception={}".format(e)) 100 | order = self.__get_error(e) 101 | 102 | return order 103 | 104 | def cancel_orders(self): 105 | 106 | orders = None 107 | 108 | try: 109 | orders = self._exchange.fetch_open_orders() 110 | for i, o in enumerate(orders): 111 | if orders[i].get('status') == 'NEW': 112 | orderId = orders[i].get('id') 113 | self.cancel_order(orderId) 114 | self._logger.debug("- cancel orders={}".format(orders)) 115 | except Exception as e: 116 | self._logger.error("- cancel orders: exception={}".format(e)) 117 | orders = self.__get_error(e) 118 | 119 | return orders 120 | 121 | def balance(self): 122 | 123 | _balance = None 124 | 125 | try: 126 | _balance = self._exchange.fetch_balance() 127 | self._logger.debug("- balance={}".format(_balance)) 128 | except Exception as e: 129 | self._logger.error("- balance: exception={}".format(e)) 130 | _balance = self.__get_error(e) 131 | 132 | return _balance 133 | 134 | def position(self): 135 | 136 | _position = None 137 | 138 | try: 139 | _position = self._exchange.fapiPrivate_get_positionrisk() 140 | self._logger.debug("- position={}".format(_position)) 141 | except Exception as e: 142 | self._logger.error("- position: exception={}".format(e)) 143 | _position = self.__get_error(e) 144 | 145 | return _position 146 | 147 | def ticker(self, symbol=SYMBOL): 148 | 149 | _ticker = None 150 | try: 151 | _ticker = self._exchange.fetch_ticker(symbol=symbol) 152 | self._logger.debug("- ticker={}".format(_ticker)) 153 | except Exception as e: 154 | self._logger.error("- ticker: exception={}".format(e)) 155 | _ticker = self.__get_error(e) 156 | 157 | return _ticker 158 | 159 | def orderbook(self, symbol=SYMBOL, limit=100): 160 | 161 | _orderbook = None 162 | 163 | try: 164 | _orderbook = self._exchange.fetch_order_book( 165 | symbol=symbol, limit=limit 166 | ) 167 | self._logger.debug("- orderbook={}".format(_orderbook)) 168 | except Exception as e: 169 | self._logger.error("- orderbook: exception={}".format(e)) 170 | _orderbook = self.__get_error(e) 171 | 172 | return _orderbook 173 | 174 | # returns ohlcv data, symbol and timeframe are mandatory 175 | def ohlcv(self, symbol=SYMBOL, timeframe="1m", since=None, limit=None, params={}): 176 | period = ["1m", "5m", "1h", "1d"] 177 | 178 | # timeframe must be one of period 1m 5m 1h 1d in this case 179 | if timeframe not in period: 180 | return None 181 | 182 | # configure retrieving limit, 100 is the minimum size 183 | fetch_count = 100 if limit is None else limit 184 | count = fetch_count 185 | 186 | ohlcvs = self._exchange.fetch_ohlcv( 187 | symbol=symbol, timeframe=timeframe, since=since, limit=count, params=params 188 | ) 189 | 190 | return self.to_candleDF(ohlcvs) 191 | 192 | def to_candleDF(self, candle): 193 | df = pd.DataFrame( 194 | candle, columns=["timestamp", "open", "high", "low", "close", "volume"] 195 | ) 196 | 197 | df["timestamp"] = pd.to_datetime( 198 | df["timestamp"], unit="ms", utc=True, infer_datetime_format=True 199 | ) 200 | df = df.set_index("timestamp") 201 | return df 202 | 203 | def change_candleDF(self, ohlcv, resolution="1m"): 204 | period = { 205 | "1m": "1T", 206 | "3m": "3T", 207 | "5m": "5T", 208 | "15m": "15T", 209 | "30m": "30T", 210 | "1h": "1H", 211 | "2h": "2H", 212 | "3h": "3H", 213 | "4h": "4H", 214 | "6h": "6H", 215 | "12h": "12H", 216 | "1d": "1D", 217 | "3d": "3D", 218 | "1w": "1W", 219 | "2w": "2W", 220 | "1M": "1M", 221 | } 222 | 223 | if resolution not in period.keys(): 224 | return None 225 | 226 | df = ( 227 | ohlcv[["open", "high", "low", "close", "volume"]] 228 | .resample(period[resolution], label="left", closed="left") 229 | .agg( 230 | { 231 | "open": "first", 232 | "high": "max", 233 | "low": "min", 234 | "close": "last", 235 | "volume": "sum", 236 | } 237 | ) 238 | ) 239 | 240 | return df 241 | --------------------------------------------------------------------------------