├── binance_candle ├── cmd │ └── __init__.py ├── exception │ ├── _base.py │ ├── param.py │ ├── rule.py │ ├── __init__.py │ └── req.py ├── __init__.py ├── code │ └── __init__.py ├── server │ ├── __init__.py │ ├── rule.py │ └── server.py ├── market │ ├── __init__.py │ ├── _base.py │ ├── ticker.py │ ├── exchange_info.py │ └── history_candle.py └── utils.py ├── vx_images ├── 176016628950838.png ├── 244075001625094.png ├── 480313210278479.png └── 495193492836002.png ├── .idea ├── misc.xml ├── vcs.xml ├── .gitignore ├── inspectionProfiles │ ├── profiles_settings.xml │ └── Project_Default.xml ├── modules.xml └── binance_candle.iml ├── example ├── 5. Market 产品深度.py ├── 2. Market 产品列表.py ├── 9. Server 每日下载历史K线.py ├── 8. Server 下载历史K线.py ├── 3. Market 最佳挂单.py ├── 4. Market 最新价格.py ├── 1. Market 交易规范.py ├── 7. Utils candle与df的转化.py ├── 10. Server 维护实时历史K线数据.py └── 6. Market 历史K线.py ├── setup.py └── readme.md /binance_candle/cmd/__init__.py: -------------------------------------------------------------------------------- 1 | import sys 2 | 3 | 4 | def binance_candle_execute(): 5 | print(sys.argv) 6 | -------------------------------------------------------------------------------- /vx_images/176016628950838.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyted/binance_candle/HEAD/vx_images/176016628950838.png -------------------------------------------------------------------------------- /vx_images/244075001625094.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyted/binance_candle/HEAD/vx_images/244075001625094.png -------------------------------------------------------------------------------- /vx_images/480313210278479.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyted/binance_candle/HEAD/vx_images/480313210278479.png -------------------------------------------------------------------------------- /vx_images/495193492836002.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/pyted/binance_candle/HEAD/vx_images/495193492836002.png -------------------------------------------------------------------------------- /binance_candle/exception/_base.py: -------------------------------------------------------------------------------- 1 | # 异常基类 2 | class AbstractEXP(Exception): 3 | error_msg: str 4 | 5 | def __str__(self): 6 | return self.error_msg 7 | -------------------------------------------------------------------------------- /.idea/misc.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /.idea/vcs.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | -------------------------------------------------------------------------------- /binance_candle/exception/param.py: -------------------------------------------------------------------------------- 1 | from binance_candle.exception._base import AbstractEXP 2 | 3 | # 参数异常 4 | class ParamException(AbstractEXP): 5 | def __init__(self, msg): 6 | self.error_msg = msg 7 | -------------------------------------------------------------------------------- /binance_candle/exception/rule.py: -------------------------------------------------------------------------------- 1 | from binance_candle.exception._base import AbstractEXP 2 | 3 | 4 | # 规则异常 5 | class RuleException(AbstractEXP): 6 | def __init__(self, msg): 7 | self.error_msg = msg 8 | -------------------------------------------------------------------------------- /.idea/.gitignore: -------------------------------------------------------------------------------- 1 | # Default ignored files 2 | /shelf/ 3 | /workspace.xml 4 | # Editor-based HTTP Client requests 5 | /httpRequests/ 6 | # Datasource local storage ignored files 7 | /dataSources/ 8 | /dataSources.local.xml 9 | -------------------------------------------------------------------------------- /binance_candle/exception/__init__.py: -------------------------------------------------------------------------------- 1 | '''binance_candle 异常信息''' 2 | from binance_candle.exception.param import * # 参数 3 | from binance_candle.exception.rule import * # 规则 4 | from binance_candle.exception.req import * # 请求 5 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/profiles_settings.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 6 | -------------------------------------------------------------------------------- /.idea/modules.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /.idea/binance_candle.iml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /binance_candle/__init__.py: -------------------------------------------------------------------------------- 1 | from binance_candle.market import (Market, MarketUM, MarketCM, MarketSPOT) 2 | from binance_candle.server import (CandleRule, CandleServer, CandleServerSPOT, CandleServerUM, CandleServerCM) 3 | from binance_candle import utils 4 | from candlelite.crypto.binace_lite import BinanceLite 5 | 6 | __version__ = '1.0.10' -------------------------------------------------------------------------------- /binance_candle/exception/req.py: -------------------------------------------------------------------------------- 1 | from binance_candle.exception._base import AbstractEXP 2 | 3 | 4 | # 请求状态码异常 5 | class CodeException(AbstractEXP): 6 | def __init__(self, msg): 7 | self.error_msg = msg 8 | 9 | 10 | # 请求异常 11 | class RequestException(AbstractEXP): 12 | def __init__(self, msg): 13 | self.error_msg = msg 14 | -------------------------------------------------------------------------------- /example/5. Market 产品深度.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'UM' 8 | # 实例化行情Market 9 | market = Market(instType,proxy_host=proxy_host) 10 | # 单个产品的深度信息 limit : 数量 11 | pprint(market.get_depth('BTCUSDT', limit=10)) 12 | -------------------------------------------------------------------------------- /example/2. Market 产品列表.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'SPOT' 8 | # 实例化行情Market 9 | market = Market(instType,proxy_host=proxy_host) 10 | # 可以交易的产品列表 11 | pprint(market.get_symbols_trading_on()) 12 | # 不可以交易的产品列表 13 | pprint(market.get_symbols_trading_off()) 14 | -------------------------------------------------------------------------------- /example/9. Server 每日下载历史K线.py: -------------------------------------------------------------------------------- 1 | from binance_candle import CandleServer, CandleRule 2 | 3 | if __name__ == '__main__': 4 | proxy_host = None # http://xxx.xx.xx.xx 5 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 6 | instType = 'SPOT' 7 | # 永续合约,默认规则 8 | candleServer = CandleServer(instType, CandleRule, proxy_host=proxy_host) 9 | # 每日在CandleRule.DOWNLOAD_TIME时刻开始下载前一日的历史K线 10 | candleServer.download_daily() 11 | # candleServer.close_download_daily() 关闭服务 12 | -------------------------------------------------------------------------------- /example/8. Server 下载历史K线.py: -------------------------------------------------------------------------------- 1 | from binance_candle import CandleServer, CandleRule 2 | 3 | if __name__ == '__main__': 4 | proxy_host = None # http://xxx.xx.xx.xx 5 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 6 | instType = 'SPOT' 7 | # 永续合约,默认规则 8 | candleServer = CandleServer(instType, CandleRule, proxy_host=proxy_host) 9 | # 下载2023-01-01 ~ 2023-01-02 全部instType的历史K线 10 | candleServer.download_candles_by_date( 11 | start='2023-01-01', 12 | end='2023-01-02', 13 | ) 14 | -------------------------------------------------------------------------------- /example/3. Market 最佳挂单.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'SPOT' 8 | # 实例化行情Market 9 | market = Market(instType,proxy_host=proxy_host) 10 | # 全部产品的最优挂单 11 | pprint(market.get_bookTickersMap()) # 字典类型 12 | pprint(market.get_bookTickers()) # 列表类型 13 | # 单个产品的最优挂单(BTTUSDT) 14 | pprint(market.get_bookTicker('BTCUSDT')) 15 | 16 | -------------------------------------------------------------------------------- /example/4. Market 最新价格.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'SPOT' 8 | # 实例化行情Market 9 | market = Market(instType,proxy_host=proxy_host) 10 | # 全部产品的最新成交价格 11 | pprint(market.get_tickerPricesMap()) # 字典类型 12 | pprint(market.get_tickerPrices()) # 列表类型 13 | # 单个产品的最新成交价格(BTTUSDT) 14 | pprint(market.get_tickerPrice('BTCUSDT')) 15 | -------------------------------------------------------------------------------- /example/1. Market 交易规范.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'SPOT' 8 | # 实例化行情Market 9 | market = Market(instType,proxy_host=proxy_host) 10 | # 产品类型的全部交易规范 11 | exchangeInfos = market.get_exchangeInfos() 12 | pprint(exchangeInfos) 13 | # BTCUSDT的交易规范 14 | BTCUSDT_enchangeInfo = market.get_exchangeInfo('BTCUSDT') 15 | pprint(BTCUSDT_enchangeInfo) 16 | -------------------------------------------------------------------------------- /binance_candle/code/__init__.py: -------------------------------------------------------------------------------- 1 | '''错误状态码与解释''' 2 | 3 | # 交易对信息错误 4 | EXCHANGE_INFO_ERROR = ['EXCHANGE_INFO_ERROR', 'Transaction rules and transaction pair errors'] 5 | # 历史K线时间起点错误 6 | CANDLE_START_ERROR = ['CANDLE_START_ERROR', 'History candle start ts error'] 7 | # 历史K线时间终点错误 8 | CANDLE_END_ERROR = ['CANDLE_END_ERROR', 'History candle end ts error'] 9 | # 历史K线长度错误 10 | CANDLE_LENGTH_ERROR = ['CANDLE_LENGTH_ERROR', 'History candle length error'] 11 | # 历史K线时间间隔错误 12 | CANDLE_INTERVAL_ERROR = ['CANDLE_INTERVAL_ERROR', 'History candle ts interval error'] 13 | -------------------------------------------------------------------------------- /example/7. Utils candle与df的转化.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from binance_candle.utils import candle_to_df, df_to_candle 3 | from pprint import pprint 4 | 5 | if __name__ == '__main__': 6 | proxy_host = None # http://xxx.xx.xx.xx 7 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 8 | instType = 'UM' 9 | # 实例化行情Market 10 | market = Market(instType, timezone='America/New_York',proxy_host=proxy_host) 11 | # 按照起始时间获取历史K线 12 | result = market.get_history_candle( 13 | symbol='BTCUSDT', 14 | start='2023-01-01 00:00:00', 15 | end='2023-01-01 10:00:00', 16 | bar='1m', 17 | ) 18 | candle = result['data'] 19 | # 转化为 20 | df = candle_to_df(candle) 21 | pprint(df) 22 | # 转化为 23 | candle = df_to_candle(df) 24 | pprint(candle) 25 | -------------------------------------------------------------------------------- /example/10. Server 维护实时历史K线数据.py: -------------------------------------------------------------------------------- 1 | from binance_candle import CandleServer, CandleRule 2 | import time 3 | from pprint import pprint 4 | from binance_candle.utils import candle_to_df 5 | 6 | if __name__ == '__main__': 7 | proxy_host = None # http://xxx.xx.xx.xx 8 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 9 | instType = 'UM' 10 | # 永续合约,默认规则 11 | candleServer = CandleServer(instType, CandleRule, proxy_host=proxy_host) 12 | # 启动K线维护服务 13 | candleServer.run_candle_map() 14 | # 被异步维护的candle_map字典 15 | pprint(candleServer.candle_map) 16 | # 打印实时更新的candle 17 | for i in range(120): 18 | # 以安全的方式从candle_map中获取历史线 19 | candle = candleServer.get_candle_security('BTCUSDT') 20 | pprint(candle) # 类型:np.ndarray 21 | pprint(candle_to_df(candle)) # 类型:pd.DataFrame 22 | time.sleep(1) 23 | # 关闭服务 24 | candleServer.close_run_candle_map() 25 | -------------------------------------------------------------------------------- /binance_candle/server/__init__.py: -------------------------------------------------------------------------------- 1 | from binance_candle.server.rule import CandleRule 2 | from binance_candle.server.server import CandleServer 3 | 4 | 5 | class CandleServerSPOT(CandleServer): 6 | def __init__(self, rule, proxies={}, proxy_host: str = None): 7 | instType = 'SPOT' 8 | super(CandleServerSPOT, self).__init__(instType=instType, rule=rule, proxies=proxies, proxy_host=proxy_host) 9 | 10 | 11 | class CandleServerUM(CandleServer): 12 | def __init__(self, rule, proxies={}, proxy_host: str = None): 13 | instType = 'UM' 14 | super(CandleServerUM, self).__init__(instType=instType, rule=rule, proxies=proxies, proxy_host=proxy_host) 15 | 16 | 17 | class CandleServerCM(CandleServer): 18 | def __init__(self, rule, proxies={}, proxy_host: str = None): 19 | instType = 'Cm' 20 | super(CandleServerCM, self).__init__(instType=instType, rule=rule, proxies=proxies, proxy_host=proxy_host) 21 | -------------------------------------------------------------------------------- /example/6. Market 历史K线.py: -------------------------------------------------------------------------------- 1 | from binance_candle import Market 2 | from pprint import pprint 3 | 4 | if __name__ == '__main__': 5 | proxy_host = None # http://xxx.xx.xx.xx 6 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 7 | instType = 'UM' 8 | # 实例化行情Market 9 | market = Market(instType, timezone='America/New_York',proxy_host=proxy_host) 10 | # 获取历史K线接口中最新数据的毫秒时间戳 11 | result = market.get_history_candle_latest_ts(bar='1m') 12 | pprint(result) 13 | # 按照起始时间获取历史K线 14 | result = market.get_history_candle( 15 | symbol='BTCUSDT', 16 | start='2023-01-01 00:00:00', 17 | end='2023-01-01 10:00:00', 18 | bar='1m', 19 | ) 20 | pprint(result) 21 | # 按照日期下载历史K线 22 | result = market.get_history_candle_by_date( 23 | symbol='BTCUSDT', 24 | date='2023-01-01', 25 | bar='1m' 26 | ) 27 | pprint(result) 28 | # 将candle更新到最新,长度为length 29 | result = market.update_history_candle( 30 | symbol='BTCUSDT', 31 | candle=result['data'], # None 表示重新获取 32 | length=1440, 33 | bar='1m', 34 | ) 35 | pprint(result) 36 | -------------------------------------------------------------------------------- /binance_candle/market/__init__.py: -------------------------------------------------------------------------------- 1 | from binance_candle.market.history_candle import HistoryCandle # 历史K线 2 | from binance_candle.market.exchange_info import ExchangeInfo # 交易对信息 3 | from binance_candle.market.ticker import Ticker # 实时价格 4 | 5 | __all__ = ['Market', 'MarketSPOT', 'MarketUM', 'MarketCM'] 6 | 7 | 8 | class Market(HistoryCandle, Ticker, ExchangeInfo): 9 | pass 10 | 11 | 12 | class MarketSPOT(Market): 13 | def __init__(self, key: str = '', secret: str = '', timezone: str = 'America/New_York', proxies={}, proxy_host: str = None, ): 14 | instType = 'SPOT' 15 | super(MarketSPOT, self).__init__(instType=instType, key=key, secret=secret, timezone=timezone, proxies=proxies, proxy_host=proxy_host) 16 | 17 | 18 | class MarketUM(Market): 19 | def __init__(self, key: str = '', secret: str = '', timezone: str = 'America/New_York',proxies={}, proxy_host: str = None, ): 20 | instType = 'UM' 21 | super(MarketUM, self).__init__(instType=instType, key=key, secret=secret, timezone=timezone, proxies=proxies, proxy_host=proxy_host) 22 | 23 | 24 | class MarketCM(Market): 25 | def __init__(self, key: str = '', secret: str = '', timezone: str = 'America/New_York',proxies={}, proxy_host: str = None, ): 26 | instType = 'CM' 27 | super(MarketCM, self).__init__(instType=instType, key=key, secret=secret, timezone=timezone, proxies=proxies, proxy_host=proxy_host) 28 | -------------------------------------------------------------------------------- /binance_candle/server/rule.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from candlelite.crypto.binace_lite import ( 3 | BINANCE_TIMEZONE, # 历史K线默认时区 4 | BINANCE_CANDLE_DATE_BASE_DIR, # 历史K线以日期为单位的默认存储路径 5 | BINANCE_DEFAULT_BAR, # 历史K线默认时间粒度 6 | ) 7 | 8 | 9 | class CandleRule(): 10 | # 服务占用权重上限的比例 现货交易权重上限1200 合约交易权重上限2400 11 | SERVER_WEIGHT = 0.75 12 | # 收集的产品 all表示全部正在交易的产品 13 | SYMBOLS: Union[str, list] = 'all' 14 | # 过滤的产品 15 | SYMBOLS_FILTER: list = [] 16 | # 产品名称中需要包含的内容 17 | SYMBOL_CONTAINS = '' 18 | # 产品名称必须以何内容结尾 19 | SYMBOL_ENDSWITH = '' 20 | # candle_map 更新时间间隔(秒) 21 | UPDATE_INTERVAL_SECONDS: int = 3 22 | # candle_map 缓存K线数据长度 23 | LENGTH: int = 1440 * 2 24 | # 时间颗粒度 25 | BAR: str = BINANCE_DEFAULT_BAR 26 | # candle_map 保存缓存文件时间间隔(秒) None:不保存缓存 27 | CACHE_DELAY_SECONDS: Union[int, None] = 60 * 60 28 | # 服务启动 需维护本地多少天的历史K线数据 29 | LOCAL_CANDLE_DAYS: int = 2 30 | # 每日下载昨日历史K线数据的时刻 格式:%H:%M:%S None表示不下载 31 | DOWNLOAD_TIME: Union[str,None] = '00:10:00' 32 | # 以天为单位的数据存储路径 33 | CANDLE_DIR = BINANCE_CANDLE_DATE_BASE_DIR 34 | # 缓存数据路径 35 | CACHE_DIR = './BINANCE_CACHE' 36 | # 时区 37 | TIMEZONE = BINANCE_TIMEZONE 38 | # KEY 通常无需填写 39 | KEY: str = None 40 | # SECRET 通常无需填写 41 | SECRET: str = None 42 | # 日志文件夹 43 | LOG_DIRPATH = './BINANCE_CANDLE_SERVER_LOG_DATA' 44 | # 日志文件级别 45 | LOG_FILE_LEVEL = 'INFO' 46 | # 日志打印级别 47 | LOG_CONSOLE_LEVEL = 'DEBUG' 48 | -------------------------------------------------------------------------------- /binance_candle/market/_base.py: -------------------------------------------------------------------------------- 1 | from typing import Literal 2 | import pbinance 3 | from binance_candle import exception 4 | 5 | 6 | # 市场信息基类 7 | class MarketBase(pbinance.Binance): 8 | 9 | def __init__( 10 | self, 11 | instType: Literal['SPOT', 'UM', 'CM'], 12 | key: str = '', 13 | secret: str = '', 14 | timezone='America/New_York', 15 | proxies={}, 16 | proxy_host: str = None, 17 | ): 18 | ''' 19 | 主要为了处理inst 20 | instType = 'SPOT' self.inst -> self.spot 21 | instType = 'UM' self.inst -> self.um 22 | instType = 'CM' self.inst -> self.cm 23 | 24 | :param instType: 产品类型 25 | SPOT: 现货 26 | UM: U本位合约 27 | CM: 币本位合约 28 | :param key: API KEY 可以不填写 29 | :param secret: API SECRET 可以不填写 30 | :param timezone: 时区 31 | America/New_York: 美国时间 32 | Asia/Shanghai: 中国时间 33 | ''' 34 | # 父类 35 | super(MarketBase, self).__init__(key=key, secret=secret, proxies=proxies, proxy_host=proxy_host) 36 | # 时区与产品类别 37 | self.timezone = timezone 38 | self.instType = instType.upper() 39 | # inst 40 | if self.instType == 'SPOT': # 现货交易 41 | self.inst = self.spot 42 | elif self.instType == 'UM': # U本位 43 | self.inst = self.um 44 | elif self.instType == 'CM': # 币本位 45 | self.inst = self.cm 46 | else: 47 | msg = '[instType error] your input instType={instType} instType must in ["SPOT","UM","CM","EO"]'.format( 48 | instType=str(instType) 49 | ) 50 | raise exception.ParamException(msg) 51 | -------------------------------------------------------------------------------- /binance_candle/utils.py: -------------------------------------------------------------------------------- 1 | from typing import Union 2 | from copy import deepcopy 3 | import numpy as np 4 | import pandas as pd 5 | from candlelite.crypto.binace_lite import BINANCE_TIMEZONE 6 | from candlelite.calculate.transform import to_candle as _to_candle 7 | from paux import date as _date 8 | 9 | 10 | # 将Binance的历史K线数据从array转换成DataFrame类型 11 | def candle_to_df( 12 | candle: np.ndarray, 13 | convert_ts: bool = True, 14 | timezone: Union[str, None] = BINANCE_TIMEZONE, 15 | ) -> pd.DataFrame: 16 | ''' 17 | :param candle: array类型的历史K线数据 18 | :param convert_ts: 是否将时间戳转化为日期字符串 19 | :param timezone: 时区 20 | 默认使用candlelite中Binance的时区 21 | None 使用本机默认时区 22 | ''' 23 | df = pd.DataFrame(candle) 24 | df.columns = [ 25 | 'openTs', # 开盘时间 Open time 26 | 'open', # 开盘价 Open 27 | 'high', # 最高价 High 28 | 'low', # 最低价 Low 29 | 'close', # 收盘价(当前K线未结束的即为最新价) Close 30 | 'volume', # 成交量 Volume 31 | 'closeTs', # 收盘时间 Close time 32 | 'turnover', # 成交额 Quote asset volume 33 | 'tradeNum', # 成交笔数 Number of trades 34 | 'buyVolume', # 主动买入成交量 Taker buy base asset volume 35 | 'buyTurnover', # 主动买入成交额 Taker buy quote asset volume 36 | 'ignore' # 请忽略该参数 Ignore 37 | ] 38 | df = df.drop(columns=['ignore']) 39 | # 是否转换时间戳为日期字符串 40 | if convert_ts: 41 | # 美国时间 42 | if timezone == 'America/New_York': 43 | fmt = '%m/%d/%Y %H:%M:%S' 44 | # 中国时间 45 | else: 46 | fmt = '%Y-%m-%d %H:%M:%S' 47 | df['openTs'] = df['openTs'].apply( 48 | lambda openTs: _date.to_fmt(date=openTs, timezone=timezone, fmt=fmt) 49 | ) 50 | df['closeTs'] = df['closeTs'].apply( 51 | lambda closeTs: _date.to_fmt(date=closeTs, timezone=timezone, fmt=fmt) 52 | ) 53 | return df 54 | 55 | 56 | # 将Binance的历史K线数据从DataFrame转换成array类型 57 | def df_to_candle( 58 | df: pd.DataFrame, 59 | timezone: Union[str,None]= BINANCE_TIMEZONE 60 | ): 61 | ''' 62 | :param df: DataFrame类型的历史K线数据 63 | :param timezone: 时区 64 | 默认使用candlelite中Binance的时区 65 | None 使用本机默认时区 66 | ''' 67 | df = deepcopy(df) 68 | df['openTs'] = df['openTs'].apply( 69 | lambda openTs: _date.to_ts(date=openTs, timezone=timezone) 70 | ) 71 | df['closeTs'] = df['closeTs'].apply( 72 | lambda closeTs: _date.to_ts(date=closeTs, timezone=timezone) 73 | ) 74 | candle = _to_candle(df) 75 | return candle 76 | -------------------------------------------------------------------------------- /.idea/inspectionProfiles/Project_Default.xml: -------------------------------------------------------------------------------- 1 | 2 | 3 | 25 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | import io 2 | import os 3 | import sys 4 | from shutil import rmtree 5 | from setuptools import find_packages, setup, Command 6 | 7 | NAME = 'binance_candle' 8 | DESCRIPTION = 'Binance local and real-time market candle server' 9 | URL = "https://github.com/pyted/binance_candle" 10 | EMAIL = 'pyted@outlook.com' 11 | AUTHOR = 'pyted' 12 | REQUIRES_PYTHON = '>=3.6.0' 13 | VERSION = '1.0.10' 14 | 15 | REQUIRED = [ 16 | 'pendulum', 17 | 'numpy', 18 | 'pandas', 19 | 'candlelite', 20 | 'paux', 21 | 'pbinance', 22 | ] 23 | 24 | EXTRAS = {} 25 | here = os.path.abspath(os.path.dirname(__file__)) 26 | try: 27 | with io.open(os.path.join(here, 'README.md'), encoding='utf-8') as f: 28 | long_description = '\n' + f.read() 29 | except FileNotFoundError: 30 | long_description = DESCRIPTION 31 | 32 | about = {} 33 | 34 | if not VERSION: 35 | project_slug = NAME.lower().replace("-", "_").replace(" ", "_") 36 | with open(os.path.join(here, project_slug, '__version__.py')) as f: 37 | exec(f.read(), about) 38 | else: 39 | about['__version__'] = VERSION 40 | 41 | 42 | class UploadCommand(Command): 43 | """Support setup.py upload.""" 44 | 45 | description = 'Build and publish the package.' 46 | user_options = [] 47 | 48 | @staticmethod 49 | def status(s): 50 | """Prints things in bold.""" 51 | print('\033[1m{0}\033[0m'.format(s)) 52 | 53 | def initialize_options(self): 54 | pass 55 | 56 | def finalize_options(self): 57 | pass 58 | 59 | def run(self): 60 | try: 61 | self.status('Removing previous builds…') 62 | rmtree(os.path.join(here, 'dist')) 63 | except OSError: 64 | pass 65 | 66 | self.status('Building Source and Wheel (universal) distribution…') 67 | os.system('{0} setup.py sdist bdist_wheel --universal'.format(sys.executable)) 68 | 69 | self.status('Uploading the package to PyPI via Twine…') 70 | os.system('twine upload dist/*') 71 | 72 | self.status('Pushing git tags…') 73 | os.system('git tag v{0}'.format(about['__version__'])) 74 | os.system('git push --tags') 75 | 76 | sys.exit() 77 | 78 | 79 | # Where the magic happens: 80 | setup( 81 | name=NAME, 82 | version=about['__version__'], 83 | description=DESCRIPTION, 84 | long_description=long_description, 85 | long_description_content_type='text/markdown', 86 | author=AUTHOR, 87 | author_email=EMAIL, 88 | python_requires=REQUIRES_PYTHON, 89 | url=URL, 90 | packages=find_packages(exclude=["tests", "*.tests", "*.tests.*", "tests.*"]), 91 | install_requires=REQUIRED, 92 | extras_require=EXTRAS, 93 | include_package_data=True, 94 | license='MIT', 95 | classifiers=[ 96 | 'License :: OSI Approved :: MIT License', 97 | 'Programming Language :: Python', 98 | 'Programming Language :: Python :: 3', 99 | 'Programming Language :: Python :: 3.6', 100 | 'Programming Language :: Python :: Implementation :: CPython', 101 | 'Programming Language :: Python :: Implementation :: PyPy' 102 | ], 103 | cmdclass={ 104 | 'upload': UploadCommand, 105 | }, 106 | ) 107 | -------------------------------------------------------------------------------- /binance_candle/market/ticker.py: -------------------------------------------------------------------------------- 1 | from binance_candle.market._base import MarketBase 2 | from typing import Union 3 | 4 | __all__ = ['Ticker'] 5 | 6 | 7 | class Ticker(MarketBase): 8 | 9 | # 获取全部产品的最优挂单 10 | # Weight: 现货 2 合约 5 11 | def get_bookTickers(self) -> dict: 12 | ''' 13 | 现货 14 | https://binance-docs.github.io/apidocs/spot/cn/#5393cd07b4 15 | U本位合约 16 | https://binance-docs.github.io/apidocs/futures/cn/#5393cd07b4 17 | 币本位合约 18 | https://binance-docs.github.io/apidocs/delivery/cn/#5393cd07b4 19 | ''' 20 | return self.inst.market.get_ticker_bookTicker() 21 | 22 | # 获取全部产品的最优挂单 (字典格式) 23 | # Weight: 现货 2 合约 5 24 | def get_bookTickersMap(self) -> dict: 25 | get_bookTickers_result = self.get_bookTickers() 26 | # [ERROR RETURN] 数据异常 27 | if get_bookTickers_result['code'] != 200: 28 | return get_bookTickers_result 29 | data_map = {} 30 | for ticker in get_bookTickers_result['data']: 31 | symbol = ticker['symbol'] 32 | data_map[symbol] = ticker 33 | 34 | get_bookTickers_result['data'] = data_map 35 | return get_bookTickers_result 36 | 37 | # 获取一个产品的最优挂单 38 | # Weight: 现货 1 合约 2 39 | def get_bookTicker(self, symbol: str) -> dict: 40 | ''' 41 | 现货 42 | https://binance-docs.github.io/apidocs/spot/cn/#5393cd07b4 43 | U本位合约 44 | https://binance-docs.github.io/apidocs/futures/cn/#5393cd07b4 45 | 币本位合约 46 | https://binance-docs.github.io/apidocs/delivery/cn/#5393cd07b4 47 | :param symbol: 产品 48 | ''' 49 | return self.inst.market.get_ticker_bookTicker(symbol=symbol) 50 | 51 | # 获取全部产品的最新价格 52 | # Weight: 2 53 | def get_tickerPrices(self): 54 | ''' 55 | 现货 56 | https://binance-docs.github.io/apidocs/spot/cn/#8ff46b58de 57 | U本位合约 58 | https://binance-docs.github.io/apidocs/futures/cn/#8ff46b58de 59 | 币本位合约 60 | https://binance-docs.github.io/apidocs/delivery/cn/#8ff46b58de 61 | ''' 62 | return self.inst.market.get_ticker_price(symbol='') 63 | 64 | # 获取全部产品的最新价格 (字典格式) 65 | # Weight: 2 66 | def get_tickerPricesMap(self): 67 | get_tickerPrices_result = self.get_tickerPrices() 68 | # [ERROR RETURN] 数据异常 69 | if get_tickerPrices_result['code'] != 200: 70 | return get_tickerPrices_result 71 | data_map = {} 72 | for ticker in get_tickerPrices_result['data']: 73 | symbol = ticker['symbol'] 74 | data_map[symbol] = ticker 75 | 76 | get_tickerPrices_result['data'] = data_map 77 | return get_tickerPrices_result 78 | 79 | # 获取一个产品的最新价格 80 | # Weight: 1 81 | def get_tickerPrice(self, symbol: str): 82 | ''' 83 | 现货 84 | https://binance-docs.github.io/apidocs/spot/cn/#8ff46b58de 85 | U本位合约 86 | https://binance-docs.github.io/apidocs/futures/cn/#8ff46b58de 87 | 币本位合约 88 | https://binance-docs.github.io/apidocs/delivery/cn/#8ff46b58de 89 | :param symbol: 产品 90 | ''' 91 | return self.inst.market.get_ticker_price(symbol=symbol) 92 | 93 | def get_depth(self, symbol: str, limit: Union[int, str] = ''): 94 | ''' 95 | 现货 96 | https://binance-docs.github.io/apidocs/spot/cn/#38a975b802 97 | U本位合约 98 | https://binance-docs.github.io/apidocs/futures/cn/#38a975b802 99 | 币本位合约 100 | https://binance-docs.github.io/apidocs/delivery/cn/#38a975b802 101 | :param symbol: 产品 102 | :param limit: 数量 103 | ''' 104 | return self.inst.market.get_depth(symbol=symbol, limit=limit) 105 | -------------------------------------------------------------------------------- /binance_candle/market/exchange_info.py: -------------------------------------------------------------------------------- 1 | import time 2 | from binance_candle import code 3 | from binance_candle.market._base import MarketBase 4 | 5 | __all__ = ['ExchangeInfo'] 6 | 7 | 8 | # 交易规则与交易对信息 9 | class ExchangeInfo(MarketBase): 10 | ''' 11 | 现货 12 | https://binance-docs.github.io/apidocs/spot/cn/#e7746f7d60 13 | U本位合约 14 | https://binance-docs.github.io/apidocs/futures/cn/#0f3f2d5ee7 15 | 币本位合约 16 | https://binance-docs.github.io/apidocs/delivery/cn/#185368440e 17 | ''' 18 | 19 | # 通过缓存获取交易规则与交易对 20 | # Weight: 现货10 合约1 使用缓存0 21 | def get_exchangeInfos( 22 | self, 23 | expire_seconds: int = 60 * 5 24 | ) -> dict: 25 | ''' 26 | :param expire_seconds: 缓存时间(秒) 27 | 28 | 29 | 使用的缓存数据格式: 30 | self._exchangeInfo_cache = [ 31 | { 32 | 'code':<状态码>, 33 | 'data':, 34 | 'msg':<提示信息>, 35 | }, 36 | <上次更新的毫秒时间戳> 37 | ] 38 | ''' 39 | if ( 40 | # 无缓存数据 41 | not hasattr(self, '_exchangeInfo_caches') 42 | or 43 | # 缓存数据过期 44 | getattr(self, '_exchangeInfo_caches')[1] - time.time() * 1000 >= expire_seconds 45 | ): 46 | # 更新数据并设置时间戳 47 | setattr(self, '_exchangeInfo_caches', [self.inst.market.get_exchangeInfo(), time.time() * 1000]) 48 | # 返回缓存数据 49 | return getattr(self, '_exchangeInfo_caches')[0] 50 | 51 | # 获取一个产品的交易规则与交易对 52 | # Weight: 现货10 合约1 使用缓存0 53 | def get_exchangeInfo( 54 | self, 55 | symbol: str, 56 | expire_seconds: int = 60 * 5, 57 | ) -> dict: 58 | ''' 59 | :param symbol: 产品 60 | :param expire_seconds: 缓存时间(秒) 61 | ''' 62 | exchangeInfos_result = self.get_exchangeInfos(expire_seconds) 63 | # [ERROR RETURN] 异常交易规则与交易 64 | if exchangeInfos_result['code'] != 200: 65 | return exchangeInfos_result 66 | # 寻找symbol的信息 67 | for symbol_data in exchangeInfos_result['data']['symbols']: 68 | if symbol_data['symbol'] == symbol: 69 | symbol_data = symbol_data 70 | break 71 | else: 72 | symbol_data = None 73 | # [ERROR RETURN] 没有找到symbol的交易规则与交易对信息 74 | if symbol_data == None: 75 | result = { 76 | 'code': code.EXCHANGE_INFO_ERROR[0], 77 | 'data': exchangeInfos_result['data'], 78 | 'msg': f'Symbol not found symbol={symbol}' 79 | } 80 | return result 81 | # 将filters中的列表转换为字典,里面可能包含下单价格与数量精度 82 | symbol_data['filter'] = {} 83 | for filter_data in symbol_data['filters']: 84 | symbol_data['filter'][ 85 | filter_data['filterType'] 86 | ] = filter_data 87 | # [RETURN] 88 | result = { 89 | 'code': 200, 90 | 'data': symbol_data, 91 | 'msg': '', 92 | } 93 | return result 94 | 95 | # 获取可以交易的产品列表 96 | # Weight: 现货10 合约1 使用缓存0 97 | def get_symbols_trading_on( 98 | self, 99 | expire_seconds: int = 60 * 5 100 | ) -> dict: 101 | ''' 102 | :param expire_seconds: 缓存时间(秒) 103 | ''' 104 | exchangeInfos_result = self.get_exchangeInfos(expire_seconds) 105 | # [ERROR RETURN] 异常交易规则与交易 106 | if exchangeInfos_result['code'] != 200: 107 | return exchangeInfos_result 108 | # 正在交易的产品名称 status == 'TRADING' 109 | if self.instType == 'CM': # 币本位合约交易的状态名称特殊 110 | status_name = 'contractStatus' 111 | else: 112 | status_name = 'status' 113 | symbols = [ 114 | data['symbol'] 115 | for data in exchangeInfos_result['data']['symbols'] 116 | if data[status_name] == 'TRADING' 117 | ] 118 | # [RETURN] 119 | result = { 120 | 'code': 200, 121 | 'data': symbols, 122 | 'msg': '' 123 | } 124 | return result 125 | 126 | # 获取不可交易的产品列表 127 | # Weight: 现货10 合约1 使用缓存0 128 | def get_symbols_trading_off( 129 | self, 130 | expire_seconds: int = 60 * 5 131 | ) -> dict: 132 | ''' 133 | :param expire_seconds: 缓存时间(秒) 134 | ''' 135 | 136 | exchangeInfos_result = self.get_exchangeInfos(expire_seconds) 137 | # [ERROR RETURN] 异常交易规则与交易 138 | if exchangeInfos_result['code'] != 200: 139 | return exchangeInfos_result 140 | # 不可交易的产品名称 status != 'TRADING' 141 | if self.instType == 'CM': # 币本位合约交易的状态名称特殊 142 | status_name = 'contractStatus' 143 | else: 144 | status_name = 'status' 145 | 146 | symbols = [ 147 | data['symbol'] 148 | for data in exchangeInfos_result['data']['symbols'] 149 | if data[status_name] != 'TRADING' 150 | ] 151 | # [RETURN] 152 | result = { 153 | 'code': 200, 154 | 'data': symbols, 155 | 'msg': '' 156 | } 157 | return result 158 | 159 | # 获取全部的产品列表 160 | # Weight: 现货10 合约1 使用缓存0 161 | def get_symbols_all( 162 | self, 163 | expire_seconds: int = 60 * 5 164 | ) -> dict: 165 | ''' 166 | :param expire_seconds: 缓存时间(秒) 167 | ''' 168 | 169 | exchangeInfos_result = self.get_exchangeInfos(expire_seconds) 170 | # [ERROR RETURN] 异常交易规则与交易 171 | if exchangeInfos_result['code'] != 200: 172 | return exchangeInfos_result 173 | # 不可交易的产品名称 status != 'TRADING' 174 | if self.instType == 'CM': # 币本位合约交易的状态名称特殊 175 | status_name = 'contractStatus' 176 | else: 177 | status_name = 'status' 178 | 179 | symbols = [ 180 | data['symbol'] 181 | for data in exchangeInfos_result['data']['symbols'] 182 | ] 183 | # [RETURN] 184 | result = { 185 | 'code': 200, 186 | 'data': symbols, 187 | 'msg': '' 188 | } 189 | return result 190 | -------------------------------------------------------------------------------- /binance_candle/market/history_candle.py: -------------------------------------------------------------------------------- 1 | import time 2 | import math 3 | import datetime 4 | import pendulum 5 | import pandas as pd 6 | import numpy as np 7 | import paux.date as _date 8 | from typing import Literal, Union 9 | from binance_candle import code 10 | from binance_candle.market._base import MarketBase 11 | from candlelite.calculate import valid as _valid 12 | from candlelite.calculate import transform as _transform 13 | from candlelite.calculate import interval as _interval 14 | 15 | __all__ = ['HistoryCandle'] 16 | 17 | 18 | # 历史K线 19 | class HistoryCandle(MarketBase): 20 | # 将API返回的原始历史K线数据列表转换为np.array类型 21 | def __candle_list_to_candle( 22 | self, 23 | symbol: str, 24 | candle_list: list, 25 | start: Union[int, float, str, datetime.date, datetime.datetime, None], 26 | end: Union[int, float, str, datetime.date, datetime.datetime, None], 27 | bar: str = '1m', 28 | valid_interval: bool = True, 29 | valid_start: bool = True, 30 | valid_end: bool = True 31 | ) -> dict: 32 | ''' 33 | :param symbol: 产品 34 | :param candle_list: API返回的K线数据列表 35 | :param start: 起始时间 36 | :param end: 终止时间 37 | start和end可以为None,如果存在数据,会截取[start,end]的数据 38 | 例如: 39 | start == None end != None 40 | -> ts <= end 41 | start != None end == None 42 | -> ts >= start 43 | start != None end != None 44 | -> start <= ts <= end 45 | :param bar: 时间粒度 46 | :param valid_interval: 是否验证时间间隔 47 | :param valid_start: 是否验证时间起点 start必须存在 48 | :param valid_end: 是否验证时间终点 end必须存在 49 | ''' 50 | # 转换DataFrame 51 | df = pd.DataFrame(candle_list).astype(float) 52 | # [ERROR RETURN] 数据为空 53 | if not df.shape[0]: 54 | result = { 55 | 'code': code.CANDLE_LENGTH_ERROR[0], 56 | 'data': np.array([]), 57 | 'msg': 'Candle empty {symbol} {start} ~ {end}'.format( 58 | symbol=str(symbol), 59 | start=_date.to_fmt(date=start, timezone=self.timezone) if start else 'None', 60 | end=_date.to_fmt(date=end, timezone=self.timezone) if end else 'None', 61 | ), 62 | } 63 | return result 64 | # 按照时间戳去重 65 | ts_column = df.columns.tolist()[0] 66 | df[ts_column] = df[ts_column].astype(int) 67 | df = df.drop_duplicates(subset=ts_column) 68 | # 按照start和end截取数据 69 | if start and end: 70 | df = df[ 71 | (df[ts_column] <= _date.to_ts(end, timezone=self.timezone)) & 72 | (df[ts_column] >= _date.to_ts(start, timezone=self.timezone)) 73 | ] 74 | elif start and not end: 75 | df = df[df[ts_column] >= _date.to_ts(start, timezone=self.timezone)] 76 | elif end and not start: 77 | df = df[df[ts_column] <= _date.to_ts(end, timezone=self.timezone)] 78 | # 排序 79 | df = df.sort_values(by=ts_column) 80 | # 转化为array 81 | candle = df.to_numpy() 82 | # 验证时间起点 83 | if valid_start: 84 | valid_start_result = _valid.valid_start(candle=candle, start=start, timezone=self.timezone) 85 | # [ERROR RETURN] 时间起点错误 86 | if not valid_start_result['code']: 87 | return { 88 | 'code': code.CANDLE_START_ERROR[0], 89 | 'data': candle, 90 | 'msg': str(symbol) + valid_start_result['msg'] 91 | } 92 | # 验证时间终点 93 | if valid_end: 94 | valid_end_result = _valid.valid_end(candle=candle, end=end, timezone=self.timezone) 95 | # [ERROR RETURN] 时间终点错误 96 | if not valid_end_result['code']: 97 | return { 98 | 'code': code.CANDLE_END_ERROR[0], 99 | 'data': candle, 100 | 'msg': str(symbol) + valid_end_result['msg'], 101 | } 102 | 103 | # 验证时间间隔 104 | if valid_interval: 105 | valid_interval_result = _valid.valid_interval(candle=candle, interval=_interval.get_interval(bar)) 106 | # [ERROR RETURN] 时间间隔错误 107 | if not valid_interval_result['code']: 108 | return { 109 | 'code': code.CANDLE_INTERVAL_ERROR[0], 110 | 'data': candle, 111 | 'msg': str(symbol) + valid_interval_result['msg'], 112 | } 113 | # [RETURN] 114 | result = {'code': 200, 'data': candle, 'msg': ''} 115 | return result 116 | 117 | # 将Binance的candle转化为DataFrame 118 | def candle_to_df( 119 | self, 120 | candle: np.ndarray, 121 | convert_ts: bool = True 122 | ) -> pd.DataFrame: 123 | ''' 124 | :param candle: 历史K线数据 125 | :param convert_ts: 是否将时间戳转化为日期字符串 126 | ''' 127 | df = pd.DataFrame(candle) 128 | df.columns = [ 129 | 'openTs', # 开盘时间 Open time 130 | 'open', # 开盘价 Open 131 | 'high', # 最高价 High 132 | 'low', # 最低价 Low 133 | 'close', # 收盘价(当前K线未结束的即为最新价) Close 134 | 'volume', # 成交量 Volume 135 | 'closeTs', # 收盘时间 Close time 136 | 'turnover', # 成交额 Quote asset volume 137 | 'tradeNum', # 成交笔数 Number of trades 138 | 'buyVolume', # 主动买入成交量 Taker buy base asset volume 139 | 'buyTurnover', # 主动买入成交额 Taker buy quote asset volume 140 | 'ignore' # 请忽略该参数 Ignore 141 | ] 142 | df = df.drop(columns=['ignore']) 143 | # 是否转换时间戳为日期字符串 144 | if convert_ts: 145 | # 美国时间 146 | if self.timezone == 'America/New_York': 147 | fmt = '%m/%d/%Y %H:%M:%S' 148 | # 中国时间 149 | else: 150 | fmt = '%Y-%m-%d %H:%M:%S' 151 | df['openTs'] = df['openTs'].apply( 152 | lambda openTs: _date.to_fmt(date=openTs, timezone=self.timezone, fmt=fmt) 153 | ) 154 | df['closeTs'] = df['closeTs'].apply( 155 | lambda closeTs: _date.to_fmt(date=closeTs, timezone=self.timezone, fmt=fmt) 156 | ) 157 | return df 158 | 159 | # 获取产品的历史K线数据 160 | # Weight >= 1 (智能节约权重) 161 | def get_history_candle( 162 | self, 163 | symbol: str, 164 | start: Union[str, int, float, datetime.datetime, datetime.date], 165 | end: Union[str, int, float, datetime.datetime, datetime.date], 166 | bar: str = '1m', 167 | valid_interval: bool = True, 168 | valid_start: bool = True, 169 | valid_end: bool = True 170 | ) -> dict: 171 | ''' 172 | :param symbol: 产品 173 | :param start: 起始日期时间 174 | :param end: 起始日期时间 175 | :param bar: 时间粒度 176 | :param valid_interval: 是否验证时间间隔 177 | :param valid_start: 是否验证起始时间 178 | :param valid_end: 是否验证终止时间 179 | 180 | 181 | get_kline包含startTime和endTime时刻的数据 182 | 合约交易: 183 | 如果endTime为空,会向后返回limit个数据 184 | 如果startTime为空,会向前返回limit个数据 185 | 现货交易: 186 | 需要填写startTime、endTime、limit 187 | ''' 188 | # 起始与终止时间戳 189 | start_ts = _date.to_ts(date=start, timezone=self.timezone) 190 | end_ts = _date.to_ts(date=end, timezone=self.timezone) 191 | # 时间间隔 192 | bar_interval = _interval.get_interval(bar=bar) 193 | # 初始化起始时间和candle列表 194 | before_ts = start_ts 195 | candle_list = [] 196 | while True: 197 | # 计算limit 198 | limit = self._get_limit(num=(end_ts - before_ts) / bar_interval + 1) 199 | # 获取K线数据 200 | result = self.inst.market.get_klines( 201 | symbol=symbol, 202 | interval=bar, 203 | startTime=before_ts, 204 | endTime=before_ts + (limit - 1) * bar_interval, 205 | limit=limit, 206 | ) 207 | # [ERROR RETURN] K线数据错误 208 | if result['code'] != 200: 209 | return result 210 | # [BREAK] 数据为空 211 | if not result['data']: 212 | break 213 | # 保存candle 214 | candle_list += result['data'] 215 | # 跳过上一次的endTime,上一次的endTime=before_ts + (limit - 1) * bar_interval 216 | before_ts = before_ts + limit * bar_interval 217 | # [BREAK] 完成 218 | if before_ts > end_ts: 219 | break 220 | # 转换为candle 221 | result = self.__candle_list_to_candle( 222 | symbol=symbol, 223 | candle_list=candle_list, 224 | start=start, 225 | end=end, 226 | bar=bar, 227 | valid_interval=valid_interval, 228 | valid_start=valid_start, 229 | valid_end=valid_end, 230 | ) 231 | # [RETURN] 232 | return result 233 | 234 | # 获取产品指定数量的最新历史K线数据 235 | # Weight > 1 (智能节约权重) 236 | def get_history_candle_latest( 237 | self, 238 | symbol: str, 239 | length: int, 240 | end: Union[str, int, float, datetime.date, datetime.datetime, None] = None, 241 | bar: str = '1m', 242 | valid_interval: bool = True, 243 | ) -> dict: 244 | ''' 245 | :param symbol: 合约产品 246 | :param length: K线长度 247 | :param end: 终止日期时间 248 | 支持: 毫秒时间戳、日期格式的字符串、日期时间对象与日期对象 -> 自动验证数据终点 249 | None: 取当前时间 -> 不验证时间终点 250 | :param bar: 时间粒度 251 | :param valid_interval: 是否验证时间间隔 252 | ''' 253 | # 时间终止 254 | if not end: 255 | end_ts = int(time.time() * 1000) 256 | else: 257 | end_ts = _date.to_ts(date=end, timezone=self.timezone) 258 | # 时间间隔 259 | bar_interval = _interval.get_interval(bar) 260 | # 从后向前获取candle 261 | after_ts = end_ts 262 | candle_list = [] 263 | while True: 264 | # 计算limit 265 | limit = self._get_limit(num=length - len(candle_list)) 266 | # 获取K线数据 267 | result = self.inst.market.get_klines( 268 | symbol=symbol, 269 | interval=bar, 270 | startTime=after_ts - (limit - 1) * bar_interval, 271 | endTime=after_ts, 272 | limit=limit, 273 | ) 274 | # [ERROR RETURN] 数据错误 275 | if result['code'] != 200: 276 | return result 277 | # [BREAK] 数据为空 278 | if not result['data']: 279 | break 280 | # 添加 281 | candle_list += result['data'] 282 | # [BREAK] 完成 283 | if len(candle_list) >= length: 284 | break 285 | # 计算下一个的终止时间 286 | after_ts = candle_list[0][0] - bar_interval 287 | # after_ts = after_ts - limit * bar_interval 288 | # 转换为np.array 289 | result = self.__candle_list_to_candle( 290 | symbol=symbol, 291 | candle_list=candle_list, 292 | start=None, 293 | end=end, 294 | bar=bar, 295 | valid_interval=valid_interval, 296 | valid_start=False, 297 | valid_end=True if end != None else False, # 是否验证终止时间 298 | ) 299 | # [ERROR RETURN] 转换失败 300 | if result['code'] != 200: 301 | return result 302 | # 取后length行数据 303 | candle = result['data'][-length:] 304 | # 验证数据长度 305 | valid_length_result = _valid.valid_length(candle=candle, length=length) 306 | # [ERROR RETURN] 数据长度错误 307 | if not valid_length_result['code']: 308 | return { 309 | 'code': code.CANDLE_LENGTH_ERROR[0], 310 | 'data': candle, 311 | 'msg': str(symbol) + valid_length_result['msg'] 312 | } 313 | # [RETURN] 314 | result['data'] = candle 315 | return result 316 | 317 | # 获取产品指定日期的历史K线数据 318 | # Weight: 1 ~ 10 (智能节约权重) 319 | def get_history_candle_by_date( 320 | self, 321 | symbol: str, 322 | date: Union[str, int, float, datetime.date], 323 | bar: str = '1m', 324 | valid_interval: bool = True, 325 | valid_start: bool = True, 326 | valid_end: bool = True 327 | ) -> dict: 328 | ''' 329 | :param symbol: 产品 330 | :param date: 日期 331 | :param bar: 时间粒度 332 | :param valid_interval: 是否验证时间间隔 333 | :param valid_start: 是否验证起始时间 334 | :param valid_end: 是否验证终止时间 335 | ''' 336 | # 日期时间对象,其中小时分钟秒均为0 337 | date = _date.to_datetime(date=date, timezone=self.timezone) 338 | # 起始毫秒时间戳 339 | start = int(date.timestamp() * 1000) 340 | bar_interval = _interval.get_interval(bar) 341 | # 终止毫秒时间戳 342 | end_date = datetime.date(year=date.year, month=date.month, day=date.day) + datetime.timedelta(days=1) 343 | end_date = pendulum.datetime( 344 | year=end_date.year, 345 | month=end_date.month, 346 | day=end_date.day, 347 | hour=0, 348 | minute=0, 349 | second=0, 350 | tz=self.timezone, 351 | ) 352 | end = int(end_date.timestamp() * 1000 - 1 * bar_interval) 353 | # [RETURN] 354 | return self.get_history_candle( 355 | symbol=symbol, 356 | start=start, 357 | bar=bar, 358 | end=end, 359 | valid_interval=valid_interval, 360 | valid_start=valid_start, 361 | valid_end=valid_end, 362 | ) 363 | 364 | # 获取历史K线数据中最新的毫秒时间戳 365 | # Weight: 1 366 | def get_history_candle_latest_ts( 367 | self, 368 | bar: str = '1m', 369 | ) -> dict: 370 | ''' 371 | :param bar: 时间粒度 372 | 现货与U本位合约使用BTCUSDT作为基准,币本位合约使用BTCUSD_PERP作为基准 373 | ''' 374 | # 币本位合约 375 | if self.instType.lower() == 'cm': 376 | symbol = 'BTCUSD_PERP' 377 | # 现货与U本位合约 378 | else: 379 | symbol = 'BTCUSDT' 380 | # 获取长度为1的最新历史K数据 381 | result = self.get_history_candle_latest( 382 | symbol=symbol, 383 | bar=bar, 384 | length=99, 385 | ) 386 | # [ERROR RETURN] 历史K线有误 387 | if result['code'] != 200: 388 | return result 389 | # 最新的毫秒时间戳,以浮点数保存 390 | latest_ts = float(result['data'][-1, 0]) 391 | result['data'] = latest_ts 392 | # [RETURN] 393 | return result 394 | 395 | # 更新产品历史K线数据到指定时间 396 | # Weight: >= 1 (Intelligent saving weight) 397 | def update_history_candle( 398 | self, 399 | symbol: str, 400 | length: int, 401 | candle: np.array = None, 402 | end: [int, float, datetime.date, datetime.datetime, str] = None, 403 | bar: str = '1m', 404 | valid_interval=True, 405 | ) -> dict: 406 | ''' 407 | :param symbol: 合约产品 408 | :param length: 更新后的有效长度 409 | :param candle: 历史K线数据 None: 重新下载candle数据 410 | :param end: 更新后的终点时间 411 | None: 使用最新的历史K线数据毫秒时间戳 412 | :param bar: 时间粒度 413 | :param valid_interval: 是否验证时间间隔 414 | 415 | 如论end是有有值,都会验证数据的时间终点与长度 416 | ''' 417 | # 终止时间戳end_ts 418 | if end: 419 | # end有值 420 | end_ts = _date.to_ts(date=end, timezone=self.timezone) 421 | else: 422 | # 获取最新的历史K线时间戳 423 | result = self.get_history_candle_latest_ts( 424 | bar=bar, 425 | ) 426 | # [ERROR RETURN] 获取时间戳异常 427 | if result['code'] != 200: 428 | return result 429 | end_ts = result['data'] 430 | # 时间间隔 431 | bar_interval = _interval.get_interval(bar) 432 | # 计算剩余需要更新的长度 length_left 433 | # 存在candle 434 | if isinstance(candle, np.ndarray) and candle.shape[0] > 0: 435 | # 数据间隔异常 更新全部 436 | if not (np.diff(candle[:, 0]) == bar_interval).all(): 437 | length_left = length 438 | else: 439 | # 数据过早 更新全部 440 | if end_ts - (length - 1) * bar_interval < candle[:, 0].min(): 441 | length_left = length 442 | # 更新剩余部分 443 | else: 444 | candle_end_ts = candle[:, 0].max() 445 | length_left = int(min((end_ts - candle_end_ts) / bar_interval, length)) 446 | # 不存在candle,更新全部 447 | else: 448 | length_left = length 449 | # [RETURN] 无需更新 450 | if length_left == 0: 451 | return {'code': 200, 'data': candle, 'msg': ''} 452 | # 获取最新数据 453 | result = self.get_history_candle_latest( 454 | symbol=symbol, 455 | length=length_left, 456 | end=end_ts, 457 | bar=bar, 458 | valid_interval=valid_interval, 459 | ) 460 | # [ERROR RETURN] 最新历史K线数据异常 461 | if result['code'] != 200: 462 | return result 463 | # 如果candle存在,二者拼接 candle_union 464 | candle_add = result['data'] 465 | if isinstance(candle, np.ndarray) and candle.shape[0] > 0: 466 | candle_union = _transform.concat_candle( 467 | candles=[candle, candle_add], 468 | drop_duplicate=True, 469 | sort=True 470 | ) 471 | else: 472 | candle_union = candle_add 473 | # 取末尾length长度 474 | candle_union = candle_union[-length:] 475 | # 验证时间间隔 476 | if valid_interval: 477 | valid_interval_result = _valid.valid_interval(candle=candle_union, interval=_interval.get_interval(bar)) 478 | # [ERROR RETURN] 时间间隔错误 479 | if not valid_interval_result['code']: 480 | return { 481 | 'code': code.CANDLE_INTERVAL_ERROR[0], 482 | 'data': candle_union, 483 | 'msg': str(symbol) + valid_interval_result['msg'] 484 | } 485 | # 验证数据长度 486 | valid_length_result = _valid.valid_length(candle=candle_union, length=length) 487 | # 【ERROR RETURN】数据长度错误 488 | if not valid_length_result['code']: 489 | return { 490 | 'code': code.CANDLE_LENGTH_ERROR[0], 491 | 'data': candle_union, 492 | 'msg': str(symbol) + valid_length_result['msg'], 493 | } 494 | 495 | # [RETURN] 496 | result = {'code': 200, 'data': candle_union, 'msg': ''} 497 | return result 498 | 499 | # 获取花费的权重 500 | def _get_weight(self, num: int) -> int: 501 | # 现货 502 | if self.instType.upper() == 'SPOT': 503 | return math.ceil(num / 1000) * 1 # 单次访问权重1 504 | # 合约 505 | else: 506 | weight = 0 507 | while True: 508 | if num > 1000: 509 | num -= 1500 # limit 1500 510 | weight += 10 # weight 10 511 | elif num >= 500: 512 | num -= 1000 # limit 1000 513 | weight += 5 # weight 5 514 | elif num >= 100: 515 | num -= 499 # limit 499 516 | weight += 2 # weight 2 517 | elif num > 0: 518 | num -= 99 # limit 99 519 | weight += 1 # weight 1 520 | else: 521 | break 522 | return weight 523 | 524 | # 历史K线 根据num获取最佳的limit 525 | def _get_limit(self, num: int) -> int: 526 | # 现货 limit 1~1000 权重均为1 527 | if self.instType.upper() == 'SPOT': 528 | return int(np.clip(num, 1, 1000)) 529 | # 合约 limit 1~1500 权重1~10 530 | else: 531 | if num > 1000: 532 | limit = 1500 # Weight 10 533 | elif num >= 500: 534 | limit = 1000 # Weight 5 535 | elif num >= 100: 536 | limit = 499 # Weight 2 537 | else: 538 | limit = 99 # Weight 1 539 | return limit 540 | -------------------------------------------------------------------------------- /binance_candle/server/server.py: -------------------------------------------------------------------------------- 1 | import random 2 | import datetime 3 | import time 4 | import numpy as np 5 | import pendulum 6 | from typing import Literal, Union 7 | from paux import filter as _filter 8 | from paux import log as _log 9 | from paux import date as _date 10 | from paux import thread as _thread 11 | from candlelite.calculate import interval as _interval 12 | from candlelite.crypto.binace_lite import BinanceLite 13 | from binance_candle import code 14 | from binance_candle.market import Market 15 | from binance_candle import exception 16 | from binance_candle.server.rule import CandleRule 17 | 18 | 19 | class CandleServer(): 20 | def __init__(self, instType: Literal['SPOT', 'UM', 'CM'], rule=CandleRule, proxies={}, proxy_host: str = None): 21 | # 规则 22 | self.rule = rule 23 | # # 每日下载历史K线的时间 -> 维护self.rule.DOWNLOAD_TIME 24 | # if self.rule.DOWNLOAD_TIME.lower() == 'auto': 25 | # minutes = int(_interval.get_interval(self.rule.BAR) / 60000 * 2) 26 | # minutes = int(np.clip(minutes, 10, 1439)) 27 | # download_time = (pendulum.time(0, 0, 0) + datetime.timedelta(minutes=minutes)).strftime('%H:%M:%S') 28 | # self.rule.DOWNLOAD_TIME = download_time 29 | # 产品类型 30 | self.instType = instType.upper() 31 | # 行情客户端 32 | self.market = Market(instType=instType, key=self.rule.KEY, secret=self.rule.SECRET, timezone=self.rule.TIMEZONE,proxies=proxies,proxy_host=proxy_host) 33 | # 产品过滤器 用于candle_map的更新 34 | self.filter = _filter.Filter() 35 | # BinanceLite 36 | self.binanceLite = BinanceLite() 37 | self.binanceLite.CANDLE_DATE_BASE_DIR = self.rule.CANDLE_DIR 38 | self.binanceLite.CANDLE_FILE_BASE_DIR = self.rule.CACHE_DIR 39 | self.binanceLite.TIMEZONE = self.rule.TIMEZONE 40 | # 日志 41 | self.log = _log.Log( 42 | log_dirpath=self.rule.LOG_DIRPATH, 43 | file_level=self.rule.LOG_FILE_LEVEL, 44 | console_level=self.rule.LOG_CONSOLE_LEVEL, 45 | ) 46 | self.lite = self.binanceLite 47 | self._run_candle_map_thread = None 48 | self.__close_run_candle_map_signal = False 49 | self._download_daily_thread = None 50 | self.__close_download_daily_thread = False 51 | 52 | # 获取过滤后的产品名称 53 | def get_symbols_filtered( 54 | self, 55 | type='trading_on' # 56 | ) -> dict: 57 | ''' 58 | :param type: 59 | trading_on: 正在交易的 60 | trading_off: 不在交易的 61 | trading_all: 全部 62 | 过滤内容: 63 | 1. rule.SYMBOLS_FILTER 64 | 2. rule.SYMBOLS_CONTAINS 65 | 3. rule.SYMBOL_ENDSWITH 66 | 4. CandleServer.filter 67 | ''' 68 | # 验证self.symbols 69 | if not ( 70 | self.rule.SYMBOLS == 'all' or 71 | isinstance(self.rule.SYMBOLS, list) 72 | ): 73 | msg = 'rule.SYMBOLS must be "all" or list type' 74 | raise exception.RuleException(msg) 75 | if type not in ['trading_on', 'trading_off', 'trading_all']: 76 | msg = 'type must in ["trading_on","trading_off","trading_all"].' 77 | raise exception.ParamException(msg) 78 | # 产品为全部 self.rule.SYMBOLS = 'all' 79 | if isinstance(self.rule.SYMBOLS, str) and self.rule.SYMBOLS.lower() == 'all': 80 | if type == 'trading_on': 81 | symbols_trading_result = self.market.get_symbols_trading_on( 82 | expire_seconds=60 * 60, # 使用的缓存过期时间 83 | ) 84 | elif type == 'trading_off': 85 | symbols_trading_result = self.market.get_symbols_trading_off( 86 | expire_seconds=60 * 60, # 使用的缓存过期时间 87 | ) 88 | else: 89 | symbols_trading_result = self.market.get_symbols_all( 90 | expire_seconds=60 * 60, # 使用的缓存过期时间 91 | ) 92 | if symbols_trading_result['code'] != 200: 93 | return symbols_trading_result 94 | symbols = symbols_trading_result['data'] 95 | # 产品类型为列表 96 | else: 97 | symbols = self.rule.SYMBOLS 98 | # 第一次过滤 使用rule中的条件 -> symbols_filtered1 99 | # SYMBOLS_FILTER 100 | # SYMBOL_CONTAINS 101 | # SYMBOL_ENDSWITH 102 | symbols_filtered1 = [] 103 | for symbol in symbols: 104 | symbol: str 105 | if self.rule.SYMBOLS_FILTER and symbol in self.rule.SYMBOLS_FILTER: 106 | continue 107 | if self.rule.SYMBOL_CONTAINS and not self.rule.SYMBOL_CONTAINS in symbol: 108 | continue 109 | if self.rule.SYMBOL_ENDSWITH and not symbol.endswith(self.rule.SYMBOL_ENDSWITH): 110 | continue 111 | symbols_filtered1.append(symbol) 112 | # 仅当trading-on是否进行第二次过滤 113 | if type == 'trading_on': 114 | # 第二次过滤 self.filter 过滤数据不足,请求错误的symbol -> symbols_filtered2 115 | symbols_filtered2 = [] 116 | for symbol in symbols_filtered1: 117 | symbol: str 118 | if self.filter.check(symbol): # True 不过滤 119 | symbols_filtered2.append(symbol) 120 | result = {'code': 200, 'data': symbols_filtered2, 'msg': ''} 121 | else: 122 | result = {'code': 200, 'data': symbols_filtered1, 'msg': ''} 123 | return result 124 | 125 | # 按照日期下载历史K线 126 | def download_candles_by_date( 127 | self, 128 | start: Union[str, int, float, datetime.date], 129 | end: Union[str, int, float, datetime.date, None] = None, 130 | type: str = 'trading_on', 131 | replace=False, 132 | ): 133 | ''' 134 | :param start: 起始日期 135 | :param end: 终止日期 136 | None 使用昨日日期 137 | :param type: 产品交易类型 138 | trading_on 正在交易的产品 139 | trading_off 不可交易的产品 140 | trading_all 全部产品 141 | :param replace: 是否替换本地文件 142 | ''' 143 | # 计算每天下载数据的延时 delay 144 | # 现货交易 145 | if self.instType.upper() == 'SPOT': 146 | # 下载一天的数据需要花费权重2 总权重1200 单次延时为 2 / (1200 * SERVER_WEIGHT) 147 | delay = 60 * 2 / (1200 * self.rule.SERVER_WEIGHT) 148 | else: 149 | # 下载一天的数据需要花费权重10 总权重2400 单次延时为 10 / (2400 * SERVER_WEIGHT) 150 | delay = 60 * 10 / (2400 * self.rule.SERVER_WEIGHT) 151 | delay = round(delay, 4) 152 | # 执行下载使用的临时过滤器,过滤数据异常的产品 153 | self._download_filter = _filter.Filter() 154 | # 日期终点 155 | if not end: 156 | end = pendulum.yesterday(tz=self.rule.TIMEZONE) 157 | # 需要下载的日期序列 158 | date_range = _date.get_range_dates(start=start, end=end, timezone=self.rule.TIMEZONE) 159 | # 反转日期序列,用于加速 160 | date_range = sorted(date_range, reverse=True) 161 | # 按照日期下载 162 | for date in date_range: 163 | self.__download_candles_by_date( 164 | date=date, 165 | type=type, 166 | replace=replace, 167 | ) 168 | 169 | if delay > 0: 170 | time.sleep(delay) 171 | # 删除临时过滤器 172 | del self._download_filter 173 | 174 | # 下载某一天多产品历史K线数据 175 | def __download_candles_by_date( 176 | self, 177 | date: Union[str, int, float, datetime.date], 178 | type='trading_on', 179 | replace=False, 180 | ): 181 | ''' 182 | :param date: 日期 183 | :param type: 产品交易类型 184 | trading_on 正在交易的产品 185 | trading_off 不可交易的产品 186 | trading_all 全部产品 187 | :param replace: 是否替换本地文件 188 | ''' 189 | # 日期字符串,用于控制台打印 190 | if self.rule.TIMEZONE == 'America/New_York': 191 | date_str = _date.to_fmt(date, self.rule.TIMEZONE, '%d/%m/%Y') 192 | else: 193 | date_str = _date.to_fmt(date, self.rule.TIMEZONE, '%Y-%m-%d') 194 | # 获取产品列表 195 | get_symbols_filtered_result = self.get_symbols_filtered(type=type, ) 196 | # **[ERROR RAISE]** 产品列表有误 197 | if get_symbols_filtered_result['code'] != 200: 198 | msg = '[get_symbols_filtered] code={code} msg={msg}'.format( 199 | msg=get_symbols_filtered_result['msg'], 200 | code=get_symbols_filtered_result['code'], 201 | ) 202 | self.log.error(msg) 203 | raise exception.CodeException(msg) 204 | symbols = get_symbols_filtered_result['data'] 205 | # 过滤下载异常的产品(仅当下载正在运行交易的产品,进行过滤) 206 | if type == 'trading_on': 207 | symbols = [symbol for symbol in symbols if self._download_filter.check(symbol)] 208 | # 产品逐个下载 209 | download_detail = { 210 | 'all': len(symbols), # 总数 211 | 'skip': 0, # 跳过的个数 212 | 'suc': 0, # 成功的个数 213 | 'warn': 0, # 警告的个数 214 | 'error': 0, # 失败的个数 215 | } 216 | for symbol in symbols: 217 | # 不替换且存在数据 218 | if not replace and self.binanceLite.check_candle_date_path( 219 | instType=self.instType, 220 | symbol=symbol, 221 | start=date, 222 | end=date, 223 | bar=self.rule.BAR, 224 | )['code']: 225 | download_detail['skip'] += 1 226 | continue 227 | # 发送请求的时间戳 用于权重延时 228 | request_time = time.time() 229 | # 按照日期下载数据 230 | get_history_candle_by_date_result = self.market.get_history_candle_by_date( 231 | symbol=symbol, 232 | date=date, 233 | bar=self.rule.BAR, 234 | valid_interval=True, 235 | valid_start=True, 236 | valid_end=True 237 | ) 238 | # 下载状态码 this_code 239 | this_code = get_history_candle_by_date_result['code'] 240 | # 下载异常 241 | if this_code != 200: 242 | msg = '[get_history_candle_by_date] code={code} symbol={symbol} date={date} bar={bar} msg={msg}'.format( 243 | code=this_code, 244 | symbol=symbol, 245 | date=date_str, 246 | bar=self.rule.BAR, 247 | msg=get_history_candle_by_date_result['msg'] 248 | ) 249 | # info 长度问题与起始错误,数据不足,过滤 250 | if this_code in [code.CANDLE_LENGTH_ERROR[0], code.CANDLE_START_ERROR[0]]: 251 | self._download_filter.set(name=symbol, filter_minute=1440) 252 | self.log.warn(msg) 253 | download_detail['warn'] += 1 254 | # error 数据间隔错误 或者 终止时间 过滤 255 | elif this_code in [code.CANDLE_INTERVAL_ERROR[0], code.CANDLE_END_ERROR[0]]: 256 | self._download_filter.set(name=symbol, filter_minute=1440) 257 | self.log.error(msg) 258 | download_detail['error'] += 1 259 | # error 其他状态码错误 不过滤 260 | else: 261 | self.log.error(msg) 262 | # 历史K线数据合规 保存 263 | else: 264 | candle = get_history_candle_by_date_result['data'] 265 | self.binanceLite.save_candle_by_date( 266 | candle=candle, 267 | instType=self.instType, 268 | symbol=symbol, 269 | start=date, 270 | end=date, 271 | bar=self.rule.BAR, 272 | ) 273 | msg = 'DOWNLOAD {symbol:<10} {bar:<3} {date}'.format( 274 | symbol=symbol, 275 | bar=self.rule.BAR, 276 | date=date_str, 277 | ) 278 | self.log.info(msg) 279 | download_detail['suc'] += 1 280 | # 延时 281 | finish_time = time.time() # 结束的时间戳 282 | request_seconds = (finish_time - request_time) # 请求花费的时间戳 283 | # 现货交易 284 | if self.instType.upper() == 'SPOT': 285 | # 下载一天的数据需要花费权重2 总权重1200 单次延时为 2 / (1200 * SERVER_WEIGHT) 286 | delay = 60 * 2 / (1200 * self.rule.SERVER_WEIGHT) - request_seconds 287 | # delay = 2 / (1200 * self.rule.SERVER_WEIGHT) 288 | else: 289 | # 下载一天的数据需要花费权重10 总权重2400 单次延时为 10 / (2400 * SERVER_WEIGHT) 290 | delay = 60 * 10 / (2400 * self.rule.SERVER_WEIGHT) - request_seconds 291 | # delay = 10 / (2400 * self.rule.SERVER_WEIGHT) 292 | delay = round(delay, 4) 293 | if delay > 0: 294 | time.sleep(delay) 295 | # pass 296 | msg = 'COMPLETE DOWNLOAD {date} (ALL:{all} SKIP:{skip} SUC:{suc} WARN:{warn} ERROR:{error})'.format( 297 | date=date_str, 298 | **download_detail, 299 | ) 300 | self.log.info(msg) 301 | 302 | def prepare_candle_map(self): 303 | # 历史K线最新毫秒时间戳 -> latest_ts 304 | latest_ts_result = self.market.get_history_candle_latest_ts(bar=self.rule.BAR) 305 | # **[ERROR RAISE]** 获取失败 306 | if latest_ts_result['code'] != 200: 307 | msg = '[get_history_candle_latest] code={code} msg={msg}'.format( 308 | code=latest_ts_result['code'], 309 | msg=latest_ts_result['msg'], 310 | ) 311 | raise exception.CodeException(msg) 312 | latest_ts = latest_ts_result['data'] 313 | # start_ts 314 | start_ts = float(latest_ts - _interval.get_interval(bar=self.rule.BAR) * (self.rule.LENGTH - 1)) 315 | # 本地date数据的起始日期start_date与终止日期end_date 316 | start_date = _date.to_datetime(date=start_ts, timezone=self.rule.TIMEZONE).date() 317 | end_date = pendulum.yesterday(tz=self.rule.TIMEZONE).date() 318 | # 产品 -> symbols 319 | get_symbols_filtered_result = self.get_symbols_filtered() 320 | # **[ERROR RAISE]** 获取失败 321 | if get_symbols_filtered_result['code'] != 200: 322 | msg = '[get_symbols_filtered] code={code} msg={msg}'.format( 323 | msg=get_symbols_filtered_result['msg'], 324 | code=get_symbols_filtered_result['code'], 325 | ) 326 | self.log.error(msg) 327 | raise exception.CodeException(msg) 328 | symbols = get_symbols_filtered_result['data'] 329 | # candle_map 330 | candle_map = {} 331 | # 逐个产品遍历 332 | for symbol in symbols: 333 | # 是否有缓存 334 | if self.binanceLite.check_candle_file_path( 335 | instType=self.instType, 336 | symbol=symbol, 337 | bar=self.rule.BAR, 338 | )['code']: 339 | # 尝试读取缓存 340 | try: 341 | # 缓存数据 342 | candle_cache = self.binanceLite.load_candle_by_file( 343 | instType=self.instType, 344 | symbol=symbol, 345 | path=None, 346 | bar=self.rule.BAR, 347 | valid_interval=True, 348 | ) 349 | # 按照时间截取 350 | candle_cache_transformed = candle_cache[ 351 | (candle_cache[:, 0] >= start_ts) & (candle_cache[:, 0] <= latest_ts) 352 | ] 353 | # 如果截取后数据不为空,使用缓存 354 | if candle_cache_transformed.shape[0] > 0: 355 | candle_map[symbol] = candle_cache_transformed 356 | continue 357 | # 有缓存但读取失败 -> 继续尝试从date数据中读取 358 | except: 359 | msg = f'[load_candle_by_file] symbol={symbol}' 360 | self.log.error(msg) 361 | # 无缓存或者缓存文件读取失败 362 | # 是否有date数据 363 | if self.binanceLite.check_candle_date_path( 364 | instType=self.instType, 365 | symbol=symbol, 366 | start=start_date, 367 | end=end_date, 368 | bar=self.rule.BAR, 369 | )['code']: 370 | # 尝试读取date数据 371 | try: 372 | # date数据 373 | candle_date = self.binanceLite.load_candle_by_date( 374 | instType=self.instType, 375 | symbol=symbol, 376 | start=start_date, 377 | end=end_date, 378 | bar=self.rule.BAR, 379 | valid_interval=True, 380 | valid_start=True, 381 | valid_end=True, 382 | ) 383 | # 时间截取 384 | candle_date_transformed = candle_date[ 385 | (candle_date[:, 0] >= start_ts) & (candle_date[:, 0] <= latest_ts) 386 | ] 387 | # 如果截取后数据不为空,使用此数据 388 | if candle_date_transformed.shape[0] > 0: 389 | candle_map[symbol] = candle_date_transformed 390 | continue 391 | # 有date数据但是读取失败 392 | except: 393 | msg = '[load_candle_by_date] symbol={symbol} start_date={start_date} end_date={end_date}'.format( 394 | symbol=symbol, 395 | start_date=str(start_date), 396 | end_date=str(end_date), 397 | ) 398 | self.log.error(msg) 399 | # 连续两次将candle_map更新到最新 400 | for i in range(2): 401 | candle_map = self.update_candle(candle_map) 402 | return candle_map 403 | 404 | def update_candle(self, candle_map=None): 405 | # 优先级 candle_map >> self.candle_map 406 | if candle_map == None: 407 | if hasattr(self, 'candle_map'): 408 | candle_map = self.candle_map 409 | # [ERROR RAISE] 没有candle_map和self.candle_map 410 | else: 411 | msg = 'candle_map and self.candle_map can not be empty together' 412 | raise exception.ParamException(msg) 413 | # 历史K线最新毫秒时间戳 -> latest_ts 414 | latest_ts_result = self.market.get_history_candle_latest_ts(bar=self.rule.BAR) 415 | if latest_ts_result['code'] != 200: 416 | msg = '[get_history_candle_latest_ts] code={code} msg={msg}'.format( 417 | code=latest_ts_result['code'], 418 | msg=latest_ts_result['msg'], 419 | ) 420 | raise exception.CodeException(msg) 421 | latest_ts = latest_ts_result['data'] 422 | # 获取过滤后的产品 -> symbols 423 | get_symbols_filtered_result = self.get_symbols_filtered() 424 | if get_symbols_filtered_result['code'] != 200: 425 | msg = '[get_symbols_filtered] code={code} msg={msg}'.format( 426 | msg=get_symbols_filtered_result['msg'], 427 | code=get_symbols_filtered_result['code'], 428 | ) 429 | self.log.error(msg) 430 | raise exception.CodeException(msg) 431 | symbols = get_symbols_filtered_result['data'] 432 | # 逐个产品遍历 433 | for symbol in symbols: 434 | candle = candle_map[symbol] if symbol in candle_map.keys() else None 435 | if isinstance(candle, np.ndarray) and candle.shape[0] > 0 and candle[-1, 0] == latest_ts: 436 | continue 437 | update_history_candle_result = self.market.update_history_candle( 438 | symbol=symbol, 439 | length=self.rule.LENGTH, 440 | candle=candle, 441 | end=latest_ts, 442 | bar=self.rule.BAR, 443 | valid_interval=True, 444 | ) 445 | this_code = update_history_candle_result['code'] 446 | if this_code != 200: 447 | msg = '[update_history_candle] code={code} symbol={symbol} msg={msg}'.format( 448 | symbol=symbol, 449 | code=this_code, 450 | msg=update_history_candle_result['msg'] 451 | ) 452 | if this_code == code.CANDLE_LENGTH_ERROR[0]: 453 | self.filter.set(name=symbol, filter_minute=60 + random.randint(1, 10)) 454 | self.log.warn(msg) 455 | elif this_code == code.CANDLE_INTERVAL_ERROR[0]: 456 | self.filter.set(name=symbol, filter_minute=1440 + random.randint(1, 10)) 457 | self.log.error(msg) 458 | else: 459 | self.log.error(msg) 460 | if symbol in candle_map.keys(): 461 | del candle_map[symbol] 462 | else: 463 | candle_map[symbol] = update_history_candle_result['data'] 464 | return candle_map 465 | 466 | # 多线程更新candle_map 467 | @_thread.thread_wrapper 468 | def thread_update_candle_map(self): 469 | while True: 470 | # 退出条件 471 | if self.__close_run_candle_map_signal == True: 472 | break 473 | # 更新数据 474 | try: 475 | if not hasattr(self, 'candle_map'): 476 | self.candle_map = {} 477 | candle_map = self.update_candle(candle_map=self.candle_map) 478 | self.candle_map = candle_map 479 | except: 480 | msg = '[thread: update_candle]' 481 | self.log.error(msg) 482 | else: 483 | # 如果需要保存缓存 484 | if self.rule.CACHE_DELAY_SECONDS: 485 | # 按时保存缓存 486 | try: 487 | # 没有上次缓存时间或者超过CACHE_DELAY_SECONDS 488 | if ( 489 | (not hasattr(self, '__last_cache_candle_time')) 490 | or 491 | (time.time() - getattr(self, 492 | '__last_cache_candle_time') >= self.rule.CACHE_DELAY_SECONDS) 493 | ): 494 | self.binanceLite.save_candle_map_by_file( 495 | candle_map=self.candle_map, 496 | instType=self.instType, 497 | symbols=[], 498 | replace=True, 499 | bar=self.rule.BAR, 500 | ) 501 | setattr(self, '__last_cache_candle_time', time.time()) 502 | msg = 'DOWNLOAD CANDLE CACHE' 503 | self.log.info(msg) 504 | except: 505 | msg = '[thread: save_candle_map_by_file]' 506 | self.log.error(msg) 507 | # 如果需要每天下载date数据 508 | if self.rule.DOWNLOAD_TIME and pendulum.now(self.rule.TIMEZONE).time().strftime( 509 | '%H:%M:%S') >= self.rule.DOWNLOAD_TIME: 510 | try: 511 | # 没有上次下载日期或者距离上次下载日期超过1天 512 | if ( 513 | (not hasattr(self, '__last_download_candle_date')) 514 | or 515 | ((pendulum.now(tz=self.rule.TIMEZONE).date() - getattr(self, 516 | '__last_download_candle_date')).days >= 1) 517 | ): 518 | self.binanceLite.save_candle_map_by_date( 519 | candle_map=self.candle_map, 520 | instType=self.instType, 521 | symbols=[], 522 | replace=False, 523 | start=pendulum.yesterday(tz=self.rule.TIMEZONE), 524 | end=pendulum.yesterday(tz=self.rule.TIMEZONE), 525 | bar=self.rule.BAR, 526 | ) 527 | setattr(self, '__last_download_candle_date', pendulum.now(tz=self.rule.TIMEZONE).date()) 528 | msg = 'DOWNLOAD CANDLE BY DATE' 529 | self.log.info(msg) 530 | except: 531 | msg = '[thread: save_candle_map_by_date]' 532 | self.log.error(msg) 533 | finally: 534 | time.sleep(self.rule.UPDATE_INTERVAL_SECONDS) 535 | self.__close_run_candle_map_signal = False 536 | 537 | # 运行服务 538 | def run_candle_map(self): 539 | # 有正在运行的run线程 540 | if self._run_candle_map_thread and self._run_candle_map_thread.isAlive(): 541 | msg = 'Server run thread is running. Cannot run repeatedly.' 542 | print(msg) 543 | return None 544 | # 补充date数据 545 | if self.rule.LOCAL_CANDLE_DAYS: 546 | self.download_candles_by_date( 547 | start=pendulum.now(tz=self.rule.TIMEZONE) - datetime.timedelta(days=self.rule.LOCAL_CANDLE_DAYS), 548 | end=pendulum.yesterday(tz=self.rule.TIMEZONE), 549 | ) 550 | # 准备candle_map 551 | self.candle_map = self.prepare_candle_map() 552 | msg = 'COMPLETE PREPARE candle_map' 553 | self.log.info(msg) 554 | # 多线程更新candle_map 555 | t = self.thread_update_candle_map() 556 | self._run_candle_map_thread = t 557 | time.sleep(1) 558 | 559 | def close_run_candle_map(self): 560 | if self._run_candle_map_thread: 561 | self.__close_run_candle_map_signal = True 562 | else: 563 | msg = 'run_candle_map is not running' 564 | print(msg) 565 | 566 | def close_download_daily(self): 567 | if self._download_daily_thread: 568 | self.__close_download_daily_thread = True 569 | else: 570 | msg = 'download_daily is not running' 571 | print(msg) 572 | 573 | # 每天定时下载以天为单位的历史数据 574 | @_thread.thread_wrapper 575 | def _download_daily( 576 | self, 577 | start: Union[str, int, float, datetime.date, None] = None, 578 | replace: bool = False, 579 | type='trading_on', 580 | ): 581 | # 下载补充数据 582 | yesterday = pendulum.yesterday(tz=self.rule.TIMEZONE) 583 | last_day = yesterday 584 | # 如果有start 下载start ~ yesterday的数据 585 | if start == None: 586 | start = yesterday 587 | # v1.0.7 防止下载start~yesterday的时间过长,中间的数据缺失 588 | while True: 589 | self.download_candles_by_date(start=start, end=yesterday, replace=replace, type=type) 590 | if pendulum.yesterday(tz=self.rule.TIMEZONE) == yesterday: 591 | break 592 | else: 593 | yesterday = pendulum.yesterday(tz=self.rule.TIMEZONE) 594 | 595 | while True: 596 | if self.__close_download_daily_thread == True: 597 | break 598 | time.sleep(0.5) 599 | # 下载的时间条件 600 | if not pendulum.now(tz=self.rule.TIMEZONE).time().strftime('%H:%M:%S') >= self.rule.DOWNLOAD_TIME: 601 | continue 602 | # 下载的日期条件 603 | yesterday = pendulum.yesterday(tz=self.rule.TIMEZONE) 604 | if (yesterday - last_day).days > 0: 605 | self.download_candles_by_date( 606 | start=yesterday, 607 | end=yesterday, 608 | type=type, 609 | ) 610 | last_day = yesterday 611 | self.__close_download_daily_thread = False 612 | 613 | def download_daily(self, 614 | start: Union[str, int, float, datetime.date, None] = None, 615 | replace: bool = False, 616 | type='trading_on', 617 | ): 618 | ''' 619 | :param start: 起始日期 620 | start != None 补充下载start ~ yesterday 的历史K线数据 621 | :param replace: 是否替换本地数据 622 | :param type: 产品交易类型 623 | trading_on 正在交易的产品 624 | trading_off 不可交易的产品 625 | trading_all 全部产品 626 | ''' 627 | if self._download_daily_thread and self._download_daily_thread.isAlive(): 628 | msg = 'Server download daily thread is running. Cannot run repeatedly.' 629 | print(msg) 630 | return None 631 | t = self._download_daily(start=start, replace=replace, type=type) 632 | self._download_daily_thread = t 633 | time.sleep(1) 634 | 635 | # 获得candle的最新时间与当前时间查小于security_seconds的k线数据 636 | def get_candle_security(self, symbol: str, security_seconds=60 * 3) -> np.ndarray: 637 | ''' 638 | :param symbol: 产品 639 | :param security_seconds: 与当前时间戳相差多少以内判定为安全 640 | ''' 641 | # 不存在 642 | if symbol not in self.candle_map.keys(): 643 | return np.array([]) 644 | candle = self.candle_map[symbol] 645 | # 是否与此时时间相差超过了security_seconds 646 | if (time.time() * 1000 - candle[-1, 0]) >= security_seconds * 1000: 647 | return np.array([]) 648 | return candle 649 | -------------------------------------------------------------------------------- /readme.md: -------------------------------------------------------------------------------- 1 | ------------------------------------------------------- 2 | # Biance_candle已停止维护,功能已整合在Binance_interface中: 3 | 4 | GITHUB:https://github.com/pyted/binance_interface 5 | ------------------------------------------------------- 6 | 7 | # Binance_candle v1.0.10 8 | 9 | ## 1 Binance_candle的设计目的? 10 | 11 | **1.为本地模拟交易提供历史数据** 12 | 13 | - 支持现货、U本位合约、币本位合约历史K线下载、管理与快速读取。 14 | 15 | **2.为实盘交易决策依据** 16 | 17 | - 支持现货与合约产品实时最新历史K线数据的缓存更新 18 | - 获取实时行情数据 19 | 20 | ## 2 安装Binance_candle 21 | 22 | 23 | ```cmd 24 | pip3 install binance_candle 25 | ``` 26 | 27 | GITHUB:https://github.com/pyted/binance_candle 28 | 29 | 里面有Binance_candle的使用例子: 30 | 31 | ![](vx_images/244075001625094.png) 32 | 33 | ## 3 快速开始 34 | 35 | ### 3.1 维护产品实时历史K线数据candle_map 36 | 37 | 以U本位产品为例,运行run_candle_map函数后,会以多线程的方式维护candle_map属性,保证candle_map的时效性。 38 | 39 | candle_map的格式:candle_map[\] = \ 40 | 41 | ```python 42 | from binance_candle import CandleServer 43 | 44 | candleServer = CandleServer('UM') # UM:U本位合约 45 | candleServer.run_candle_map() 46 | print(candleServer.candle_map) 47 | ``` 48 | 49 | 输出: 50 | 51 | ```text 52 | >> { 53 | >> '1000LUNCBUSD': array( 54 | >> [[1.67496882e+12, 1.74100000e-01, 1.74100000e-01, ..., 55 | >> 7.31000000e+03, 1.27199670e+03, 0.00000000e+00], 56 | >> [1.67496888e+12, 1.74000000e-01, 1.74200000e-01, ..., 57 | >> 1.90710000e+04, 3.31999150e+03, 0.00000000e+00], 58 | >> [1.67496894e+12, 1.74200000e-01, 1.74200000e-01, ..., 59 | >> 3.23500000e+03, 5.63537000e+02, 0.00000000e+00], 60 | >> ..., 61 | >> [1.67514144e+12, 1.66000000e-01, 1.66000000e-01, ..., 62 | >> 1.00560000e+04, 1.66929600e+03, 0.00000000e+00], 63 | >> [1.67514150e+12, 1.65800000e-01, 1.65800000e-01, ..., 64 | >> 3.56710000e+04, 5.91068840e+03, 0.00000000e+00], 65 | >> [1.67514156e+12, 1.65700000e-01, 1.65800000e-01, ..., 66 | >> 7.88270000e+04, 1.30688056e+04, 0.00000000e+00]] 67 | >> ), 68 | >> ... ... 69 | >> } 70 | ``` 71 | 72 | ### 3.2 每日定时下载历史K线 73 | 74 | 以现货交易为例,每天定时下载前一天的历史K线数据。 75 | 76 | ```python 77 | from binance_candle import CandleServer 78 | 79 | candleServer = CandleServer('SPOT') # SPOT:现货交易 80 | candleServer.download_daily() 81 | ``` 82 | 83 | ### 3.3 获取实时最优挂单价格 84 | 85 | 以币本位合约为例,获取实时最优挂单价格 86 | 87 | ```python 88 | from binance_candle import CandleServer 89 | 90 | candleServer = CandleServer('CM') # CM:币本位合约 91 | bookTickerMap = candleServer.market.get_bookTickersMap() 92 | print(bookTickerMap) 93 | ``` 94 | 95 | 输出: 96 | 97 | ```text 98 | >> { 99 | >> 'code': 200, 100 | >> 'data': {'AAVEUSD_PERP': {'askPrice': '81.300', 101 | >> 'askQty': '92', 102 | >> 'bidPrice': '81.260', 103 | >> 'bidQty': '42', 104 | >> 'pair': 'AAVEUSD', 105 | >> 'symbol': 'AAVEUSD_PERP', 106 | >> 'time': 1675143719137}, 107 | >> 'ADAUSD_230331': {'askPrice': '0.37198', 108 | >> 'askQty': '100', 109 | >> 'bidPrice': '0.37170', 110 | >> 'bidQty': '30', 111 | >> 'pair': 'ADAUSD', 112 | >> 'symbol': 'ADAUSD_230331', 113 | >> 'time': 1675143718074}, 114 | >> ... ... 115 | >> } 116 | >> 'msg': '' 117 | >> } 118 | ``` 119 | 120 | ## 4 历史K线candle的数据说明 121 | 122 | ### 4.1 K线的格式 123 | 124 | 为了保证运算的速度,candle采用np.ndarray类型存储。 125 | 126 | |列索引|字段解释|例子| 127 | |:---|:---|:---| 128 | |0|K线开盘时间|1499040000000.0| 129 | |1|开盘价|0.01634790| 130 | |2|最高价|0.80000000| 131 | |3|最低价|0.01575800| 132 | |4|收盘价|0.01577100| 133 | |5|成交量|148976.11427815| 134 | |6|K线收盘时间|1499644799999.0| 135 | |7|成交额|2434.19055334| 136 | |8|成交笔数|308.0| 137 | |9|主动买入成交量|1756.87402397| 138 | |10|主动买入成交额|28.46694368| 139 | |11|<官方解释为可忽略的参数>|17928899.62484339| 140 | 141 | 官方返回的字段类型包含整数和字符串,但对于历史K线数据,binance_candle中均以np.float64作为存储格式。 142 | 143 | 注:Python对于数字精度的支持并不优秀,对于模拟运算或者实盘决策,使用浮点数是安全的,但对于交易接口,例如下单的数量和金额,采用字符串类型更为安全,在全部pyted的量化交易架构中,与订单相关的数字对象均采用字符串用于提交。 144 | 145 | ### 4.2 K线的存储规则 146 | 147 | 本地历史K线数据是按照日期拆分拆分存储,每个CSV文件保存指定日期指定产品的全部K线数据。 148 | 149 | 每个CSV文件的时间跨度为:00:00:00 ~ 24:00:00 - \ 150 | 151 | 如果保存ETH 2023年1月1日 时间粒度bar是1分钟的数据:CSV中的数据条数是1440条,起点为时间:00:00:00,终点时间为:23:59:00,同样如果保存时间粒度bar是1小时的数据,存储的数据时间终点是:23:00:00 152 | 153 | 由于是按照日期分片存储,'2023-01-01'在美式日期与中国日期对应的时间戳并不相同,Binance_candle默认采用'America/New_York'时区。 154 | 155 | ![](vx_images/495193492836002.png) 156 | 157 | ### 4.3 K线的安全性 158 | 159 | Binance_candle中的全部K线数据均会受到严格的验证,保证数据的安全性。 160 | 161 | - valid_interval 验证数据时间间隔 = 162 | - valid_start 验证数据时间起点 163 | - valid_end 验证数据时间终点 164 | - valid_length 验证数据长度 (仅在维护实时历史K线数据服务的时候使用) 165 | 166 | ## 5 产品类别instType代号 167 | 168 | 在实例化CandlerServer时,需要可以传递两个参数,一个是instType产品类别,另一个是rule服务规则。 169 | 170 | |产品类别|代号| 171 | |:---|:---| 172 | |现货交易|SPOT| 173 | |U本位合约|UM| 174 | |币本位合约|CM| 175 | |欧式期权|EO **注:Binance_candle不支持欧式期权**| 176 | 177 | ## 6 K线服务规则CandleRule 178 | 179 | ### 6.1 规则属性总览 180 | 181 | CandleRule是CandleServer的运行规则,用户可以自定义规则内容以适应不同的交易场景。 182 | 183 | ```python 184 | from binance_candle import CandleServer, CandleRule 185 | 186 | CandleRule.BAR = '5m' # K线的时间粒度调整为5分钟 187 | candleServer = CandleServer('SPOT', CandleRule) 188 | ``` 189 | 190 | 在CandleServer中,有两个重要的异步方法: 191 | 192 | - run_candle_map 异步的方式维护实时历史K线字典 candle_map 193 | - download_daily 异步的方式每日下载上一天的K线数据 194 | 195 | 这两个方法均受到CandleRule的规则约束。下面是CandleRule中各个属性的作用规则。 196 | 197 | ![](vx_images/480313210278479.png) 198 | 199 | ### 6.2 服务权重系数 200 | 201 | #### 6.2.1 SERVER_WEIGHT 服务权重系数 202 | 203 | 类型:Union[int,float],默认值:0.9 204 | 205 | Binance官方会计算每个IP每分钟发送的请求权重,超过权重上限将会被短暂封禁。各个产品间的权重计量彼此独立。 206 | 207 | 权重上限: 208 | 209 | - 现货:1200 weight / minute 210 | - U本位合约:2400 weight / minute 211 | - 币本位合约:2400 weight / minute 212 | 213 | SERVER_WEIGHT 默认值0.9,会将请求的权重维持在每分钟请求上限 * 0.9左右。调整SERVER_WEIGHT可以控制下载与更新历史K线的速度,如果这台计算机不进行实盘交易,建议使用默认值0.9,如果同一台计算机既要维护实时K线,又要发送下单请求,建议降低SERVER_WEIGHT,避免订单请求被ban。 214 | 215 | ### 6.3 产品名称条件 216 | 217 | #### 6.3.1 SYMBOLS 产品名称 218 | 219 | 类型:Union[str,list],默认值:'all' 220 | 221 | |例子|解释| 222 | |:---|:---| 223 | |SYMBOLS = 'all'|维护产品类型中的全部子产品| 224 | |SYMBOLS = ['BTCUSDT','ETCUSDT']|仅维护BTCUSDT与ETCUSDT| 225 | 226 | #### 6.3.2 SYMBOLS_FILTER 过滤产品 227 | 228 | 类型:list,默认值:'' 229 | 230 | |例如|解释| 231 | |:---|:---| 232 | |SYMBOLS = 'all' ; SYMBOLS_FILTER = ['BTCUSDT']|维护除了BTCUSDT之外全部的产品| 233 | 234 | #### 6.3.3 SYMBOL_CONTAINS 产品名称中需包含的内容 235 | 236 | 类型:str,默认值:'' 237 | 238 | 过滤名称中不包含此内容的产品(区分大小写)。 239 | 240 | 241 | |例如|解释| 242 | |:---|:---| 243 | |SYMBOLS = 'all' ; SYMBOL_CONTAINS = 'BTC'|维护名称中包含'BTC'的全部产品| 244 | 245 | #### 6.3.4 SYMBOL_ENDSWITH 产品名称必须以何结尾 246 | 247 | 类型:str,默认值:'' 248 | 249 | 过滤名称中不以此内容结尾的产品(区分大小写)。 250 | 251 | 252 | |例如|解释| 253 | |:---|:---| 254 | |SYMBOLS = 'all' ; SYMBOL_ENDSWITH = 'USDT'|维护名称以USDT结尾的全部产品| 255 | 256 | 如果你仅要维护以USDT为交易货币的产品,可以设置: 257 | 258 | - SYMBOLS = 'all' 259 | - SYMBOLS_ENDSWITH = 'USDT' 260 | 261 | ### 6.4 K线参数 262 | 263 | #### 6.4.1 BAR 时间粒度 264 | 265 | 类型:str,默认值:candlelite.crypto.binance_lite.BINANCE_DEFAULT_BAR 266 | 267 | 历史K线的时间粒度,目前仅支持以分m、小时h、天d为单位。 268 | 269 | |例子|解释| 270 | |:---|:---| 271 | |1m|1分钟| 272 | |5m|5分钟| 273 | |15m|15分钟| 274 | |1h|1小时| 275 | |4h|4小时| 276 | |1d|1天| 277 | |..|...| 278 | 279 | 因为Binance_candle对于K线数据的读取使用的是candlelite,许多默认值使用的是candlelite中settings的配置内容。 280 | 281 | ```python 282 | # 查看默认配置信息 283 | candlelite show_settings 284 | # 在终端中修改默认配置 285 | candlelite console_settings 286 | # 查看配置文件的路径->用编辑器自行编辑修改 287 | candlelite settings_path 288 | ``` 289 | 290 | ### 6.5 K线下载相关 291 | 292 | #### 6.5.1 TIMEZONE 时区 293 | 294 | 类型:Union[str,None],默认值:candlelite.crypto.binace_lite.BINANCE_TIMEZONE 295 | 296 | TIMEZONE所起的作用: 297 | 298 | - 数据存储路径中的文件夹命名 299 | - 划分一天的起始与终止时间戳 300 | 301 | #### 6.5.2 CANDLE_DIR 日期为分割存储的数据路径 302 | 303 | 类型:str,默认值:candlelite.crypto.binace_lite.BINANCE_CANDLE_DATE_BASE_DIR 304 | 305 | #### 6.5.3 DOWNLOAD_TIME 每日下载昨日K线数据的时间 306 | 307 | 类型:str,默认值:'00:10:00' 308 | 309 | DOWNLAOD_TIME可以决定每天的哪个时间点下载昨日的历史K线数据,作用于两个异步函数: 310 | 311 | - run_candle_map 312 | - download_daily 313 | 314 | 这两个函数的下载方式不相同,run_candle_map函数会在DOWNLOAD_TIME时刻取出candle_map字典中昨日的K线数据,保存到本地。donwload_daily函数会在DOWNLOAD_TIME时刻向官方发送请求获取昨日K线数据,再保存到本地。 315 | 316 | 317 | DOWNLOAD_TIME的时间格式为:%H:%M:%S,例如DOWNLOAD_TIME = '01:00:00'。 318 | 319 | DOWNLOAD_TIME = None 表示禁用实时K线维护的每日下载功能。 320 | 321 | 需要注意的问题: 322 | 323 | 1. DOWNLOAD_TIME不应该过早,官方整合上一个单位时间的历史K线一般会有0~2分钟的延误,所以如果BAR='1m',在'00:00:00'可能无法获取昨日'23:59:00'时刻的历史K线,这样会引发错误。 324 | 325 | 2. DOWNLOAD_TIME对于run_candle_map异步线程来说,会从candle_map字典中获取昨日的K线数据,所以如果此时candle_map中昨日历史K线数据不足,也会引发异常,可以加大维护的K线长度LENGTH并调整合理的DOWNLOAD_TIME时刻。 326 | 327 | ### 6.6 秘钥 328 | 329 | #### 6.6.1 KEY与SECRET 330 | 331 | 类型:str,默认值:'' 332 | 333 | Binance获取行情数据,一般可以不填写秘钥,但无秘钥或者普通秘钥官方会限制访问IP权重,默认现货:1200、合约:2400。 334 | 335 | ### 6.7 run_candle_map实时K线相关配置 336 | 337 | #### 6.7.1 LOCAL_CANDLE_DAYS 启动后下载近期多少日的历史K线数据 338 | 339 | 类型:Union[int,None],默认值:2(单位:天) 340 | 341 | 在启动实时K线服务后,CandleServer将校验本地是否有最近的LOCAL_CANDLE_DAYS产品K线数据,如果没有,则会自动下载 342 | 343 | LOCAL_CANDLE_DAYS = None 表示禁用此功能 344 | 345 | #### 6.7.2 LENGTH 保留实时K线的长度 346 | 347 | 类型:int,默认值:2880 348 | 349 | 保留candle_map字典中各个产品的最新K线数据的行数。 350 | 351 | 如果某产品刚刚上线,数据长度小于LENGTH,则会被过滤。 352 | 353 | #### 6.7.3 UPDATE_INTERVAL_SECONDS 实时K线的更新间隔 354 | 355 | 类型:int,默认值:3(单位:秒) 356 | 357 | 将candle_map中的历史K线数据更新到最新时间,每次更新的时间间隔。 358 | 359 | #### 6.7.4 CACHE_DELAY_SECONDS 实时K线本地缓存的时间间隔 360 | 361 | 类型:Union[int,None],默认值:3600(单位:秒) 362 | 363 | 间隔多少秒,将candle_map保存到本地,这样可以在服务重启后,快速读取缓存数据,减少访问量与等待时间。 364 | 365 | CACHE_DELAY_SECONDS = None 表示禁用此功能 366 | 367 | #### 6.7.5 CACHE_DIR 缓存数据路径 368 | 369 | 类型:str,默认值:'./BINANCE_CACHE' 370 | 371 | candle_map缓存保存到的文件路径。 372 | 373 | ### 6.8 日志 374 | 375 | #### 6.8.1 LOG_DIRPATH 日志文件夹路径 376 | 377 | 类型:str,默认值:./BINANCE_CANDLE_SERVER_LOG_DATA' 378 | 379 | binance_candle中的日志文件按照本地日期与级别分割存储在文件夹中。 380 | 381 | #### 6.8.2 LOG_FILE_LEVEL 日志文件级别 382 | 383 | 类型:str,默认值:'INFO' 384 | 385 | 储存在日志文件中的级别。 386 | 387 | #### 6.8.3 LOG_CONSOLE_LEVEL 日志打印级别 388 | 389 | 类型:str,默认值:'DEBUG' 390 | 391 | 终端打印日志的级别。 392 | 393 | ## 7 维护实时K线字典 candle_map 394 | 395 | ### 7.1 run_candle_map 方法 396 | 397 | CandleServer.run_candle_map先会以堵塞的模式等待第一次candle_map更新完毕,随后以异步多线程的方式实时更新candle_map,以保证数据的实效性与安全性。 398 | 399 | 如果在更新candle_map中存在某个历史K线有错误,则会从candle_map字典中删除这个symbol的candle。 400 | 401 | 更新candle_map中默认验证的内容有: 402 | 403 | - valid_interval 验证K线时间间隔需要与bar相同 404 | - valid_end 验证K线的最大时间戳要等于被更新时间 405 | - valid_length 验证K线的长度需要严格等于LENGTH 406 | 407 | run_candle_map方法执行流程简图: 408 | 409 | ![](vx_images/176016628950838.png) 410 | 411 | ### 7.2 candle_map 实时K线字典属性 412 | 413 | 格式:candle_map[\] = \ 414 | 415 | - KEY:symbol 产品名称 416 | - VALUE:candle np.ndarray类型的K线数据 417 | 418 | ### 7.3 get_candle_security 安全获取实时K线 419 | 420 | |参数|类型|说明| 421 | |:---|:---|:---| 422 | |symbol|str|产品名称| 423 | |security_seconds|int|安全间隔(单位:秒)| 424 | 425 | 为什么要有这个方法: 426 | 427 | candle_map会使用基准产品的历史K线最新时间点为基准更新,但是官方更新最近时间点的历史K线数据往往存在0~2分钟的时间差甚至更多,服务器访问更新也需要花费时间,get_candle_security可以验证candle_map中产品的K线最大时间与当前时间的差值,如果差值过大,视为不安全,返回空数组,如果满足在预设时间差值以内,则正常返回。 428 | 429 | 430 | 以U本位产品为例,实时更新全部产品的历史K线数据 431 | 432 | ```python 433 | from binance_candle import CandleServer 434 | import time 435 | 436 | candleServer = CandleServer('UM') 437 | candleServer.run_candle_map() 438 | 439 | while True: 440 | for symbol in candleServer.candle_map.keys(): 441 | print( 442 | symbol, 443 | candleServer.get_candle_security(symbol, security_seconds=60) 444 | ) 445 | time.sleep(1) 446 | ``` 447 | 448 | ### 7.4 close_run_candle_map 关闭实时K线数据更新服务 449 | 450 | close_run_candle_map仅作用于异步运行,为了保证异步的安全性,关闭方法会等待异步程序阶段性运行结束后关闭,例如避免在保存本地数据的时候发生了终止,造成数据错误。 451 | 452 | ## 8 下载历史K线 453 | 454 | ### 8.1 download_candles_by_date 方法 455 | 456 | 下载从start日期到end日期(包括end),规则产品的历史K线数据。 457 | 458 | |参数|类型|默认值|说明| 459 | |:---|:---|:---|:---| 460 | |start|Union[int,float,str,datetime.date]|无|起始日期| 461 | |end|Union[int,float,str,datetime.date,None]|None|终止日期 None表示昨日| 462 | |replace|bool|False|如果本地数据已存在,是否下载替换| 463 | 464 | start与end支持整数与浮点数类型的**毫秒**时间戳、日期格式的字符串与日期类型。 465 | 466 | 例如: 467 | 468 | ```python 469 | start = 1672502400000 # 毫秒时间戳(北京时间2023-01-01 00:00:00) 470 | start = '2023-01-02' # 日期类型字符串(中国时间格式) 471 | start = '01/02/2023' # 日期类型字符串(美国时间格式) 472 | start = pendulum.yesterday('Asia/Shanghai') # 日期类型 473 | start = datetime.date(2023,1,2) # 日期格式 474 | ``` 475 | 476 | 如果start或end为日期形式的字符串,Binance_candle会将其转化为CandleRule中TIMEZONE对应的日期。所以中国时间格式与美国时间格式是等价的。 477 | 478 | 下载从start~end的数据,数据终点是被包含的。 479 | 480 | 例如:start = '2023-01-01' end = '2023-01-03',会下载 ['2023-01-01','2023-01-02','2023-01-03'] 三天的数据。 481 | 482 | 以日期为拆分存储,每一天的数据不会包含下一天的时间起点。 483 | 484 | 例如: 485 | 486 | |BAR|每天数据的最后时刻K线的开盘时间| 487 | |:---|:---| 488 | |1m|23:59:00| 489 | |5m|23:55:00| 490 | |1h|23:00:00| 491 | 492 | 例子: 493 | 494 | 下载现货BTCUSDT与ETCUSDT,从美国时间2023-01-01~2023-01-10,时间粒度为1m的K线数据。 495 | 496 | ```python 497 | from binance_candle import CandleServer, CandleRule 498 | 499 | CandleRule.BAR = '1m' 500 | CandleRule.SYMBOLS = ['BTCUSDT', 'ETCUSDT'] 501 | CandleRule.TIMEZONE = 'America/New_York' 502 | 503 | candleServer = CandleServer('SPOT', CandleRule) 504 | 505 | candleServer.download_candles_by_date( 506 | start='2022-01-01', 507 | end='2022-01-10', 508 | ) 509 | ``` 510 | 511 | ### 8.2 download_daily 方法 512 | 513 | 每日定点下载昨日历史K线数据的服务,异步执行。 514 | 515 | |参数|类型|默认值|说明| 516 | |:---|:---|:---|:---| 517 | |start|Union[int,float,str,datetime.date,None]|None|补充下载从start~yesterday的数据 start=None等价于start=yesterday| 518 | |replace|bool|False|本地已存在的数据是否重新下载替换| 519 | 520 | download_daily是完全异步执行的,先补充下载start~yesterday的历史K线数据,随后在每天的DOWNLOAD_TIME下载昨日的历史K线数据。 521 | 522 | 例子: 523 | 524 | 开启服务,每天定时下载现货产品的历史K线数据,K线参数与时区均等均选择默认值。 525 | 526 | ```python 527 | from binance_candle import CandleServer 528 | 529 | candleServer = CandleServer('SPOT') 530 | candleServer.download_daily() 531 | ``` 532 | 533 | ### 8.3 close_download_daily 关闭每日K线下载服务 534 | 535 | 为了保证异步的安全性,关闭方法会等待异步程序阶段性运行结束后关闭,例如避免在保存本地数据的时候发生了终止,造成数据错误。 536 | 537 | ## 9 行情数据 538 | 539 | ### 9.1 行情数据的规则 540 | 541 | 行情数据支持:现货、U本位合约与币本位合约,**目前不支持欧式期权**。 542 | 543 | 导入行情数据模块 544 | 545 | ```python 546 | from binance_candle import Market 547 | 548 | market = Market( 549 | instType='SPOT', # 产品类型 SPOT | UM | CM 550 | key='', # 秘钥 551 | secret='', # 秘钥 552 | timezone='America/New_York' # 时区 553 | ) 554 | ``` 555 | 556 | 也可以使用CandleServer中的market模块 557 | 558 | ```python 559 | from binance_candle import CandleServer 560 | 561 | candleServer = CandleServer('SPOT') 562 | market = candleServer.market 563 | ``` 564 | 565 | 行情数据的返回格式均为:{'code':\<状态码\>, 'data':\<主体数据\>, 'msg':\<提示信息\>} 566 | 567 | 状态码为200表示成功。 568 | 569 | ### 9.2 最优挂单价格 570 | 571 | #### 9.2.1 get_bookTickers 全部产品的最优挂单列表 572 | 573 | 例如获取现货交易全部的最优挂单 574 | 575 | ```python 576 | from binance_candle.market import Market 577 | 578 | bookTickers = Market('SPOT').get_bookTickers() 579 | print(bookTickers) 580 | ``` 581 | 582 | 输出: 583 | 584 | ```text 585 | >> { 586 | >> 'code': 200, 587 | >> 'data': [ 588 | >> { 589 | >> 'askPrice': '0.06861100', 590 | >> 'askQty': '28.32510000', 591 | >> 'bidPrice': '0.06861000', 592 | >> 'bidQty': '20.86970000', 593 | >> 'symbol': 'ETHBTC' 594 | >> }, 595 | >> { 596 | >> 'askPrice': '0.00418600', 597 | >> 'askQty': '11.67400000', 598 | >> 'bidPrice': '0.00418500', 599 | >> 'bidQty': '4.67100000', 600 | >> 'symbol': 'LTCBTC' 601 | >> }, 602 | >> ... ... 603 | >> ], 604 | >> 'msg': '', 605 | >> } 606 | ``` 607 | 608 | #### 9.2.2 get_bookTickersMap 全部产品的最优挂单字典 609 | 610 | 例如获取U本位交易全部的最优挂单 611 | 612 | ```python 613 | from binance_candle.market import Market 614 | 615 | bookTickersMap = Market('UM').get_bookTickersMap() 616 | print(bookTickersMap) 617 | ``` 618 | 619 | 输出: 620 | 621 | ```python 622 | >> { 623 | >> 'code': 200, 624 | >> 'data': { 625 | >> '1000LUNCBUSD': { 626 | >> 'askPrice': '0.1713000', 627 | >> 'askQty': '71623', 628 | >> 'bidPrice': '0.1712000', 629 | >> 'bidQty': '96247', 630 | >> 'symbol': '1000LUNCBUSD', 631 | >> 'time': 1675216339831 632 | >> }, 633 | >> '1000LUNCUSDT': { 634 | >> 'askPrice': '0.1713000', 635 | >> 'askQty': '98341', 636 | >> 'bidPrice': '0.1712000', 637 | >> 'bidQty': '71166', 638 | >> 'symbol': '1000LUNCUSDT', 639 | >> 'time': 1675216339829 640 | >> }, 641 | >> ... ... 642 | >> }, 643 | >> 'msg': '' 644 | >> } 645 | ``` 646 | 647 | #### 9.2.3 get_bookTicker单个产品的最优挂单 648 | 649 | 获取现货交易BTCUSDT的最优挂单 650 | 651 | ```python 652 | from binance_candle.market import Market 653 | 654 | bookTicker = Market('SPOT').get_bookTicker('BTCUSDT') 655 | print(bookTicker) 656 | ``` 657 | 658 | 输出: 659 | 660 | ```text 661 | >> { 662 | >> 'code': 200, 663 | >> 'data': 664 | >> { 665 | >> 'symbol': 'BTCUSDT', 666 | >> 'bidPrice': '23122.18000000', 667 | >> 'bidQty': '0.07635000', 668 | >> 'askPrice': '23122.81000000', 669 | >> 'askQty': '0.00200000' 670 | >> }, 671 | >> 'msg': '' 672 | >> } 673 | ``` 674 | 675 | ### 9.3 最新成交价 676 | 677 | #### 9.3.1 get_tickerPrices 全部产品的最新成交价列表 678 | 679 | 获取币本位合约全部产品的最新成交价格列表 680 | 681 | ```python 682 | from binance_candle.market import Market 683 | 684 | tickerPrices = Market('CM').get_tickerPrices() 685 | print(tickerPrices) 686 | ``` 687 | 688 | 输出: 689 | 690 | ```text 691 | >> { 692 | >> 'code': 200, 693 | >> 'data': [ 694 | >> { 695 | >> 'price': '23312.5', 696 | >> 'ps': 'BTCUSD', 697 | >> 'symbol': 'BTCUSD_230331', 698 | >> 'time': 1675217026295 699 | >> }, 700 | >> { 701 | >> 'price': '0.80450', 702 | >> 'ps': 'KNCUSD', 703 | >> 'symbol': 'KNCUSD_PERP', 704 | >> 'time': 1675216973427 705 | >> }, 706 | >> ... ... 707 | >> ], 708 | >> 'msg': '' 709 | >> } 710 | ``` 711 | 712 | #### 9.3.2 get_tickerPricesMap 全部产品的最新成交价字典 713 | 714 | 获取币本位合约全部产品的最新成交价格字典 715 | 716 | ```python 717 | from binance_candle.market import Market 718 | 719 | tickerPricesMap = Market('CM').get_tickerPricesMap() 720 | print(tickerPricesMap) 721 | ``` 722 | 723 | 输出: 724 | 725 | ```text 726 | >> { 727 | >> 'code': 200, 728 | >> 'data': { 729 | >> 'AAVEUSD_PERP': { 730 | >> 'price': '83.080', 731 | >> 'ps': 'AAVEUSD', 732 | >> 'symbol': 'AAVEUSD_PERP', 733 | >> 'time': 1675217116068 734 | >> }, 735 | >> 'ADAUSD_230331': { 736 | >> 'price': '0.38853', 737 | >> 'ps': 'ADAUSD', 738 | >> 'symbol': 'ADAUSD_230331', 739 | >> 'time': 1675217149962 740 | >> }, 741 | >> ... ... 742 | >> }, 743 | >> 'msg': '' 744 | >> } 745 | ``` 746 | 747 | #### 9.3.3 get_tickerPrice 单个产品的最新成交价 748 | 749 | 获取币本位合约中ETCUSD_PERP的最新成交价格 750 | 751 | ```python 752 | from binance_candle.market import Market 753 | 754 | tickerPrice = Market('CM').get_tickerPrice('ETCUSD_PERP') 755 | print(tickerPrice) 756 | ``` 757 | 758 | 输出: 759 | 760 | ```text 761 | >> { 762 | >> 'code': 200, 763 | >> 'data': [ 764 | >> { 765 | >> 'symbol': 'ETCUSD_PERP', 766 | >> 'ps': 'ETCUSD', 767 | >> 'price': '21.589', 768 | >> 'time': 1675217398417 769 | >> } 770 | >> ], 771 | >> 'msg': '' 772 | >> } 773 | ``` 774 | 775 | 776 | 777 | ### 9.4 交易规范 778 | 779 | #### 9.4.1 缓存机制 780 | 781 | 获取交易规范的API文档: 782 | 783 | - 现货交易:https://binance-docs.github.io/apidocs/spot/cn/#3f1907847c 784 | - U本位合约:https://binance-docs.github.io/apidocs/futures/cn/#0f3f2d5ee7 785 | - 币本位合约:https://binance-docs.github.io/apidocs/delivery/cn/#185368440e 786 | 787 | 交易规范的返回信息内容较多,现货交易权重为10,合约交易权重为1,因为交易规范本身改动频率较小,在Binance_candle中默认采用缓存的方式获取交易规范结果,以提高速度和节约权重。 788 | 789 | 第一次获取交易规范信息会向官方发送请求,将请求结果保存在缓存中,随后再次获取交易规范信息会验证上次缓存的数据是否符合过期,过期则重新访问,否则返回缓存数据。 790 | 791 | 如果不使用缓存,可以将过期时间expire_seconds设置为0(单位:秒)。 792 | 793 | #### 9.4.2 get_exchangeInfos 完整的交易规范 794 | 795 | |参数|类型|默认值|说明| 796 | |:---|:---|:---|:---| 797 | |expire_seconds|int|300|缓存过期时间| 798 | 799 | 每种产品类型之间的交易规则式独立的,例如获取现货交易的交易规则信息。 800 | 801 | ```python 802 | from binance_candle import Market 803 | 804 | exchangeInfos = Market('SPOT').get_exchangeInfos() 805 | print(exchangeInfos) 806 | ``` 807 | 808 | 输出: 809 | 810 | ```text 811 | >> { 812 | >> 'code': 200, 813 | >> 'data': {'exchangeFilters': [], 814 | >> 'rateLimits': [{'interval': 'MINUTE', 815 | >> 'intervalNum': 1, 816 | >> 'limit': 1200, 817 | >> 'rateLimitType': 'REQUEST_WEIGHT'}, 818 | >> {'interval': 'SECOND', 819 | >> 'intervalNum': 10, 820 | >> 'limit': 50, 821 | >> 'rateLimitType': 'ORDERS'}, 822 | >> {'interval': 'DAY', 823 | >> 'intervalNum': 1, 824 | >> 'limit': 160000, 825 | >> 'rateLimitType': 'ORDERS'}, 826 | >> {'interval': 'MINUTE', 827 | >> 'intervalNum': 5, 828 | >> 'limit': 6100, 829 | >> 'rateLimitType': 'RAW_REQUESTS'}], 830 | >> 'serverTime': 1675220759508, 831 | >> 'symbols': [ 832 | >> {'allowTrailingStop': True, 833 | >> 'allowedSelfTradePreventionModes': ['NONE', 834 | >> 'EXPIRE_TAKER', 835 | >> 'EXPIRE_MAKER', 836 | >> 'EXPIRE_BOTH'], 837 | >> 'baseAsset': 'ETH', 838 | >> 'baseAssetPrecision': 8, 839 | >> 'baseCommissionPrecision': 8, 840 | >> 'cancelReplaceAllowed': True, 841 | >> 'defaultSelfTradePreventionMode': 'NONE', 842 | >> 'filters': [{'filterType': 'PRICE_FILTER', 843 | >> 'maxPrice': '922327.00000000', 844 | >> 'minPrice': '0.00000100', 845 | >> 'tickSize': '0.00000100'}, 846 | >> {'filterType': 'LOT_SIZE', 847 | >> 'maxQty': '100000.00000000', 848 | >> 'minQty': '0.00010000', 849 | >> 'stepSize': '0.00010000'}, 850 | >> {'applyToMarket': True, 851 | >> 'avgPriceMins': 5, 852 | >> 'filterType': 'MIN_NOTIONAL', 853 | >> 'minNotional': '0.00010000'}, 854 | >> {'filterType': 'ICEBERG_PARTS', 'limit': 10}, 855 | >> {'filterType': 'MARKET_LOT_SIZE', 856 | >> 'maxQty': '1657.19015357', 857 | >> 'minQty': '0.00000000', 858 | >> 'stepSize': '0.00000000'}, 859 | >> {'filterType': 'TRAILING_DELTA', 860 | >> 'maxTrailingAboveDelta': 2000, 861 | >> 'maxTrailingBelowDelta': 2000, 862 | >> 'minTrailingAboveDelta': 10, 863 | >> 'minTrailingBelowDelta': 10}, 864 | >> {'askMultiplierDown': '0.2', 865 | >> 'askMultiplierUp': '5', 866 | >> 'avgPriceMins': 5, 867 | >> 'bidMultiplierDown': '0.2', 868 | >> 'bidMultiplierUp': '5', 869 | >> 'filterType': 'PERCENT_PRICE_BY_SIDE'}, 870 | >> {'filterType': 'MAX_NUM_ORDERS', 871 | >> 'maxNumOrders': 200}, 872 | >> {'filterType': 'MAX_NUM_ALGO_ORDERS', 873 | >> 'maxNumAlgoOrders': 5}], 874 | >> 'icebergAllowed': True, 875 | >> 'isMarginTradingAllowed': True, 876 | >> 'isSpotTradingAllowed': True, 877 | >> 'ocoAllowed': True, 878 | >> 'orderTypes': ['LIMIT', 879 | >> 'LIMIT_MAKER', 880 | >> 'MARKET', 881 | >> 'STOP_LOSS_LIMIT', 882 | >> 'TAKE_PROFIT_LIMIT'], 883 | >> 'permissions': ['SPOT', 884 | >> 'MARGIN', 885 | >> 'TRD_GRP_004', 886 | >> 'TRD_GRP_005', 887 | >> 'TRD_GRP_006'], 888 | >> 'quoteAsset': 'BTC', 889 | >> 'quoteAssetPrecision': 8, 890 | >> 'quoteCommissionPrecision': 8, 891 | >> 'quoteOrderQtyMarketAllowed': True, 892 | >> 'quotePrecision': 8, 893 | >> 'status': 'TRADING', 894 | >> 'symbol': 'ETHBTC'}, 895 | >> ... ... 896 | >> ], 897 | >> 'timezone': 'UTC'}, 898 | >> 'msg': '' 899 | >> } 900 | ``` 901 | 902 | #### 9.4.3 get_exchangeInfo 单个产品的交易规范 903 | 904 | |参数|类型|默认值|说明| 905 | |:---|:---|:---|:---| 906 | |symbol|str|无|产品名称| 907 | |expire_seconds|int|300|缓存过期时间| 908 | 909 | 获取U本位合约中BTCUSDT的交易规范 910 | 911 | 912 | ```python 913 | from binance_candle import Market 914 | 915 | exchangeInfos = Market('UM').get_exchangeInfo('BTCUSDT') 916 | print(exchangeInfos) 917 | ``` 918 | 919 | 920 | ```text 921 | >> { 922 | >> 'code': 200, 923 | >> 'data': { 924 | >> 'baseAsset': 'BTC', 925 | >> 'baseAssetPrecision': 8, 926 | >> 'contractType': 'PERPETUAL', 927 | >> 'deliveryDate': 4133404800000, 928 | >> 'filter': {'LOT_SIZE': {'filterType': 'LOT_SIZE', 929 | >> 'maxQty': '1000', 930 | >> 'minQty': '0.001', 931 | >> 'stepSize': '0.001'}, 932 | >> 'MARKET_LOT_SIZE': {'filterType': 'MARKET_LOT_SIZE', 933 | >> 'maxQty': '120', 934 | >> 'minQty': '0.001', 935 | >> 'stepSize': '0.001'}, 936 | >> 'MAX_NUM_ALGO_ORDERS': {'filterType': 'MAX_NUM_ALGO_ORDERS', 937 | >> 'limit': 10}, 938 | >> 'MAX_NUM_ORDERS': {'filterType': 'MAX_NUM_ORDERS', 939 | >> 'limit': 200}, 940 | >> 'MIN_NOTIONAL': {'filterType': 'MIN_NOTIONAL', 941 | >> 'notional': '5'}, 942 | >> 'PERCENT_PRICE': {'filterType': 'PERCENT_PRICE', 943 | >> 'multiplierDecimal': '4', 944 | >> 'multiplierDown': '0.9500', 945 | >> 'multiplierUp': '1.0500'}, 946 | >> 'PRICE_FILTER': {'filterType': 'PRICE_FILTER', 947 | >> 'maxPrice': '4529764', 948 | >> 'minPrice': '556.80', 949 | >> 'tickSize': '0.10'}}, 950 | >> 'filters': [{'filterType': 'PRICE_FILTER', 951 | >> 'maxPrice': '4529764', 952 | >> 'minPrice': '556.80', 953 | >> 'tickSize': '0.10'}, 954 | >> {'filterType': 'LOT_SIZE', 955 | >> 'maxQty': '1000', 956 | >> 'minQty': '0.001', 957 | >> 'stepSize': '0.001'}, 958 | >> {'filterType': 'MARKET_LOT_SIZE', 959 | >> 'maxQty': '120', 960 | >> 'minQty': '0.001', 961 | >> 'stepSize': '0.001'}, 962 | >> {'filterType': 'MAX_NUM_ORDERS', 'limit': 200}, 963 | >> {'filterType': 'MAX_NUM_ALGO_ORDERS', 'limit': 10}, 964 | >> {'filterType': 'MIN_NOTIONAL', 'notional': '5'}, 965 | >> {'filterType': 'PERCENT_PRICE', 966 | >> 'multiplierDecimal': '4', 967 | >> 'multiplierDown': '0.9500', 968 | >> 'multiplierUp': '1.0500'}], 969 | >> 'liquidationFee': '0.012500', 970 | >> 'maintMarginPercent': '2.5000', 971 | >> 'marginAsset': 'USDT', 972 | >> 'marketTakeBound': '0.05', 973 | >> 'onboardDate': 1569398400000, 974 | >> 'orderTypes': ['LIMIT', 975 | >> 'MARKET', 976 | >> 'STOP', 977 | >> 'STOP_MARKET', 978 | >> 'TAKE_PROFIT', 979 | >> 'TAKE_PROFIT_MARKET', 980 | >> 'TRAILING_STOP_MARKET'], 981 | >> 'pair': 'BTCUSDT', 982 | >> 'pricePrecision': 2, 983 | >> 'quantityPrecision': 3, 984 | >> 'quoteAsset': 'USDT', 985 | >> 'quotePrecision': 8, 986 | >> 'requiredMarginPercent': '5.0000', 987 | >> 'settlePlan': 0, 988 | >> 'status': 'TRADING', 989 | >> 'symbol': 'BTCUSDT', 990 | >> 'timeInForce': ['GTC', 'IOC', 'FOK', 'GTX'], 991 | >> 'triggerProtect': '0.0500', 992 | >> 'underlyingSubType': ['PoW'], 993 | >> 'underlyingType': 'COIN'}, 994 | >> 'msg': '' 995 | >> } 996 | ``` 997 | 998 | 注:其中的filter字典属性是binance_candle对filters列表的整合,并不属于官方返回的结果。 999 | 1000 | #### 9.4.4 get_symbols_trading_on 获取可以交易的产品名称 1001 | 1002 | |参数|类型|默认值|说明| 1003 | |:---|:---|:---|:---| 1004 | |expire_seconds|int|300|缓存过期时间| 1005 | 1006 | 获取U本位合约正在交易的产品名称列表 1007 | 1008 | ```python 1009 | from binance_candle import Market 1010 | 1011 | symbols_trading_on = Market('UM').get_symbols_trading_on() 1012 | print(symbols_trading_on) 1013 | ``` 1014 | 1015 | 输出: 1016 | 1017 | ```text 1018 | >> { 1019 | >> 'code': 200, 1020 | >> 'data': [ 1021 | >> 'BTCUSDT', 'ETHUSDT', 'BCHUSDT', 'XRPUSDT', 'EOSUSDT', 'LTCUSDT', 'TRXUSDT', 'ETCUSDT', 'LINKUSDT', 'XLMUSDT', 1022 | >> 'ADAUSDT', 'XMRUSDT', 'DASHUSDT', 'ZECUSDT', 'XTZUSDT', 'BNBUSDT', 'ATOMUSDT', 'ONTUSDT', 'IOTAUSDT', 'BATUSDT', 1023 | >> 'VETUSDT', 'NEOUSDT', 'QTUMUSDT', 'IOSTUSDT', 'THETAUSDT', 'ALGOUSDT', 'ZILUSDT', 'KNCUSDT', 'ZRXUSDT', 1024 | >> 'COMPUSDT', 'OMGUSDT', 'DOGEUSDT', 'SXPUSDT', 'KAVAUSDT', 'BANDUSDT', 'RLCUSDT', 'WAVESUSDT', 'MKRUSDT', 1025 | >> 'SNXUSDT', 'DOTUSDT', 'DEFIUSDT', 'YFIUSDT', 'BALUSDT', 'CRVUSDT', 'TRBUSDT', 'RUNEUSDT', 'SUSHIUSDT', 1026 | >> 'EGLDUSDT', 'SOLUSDT', 'ICXUSDT', 'STORJUSDT', 'BLZUSDT', 'UNIUSDT', 'AVAXUSDT', 'FTMUSDT', 'HNTUSDT', 1027 | >> 'ENJUSDT', 'FLMUSDT', 'TOMOUSDT', 'RENUSDT', 'KSMUSDT', 'NEARUSDT', 'AAVEUSDT', 'FILUSDT', 'RSRUSDT', 'LRCUSDT', 1028 | >> 'MATICUSDT', 'OCEANUSDT', 'BELUSDT', 'CTKUSDT', 'AXSUSDT', 'ALPHAUSDT', 'ZENUSDT', 'SKLUSDT', 'GRTUSDT', 1029 | >> '1INCHUSDT', 'BTCBUSD', 'CHZUSDT', 'SANDUSDT', 'ANKRUSDT', 'LITUSDT', 'UNFIUSDT', 'REEFUSDT', 'RVNUSDT', 1030 | >> 'SFPUSDT', 'XEMUSDT', 'COTIUSDT', 'CHRUSDT', 'MANAUSDT', 'ALICEUSDT', 'HBARUSDT', 'ONEUSDT', 'LINAUSDT', 1031 | >> 'STMXUSDT', 'DENTUSDT', 'CELRUSDT', 'HOTUSDT', 'MTLUSDT', 'OGNUSDT', 'NKNUSDT', 'DGBUSDT', '1000SHIBUSDT', 1032 | >> 'BAKEUSDT', 'GTCUSDT', 'ETHBUSD', 'BTCDOMUSDT', 'BNBBUSD', 'ADABUSD', 'XRPBUSD', 'IOTXUSDT', 'DOGEBUSD', 1033 | >> 'AUDIOUSDT', 'C98USDT', 'MASKUSDT', 'ATAUSDT', 'SOLBUSD', 'DYDXUSDT', '1000XECUSDT', 'GALAUSDT', 'CELOUSDT', 1034 | >> 'ARUSDT', 'KLAYUSDT', 'ARPAUSDT', 'CTSIUSDT', 'LPTUSDT', 'ENSUSDT', 'PEOPLEUSDT', 'ANTUSDT', 'ROSEUSDT', 1035 | >> 'DUSKUSDT', 'FLOWUSDT', 'IMXUSDT', 'API3USDT', 'GMTUSDT', 'APEUSDT', 'BNXUSDT', 'WOOUSDT', 'JASMYUSDT', 1036 | >> 'DARUSDT', 'GALUSDT', 'AVAXBUSD', 'NEARBUSD', 'GMTBUSD', 'APEBUSD', 'GALBUSD', 'FTMBUSD', 'DODOBUSD', 1037 | >> 'GALABUSD', 'TRXBUSD', '1000LUNCBUSD', 'LUNA2BUSD', 'OPUSDT', 'DOTBUSD', 'TLMBUSD', 'ICPBUSD', 'WAVESBUSD', 1038 | >> 'LINKBUSD', 'SANDBUSD', 'LTCBUSD', 'MATICBUSD', 'CVXBUSD', 'FILBUSD', '1000SHIBBUSD', 'LEVERBUSD', 'ETCBUSD', 1039 | >> 'LDOBUSD', 'UNIBUSD', 'INJUSDT', 'STGUSDT', 'FOOTBALLUSDT', 'SPELLUSDT', '1000LUNCUSDT', 'LUNA2USDT', 'AMBBUSD', 1040 | >> 'PHBBUSD', 'LDOUSDT', 'CVXUSDT', 'ICPUSDT', 'APTUSDT', 'QNTUSDT', 'APTBUSD', 'BLUEBIRDUSDT', 'ETHUSDT_230331', 1041 | >> 'BTCUSDT_230331', 'FETUSDT', 'AGIXBUSD', 'FXSUSDT', 'HOOKUSDT', 'MAGICUSDT' 1042 | >> ], 1043 | >> 'msg': '' 1044 | >> } 1045 | ``` 1046 | 1047 | #### 9.4.5 get_symbols_trading_off 获取不可交易的产品名称 1048 | 1049 | |参数|类型|默认值|说明| 1050 | |:---|:---|:---|:---| 1051 | |expire_seconds|int|300|缓存过期时间| 1052 | 1053 | 获取U本位合约不可交易的产品名称列表 1054 | 1055 | ```python 1056 | from binance_candle import Market 1057 | 1058 | symbols_trading_off = Market('UM').get_symbols_trading_off() 1059 | print(symbols_trading_off) 1060 | ``` 1061 | 1062 | 输出: 1063 | 1064 | ```text 1065 | >> { 1066 | >> 'code': 200, 1067 | >> 'data': [ 1068 | >> 'SRMUSDT', 'CVCUSDT', 'BTSUSDT', 'BTCSTUSDT', 'SCUSDT', 'TLMUSDT', 'RAYUSDT', 'FTTBUSD', 'FTTUSDT', 1069 | >> 'ANCBUSD', 'AUCTIONBUSD' 1070 | >> ], 1071 | >> 'msg': '' 1072 | >> } 1073 | ``` 1074 | 1075 | 1076 | ### 9.5 深度信息 1077 | 1078 | #### 9.5.1 get_get_depth 获取单个产品的深度信息 1079 | 1080 | ```python 1081 | from binance_candle import Market 1082 | from pprint import pprint 1083 | 1084 | if __name__ == '__main__': 1085 | # 币币交易:SPOT;U本位合约:UM;币本位合约:CM 1086 | instType = 'UM' 1087 | # 实例化行情Market 1088 | market = Market(instType) 1089 | # 单个产品的深度信息 limit : 数量 1090 | pprint(market.get_depth('BTCUSDT', limit=10)) 1091 | ``` 1092 | 1093 | 输出: 1094 | 1095 | ```text 1096 | >> {'code': 200, 1097 | >> 'data': {'E': 1675823293991, 1098 | >> 'T': 1675823293984, 1099 | >> 'asks': [['23290.50', '15.695'], 1100 | >> ['23290.80', '0.003'], 1101 | >> ['23290.90', '0.001'], 1102 | >> ['23291.00', '0.084'], 1103 | >> ['23291.10', '0.006'], 1104 | >> ['23291.20', '0.034'], 1105 | >> ['23291.30', '0.017'], 1106 | >> ['23291.40', '0.258'], 1107 | >> ['23291.50', '0.010'], 1108 | >> ['23291.70', '0.821']], 1109 | >> 'bids': [['23290.40', '18.252'], 1110 | >> ['23290.30', '2.230'], 1111 | >> ['23290.20', '1.156'], 1112 | >> ['23290.10', '0.130'], 1113 | >> ['23290.00', '2.182'], 1114 | >> ['23289.90', '0.010'], 1115 | >> ['23289.80', '0.172'], 1116 | >> ['23289.70', '0.151'], 1117 | >> ['23289.60', '0.044'], 1118 | >> ['23289.50', '0.002']], 1119 | >> 'lastUpdateId': 2476916281979}, 1120 | >> 'msg': ''} 1121 | ``` 1122 | 1123 | ## 10 历史K线管理 1124 | 1125 | ### 10.1 BinanceLite 简介 1126 | 1127 | 1128 | BinanceLite基于candlelite的IO为底层,用于维护本地Binance历史K线数据,由于BinanceLite采用日期分割的CSV文件存储,不需要安装第三方数据库。并且由于默认系统路径的配置可以使不同路径的项目共享历史K线数据。 1129 | 1130 | ```python 1131 | from binance_candle import BinanceLite 1132 | 1133 | binanceLite = BinanceLite() 1134 | candle = binanceLite.load_candle_by_date( 1135 | instType='SPOT', 1136 | symbol='BTCUSDT', 1137 | start='2023-01-01', 1138 | end='2023-01-10', 1139 | ) 1140 | ``` 1141 | 1142 | 对于任何历史K线数据的读取,都需要时区timezone、时间粒度bar、数据位置base_dir,如果在调用函数的时候不指定这三个内容,将使用candlelite的默认配置。 1143 | 1144 | 你可以在candlelite中查看Binance默认的配置内容。 1145 | 1146 | ```cmd 1147 | candlelite show_settings # 查看配置信息 1148 | ``` 1149 | 1150 | 输出: 1151 | 1152 | ```text 1153 | >> # 历史K线数据根目录 1154 | >> CANDLE_BASE_DIR = 'CANDLELITE_DATA' 1155 | >> # OKX以日期为单位的存储目录 1156 | >> OKX_DATE_DIRNAME = 'OKX' 1157 | >> # OKX以文件为单位的存储目录 1158 | >> OKX_FILE_DIRNAME = 'OKX_FILE' 1159 | >> # OKX的默认时区 1160 | >> OKX_TIMEZONE = 'Asia/Shanghai' 1161 | >> # OKX的默认时间粒度 1162 | >> OKX_DEFAULT_BAR = '1m' 1163 | >> # BINANCE以日期为单位的存储目录 1164 | >> BINANCE_DATE_DIRNAME = 'BINANCE' 1165 | >> # BINANCE以文件为单位的存储目录 1166 | >> BINANCE_FILE_DIRNAME = 'BINANCE_FILE' 1167 | >> # BINANCE的默认时区 1168 | >> BINANCE_TIMEZONE = 'America/New_York' 1169 | >> # BINANCE的默认时间粒度 1170 | >> BINANCE_DEFAULT_BAR = '1m' 1171 | ``` 1172 | 1173 | 修改配置内容 1174 | 1175 | ```cmd 1176 | candlelite console_settings 1177 | ``` 1178 | 1179 | 为了让多个不同路径本地项目可以共享数据,可以将"CANDLE_BASE_DIR"设置为绝对路径,例如:'/root/CANDLELITE_DATA' 或 'd:\CANDLELITE_DATA'。 1180 | 1181 | ### 10.2 读取数据 1182 | 1183 | #### 10.2.1 load_candle_by_date 1184 | 1185 | load_candle_by_date 以日期为单位读取单个产品历史K线数据 1186 | 1187 | |参数|类型|默认值|说明| 1188 | |:---|:---|:---|:---| 1189 | |instType|str|无|产品类别(现货:SPOT、U本位:UM、币本位:CM)| 1190 | |symbol|str|无|产品名称| 1191 | |start|Union[int,float,str,datetime.date]|无|起始日期| 1192 | |end|Union[int,float,str,datetime.date]|无|终止日期| 1193 | |base_dir|Union[str,None]|None|数据根目录,None使用candlelite中Binance的默认根目录| 1194 | |timezone|Union[str,None]|None|时区,None使用candlelite中Binance的默认时区| 1195 | |bar|Union[str,None]|None|时间粒度,None使用candlelite中Binance的默认时间粒度| 1196 | |valid_interval|bool|True|是否验证数据的时间间隔| 1197 | |valid_start|bool|True|是否验证数据的时间起点| 1198 | |valid_end|bool|True|是否验证数据的时间终点| 1199 | 1200 | 1201 | 读取U本位合约中BTCUSDT从2023-01-01 ~ 2023-01-10 的历史K线数据 1202 | 1203 | ```python 1204 | from binance_candle import BinanceLite 1205 | 1206 | binanceLite = BinanceLite() 1207 | candle = binanceLite.load_candle_by_date( 1208 | instType='UM', 1209 | symbol='BTCUSDT', 1210 | start='2023-01-01', 1211 | end='2023-01-10', 1212 | ) 1213 | print(candle) 1214 | ``` 1215 | 1216 | #### 10.2.1 load_candle_map_by_date 1217 | 1218 | load_candle_map_by_date 以日期为单位读取全部产品历史K线数据字典 1219 | 1220 | |参数|类型|默认值|说明| 1221 | |:---|:---|:---|:---| 1222 | |instType|str|无|产品类别(现货:SPOT、U本位:UM、币本位:CM)| 1223 | |symbols|list|无|产品名称列表,如果为空列表,读取从start~end范围数据完整的全部产品| 1224 | |start|Union[int,float,str,datetime.date]|无|起始日期| 1225 | |end|Union[int,float,str,datetime.date]|无|终止日期| 1226 | |base_dir|Union[str,None]|None|数据根目录,None使用candlelite中Binance的默认根目录| 1227 | |timezone|Union[str,None]|None|时区,None使用candlelite中Binance的默认时区| 1228 | |bar|Union[str,None]|None|时间粒度,None使用candlelite中Binance的默认时间粒度| 1229 | |contains|str|''|产品名称中需包含的内容,不满足则过滤读取| 1230 | |endswith|str|''|产品名称中需以何结尾,不满足则过滤读取| 1231 | |p_num|int|4|使用多进程加速读取,进程个数| 1232 | |valid_interval|bool|True|是否验证数据的时间间隔| 1233 | |valid_start|bool|True|是否验证数据的时间起点| 1234 | |valid_end|bool|True|是否验证数据的时间终点| 1235 | 1236 | 1237 | 1238 | 读取U本位合约从2023-01-01 ~ 2023-01-10 的历史K线数据字典 1239 | 1240 | ```python 1241 | from binance_candle import BinanceLite 1242 | 1243 | binanceLite = BinanceLite() 1244 | candle_map = binanceLite.load_candle_map_by_date( 1245 | instType='UM', 1246 | symbols=[], 1247 | start='2023-01-01', 1248 | end='2023-01-10', 1249 | ) 1250 | print(candle_map) 1251 | ``` 1252 | 1253 | ### 10.3 保存数据 1254 | 1255 | #### 10.3.1 save_candle_by_date 1256 | 1257 | save_candle_by_date 以日期为单位保存单个产品历史K线数据 1258 | 1259 | |参数|类型|默认值|说明| 1260 | |:---|:---|:---|:---| 1261 | |candle|np.ndarray|无|历史K线数据| 1262 | |instType|str|无|产品类别(现货:SPOT、U本位:UM、币本位:CM)| 1263 | |symbol|str|无|产品名称| 1264 | |start|Union[int,float,str,datetime.date]|无|起始日期| 1265 | |end|Union[int,float,str,datetime.date]|无|终止日期| 1266 | |base_dir|Union[str,None]|None|数据根目录,None使用candlelite中Binance的默认根目录| 1267 | |timezone|Union[str,None]|None|时区,None使用candlelite中Binance的默认时区| 1268 | |bar|Union[str,None]|None|时间粒度,None使用candlelite中Binance的默认时间粒度| 1269 | |replace|bool|False|是否替换本地数据| 1270 | |drop_duplicate|bool|True|是否去重| 1271 | |sort|bool|True|是否按照时间戳排序| 1272 | |valid_interval|bool|True|是否验证数据的时间间隔| 1273 | |valid_start|bool|True|是否验证数据的时间起点| 1274 | |valid_end|bool|True|是否验证数据的时间终点| 1275 | 1276 | 读取U本位合约中BTCUSDT从2023-01-01~ 2023-01-10 的历史K线数据,截取2023-01-05 ~ 2023-01-06 日期范围数据保存到指定文件夹中。 1277 | 1278 | ```python 1279 | from binance_candle import BinanceLite 1280 | 1281 | binanceLite = BinanceLite() 1282 | candle = binanceLite.load_candle_by_date( 1283 | instType='UM', 1284 | symbol='BTCUSDT', 1285 | start='2023-01-01', 1286 | end='2023-01-10', 1287 | ) 1288 | 1289 | binanceLite.save_candle_by_date( 1290 | candle=candle, 1291 | instType='UM', 1292 | symbol='BTCUSDT', 1293 | start='2023-01-05', 1294 | end='2023-01-06', 1295 | base_dir='./target_dir' 1296 | ) 1297 | ``` 1298 | 1299 | 1300 | #### 10.3.2 save_candle_map_by_date 1301 | 1302 | save_candle_by_date 以日期为单位保存产品历史K线数据字典 1303 | 1304 | |参数|类型|默认值|说明| 1305 | |:---|:---|:---|:---| 1306 | |candle_map|dict|无|历史K线数据字典| 1307 | |instType|str|无|产品类别(现货:SPOT、U本位:UM、币本位:CM)| 1308 | |symbols|list|无|保存的产品名称,空列表表示candle_map中的全部产品| 1309 | |start|Union[int,float,str,datetime.date]|无|起始日期| 1310 | |end|Union[int,float,str,datetime.date]|无|终止日期| 1311 | |base_dir|Union[str,None]|None|数据根目录,None使用candlelite中Binance的默认根目录| 1312 | |timezone|Union[str,None]|None|时区,None使用candlelite中Binance的默认时区| 1313 | |bar|Union[str,None]|None|时间粒度,None使用candlelite中Binance的默认时间粒度| 1314 | |replace|bool|False|是否替换本地数据| 1315 | |drop_duplicate|bool|True|是否去重| 1316 | |sort|bool|True|是否按照时间戳排序| 1317 | |valid_interval|bool|True|是否验证数据的时间间隔| 1318 | |valid_start|bool|True|是否验证数据的时间起点| 1319 | |valid_end|bool|True|是否验证数据的时间终点| 1320 | 1321 | 读取U本位合约从2023-01-01 ~ 2023-01-10 的历史K线数据字典,截取2023-01-05 ~ 2023-01-06 日期范围数据保存到指定文件夹中。 1322 | 1323 | ```python 1324 | from binance_candle import BinanceLite 1325 | 1326 | binanceLite = BinanceLite() 1327 | candle_map = binanceLite.load_candle_map_by_date( 1328 | instType='UM', 1329 | symbols=[], 1330 | start='2023-01-01', 1331 | end='2023-01-10', 1332 | ) 1333 | 1334 | binanceLite.save_candle_map_by_date( 1335 | candle_map=candle_map, 1336 | instType='UM', 1337 | symbols=[], 1338 | start='2023-01-05', 1339 | end='2023-01-06', 1340 | base_dir='./target_dir' 1341 | ) 1342 | ``` 1343 | 1344 | --------------------------------------------------------------------------------