├── 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 |
4 |
5 |
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 |
4 |
5 |
6 |
7 |
8 |
9 |
10 |
11 |
12 |
13 |
14 |
15 |
16 |
17 |
18 |
19 |
20 |
21 |
22 |
23 |
24 |
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 | 
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 | 
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 | 
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 | 
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 |
--------------------------------------------------------------------------------