├── .gitignore ├── README.md ├── backtest.py ├── chart ├── chart.py ├── cycle_indicators.py ├── momentum_indicators.py ├── other_indicators.py ├── overlap_studies.py ├── pattern_recognition.py ├── price_transform.py ├── statistic_functions.py ├── volatility_indicators.py └── volume_indicators.py ├── common ├── __init__.py ├── bill.py ├── instance.py ├── kline.py ├── log.py ├── signal.py └── xquant.py ├── db ├── __init__.py └── mongodb.py ├── engine ├── __init__.py ├── backtestengine.py ├── engine.py ├── order.py ├── realengine.py └── signalengine.py ├── exchange ├── __init__.py ├── binance │ ├── __init__.py │ ├── client.py │ ├── depthcache.py │ ├── enums.py │ ├── exceptions.py │ ├── future.py │ ├── margin.py │ └── websockets.py ├── binanceExchange.py ├── binanceFuture.py ├── binanceMargin.py ├── exchange.py ├── kuaiqiBroker.py ├── okex │ ├── Client.py │ ├── HttpMD5Util.py │ ├── OkcoinFutureAPI.py │ ├── OkcoinSpotAPI.py │ └── __init__.py └── okexExchange.py ├── importer ├── binance.py ├── check.py ├── download.py ├── download.sh ├── download2.py ├── download_binance.sh ├── download_kuaiqi.sh ├── fix.py ├── fixKline.py └── importer.py ├── md ├── dbmd.py ├── exmd.py └── md.py ├── monitor ├── daily_report.py ├── daily_report.sh ├── insert_trade.py ├── monitor.py ├── monitor.sh └── template │ └── template.html ├── real.py ├── real2.py ├── scripts └── auto_repay.sh ├── setup.py ├── strategy ├── __init__.py ├── kd │ ├── kd.py │ └── kd_btc_usdt.jsn └── strategy.py ├── tests ├── binance │ ├── bnFuture_test.py │ ├── bnMargin_test.py │ ├── future_test.py │ ├── margin_test.py │ └── spot_test.py └── md_test.py ├── tools ├── account.py ├── auto_repay.py ├── balance.py ├── order.py ├── position.py ├── show.py ├── slippage.py ├── strategy.py └── trade.py ├── utils ├── __init__.py ├── email_obj.py ├── indicator.py ├── indicator_test.py ├── tal.py ├── ti.py ├── ti_test.py └── tools.py └── xlib └── step.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | *.egg-info/ 24 | .installed.cfg 25 | *.egg 26 | MANIFEST 27 | 28 | # PyInstaller 29 | # Usually these files are written by a python script from a template 30 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 31 | *.manifest 32 | *.spec 33 | 34 | # Installer logs 35 | pip-log.txt 36 | pip-delete-this-directory.txt 37 | 38 | # Unit test / coverage reports 39 | htmlcov/ 40 | .tox/ 41 | .coverage 42 | .coverage.* 43 | .cache 44 | nosetests.xml 45 | coverage.xml 46 | *.cover 47 | .hypothesis/ 48 | .pytest_cache/ 49 | 50 | # Translations 51 | *.mo 52 | *.pot 53 | 54 | # Django stuff: 55 | *.log 56 | local_settings.py 57 | db.sqlite3 58 | 59 | # Flask stuff: 60 | instance/ 61 | .webassets-cache 62 | 63 | # Scrapy stuff: 64 | .scrapy 65 | 66 | # Sphinx documentation 67 | docs/_build/ 68 | 69 | # PyBuilder 70 | target/ 71 | 72 | # Jupyter Notebook 73 | .ipynb_checkpoints 74 | 75 | # pyenv 76 | .python-version 77 | 78 | # celery beat schedule file 79 | celerybeat-schedule 80 | 81 | # SageMath parsed files 82 | *.sage.py 83 | 84 | # Environments 85 | .env 86 | .venv 87 | env/ 88 | venv/ 89 | ENV/ 90 | env.bak/ 91 | venv.bak/ 92 | 93 | # Spyder project settings 94 | .spyderproject 95 | .spyproject 96 | 97 | # Rope project settings 98 | .ropeproject 99 | 100 | # mkdocs documentation 101 | /site 102 | 103 | # mypy 104 | .mypy_cache/ 105 | 106 | .vscode 107 | 108 | # xquant 109 | strategies 110 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # xquant 2 | 量化交易 3 | 4 | ## 运行环境 5 | python3.6 6 | mongodb3.6 7 | ## 实盘 8 | python3 real.py -e binance -sc strategy/kd/kd_btc_usdt.jsn -sii testkdj -v 500 --log 9 | ## 回测 10 | python3 backtest.py -m binance -sc strategy/kd/kd_btc_usdt.jsn -r 2020-4-1~2020-4-12 11 | ## 目录说明 12 | ### utils 13 | 工具,不仅限于本项目 14 | ### common 15 | 本项目共用 16 | ### db 17 | 数据库
18 | 目前支持mongodb 19 | ### exchange 20 | 交易所
21 | 目前完全支持binance(现货、保证金杠杆、期货合约,都支持)
22 | 部分支持okex(调试未完成) 23 | ### md 24 | 市场行情数据
25 | 目前支持本地数据库行情数据、交易所实时行情数据 26 | ### engine 27 | 引擎
28 | 目前已经支持实盘、历史回测
29 | 实时仿真待续... 30 | ### strategy 31 | 策略库 32 | 33 | -------------------------------------------------------------------------------- /chart/cycle_indicators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.tools as ts 7 | import utils.indicator as ic 8 | 9 | 10 | def add_argument_cycle_indicators(parser): 11 | 12 | # talib 13 | group = parser.add_argument_group('Cycle Indicators (TaLib)') 14 | group.add_argument('--HT_DCPERIOD' , action="store_true", help='Hilbert Transform - Dominant Cycle Period') 15 | group.add_argument('--HT_DCPHASE' , action="store_true", help='Hilbert Transform - Dominant Cycle Phase') 16 | group.add_argument('--HT_PHASOR' , action="store_true", help='Hilbert Transform - Phasor Components') 17 | group.add_argument('--HT_SINE' , action="store_true", help='Hilbert Transform - SineWave') 18 | group.add_argument('--HT_TRENDMODE', action="store_true", help='Hilbert Transform - Trend vs Cycle Mode') 19 | 20 | 21 | def get_cycle_indicators_count(args): 22 | count = 0 23 | 24 | if args.HT_DCPERIOD: 25 | count += 1 26 | if args.HT_DCPHASE: 27 | count += 1 28 | if args.HT_PHASOR: 29 | count += 1 30 | if args.HT_SINE: 31 | count += 1 32 | if args.HT_TRENDMODE: 33 | count += 1 34 | 35 | return count 36 | 37 | 38 | def handle_cycle_indicators(args, axes, i, klines_df, close_times, display_count): 39 | # talib 40 | if args.HT_DCPERIOD: 41 | name = 'HT_DCPERIOD' 42 | real = talib.HT_DCPERIOD(klines_df["close"]) 43 | i += 1 44 | axes[i].set_ylabel(name) 45 | axes[i].grid(True) 46 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 47 | 48 | if args.HT_DCPHASE: 49 | name = 'HT_DCPHASE' 50 | real = talib.HT_DCPHASE(klines_df["close"]) 51 | i += 1 52 | axes[i].set_ylabel(name) 53 | axes[i].grid(True) 54 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 55 | 56 | if args.HT_PHASOR: 57 | name = 'HT_PHASOR' 58 | real = talib.HT_PHASOR(klines_df["close"]) 59 | i += 1 60 | axes[i].set_ylabel(name) 61 | axes[i].grid(True) 62 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 63 | 64 | if args.HT_SINE: 65 | name = 'HT_SINE' 66 | real = talib.HT_SINE(klines_df["close"]) 67 | i += 1 68 | axes[i].set_ylabel(name) 69 | axes[i].grid(True) 70 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 71 | 72 | if args.HT_TRENDMODE: 73 | name = 'HT_TRENDMODE' 74 | real = talib.HT_TRENDMODE(klines_df["close"]) 75 | i += 1 76 | axes[i].set_ylabel(name) 77 | axes[i].grid(True) 78 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 79 | 80 | 81 | -------------------------------------------------------------------------------- /chart/other_indicators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.indicator as ic 7 | from .overlap_studies import plot_colors 8 | 9 | 10 | def add_argument_other_indicators(parser): 11 | # other Indicators 12 | 13 | group = parser.add_argument_group('Other Indicators') 14 | group.add_argument('--nBIAS', type=int, nargs='*', help='Bias Ratio: close/ma - 1') 15 | group.add_argument('--nmBIAS', type=int, nargs='*', help='Bias Ratio: nBIAS - mBIAS') 16 | group.add_argument('--BIAS', type=int, nargs='*', help='Bias Ratio: ma_short/ma_long - 1') 17 | 18 | 19 | def get_other_indicators_count(args): 20 | count = 0 21 | 22 | if args.nBIAS is not None or args.BIAS is not None: 23 | count += 1 24 | if args.nmBIAS is not None: 25 | count += 1 26 | 27 | return count 28 | 29 | 30 | def handle_other_indicators(args, axes, i, klines_df, close_times, display_count): 31 | if args.nBIAS is not None or args.BIAS is not None: 32 | name = 'BIAS' 33 | i += 1 34 | axes[i].grid(True) 35 | 36 | if args.nBIAS is not None: 37 | if len(args.nBIAS) == 0: 38 | tps = [55] 39 | else: 40 | tps = args.nBIAS 41 | 42 | name = "%s %s "%(name, tps) 43 | axes[i].set_ylabel(name) 44 | for idx, tp in enumerate(tps): 45 | ta_emas = talib.EMA(klines_df["close"], tp) 46 | real = ic.pd_biases(pd.to_numeric(klines_df["close"]), ta_emas) 47 | axes[i].plot(close_times, real[-display_count:], plot_colors[idx], label=tp) 48 | 49 | if args.BIAS is not None: 50 | if len(args.BIAS) == 0: 51 | tps = [13, 55] 52 | else: 53 | tps = args.BIAS 54 | name += " %s" % tps 55 | axes[i].set_ylabel(name) 56 | idx = 0 57 | while idx < len(tps)-1: 58 | emas_s = talib.EMA(klines_df["close"], tps[idx]) 59 | emas_l = talib.EMA(klines_df["close"], tps[idx+1]) 60 | real = ic.pd_biases(emas_s, emas_l) 61 | axes[i].plot(close_times, real[-display_count:], plot_colors[idx//2]+"--", label="%d-%d"%(tps[idx], tps[idx+1])) 62 | idx += 2 63 | 64 | if args.nmBIAS is not None: 65 | name = 'nmBIAS' 66 | i += 1 67 | axes[i].grid(True) 68 | 69 | if len(args.nmBIAS) == 0: 70 | tps = [13, 55] 71 | else: 72 | tps = args.nBIAS 73 | cs = ["r", "y", "b", "m"] 74 | axes[i].set_ylabel("%s%s"%(name, tps)) 75 | 76 | closes = pd.to_numeric(klines_df["close"]) 77 | emas_s = talib.EMA(closes, tps[0]) 78 | emas_l = talib.EMA(closes, tps[1]) 79 | real = ic.pd_biases(closes, emas_s) - ic.pd_biases(closes, emas_l) 80 | axes[i].plot(close_times, real[-display_count:], cs[0], label=tps) 81 | 82 | -------------------------------------------------------------------------------- /chart/overlap_studies.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import talib 3 | 4 | plot_colors = ['y', 'r', 'b', 'm', 'k'] 5 | 6 | def add_argument_overlap_studies(parser): 7 | # Overlap Studies 8 | group = parser.add_argument_group('Overlap Studies') 9 | 10 | group.add_argument('--ABANDS', nargs='*', help='ATR Bands') 11 | group.add_argument('--BANDS', type=float, nargs='?', const=0.1, help=' Bands') 12 | 13 | # talib 14 | group = parser.add_argument_group('Overlap Studies (TaLib)') 15 | group.add_argument('--BBANDS', action="store_true", help='Bollinger Bands') 16 | group.add_argument('--DEMA', type=int, nargs='?', const=30, help='Double Exponential Moving Average') 17 | group.add_argument('--EMA', nargs='*', help='Exponential Moving Average') 18 | group.add_argument('--HT_TRENDLINE', action="store_true", help='Hilbert Transform - Instantaneous Trendline') 19 | group.add_argument('--KAMA', nargs='*', help='Kaufman Adaptive Moving Average') 20 | group.add_argument('--MA', nargs='*', help='Moving average') 21 | group.add_argument('--MAMA', action="store_true", help='MESA Adaptive Moving Average') 22 | group.add_argument('--MIDPOINT', type=int, nargs='?', const=14, help='MidPoint over period') 23 | group.add_argument('--MIDPRICE', type=int, nargs='?', const=14, help='Midpoint Price over period') 24 | group.add_argument('--SAR', action="store_true", help='Parabolic SAR') 25 | group.add_argument('--SAREXT', action="store_true", help='Parabolic SAR - Extended') 26 | group.add_argument('--SMA', type=int, nargs='?', const=30, help='Simple Moving Average') 27 | group.add_argument('--T3', type=int, nargs='?', const=5, help='Triple Exponential Moving Average') 28 | group.add_argument('--TEMA', type=int, nargs='?', const=30, help='Triple Exponential Moving Average') 29 | group.add_argument('--TRIMA', type=int, nargs='?', const=30, help='Triangular Moving Average') 30 | group.add_argument('--WMA', type=int, nargs='?', const=30, help='Weighted Moving Average') 31 | 32 | group.add_argument('--TSF', action="store_true", help='Time Series Forecast') 33 | 34 | def handle_overlap_studies(args, kax, klines_df, close_times, display_count): 35 | all_name = "" 36 | if args.ABANDS: # ATR BANDS 37 | name = 'ABANDS' 38 | real = talib.ATR(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 39 | emas = talib.EMA(klines_df["close"], timeperiod=26) 40 | kax.plot(close_times, emas[-display_count:], "b--", label=name) 41 | 42 | #cs = ['y', 'c', 'm', 'k'] 43 | for idx, n in enumerate(args.ABANDS): 44 | """ 45 | if idx >= len(cs): 46 | break 47 | c = cs[idx] 48 | """ 49 | c = 'y' 50 | cl = c + '--' 51 | n = int(n) 52 | kax.plot(close_times, (emas+n*real)[-display_count:], cl, label=name+' upperband') 53 | kax.plot(close_times, (emas-n*real)[-display_count:], cl, label=name+' lowerband') 54 | 55 | if args.BANDS: # BANDS 56 | name = 'BANDS' 57 | emas = talib.EMA(klines_df["close"], timeperiod=26) 58 | kax.plot(close_times, emas[-display_count:], "b--", label=name) 59 | r= args.BANDS 60 | kax.plot(close_times, (1+r)*emas[-display_count:], 'y--', label=name+' upperband') 61 | kax.plot(close_times, (1-r)*emas[-display_count:], 'y--', label=name+' lowerband') 62 | 63 | # talib 64 | os_key = 'BBANDS' 65 | if args.BBANDS: 66 | upperband, middleband, lowerband = talib.BBANDS(klines_df["close"], timeperiod=5, nbdevup=2, nbdevdn=2, matype=0) 67 | kax.plot(close_times, upperband[-display_count:], "y", label=os_key+' upperband') 68 | kax.plot(close_times, middleband[-display_count:], "b", label=os_key+' middleband') 69 | kax.plot(close_times, lowerband[-display_count:], "y", label=os_key+' lowerband') 70 | 71 | os_key = 'DEMA' 72 | if args.DEMA: 73 | real = talib.DEMA(klines_df["close"], timeperiod=args.DEMA) 74 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 75 | 76 | if args.EMA: 77 | name = 'EMA' 78 | all_name += " %s%s" % (name, args.EMA) 79 | for idx, e_p in enumerate(args.EMA): 80 | if idx >= len(plot_colors): 81 | break 82 | e_p = int(e_p) 83 | emas = talib.EMA(klines_df["close"], timeperiod=e_p) 84 | kax.plot(close_times, emas[-display_count:], plot_colors[idx]+'--', label="%sEMA" % (e_p)) 85 | 86 | if args.MA: 87 | name = 'MA' 88 | all_name += " %s%s" % (name, args.MA) 89 | for idx, e_p in enumerate(args.MA): 90 | if idx >= len(plot_colors): 91 | break 92 | e_p = int(e_p) 93 | emas = talib.MA(klines_df["close"], timeperiod=e_p) 94 | kax.plot(close_times, emas[-display_count:], plot_colors[idx], label="%sMA" % (e_p)) 95 | 96 | os_key = 'KAMA' 97 | if args.KAMA: 98 | all_name += " %s%s" % (os_key, args.KAMA) 99 | for idx, e_p in enumerate(args.KAMA): 100 | if idx >= len(plot_colors): 101 | break 102 | e_p = int(e_p) 103 | real = talib.KAMA(klines_df["close"], timeperiod=e_p) 104 | kax.plot(close_times, real[-display_count:], plot_colors[idx]+'.', label="%s%s" % (e_p, os_key)) 105 | 106 | os_key = 'MAMA' 107 | if args.MAMA: 108 | mama, fama = talib.MAMA(klines_df["close"], fastlimit=0, slowlimit=0) 109 | kax.plot(close_times, mama[-display_count:], "b", label=os_key) 110 | kax.plot(close_times, fama[-display_count:], "c", label=os_key) 111 | 112 | os_key = 'HT_TRENDLINE' 113 | if args.HT_TRENDLINE: 114 | real = talib.HT_TRENDLINE(klines_df["close"]) 115 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 116 | 117 | os_key = 'MIDPOINT' 118 | if args.MIDPOINT: 119 | real = talib.MIDPOINT(klines_df["close"], timeperiod=args.MIDPOINT) 120 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 121 | 122 | os_key = 'MIDPRICE' 123 | if args.MIDPRICE: 124 | real = talib.MIDPRICE(klines_df["high"], klines_df["low"], timeperiod=args.MIDPRICE) 125 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 126 | 127 | os_key = 'SAR' 128 | if args.SAR: 129 | real = talib.SAR(klines_df["high"], klines_df["low"], acceleration=0, maximum=0) 130 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 131 | 132 | os_key = 'SAREXT' 133 | if args.SAREXT: 134 | real = talib.SAREXT(klines_df["high"], klines_df["low"], 135 | startvalue=0, offsetonreverse=0, 136 | accelerationinitlong=0, accelerationlong=0, accelerationmaxlong=0, 137 | accelerationinitshort=0, accelerationshort=0, accelerationmaxshort=0) 138 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 139 | 140 | os_key = 'SMA' 141 | if args.SMA: 142 | real = talib.SMA(klines_df["close"], timeperiod=args.SMA) 143 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 144 | 145 | os_key = 'T3' 146 | if args.T3: 147 | real = talib.T3(klines_df["close"], timeperiod=args.T3, vfactor=0) 148 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 149 | 150 | os_key = 'TEMA' 151 | if args.TEMA: 152 | real = talib.TEMA(klines_df["close"], timeperiod=args.TEMA) 153 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 154 | 155 | os_key = 'TRIMA' 156 | if args.TRIMA: 157 | real = talib.TRIMA(klines_df["close"], timeperiod=args.TRIMA) 158 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 159 | 160 | os_key = 'WMA' 161 | if args.WMA: 162 | real = talib.WMA(klines_df["close"], timeperiod=args.WMA) 163 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 164 | 165 | if args.TSF: 166 | name = 'TSF' 167 | real = talib.TSF(klines_df["close"], timeperiod=14) 168 | kax.plot(close_times, real[-display_count:], "y:", label=name) 169 | 170 | return all_name 171 | 172 | -------------------------------------------------------------------------------- /chart/pattern_recognition.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.tools as ts 7 | import utils.indicator as ic 8 | 9 | 10 | def add_argument_pattern_recognition(parser): 11 | # Pattern Recognition 12 | 13 | # talib 14 | group = parser.add_argument_group('Pattern Recognition (TaLib)') 15 | group.add_argument('--CDL2CROWS' , action="store_true", help='Two Crows') 16 | group.add_argument('--CDL3BLACKCROWS' , action="store_true", help='Three Black Crows') 17 | group.add_argument('--CDL3INSIDE' , action="store_true", help='Three Inside Up/Down') 18 | group.add_argument('--CDL3LINESTRIKE' , action="store_true", help='Three-Line Strike') 19 | group.add_argument('--CDL3OUTSIDE' , action="store_true", help='Three Outside Up/Down') 20 | group.add_argument('--CDL3STARSINSOUTH' , action="store_true", help='Three Stars In The South') 21 | group.add_argument('--CDL3WHITESOLDIERS' , action="store_true", help='Three Advancing White Soldiers') 22 | 23 | 24 | group.add_argument('--CDLABANDONEDBABY' , action="store_true", help='Abandoned Baby') 25 | group.add_argument('--CDLADVANCEBLOCK' , action="store_true", help='Advance Block') 26 | #group.add_argument('--' , action="store_true", help='') 27 | 28 | def get_pattern_recognition_count(args): 29 | count = 0 30 | 31 | if args.CDL2CROWS: 32 | count += 1 33 | if args.CDL3BLACKCROWS: 34 | count += 1 35 | if args.CDL3INSIDE: 36 | count += 1 37 | if args.CDL3LINESTRIKE: 38 | count += 1 39 | if args.CDL3OUTSIDE: 40 | count += 1 41 | if args.CDL3STARSINSOUTH: 42 | count += 1 43 | if args.CDL3WHITESOLDIERS: 44 | count += 1 45 | 46 | if args.CDLABANDONEDBABY: 47 | count += 1 48 | if args.CDLADVANCEBLOCK: 49 | count += 1 50 | #if args.: 51 | # count += 1 52 | return count 53 | 54 | 55 | def handle_pattern_recognition(args, axes, i, klines_df, close_times, display_count): 56 | # talib 57 | if args.CDL2CROWS: 58 | name = 'CDL2CROWS' 59 | integer = talib.CDL2CROWS(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 60 | i += 1 61 | axes[i].set_ylabel(name) 62 | axes[i].grid(True) 63 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 64 | 65 | if args.CDL3BLACKCROWS: 66 | name = 'CDL3BLACKCROWS' 67 | integer = talib.CDL3BLACKCROWS(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 68 | i += 1 69 | axes[i].set_ylabel(name) 70 | axes[i].grid(True) 71 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 72 | 73 | if args.CDL3INSIDE: 74 | name = 'CDL3INSIDE' 75 | integer = talib.CDL3INSIDE(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 76 | i += 1 77 | axes[i].set_ylabel(name) 78 | axes[i].grid(True) 79 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 80 | 81 | if args.CDL3LINESTRIKE: 82 | name = 'CDL3LINESTRIKE' 83 | integer = talib.CDL3LINESTRIKE(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 84 | i += 1 85 | axes[i].set_ylabel(name) 86 | axes[i].grid(True) 87 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 88 | 89 | if args.CDL3OUTSIDE: 90 | name = 'CDL3OUTSIDE' 91 | integer = talib.CDL3OUTSIDE(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 92 | i += 1 93 | axes[i].set_ylabel(name) 94 | axes[i].grid(True) 95 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 96 | 97 | if args.CDL3STARSINSOUTH: 98 | name = 'CDL3STARSINSOUTH' 99 | integer = talib.CDL3STARSINSOUTH(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 100 | i += 1 101 | axes[i].set_ylabel(name) 102 | axes[i].grid(True) 103 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 104 | 105 | if args.CDL3WHITESOLDIERS: 106 | name = 'CDL3WHITESOLDIERS' 107 | integer = talib.CDL3WHITESOLDIERS(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 108 | i += 1 109 | axes[i].set_ylabel(name) 110 | axes[i].grid(True) 111 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 112 | 113 | if args.CDLABANDONEDBABY: 114 | name = 'CDLABANDONEDBABY' 115 | integer = talib.CDLABANDONEDBABY(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 116 | i += 1 117 | axes[i].set_ylabel(name) 118 | axes[i].grid(True) 119 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 120 | 121 | if args.CDLADVANCEBLOCK: 122 | name = 'CDLADVANCEBLOCK' 123 | integer = talib.CDLADVANCEBLOCK(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 124 | i += 1 125 | axes[i].set_ylabel(name) 126 | axes[i].grid(True) 127 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 128 | 129 | ''' 130 | if args.: 131 | name = '' 132 | integer = talib.(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 133 | i += 1 134 | axes[i].set_ylabel(name) 135 | axes[i].grid(True) 136 | axes[i].plot(close_times, integer[-display_count:], "y:", label=name) 137 | ''' 138 | 139 | -------------------------------------------------------------------------------- /chart/price_transform.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import talib 3 | 4 | 5 | def add_argument_price_transform(parser): 6 | # talib 7 | group = parser.add_argument_group('Price Transform (TaLib)') 8 | group.add_argument('--AVGPRICE', action="store_true", help='Average Price') 9 | group.add_argument('--MEDPRICE', action="store_true", help='Median Price') 10 | group.add_argument('--TYPPRICE', action="store_true", help='Typical Price') 11 | group.add_argument('--WCLPRICE', action="store_true", help='Weighted Close Price') 12 | 13 | def handle_price_transform(args, kax, klines_df, close_times, display_count): 14 | 15 | os_key = 'AVGPRICE' 16 | if args.AVGPRICE: 17 | real = talib.AVGPRICE(klines_df["open"], klines_df["high"], klines_df["low"], klines_df["close"]) 18 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 19 | 20 | os_key = 'MEDPRICE' 21 | if args.MEDPRICE: 22 | real = talib.MEDPRICE(klines_df["high"], klines_df["low"]) 23 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 24 | 25 | os_key = 'TYPPRICE' 26 | if args.TYPPRICE: 27 | real = talib.TYPPRICE(klines_df["high"], klines_df["low"], klines_df["close"]) 28 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 29 | 30 | os_key = 'WCLPRICE' 31 | if args.WCLPRICE: 32 | real = talib.WCLPRICE(klines_df["high"], klines_df["low"], klines_df["close"]) 33 | kax.plot(close_times, real[-display_count:], "y", label=os_key) 34 | 35 | -------------------------------------------------------------------------------- /chart/statistic_functions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.tools as ts 7 | import utils.indicator as ic 8 | 9 | 10 | def add_argument_statistic_functions(parser): 11 | # Statistic Functions 12 | 13 | # talib 14 | group = parser.add_argument_group('Volatility Indicators (TaLib)') 15 | group.add_argument('--BETA', action="store_true", help='Beta') 16 | group.add_argument('--CORREL', action="store_true", help='Pearson’s Correlation Coefficient (r)') 17 | group.add_argument('--LINEARREG', action="store_true", help='Linear Regression') 18 | group.add_argument('--LINEARREG_ANGLE', action="store_true", help='Linear Regression Angle') 19 | group.add_argument('--LINEARREG_INTERCEPT', action="store_true", help='Linear Regression Intercept') 20 | group.add_argument('--LINEARREG_SLOPE', action="store_true", help='Linear Regression Slope') 21 | group.add_argument('--STDDEV', action="store_true", help='Standard Deviation') 22 | group.add_argument('--VAR', action="store_true", help='VAR') 23 | 24 | 25 | def get_statistic_functions_count(args): 26 | count = 0 27 | 28 | if args.BETA: 29 | count += 1 30 | if args.CORREL: 31 | count += 1 32 | if args.LINEARREG: 33 | count += 1 34 | if args.LINEARREG_ANGLE: 35 | count += 1 36 | if args.LINEARREG_INTERCEPT: 37 | count += 1 38 | if args.LINEARREG_SLOPE: 39 | count += 1 40 | if args.STDDEV: 41 | count += 1 42 | if args.VAR: 43 | count += 1 44 | 45 | return count 46 | 47 | 48 | def handle_statistic_functions(args, axes, i, klines_df, close_times, display_count): 49 | # talib 50 | if args.BETA: 51 | name = 'BETA' 52 | real = talib.BETA(klines_df["high"], klines_df["low"], timeperiod=5) 53 | i += 1 54 | axes[i].set_ylabel(name) 55 | axes[i].grid(True) 56 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 57 | 58 | if args.CORREL: 59 | name = 'CORREL' 60 | real = talib.CORREL(klines_df["high"], klines_df["low"], timeperiod=30) 61 | i += 1 62 | axes[i].set_ylabel(name) 63 | axes[i].grid(True) 64 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 65 | 66 | if args.LINEARREG: 67 | name = 'LINEARREG' 68 | real = talib.LINEARREG(klines_df["close"], timeperiod=14) 69 | i += 1 70 | axes[i].set_ylabel(name) 71 | axes[i].grid(True) 72 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 73 | 74 | if args.LINEARREG_ANGLE: 75 | name = 'LINEARREG_ANGLE' 76 | real = talib.LINEARREG_ANGLE(klines_df["close"], timeperiod=14) 77 | i += 1 78 | axes[i].set_ylabel(name) 79 | axes[i].grid(True) 80 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 81 | 82 | if args.LINEARREG_INTERCEPT: 83 | name = 'LINEARREG_INTERCEPT' 84 | real = talib.LINEARREG_INTERCEPT(klines_df["close"], timeperiod=14) 85 | i += 1 86 | axes[i].set_ylabel(name) 87 | axes[i].grid(True) 88 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 89 | 90 | if args.LINEARREG_SLOPE: 91 | name = 'LINEARREG_SLOPE' 92 | real = talib.LINEARREG_SLOPE(klines_df["close"], timeperiod=14) 93 | i += 1 94 | axes[i].set_ylabel(name) 95 | axes[i].grid(True) 96 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 97 | 98 | if args.STDDEV: 99 | name = 'STDDEV' 100 | real = talib.STDDEV(klines_df["close"], timeperiod=5, nbdev=1) 101 | i += 1 102 | axes[i].set_ylabel(name) 103 | axes[i].grid(True) 104 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 105 | 106 | if args.VAR: 107 | name = 'VAR' 108 | real = talib.VAR(klines_df["close"], timeperiod=5, nbdev=1) 109 | i += 1 110 | axes[i].set_ylabel(name) 111 | axes[i].grid(True) 112 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 113 | 114 | 115 | 116 | -------------------------------------------------------------------------------- /chart/volatility_indicators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.tools as ts 7 | import utils.indicator as ic 8 | 9 | 10 | def add_argument_volatility_indicators(parser): 11 | # Volume Indicators 12 | 13 | # talib 14 | group = parser.add_argument_group('Volatility Indicators (TaLib)') 15 | group.add_argument('--ATR' , action="store_true", help='Average True Range') 16 | group.add_argument('--NATR' , action="store_true", help='Normalized Average True Range') 17 | group.add_argument('--TRANGE', action="store_true", help='On Balance Volume') 18 | 19 | 20 | def get_volatility_indicators_count(args): 21 | count = 0 22 | 23 | if args.ATR: 24 | count += 1 25 | if args.NATR: 26 | count += 1 27 | if args.TRANGE: 28 | count += 1 29 | 30 | return count 31 | 32 | 33 | def handle_volatility_indicators(args, axes, i, klines_df, close_times, display_count): 34 | # talib 35 | if args.ATR: 36 | name = 'ATR' 37 | real = talib.ATR(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 38 | i += 1 39 | axes[i].set_ylabel(name) 40 | axes[i].grid(True) 41 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 42 | 43 | if args.NATR: 44 | name = 'NATR' 45 | real = talib.NATR(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 46 | i += 1 47 | axes[i].set_ylabel(name) 48 | axes[i].grid(True) 49 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 50 | 51 | if args.TRANGE: 52 | name = 'TRANGE' 53 | real = talib.TRANGE(klines_df["high"], klines_df["low"], klines_df["close"]) 54 | i += 1 55 | axes[i].set_ylabel(name) 56 | axes[i].grid(True) 57 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /chart/volume_indicators.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | 3 | import pandas as pd 4 | import talib 5 | 6 | import utils.tools as ts 7 | import utils.indicator as ic 8 | 9 | 10 | def add_argument_volume_indicators(parser): 11 | # Volume Indicators 12 | 13 | # talib 14 | group = parser.add_argument_group('Volume Indicators (TaLib)') 15 | group.add_argument('--AD' , action="store_true", help='Chaikin A/D Line') 16 | group.add_argument('--ADOSC', action="store_true", help='Chaikin A/D Oscillator') 17 | group.add_argument('--OBV' , action="store_true", help='On Balance Volume') 18 | 19 | 20 | def get_volume_indicators_count(args): 21 | count = 0 22 | 23 | if args.AD: # 24 | count += 1 25 | if args.ADOSC: # 26 | count += 1 27 | if args.OBV: # 28 | count += 1 29 | 30 | return count 31 | 32 | 33 | def handle_volume_indicators(args, axes, i, klines_df, close_times, display_count): 34 | # talib 35 | if args.AD: # AD 36 | name = 'AD' 37 | real = talib.AD(klines_df["high"], klines_df["low"], klines_df["close"], klines_df["volume"]) 38 | i += 1 39 | axes[i].set_ylabel(name) 40 | axes[i].grid(True) 41 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 42 | 43 | if args.ADOSC: # ADOSC 44 | name = 'ADOSC' 45 | real = talib.ADOSC(klines_df["high"], klines_df["low"], klines_df["close"], klines_df["volume"], fastperiod=3, slowperiod=10) 46 | i += 1 47 | axes[i].set_ylabel(name) 48 | axes[i].grid(True) 49 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 50 | 51 | if args.OBV: # OBV 52 | name = 'OBV' 53 | real = talib.OBV(klines_df["close"], klines_df["volume"]) 54 | i += 1 55 | axes[i].set_ylabel(name) 56 | axes[i].grid(True) 57 | axes[i].plot(close_times, real[-display_count:], "y:", label=name) 58 | 59 | 60 | 61 | -------------------------------------------------------------------------------- /common/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/common/__init__.py -------------------------------------------------------------------------------- /common/bill.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """bill""" 3 | 4 | DIRECTION_LONG = "LONG" # 做多 5 | DIRECTION_SHORT = "SHORT" # 做空 6 | 7 | OPEN_POSITION = "OPEN" # 开仓 8 | CLOSE_POSITION = "CLOSE" # 平仓 9 | LOCK_POSITION = "LOCK" # 锁仓 10 | UNLOCK_POSITION = "UNLOCK" # 解锁仓位 11 | 12 | directions = [DIRECTION_LONG, DIRECTION_SHORT] 13 | actions = [OPEN_POSITION, CLOSE_POSITION, LOCK_POSITION, UNLOCK_POSITION] 14 | 15 | def create_bill(direction, action, pst_rate, describe, rmk, can_open_time=None, stop_loss_price=None): 16 | """创建单据""" 17 | return {"direction": direction, "action": action, "pst_rate": pst_rate, "describe": describe, "rmk": rmk, "can_open_time": can_open_time, 18 | "stop_loss_price": stop_loss_price 19 | } 20 | 21 | def open_long_bill(pst_rate, describe, rmk, can_open_time=None, stop_loss_price=None): 22 | """创建买单""" 23 | return create_bill(DIRECTION_LONG, OPEN_POSITION, pst_rate, describe, rmk, can_open_time, stop_loss_price) 24 | 25 | def close_long_bill(pst_rate, describe, rmk, can_open_time=None, stop_loss_price=None): 26 | """创建卖单""" 27 | return create_bill(DIRECTION_LONG, CLOSE_POSITION, pst_rate, describe, rmk, can_open_time, stop_loss_price) 28 | 29 | def open_short_bill(pst_rate, describe, rmk, can_open_time=None, stop_loss_price=None): 30 | """创建买信号""" 31 | return create_bill(DIRECTION_SHORT, OPEN_POSITION, pst_rate, describe, rmk, can_open_time, stop_loss_price) 32 | 33 | def close_short_bill(pst_rate, describe, rmk, can_open_time=None, stop_loss_price=None): 34 | """创建卖信号""" 35 | return create_bill(DIRECTION_SHORT, CLOSE_POSITION, pst_rate, describe, rmk, can_open_time, stop_loss_price) 36 | 37 | def is_open_bill(bill): 38 | return bill["action"] == OPEN_POSITION 39 | 40 | def is_close_bill(bill): 41 | return bill["action"] == CLOSE_POSITION 42 | 43 | def is_long_bill(bill): 44 | return bill["direction"] == DIRECTION_LONG 45 | 46 | def is_short_bill(bill): 47 | return bill["direction"] == DIRECTION_SHORT 48 | 49 | def lock_long_bill(pst_rate, describe, rmk): 50 | return create_bill(DIRECTION_LONG, LOCK_POSITION, pst_rate, describe, rmk, None, None) 51 | 52 | def unlock_long_bill(pst_rate, describe, rmk): 53 | return create_bill(DIRECTION_LONG, UNLOCK_POSITION, pst_rate, describe, rmk, None, None) 54 | 55 | def lock_short_bill(pst_rate, describe, rmk): 56 | return create_bill(DIRECTION_SHORT, LOCK_POSITION, pst_rate, describe, rmk, None, None) 57 | 58 | def unlock_short_bill(pst_rate, describe, rmk): 59 | return create_bill(DIRECTION_SHORT, UNLOCK_POSITION, pst_rate, describe, rmk, None, None) 60 | 61 | def decision_bills(bills): 62 | """决策交易信号""" 63 | if not bills: 64 | return None 65 | 66 | ds_bill = bills[0] 67 | 68 | for bill in bills[1:]: 69 | # 暂时不支持同时做多、做空 70 | if ds_bill["direction"] != bill["direction"]: 71 | return None 72 | 73 | if ds_bill["action"] == bill["action"]: 74 | # 持仓率低的信号优先 75 | if ds_bill["pst_rate"] > bill["pst_rate"]: 76 | ds_bill = bill 77 | elif ds_bill["pst_rate"] == bill["pst_rate"]: 78 | # 合并信号 79 | ds_bill["describe"] += ", " + bill["describe"] 80 | ds_bill["rmk"] += ", " + bill["rmk"] 81 | # 限制开仓时间长的优先 82 | if bill["can_open_time"]: 83 | if (not ds_bill["can_open_time"]) or (ds_bill["can_open_time"] < bill["can_open_time"]): 84 | ds_bill["can_open_time"] = bill["can_open_time"] 85 | else: 86 | # 平仓信号优先于开仓信号 87 | if ds_bill["action"] == OPEN_POSITION: 88 | ds_bill = bill 89 | 90 | return ds_bill 91 | 92 | -------------------------------------------------------------------------------- /common/instance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | from db.mongodb import get_mongodb 3 | import setup 4 | 5 | STRATEGY_INSTANCE_COLLECTION_NAME = 'strategies' 6 | 7 | STRATEGY_INSTANCE_STATUS_START = "start" 8 | STRATEGY_INSTANCE_STATUS_STOP = "stop" 9 | strategy_instance_statuses = [STRATEGY_INSTANCE_STATUS_START, STRATEGY_INSTANCE_STATUS_STOP] 10 | 11 | def get_strategy_instance(sii): 12 | db = get_mongodb(setup.trade_db_name) 13 | db.ensure_index(STRATEGY_INSTANCE_COLLECTION_NAME, [("instance_id",1)]) 14 | 15 | instances = db.find(STRATEGY_INSTANCE_COLLECTION_NAME, {"instance_id": sii}) 16 | #print(instances) 17 | if len(instances) is 0: 18 | print("strategy instance id (%s) not exist!" % (sii)) 19 | exit(1) 20 | elif len(instances) > 1: 21 | exit(1) 22 | return instances[0] 23 | 24 | def add_strategy_instance(record): 25 | db = get_mongodb(setup.trade_db_name) 26 | db.insert_one(STRATEGY_INSTANCE_COLLECTION_NAME, record) 27 | 28 | def update_strategy_instance(query, record): 29 | db = get_mongodb(setup.trade_db_name) 30 | db.update(STRATEGY_INSTANCE_COLLECTION_NAME, query, record) 31 | 32 | def delete_strategy_instance(query): 33 | db = get_mongodb(setup.trade_db_name) 34 | db.delete_one(STRATEGY_INSTANCE_COLLECTION_NAME, query) 35 | -------------------------------------------------------------------------------- /common/kline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | 3 | from datetime import datetime, timedelta, time 4 | 5 | 6 | KLINE_DATA_TYPE_LIST = 0 7 | KLINE_DATA_TYPE_JSON = 1 8 | 9 | KLINE_KEY_OPEN_TIME = "open_time" 10 | KLINE_KEY_CLOSE_TIME = "close_time" 11 | KLINE_KEY_OPEN = "open" 12 | KLINE_KEY_CLOSE = "close" 13 | KLINE_KEY_HIGH = "high" 14 | KLINE_KEY_LOW = "low" 15 | KLINE_KEY_VOLUME = "volume" 16 | 17 | KLINE_INTERVAL_1MINUTE = '1m' 18 | KLINE_INTERVAL_3MINUTE = '3m' 19 | KLINE_INTERVAL_5MINUTE = '5m' 20 | KLINE_INTERVAL_15MINUTE = '15m' 21 | KLINE_INTERVAL_30MINUTE = '30m' 22 | KLINE_INTERVAL_1HOUR = '1h' 23 | KLINE_INTERVAL_2HOUR = '2h' 24 | KLINE_INTERVAL_4HOUR = '4h' 25 | KLINE_INTERVAL_6HOUR = '6h' 26 | KLINE_INTERVAL_8HOUR = '8h' 27 | KLINE_INTERVAL_12HOUR = '12h' 28 | KLINE_INTERVAL_1DAY = '1d' 29 | KLINE_INTERVAL_3DAY = '3d' 30 | KLINE_INTERVAL_1WEEK = '1w' 31 | KLINE_INTERVAL_1MONTH = '1M' 32 | 33 | SECONDS_MINUTE = 60 34 | SECONDS_HOUR = 60 * SECONDS_MINUTE 35 | SECONDS_DAY = 24 * SECONDS_HOUR 36 | 37 | def get_kline_collection(symbol, interval): 38 | return "kline_%s_%s" % (symbol, interval) 39 | 40 | def get_open_time(interval, dt): 41 | if interval == KLINE_INTERVAL_1MINUTE: 42 | return datetime.combine(dt.date(), time(dt.hour, dt.minute, 0)) 43 | elif interval == KLINE_INTERVAL_3MINUTE: 44 | open_minute = (dt.minute // 3) * 3 45 | return datetime.combine(dt.date(), time(dt.hour, open_minute, 0)) 46 | elif interval == KLINE_INTERVAL_5MINUTE: 47 | open_minute = (dt.minute // 5) * 5 48 | return datetime.combine(dt.date(), time(dt.hour, open_minute, 0)) 49 | elif interval == KLINE_INTERVAL_15MINUTE: 50 | open_minute = (dt.minute // 15) * 15 51 | return datetime.combine(dt.date(), time(dt.hour, open_minute, 0)) 52 | elif interval == KLINE_INTERVAL_30MINUTE: 53 | if dt.minute < 30: 54 | open_minute = 0 55 | else: 56 | open_minute = 30 57 | return datetime.combine(dt.date(), time(dt.hour, open_minute, 0)) 58 | 59 | elif interval == KLINE_INTERVAL_1HOUR: 60 | open_hour = dt.hour 61 | return datetime.combine(dt.date(), time(open_hour, 0, 0)) 62 | elif interval == KLINE_INTERVAL_2HOUR: 63 | open_hour = (dt.hour // 2) * 2 64 | return datetime.combine(dt.date(), time(open_hour, 0, 0)) 65 | elif interval == KLINE_INTERVAL_4HOUR: 66 | open_hour = (dt.hour // 4) * 4 67 | return datetime.combine(dt.date(), time(open_hour, 0, 0)) 68 | 69 | elif interval == KLINE_INTERVAL_6HOUR: 70 | if dt.hour < 2: 71 | return datetime.combine(dt.date() - timedelta(days=1), time(20, 0, 0)) 72 | elif dt.hour < 8: 73 | return datetime.combine(dt.date(), time(2, 0, 0)) 74 | elif dt.hour < 14: 75 | return datetime.combine(dt.date(), time(8, 0, 0)) 76 | elif dt.hour < 20: 77 | return datetime.combine(dt.date(), time(14, 0, 0)) 78 | else: 79 | return datetime.combine(dt.date(), time(20, 0, 0)) 80 | 81 | elif interval == KLINE_INTERVAL_8HOUR: 82 | open_hour = (dt.hour // 8) * 8 83 | return datetime.combine(dt.date(), time(open_hour, 0, 0)) 84 | 85 | elif interval == KLINE_INTERVAL_12HOUR: 86 | if dt.hour < 8: 87 | return datetime.combine(dt.date() - timedelta(days=1), time(20, 0, 0)) 88 | elif dt.hour >= 20: 89 | return datetime.combine(dt.date(), time(20, 0, 0)) 90 | else: 91 | return datetime.combine(dt.date(), time(8, 0, 0)) 92 | 93 | elif interval == KLINE_INTERVAL_1DAY: 94 | if dt.hour < 8: 95 | return datetime.combine(dt.date() - timedelta(days=1), time(8, 0, 0)) 96 | else: 97 | return datetime.combine(dt.date(), time(8, 0, 0)) 98 | else: 99 | return None 100 | 101 | def get_timedelta(interval, size): 102 | if interval == KLINE_INTERVAL_1MINUTE: 103 | return timedelta(minutes=size-1) 104 | elif interval == KLINE_INTERVAL_3MINUTE: 105 | return timedelta(minutes=3*size-1) 106 | elif interval == KLINE_INTERVAL_5MINUTE: 107 | return timedelta(minutes=5*size-1) 108 | elif interval == KLINE_INTERVAL_15MINUTE: 109 | return timedelta(minutes=15*size-1) 110 | elif interval == KLINE_INTERVAL_30MINUTE: 111 | return timedelta(minutes=30*size-1) 112 | 113 | elif interval == KLINE_INTERVAL_1HOUR: 114 | return timedelta(hours=1*size-1) 115 | elif interval == KLINE_INTERVAL_2HOUR: 116 | return timedelta(hours=2*size-1) 117 | elif interval == KLINE_INTERVAL_4HOUR: 118 | return timedelta(hours=4*size-1) 119 | elif interval == KLINE_INTERVAL_6HOUR: 120 | return timedelta(hours=6*size-1) 121 | elif interval == KLINE_INTERVAL_8HOUR: 122 | return timedelta(hours=8*size-1) 123 | elif interval == KLINE_INTERVAL_12HOUR: 124 | return timedelta(hours=12*size-1) 125 | 126 | elif interval == KLINE_INTERVAL_1DAY: 127 | return timedelta(days=size-1) 128 | 129 | else: 130 | return None 131 | 132 | def get_interval_timedelta(interval): 133 | if interval == KLINE_INTERVAL_1MINUTE: 134 | return timedelta(minutes=1) 135 | elif interval == KLINE_INTERVAL_3MINUTE: 136 | return timedelta(minutes=3) 137 | elif interval == KLINE_INTERVAL_5MINUTE: 138 | return timedelta(minutes=5) 139 | elif interval == KLINE_INTERVAL_15MINUTE: 140 | return timedelta(minutes=15) 141 | elif interval == KLINE_INTERVAL_30MINUTE: 142 | return timedelta(minutes=30) 143 | 144 | elif interval == KLINE_INTERVAL_1HOUR: 145 | return timedelta(hours=1) 146 | elif interval == KLINE_INTERVAL_2HOUR: 147 | return timedelta(hours=2) 148 | elif interval == KLINE_INTERVAL_4HOUR: 149 | return timedelta(hours=4) 150 | elif interval == KLINE_INTERVAL_6HOUR: 151 | return timedelta(hours=6) 152 | elif interval == KLINE_INTERVAL_8HOUR: 153 | return timedelta(hours=8) 154 | elif interval == KLINE_INTERVAL_12HOUR: 155 | return timedelta(hours=12) 156 | 157 | elif interval == KLINE_INTERVAL_1DAY: 158 | return timedelta(days=1) 159 | 160 | else: 161 | return None 162 | 163 | def get_interval_seconds(interval): 164 | if interval == KLINE_INTERVAL_1MINUTE: 165 | return 1 * SECONDS_MINUTE 166 | elif interval == KLINE_INTERVAL_3MINUTE: 167 | return 3 * SECONDS_MINUTE 168 | elif interval == KLINE_INTERVAL_5MINUTE: 169 | return 5 * SECONDS_MINUTE 170 | elif interval == KLINE_INTERVAL_15MINUTE: 171 | return 15 * SECONDS_MINUTE 172 | elif interval == KLINE_INTERVAL_30MINUTE: 173 | return 30 * SECONDS_MINUTE 174 | 175 | elif interval == KLINE_INTERVAL_1HOUR: 176 | return 1 * SECONDS_HOUR 177 | elif interval == KLINE_INTERVAL_2HOUR: 178 | return 2 * SECONDS_HOUR 179 | elif interval == KLINE_INTERVAL_4HOUR: 180 | return 4 * SECONDS_HOUR 181 | elif interval == KLINE_INTERVAL_6HOUR: 182 | return 6 * SECONDS_HOUR 183 | elif interval == KLINE_INTERVAL_8HOUR: 184 | return 8 * SECONDS_HOUR 185 | elif interval == KLINE_INTERVAL_12HOUR: 186 | return 12 * SECONDS_HOUR 187 | 188 | elif interval == KLINE_INTERVAL_1DAY: 189 | return SECONDS_DAY 190 | 191 | else: 192 | return None 193 | 194 | def get_next_open_time(interval, dt): 195 | return get_open_time(interval, dt) + get_interval_timedelta(interval) 196 | 197 | def get_next_open_timedelta(interval, dt): 198 | return get_next_open_time(interval, dt) - dt 199 | 200 | def get_kline_index(key, kline_column_names): 201 | for index, value in enumerate(kline_column_names): 202 | if value == key: 203 | return index 204 | 205 | def trans_from_json_to_list(kls, kline_column_names): 206 | return [[(kline[column_name] if (column_name in kline) else "0") for column_name in kline_column_names] for kline in kls] 207 | 208 | def trans_from_list_to_json(kls_list, kline_column_names): 209 | kls_json = [] 210 | for kl_list in kls_list: 211 | kl_json = {} 212 | for idx, v in enumerate(kl_list): 213 | kl_json[kline_column_names[idx]] = v 214 | kls_json.append(kl_json) 215 | return kls_json 216 | -------------------------------------------------------------------------------- /common/log.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """xquant log""" 3 | 4 | import os 5 | import logging 6 | import logging.handlers 7 | 8 | logger = logging.getLogger('QuantLogger') 9 | logger.setLevel(logging.DEBUG) 10 | 11 | def init(path, logname, server_ip=None, server_port=None): 12 | global logger 13 | #logpath = os.path.join(os.getcwd(), "log") 14 | logpath = os.path.join("log") 15 | if not os.path.exists(logpath): 16 | os.mkdir(logpath) 17 | 18 | fullpath = os.path.join(logpath, path) 19 | if not os.path.exists(fullpath): 20 | os.mkdir(fullpath) 21 | 22 | filename = os.path.join(fullpath, logname) 23 | logging.basicConfig(level=logging.NOTSET, filename=filename) 24 | 25 | if server_ip != None: 26 | extra = {'tags':logname} 27 | handler = logging.handlers.SysLogHandler(address = (server_ip, int(server_port))) 28 | f = logging.Formatter('%(tags)s: %(message)s') 29 | handler.setFormatter(f) 30 | logger.addHandler(handler) 31 | logger = logging.LoggerAdapter(logger, extra) 32 | 33 | def info(info): 34 | logger.info(info) 35 | 36 | def warning(info): 37 | logger.warning(info) 38 | 39 | def error(info): 40 | logger.error(info) 41 | 42 | def critical(info): 43 | logger.critical(info) 44 | 45 | def debug(info): 46 | logger.debug(info) 47 | -------------------------------------------------------------------------------- /common/signal.py: -------------------------------------------------------------------------------- 1 | """signal""" 2 | 3 | SIGNAL_COLOR_BLACK = "k" 4 | SIGNAL_COLOR_WHITE = "w" 5 | SIGNAL_COLOR_RED = "r" 6 | SIGNAL_COLOR_YELLOW = "y" 7 | SIGNAL_COLOR_BLUE = "b" 8 | SIGNAL_COLOR_GREEN = "g" 9 | SIGNAL_COLOR_CYAN = "c" 10 | SIGNAL_COLOR_MAGENTA = "m" 11 | 12 | SIGNAL_COLOR_GREY = "grey" 13 | SIGNAL_COLOR_SILVER = "silver" 14 | 15 | SIGNAL_COLOR_BROWN = "brown" 16 | SIGNAL_COLOR_TAN = "tan" 17 | 18 | SIGNAL_COLOR_GOLD = "gold" 19 | 20 | SIGNAL_COLOR_ORANGE = "orange" 21 | SIGNAL_COLOR_ORANGERED = "orangered" 22 | 23 | SIGNAL_COLOR_FUCHSIA = "fuchsia" 24 | SIGNAL_COLOR_ORCHID = "orchid" 25 | SIGNAL_COLOR_PURPLE = "purple" 26 | 27 | SIGNAL_COLOR_SALMON = "salmon" 28 | 29 | SIGNAL_COLOR_SIENNA = "sienna" 30 | SIGNAL_COLOR_MAROON = "maroon" 31 | 32 | 33 | def create_signal(name, describe="", color=None, result=None): 34 | """创建信号""" 35 | signal = {"name": name, "describe": describe, "result": result} 36 | if color: 37 | signal["color"] = color 38 | return signal 39 | 40 | 41 | -------------------------------------------------------------------------------- /common/xquant.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """xquant公共定义及公用函数""" 3 | 4 | import utils.tools as ts 5 | from datetime import datetime, timedelta, time 6 | import json 7 | 8 | 9 | time_range_split = "~" 10 | 11 | ORDER_TYPE_LIMIT = "LIMIT" 12 | ORDER_TYPE_MARKET = "MARKET" 13 | 14 | ORDER_STATUS_WAIT = "wait" 15 | ORDER_STATUS_OPEN = "open" 16 | ORDER_STATUS_CLOSE = "close" 17 | ORDER_STATUS_CANCELLING = "cancelling" 18 | ORDER_STATUS_CANCELLED = "cancelled" 19 | 20 | ordertypes = [ORDER_TYPE_LIMIT, ORDER_TYPE_MARKET] 21 | 22 | def creat_symbol(target_coin, base_coin): 23 | """create symbol""" 24 | return "%s_%s" % (target_coin.lower(), base_coin.lower()) 25 | 26 | 27 | def get_symbol_coins(symbol): 28 | """获取coins""" 29 | coins = symbol.split("_") 30 | return tuple(coins) 31 | 32 | 33 | def create_balance(coin, free, frozen): 34 | """ 创建余额 """ 35 | return {"coin": coin, "free": free, "frozen": frozen} 36 | 37 | 38 | def get_balance_coin(balance): 39 | return balance["coin"] 40 | 41 | 42 | def get_balance_free(balance): 43 | """ 获取可用数 """ 44 | return float(balance["free"]) 45 | 46 | 47 | def get_balance_frozen(balance): 48 | """ 获取冻结数 """ 49 | return float(balance["frozen"]) 50 | 51 | 52 | def down_area(ss, ls): 53 | total_len = len(ss) 54 | 55 | ei = -1 56 | i = -2 57 | while i >= -total_len: 58 | v = ss[i]-ls[i] 59 | if v > 0: 60 | return -1, -1, -1 61 | if v < ss[ei]-ls[ei]: 62 | break 63 | ei = i 64 | i -= 1 65 | 66 | mi = i 67 | bi = i 68 | while i >= -total_len: 69 | v = ss[i]-ls[i] 70 | if v > 0: 71 | break 72 | 73 | if ss[mi] - ls[mi] > v: 74 | mi = i 75 | bi = i 76 | i -= 1 77 | 78 | return bi, mi, ei 79 | 80 | def get_strategy_config(config_path): 81 | fo = open(config_path, "r") 82 | config = json.loads(fo.read()) 83 | fo.close() 84 | return config 85 | -------------------------------------------------------------------------------- /db/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/db/__init__.py -------------------------------------------------------------------------------- /db/mongodb.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """mongodb""" 3 | import common.log as log 4 | from pymongo import MongoClient 5 | from pymongo.errors import BulkWriteError 6 | from bson import ObjectId 7 | from setup import mongo_user, mongo_pwd, db_url 8 | 9 | 10 | def get_mongodb(db_name): 11 | return MongoDB(mongo_user, mongo_pwd, db_name, db_url) 12 | 13 | def get_datetime_by_id(_id): 14 | """get datetime from _id""" 15 | return ObjectId(_id).generation_time 16 | 17 | 18 | class MongoDB: 19 | """MongoDB""" 20 | 21 | def __init__(self, user, password, db_name, db_url): 22 | client = MongoClient(db_url) 23 | self.__client = eval("%s.%s" % (client, db_name)) 24 | if user: 25 | self.__client.authenticate(user, password) 26 | 27 | def create_index(self, collection, index): 28 | self.__client[collection].create_index(index, unique=True) 29 | 30 | def ensure_index(self, collection, index, unique=False): 31 | return self.__client[collection].ensure_index(index, unique=unique) 32 | 33 | def insert_one(self, collection, record): 34 | """insert_one""" 35 | try: 36 | log.debug("mongodb %s insert : %s" % (collection, record)) 37 | _id = self.__client[collection].insert_one(record).inserted_id 38 | return _id 39 | except Exception as exc: 40 | print(exc) 41 | return None 42 | 43 | def insert_many(self, collection, records): 44 | """insert_many""" 45 | try: 46 | ret = self.__client[collection].insert_many(records) 47 | return ret 48 | except BulkWriteError as exc: 49 | print(exc.details) 50 | return None 51 | 52 | def update_one(self, collection, _id, record): 53 | """update_one""" 54 | log.debug("mongodb %s(_id=%s) update: %s" % (collection, _id, record)) 55 | self.__client[collection].update_one({"_id": ObjectId(_id)}, {"$set": record}) 56 | 57 | def update(self, collection, query, record): 58 | """update""" 59 | log.debug("mongodb %s(qurey=%s) update: %s" % (collection, query, record)) 60 | self.__client[collection].update_many(query, {"$set": record}) 61 | 62 | def delete_one(self, collection, query): 63 | """delete_one""" 64 | log.debug("mongodb %s(query=%s) delete" % (collection, query)) 65 | self.__client[collection].delete_one(query) 66 | 67 | def find(self, collection, query, projection=None): 68 | """find""" 69 | #print(query) 70 | #print(projection) 71 | if projection: 72 | ret = self.__client[collection].find(query, projection=projection) 73 | else: 74 | ret = self.__client[collection].find(query) 75 | 76 | records = [] 77 | for i in ret: 78 | records.append(i) 79 | return records 80 | 81 | def find_sort(self, collection, query, sort_field, sort_dir, limit=None, projection=None): 82 | """find and sort 83 | sort_dir: 1. ASCENDING, 84 | -1. DESCENDING 85 | """ 86 | #print(query) 87 | #print(projection) 88 | if limit: 89 | ret = self.__client[collection].find(query, projection=projection).sort(sort_field, sort_dir).limit(limit) 90 | else: 91 | ret = self.__client[collection].find(query, projection=projection).sort(sort_field, sort_dir) 92 | """ 93 | if projection: 94 | ret = self.__client[collection].find(query, projection=projection).sort(sort_field, sort_dir) 95 | else: 96 | ret = self.__client[collection].find(query).sort(sort_field, sort_dir) 97 | """ 98 | records = [] 99 | for i in ret: 100 | records.append(i) 101 | return records 102 | 103 | def count(self, collection, query, projection=None): 104 | """count""" 105 | #print(query) 106 | #print(projection) 107 | if projection: 108 | ret = self.__client[collection].find(query, projection=projection).count() 109 | else: 110 | ret = self.__client[collection].find(query).count() 111 | return ret 112 | -------------------------------------------------------------------------------- /engine/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/engine/__init__.py -------------------------------------------------------------------------------- /engine/backtestengine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """回测环境""" 3 | import sys 4 | from datetime import datetime, timedelta, time 5 | import uuid 6 | import utils.tools as ts 7 | import common.xquant as xq 8 | import common.kline as kl 9 | import common.bill as bl 10 | from .engine import Engine 11 | from .order import * 12 | from md.dbmd import DBMD 13 | 14 | 15 | class BackTest(Engine): 16 | """回测引擎""" 17 | 18 | def __init__(self, instance_id, exchange_name, config, log_switch=False, *symbols): 19 | super().__init__(instance_id, config, 10000, log_switch) 20 | 21 | self.md = DBMD(exchange_name, kl.KLINE_DATA_TYPE_JSON) 22 | self.orders = [] 23 | 24 | def now(self): 25 | return self.md.tick_time 26 | 27 | def get_balances(self, *coins): 28 | """ 获取账户余额,回测默认1个亿,哈哈 """ 29 | coin_balances = [] 30 | for coin in coins: 31 | balance = xq.create_balance(coin, "100000000", "0") 32 | coin_balances.append(balance) 33 | 34 | if len(coin_balances) <= 0: 35 | return 36 | elif len(coin_balances) == 1: 37 | return coin_balances[0] 38 | else: 39 | return tuple(coin_balances) 40 | 41 | def get_position(self, symbol, cur_price): 42 | """ 获取持仓信息 """ 43 | if len(self.orders) > 0 and self.orders[-1][ORDER_ACTION_KEY] in [bl.OPEN_POSITION, bl.UNLOCK_POSITION]: 44 | pst_first_order = get_pst_first_order(self.orders) 45 | if "high" not in pst_first_order or pst_first_order["high"] < cur_price: 46 | pst_first_order["high"] = cur_price 47 | pst_first_order["high_time"] = self.now().timestamp() 48 | if "low" not in pst_first_order or pst_first_order["low"] > cur_price: 49 | pst_first_order["low"] = cur_price 50 | pst_first_order["low_time"] = self.now().timestamp() 51 | return self._get_position(symbol, self.orders, cur_price) 52 | 53 | def send_order_limit( 54 | self, direction, action, symbol, pst_rate, cur_price, limit_price, amount, stop_loss_price, rmk 55 | ): 56 | """ 提交委托,回测默认以当前价全部成交 """ 57 | # order_id = uuid.uuid1() 58 | order_id = "" 59 | self.orders.append({ 60 | "create_time": self.now().timestamp(), 61 | "instance_id": self.instance_id, 62 | "symbol": symbol, 63 | "direction": direction, 64 | "action": action, 65 | "pst_rate": pst_rate, 66 | "type": xq.ORDER_TYPE_LIMIT, 67 | "market_price": cur_price, 68 | "price": limit_price, 69 | "amount": amount, 70 | "stop_loss_price": stop_loss_price, 71 | "status": xq.ORDER_STATUS_CLOSE, 72 | "order_id": order_id, 73 | "cancle_amount": 0, 74 | "deal_amount": amount, 75 | "deal_value": amount * cur_price, 76 | "rmk": rmk, 77 | }) 78 | 79 | return order_id 80 | 81 | def cancle_orders(self, symbol): 82 | """ 撤掉本策略的所有挂单委托 """ 83 | pass 84 | 85 | def view(self, symbol, orders): 86 | if len(orders) == 0: 87 | return 88 | 89 | pst_info = self.get_pst_by_orders(orders) 90 | self.view_history(symbol, orders, pst_info) 91 | 92 | 93 | def set_pst_lock_to_close(self, symbol, rmk): 94 | trans_lock_to_close(self.orders[-1], rmk, self.now()) 95 | 96 | -------------------------------------------------------------------------------- /engine/realengine.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """实盘""" 3 | import time 4 | import datetime 5 | import utils.tools as ts 6 | import common.xquant as xq 7 | import common.kline as kl 8 | import common.bill as bl 9 | from .engine import Engine 10 | from .order import * 11 | from exchange.exchange import create_exchange 12 | from md.exmd import ExchangeMD 13 | from db.mongodb import get_mongodb 14 | import setup 15 | from pprint import pprint 16 | 17 | DB_ORDERS_NAME = "orders" 18 | 19 | 20 | class RealEngine(Engine): 21 | """实盘引擎""" 22 | 23 | def __init__(self, instance_id, exchange_name, config, value, log_switch=False): 24 | super().__init__(instance_id, config, value, log_switch) 25 | 26 | self.db_orders_name = DB_ORDERS_NAME 27 | self.td_db = get_mongodb(setup.trade_db_name) 28 | self.td_db.ensure_index(self.db_orders_name, [("instance_id",1),("symbol",1)]) 29 | 30 | self.__exchange = create_exchange(exchange_name) 31 | if not self.__exchange: 32 | print("Wrong exchange name: %s" % exchange_name) 33 | exit(1) 34 | self.__exchange.connect() 35 | 36 | self.md = ExchangeMD(self.__exchange, kl.KLINE_DATA_TYPE_JSON) 37 | 38 | def now(self): 39 | return datetime.datetime.now() 40 | 41 | def get_account(self): 42 | """ 获取账户信息 """ 43 | return self.__exchange.get_account() 44 | 45 | def get_balances(self, *coins): 46 | """ 获取余额 """ 47 | return self.__exchange.get_balances(*coins) 48 | 49 | def get_position(self, symbol, cur_price): 50 | """ 获取持仓信息 """ 51 | self.sync_orders(symbol) 52 | 53 | orders = self.get_orders(symbol) 54 | 55 | if len(orders) > 0 and orders[-1][ORDER_ACTION_KEY] in [bl.OPEN_POSITION, bl.UNLOCK_POSITION]: 56 | pst_first_order = get_pst_first_order(orders) 57 | now_ts = self.now().timestamp() 58 | 59 | if "high" not in pst_first_order or pst_first_order["high"] < cur_price: 60 | pst_first_order["high"] = cur_price 61 | pst_first_order["high_time"] = now_ts 62 | self.td_db.update_one( 63 | self.db_orders_name, 64 | pst_first_order["_id"], 65 | { 66 | "high": cur_price, 67 | "high_time": now_ts, 68 | }, 69 | ) 70 | if "low" not in pst_first_order or pst_first_order["low"] > cur_price: 71 | pst_first_order["low"] = cur_price 72 | pst_first_order["low_time"] = now_ts 73 | self.td_db.update_one( 74 | self.db_orders_name, 75 | pst_first_order["_id"], 76 | { 77 | "low": cur_price, 78 | "low_time": now_ts, 79 | }, 80 | ) 81 | 82 | return self._get_position(symbol, orders, cur_price) 83 | 84 | def set_pst_lock_to_close(self, symbol, rmk): 85 | orders = self.get_orders(symbol) 86 | if len(orders) == 0: 87 | return 88 | lastly_order = orders[-1] 89 | trans_lock_to_close(lastly_order, rmk, self.now()) 90 | self.td_db.update_one(self.db_orders_name, lastly_order["_id"], lastly_order) 91 | return 92 | 93 | 94 | def get_orders(self, symbol): 95 | return self.td_db.find( 96 | DB_ORDERS_NAME, 97 | { 98 | "instance_id": self.instance_id, 99 | "symbol": symbol, 100 | }, 101 | ) 102 | 103 | 104 | def get_open_orders(self, symbol): 105 | """ 是否有open状态的委托 """ 106 | return self.td_db.find( 107 | DB_ORDERS_NAME, 108 | { 109 | "instance_id": self.instance_id, 110 | "symbol": symbol, 111 | "status": xq.ORDER_STATUS_OPEN, 112 | }, 113 | ) 114 | 115 | def sync_orders(self, symbol): 116 | orders = self.get_open_orders(symbol) 117 | if not orders: 118 | return 119 | 120 | df_amount, df_value = self.__exchange.get_deals(symbol) 121 | for order in orders: 122 | self.log_debug("order: %r" % order) 123 | order_id = order["order_id"] 124 | order_amount = order["amount"] 125 | 126 | if order_id not in df_amount.index: 127 | """ 没有成交信息 """ 128 | continue 129 | 130 | deal_amount = df_amount[order_id] 131 | 132 | target_coin, base_coin = xq.get_symbol_coins(symbol) 133 | deal_amount = ts.reserve_float(deal_amount, self.config["digits"][target_coin]) 134 | deal_value = df_value[order_id] 135 | 136 | status = xq.ORDER_STATUS_OPEN 137 | if deal_amount > order_amount: 138 | self.log_error("最新成交数量(%f)大于委托数量(%f) %g" % (deal_amount, order_amount, (deal_amount-order_amount))) 139 | continue 140 | elif deal_amount == order_amount: 141 | status = xq.ORDER_STATUS_CLOSE 142 | else: 143 | if deal_amount < order["deal_amount"]: 144 | self.log_warning("最新成交数量小于委托里记载的旧成交数量") 145 | continue 146 | elif deal_amount == order["deal_amount"]: 147 | self.log_info("成交数量没有更新") 148 | else: 149 | pass 150 | if self.__exchange.order_status_is_close(symbol, order_id): 151 | status = xq.ORDER_STATUS_CLOSE 152 | self.log_debug("deal_amount: %f, deal_value: %g, deal_price: %g" % (deal_amount, deal_value, deal_value/deal_amount)) 153 | self.td_db.update_one( 154 | DB_ORDERS_NAME, 155 | order["_id"], 156 | { 157 | "deal_amount": deal_amount, 158 | "deal_value": deal_value, 159 | "status": status, 160 | }, 161 | ) 162 | return 163 | 164 | def send_order_limit( 165 | self, direction, action, symbol, pst_rate, cur_price, limit_price, amount, stop_loss_price, rmk 166 | ): 167 | """ 提交委托 """ 168 | """ 169 | _id = self._db.insert_one( 170 | DB_ORDERS_NAME, 171 | { 172 | "create_time": self.now().timestamp(), 173 | "instance_id": self.instance_id, 174 | "symbol": symbol, 175 | "side": side, 176 | "pst_rate": pst_rate, 177 | "type": xq.ORDER_TYPE_LIMIT, 178 | "price": limit_price, 179 | "amount": amount, 180 | "status": xq.ORDER_STATUS_WAIT, 181 | "order_id": "", 182 | "cancle_amount": 0, 183 | "deal_amount": 0, 184 | "deal_value": 0, 185 | }, 186 | ) 187 | 188 | order_id = self.__exchange.send_order( 189 | side, xq.ORDER_TYPE_LIMIT, symbol, limit_price, amount, _id 190 | ) 191 | 192 | self._db.update_one( 193 | DB_ORDERS_NAME, _id, {"order_id": order_id, "status": xq.ORDER_STATUS_OPEN} 194 | ) 195 | """ 196 | # 暂时简单处理 197 | order_id = self.__exchange.send_order( 198 | direction, action, xq.ORDER_TYPE_LIMIT, symbol, limit_price, amount 199 | ) 200 | 201 | _id = self.td_db.insert_one( 202 | DB_ORDERS_NAME, 203 | { 204 | "create_time": self.now().timestamp(), 205 | "instance_id": self.instance_id, 206 | "symbol": symbol, 207 | "direction": direction, 208 | "action": action, 209 | "pst_rate": pst_rate, 210 | "type": xq.ORDER_TYPE_LIMIT, 211 | "market_price": cur_price, 212 | "price": limit_price, 213 | "amount": amount, 214 | "stop_loss_price": stop_loss_price, 215 | "status": xq.ORDER_STATUS_OPEN, 216 | "order_id": order_id, 217 | "cancle_amount": 0, 218 | "deal_amount": 0, 219 | "deal_value": 0, 220 | "rmk": rmk, 221 | }, 222 | ) 223 | 224 | return order_id 225 | 226 | 227 | def cancle_orders(self, symbol): 228 | """ 撤掉本策略的所有挂单委托 """ 229 | orders = self.get_open_orders(symbol) 230 | if not orders: 231 | return 232 | 233 | e_order_ids = self.__exchange.get_open_order_ids(symbol) 234 | 235 | for order in orders: 236 | order_id = order["order_id"] 237 | if order_id not in e_order_ids: 238 | continue 239 | self.__exchange.cancel_order(symbol, order_id) 240 | self.td_db.update_one( 241 | DB_ORDERS_NAME, order["_id"], {"status": xq.ORDER_STATUS_CANCELLING} 242 | ) 243 | 244 | 245 | def run(self, strategy, debug): 246 | """ run """ 247 | while True: 248 | tick_start = datetime.datetime.now() 249 | self.log_info( 250 | "%s tick start......................................" % tick_start 251 | ) 252 | 253 | if debug: 254 | strategy.on_tick() 255 | else: 256 | try: 257 | strategy.on_tick() 258 | except Exception as ept: 259 | self.log_critical(ept) 260 | 261 | tick_end = datetime.datetime.now() 262 | self.log_info( 263 | "%s tick end...; tick cost: %s -----------------------\n\n" % ( 264 | tick_end, 265 | tick_end - tick_start 266 | ) 267 | ) 268 | time.sleep(strategy.config["sec"]) 269 | 270 | def get_floating(self, symbol, pst_info): 271 | if pst_info[POSITON_AMOUNT_KEY] == 0: 272 | return 0, 0, 0, None 273 | kls = self.md.get_klines_1day(symbol, 1) 274 | cur_price = float(kls[-1][self.md.get_kline_seat_close()]) 275 | floating_profit, total_profit, floating_profit_rate, total_profit_rate = get_floating_profit(pst_info, self.value, self.config["mode"], cur_price) 276 | floating_commission = pst_info[POSITON_COMMISSION_KEY] 277 | return floating_profit, floating_profit_rate, floating_commission, cur_price 278 | 279 | 280 | def view(self, symbol, orders): 281 | if len(orders) == 0: 282 | return 283 | 284 | pst_info = self.get_pst_by_orders(orders) 285 | self.view_history(symbol, orders, pst_info) 286 | 287 | floating_profit, floating_profit_rate, floating_commission, cur_price = self.get_floating(symbol, pst_info) 288 | print("floating: profit = %.2f(%.2f%%) commission = %.2f cur_price = %s" % (floating_profit, floating_profit_rate*100, floating_commission, cur_price)) 289 | print("\nposition infomation:") 290 | pprint(pst_info) 291 | -------------------------------------------------------------------------------- /engine/signalengine.py: -------------------------------------------------------------------------------- 1 | import common.log as log 2 | 3 | 4 | class SignalEngine: 5 | """引擎""" 6 | 7 | def __init__(self, instance_id, config, log_switch): 8 | self.instance_id = instance_id 9 | self.log_switch = log_switch 10 | 11 | 12 | def log_info(self, info): 13 | if self.log_switch: 14 | log.info(info) 15 | 16 | def log_warning(self, info): 17 | if self.log_switch: 18 | log.warngin(info) 19 | 20 | def log_error(self, info): 21 | if self.log_switch: 22 | log.error(info) 23 | 24 | def log_critical(self, info): 25 | if self.log_switch: 26 | log.critical(info) 27 | 28 | def log_debug(self, info): 29 | if self.log_switch: 30 | log.debug(info) 31 | 32 | 33 | class TestSignal(SignalEngine): 34 | def __init__(self, md, instance_id, config, log_switch=False): 35 | super().__init__(instance_id, config, log_switch) 36 | self.md = md 37 | self.signals = [] 38 | 39 | def handle_signal(self, symbol, signals, price, create_time): 40 | for s in signals: 41 | s["price"] = price 42 | s["create_time"] = create_time 43 | self.signals += signals 44 | 45 | def get_signalsets(self): 46 | signalsets = {} 47 | for s in self.signals: 48 | s_name = s["name"] 49 | if s_name not in signalsets: 50 | signalsets[s_name] = [s] 51 | else: 52 | signalsets[s_name].append(s) 53 | return signalsets 54 | 55 | def handle(self, symbol, strategy, price, create_time, info): 56 | signals, infos = strategy.check_signal_single() 57 | self.log_info(strategy.merge_infos(infos, strategy.aligning_log)) 58 | self.handle_signal(symbol, signals, price, create_time) 59 | return 60 | 61 | -------------------------------------------------------------------------------- /exchange/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/exchange/__init__.py -------------------------------------------------------------------------------- /exchange/binance/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/exchange/binance/__init__.py -------------------------------------------------------------------------------- /exchange/binance/depthcache.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | from operator import itemgetter 5 | 6 | from .websockets import BinanceSocketManager 7 | 8 | 9 | class DepthCache(object): 10 | 11 | def __init__(self, symbol): 12 | """Intialise the DepthCache 13 | 14 | :param symbol: Symbol to create depth cache for 15 | :type symbol: string 16 | 17 | """ 18 | self.symbol = symbol 19 | self._bids = {} 20 | self._asks = {} 21 | 22 | def add_bid(self, bid): 23 | """Add a bid to the cache 24 | 25 | :param bid: 26 | :return: 27 | 28 | """ 29 | self._bids[bid[0]] = float(bid[1]) 30 | if bid[1] == "0.00000000": 31 | del self._bids[bid[0]] 32 | 33 | def add_ask(self, ask): 34 | """Add an ask to the cache 35 | 36 | :param ask: 37 | :return: 38 | 39 | """ 40 | self._asks[ask[0]] = float(ask[1]) 41 | if ask[1] == "0.00000000": 42 | del self._asks[ask[0]] 43 | 44 | def get_bids(self): 45 | """Get the current bids 46 | 47 | :return: list of bids with price and quantity as floats 48 | 49 | .. code-block:: python 50 | 51 | [ 52 | [ 53 | 0.0001946, # Price 54 | 45.0 # Quantity 55 | ], 56 | [ 57 | 0.00019459, 58 | 2384.0 59 | ], 60 | [ 61 | 0.00019158, 62 | 5219.0 63 | ], 64 | [ 65 | 0.00019157, 66 | 1180.0 67 | ], 68 | [ 69 | 0.00019082, 70 | 287.0 71 | ] 72 | ] 73 | 74 | """ 75 | return DepthCache.sort_depth(self._bids, reverse=True) 76 | 77 | def get_asks(self): 78 | """Get the current asks 79 | 80 | :return: list of asks with price and quantity as floats 81 | 82 | .. code-block:: python 83 | 84 | [ 85 | [ 86 | 0.0001955, # Price 87 | 57.0' # Quantity 88 | ], 89 | [ 90 | 0.00019699, 91 | 778.0 92 | ], 93 | [ 94 | 0.000197, 95 | 64.0 96 | ], 97 | [ 98 | 0.00019709, 99 | 1130.0 100 | ], 101 | [ 102 | 0.0001971, 103 | 385.0 104 | ] 105 | ] 106 | 107 | """ 108 | return DepthCache.sort_depth(self._asks, reverse=False) 109 | 110 | @staticmethod 111 | def sort_depth(vals, reverse=False): 112 | """Sort bids or asks by price 113 | """ 114 | lst = [[float(price), quantity] for price, quantity in vals.items()] 115 | lst = sorted(lst, key=itemgetter(0), reverse=reverse) 116 | return lst 117 | 118 | 119 | class DepthCacheManager(object): 120 | 121 | def __init__(self, client, symbol, callback): 122 | """Intialise the DepthCacheManager 123 | 124 | :param client: Binance API client 125 | :type client: binance.Client 126 | :param symbol: Symbol to create depth cache for 127 | :type symbol: string 128 | :param callback: Function to receive depth cache updates 129 | :type callback: function 130 | 131 | """ 132 | self._client = client 133 | self._symbol = symbol 134 | self._callback = callback 135 | self._first_update_id = 0 136 | self._bm = None 137 | self._depth_cache = DepthCache(self._symbol) 138 | 139 | self._init_cache() 140 | self._start_socket() 141 | 142 | def _init_cache(self): 143 | res = self._client.get_order_book(symbol=self._symbol, limit=500) 144 | 145 | self._first_update_id = res['lastUpdateId'] 146 | 147 | for bid in res['bids']: 148 | self._depth_cache.add_bid(bid) 149 | for ask in res['asks']: 150 | self._depth_cache.add_ask(ask) 151 | 152 | def _start_socket(self): 153 | self._bm = BinanceSocketManager(self._client) 154 | 155 | self._bm.start_depth_socket(self._symbol, self._depth_event) 156 | 157 | self._bm.start() 158 | 159 | def _depth_event(self, msg): 160 | """ 161 | 162 | :param msg: 163 | :return: 164 | 165 | """ 166 | # ignore any updates before the initial update id 167 | if msg['u'] <= self._first_update_id: 168 | return 169 | 170 | # add any bid or ask values 171 | for bid in msg['b']: 172 | self._depth_cache.add_bid(bid) 173 | for ask in msg['a']: 174 | self._depth_cache.add_ask(ask) 175 | 176 | # call the callback with the updated depth cache 177 | self._callback(self._depth_cache) 178 | 179 | def get_depth_cache(self): 180 | """Get the current depth cache 181 | 182 | :return: DepthCache object 183 | 184 | """ 185 | return self._depth_cache 186 | 187 | def close(self): 188 | """Close the open socket for this manager 189 | 190 | :return: 191 | """ 192 | self._bm.close() 193 | -------------------------------------------------------------------------------- /exchange/binance/enums.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | SYMBOL_TYPE_SPOT = 'SPOT' 5 | 6 | ORDER_STATUS_NEW = 'NEW' 7 | ORDER_STATUS_PARTIALLY_FILLED = 'PARTIALLY_FILLED' 8 | ORDER_STATUS_FILLED = 'FILLED' 9 | ORDER_STATUS_CANCELED = 'CANCELED' 10 | ORDER_STATUS_PENDING_CANCEL = 'PENDING_CANCEL' 11 | ORDER_STATUS_REJECTED = 'REJECTED' 12 | ORDER_STATUS_EXPIRED = 'EXPIRED' 13 | 14 | KLINE_INTERVAL_1MINUTE = '1m' 15 | KLINE_INTERVAL_3MINUTE = '3m' 16 | KLINE_INTERVAL_5MINUTE = '5m' 17 | KLINE_INTERVAL_15MINUTE = '15m' 18 | KLINE_INTERVAL_30MINUTE = '30m' 19 | KLINE_INTERVAL_1HOUR = '1h' 20 | KLINE_INTERVAL_2HOUR = '2h' 21 | KLINE_INTERVAL_4HOUR = '4h' 22 | KLINE_INTERVAL_6HOUR = '6h' 23 | KLINE_INTERVAL_8HOUR = '8h' 24 | KLINE_INTERVAL_12HOUR = '12h' 25 | KLINE_INTERVAL_1DAY = '1d' 26 | KLINE_INTERVAL_3DAY = '3d' 27 | KLINE_INTERVAL_1WEEK = '1w' 28 | KLINE_INTERVAL_1MONTH = '1M' 29 | 30 | SIDE_BUY = 'BUY' 31 | SIDE_SELL = 'SELL' 32 | 33 | ORDER_TYPE_LIMIT = 'LIMIT' 34 | ORDER_TYPE_MARKET = 'MARKET' 35 | ORDER_TYPE_STOP_LOSS = 'STOP_LOSS' 36 | ORDER_TYPE_STOP_LOSS_LIMIT = 'STOP_LOSS_LIMIT' 37 | ORDER_TYPE_TAKE_PROFIT = 'TAKE_PROFIT' 38 | ORDER_TYPE_TAKE_PROFIT_LIMIT = 'TAKE_PROFIT_LIMIT' 39 | ORDER_TYPE_LIMIT_MAKER = 'LIMIT_MAKER' 40 | 41 | TIME_IN_FORCE_GTC = 'GTC' # Good till cancelled 42 | TIME_IN_FORCE_IOC = 'IOC' # Immediate or cancel 43 | TIME_IN_FORCE_FOK = 'FOK' # Fill or kill 44 | 45 | ORDER_RESP_TYPE_ACK = 'ACK' 46 | ORDER_RESP_TYPE_RESULT = 'RESULT' 47 | ORDER_RESP_TYPE_FULL = 'FULL' 48 | 49 | WEBSOCKET_DEPTH_1 = '1' 50 | WEBSOCKET_DEPTH_5 = '5' 51 | WEBSOCKET_DEPTH_10 = '10' 52 | WEBSOCKET_DEPTH_20 = '20' 53 | -------------------------------------------------------------------------------- /exchange/binance/exceptions.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | # coding=utf-8 3 | 4 | 5 | class BinanceAPIException(Exception): 6 | 7 | LISTENKEY_NOT_EXIST = '-1125' 8 | 9 | def __init__(self, response): 10 | json_res = response.json() 11 | self.status_code = response.status_code 12 | self.response = response 13 | self.code = json_res['code'] 14 | self.message = json_res['msg'] 15 | self.request = getattr(response, 'request', None) 16 | 17 | def __str__(self): # pragma: no cover 18 | return 'APIError(code=%s): %s' % (self.code, self.message) 19 | 20 | 21 | class BinanceRequestException(Exception): 22 | def __init__(self, message): 23 | self.message = message 24 | 25 | def __str__(self): 26 | return 'BinanceRequestException: %s' % self.message 27 | 28 | 29 | class BinanceOrderException(Exception): 30 | 31 | def __init__(self, code, message): 32 | self.code = code 33 | self.message = message 34 | 35 | def __str__(self): 36 | return 'BinanceOrderException(code=%s): %s' % (self.code, self.message) 37 | 38 | 39 | class BinanceOrderMinAmountException(BinanceOrderException): 40 | 41 | def __init__(self, value): 42 | message = "Amount must be a multiple of %s" % value 43 | super(BinanceOrderMinAmountException, self).__init__(-1013, message) 44 | 45 | 46 | class BinanceOrderMinPriceException(BinanceOrderException): 47 | 48 | def __init__(self, value): 49 | message = "Price must be at least %s" % value 50 | super(BinanceOrderMinPriceException, self).__init__(-1013, message) 51 | 52 | 53 | class BinanceOrderMinTotalException(BinanceOrderException): 54 | 55 | def __init__(self, value): 56 | message = "Total must be at least %s" % value 57 | super(BinanceOrderMinTotalException, self).__init__(-1013, message) 58 | 59 | 60 | class BinanceOrderUnknownSymbolException(BinanceOrderException): 61 | 62 | def __init__(self, value): 63 | message = "Unknown symbol %s" % value 64 | super(BinanceOrderUnknownSymbolException, self).__init__(-1013, message) 65 | 66 | 67 | class BinanceOrderInactiveSymbolException(BinanceOrderException): 68 | 69 | def __init__(self, value): 70 | message = "Attempting to trade an inactive symbol %s" % value 71 | super(BinanceOrderInactiveSymbolException, self).__init__(-1013, message) 72 | 73 | 74 | class BinanceWithdrawException(Exception): 75 | def __init__(self, message): 76 | if message == u'参数异常': 77 | message = 'Withdraw to this address through the website first' 78 | self.message = message 79 | 80 | def __str__(self): 81 | return 'BinanceWithdrawException: %s' % self.message 82 | -------------------------------------------------------------------------------- /exchange/exchange.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """交易所""" 3 | 4 | from exchange.binanceExchange import BinanceExchange 5 | from exchange.binanceMargin import BinanceMargin 6 | from exchange.binanceFuture import BinanceFuture 7 | from exchange.okexExchange import OkexExchange 8 | from exchange.kuaiqiBroker import KuaiqiBroker 9 | 10 | 11 | exchangeClasses = [BinanceExchange, BinanceMargin, BinanceFuture, OkexExchange, KuaiqiBroker] 12 | 13 | 14 | def get_exchange_names(): 15 | return [ ec.name for ec in exchangeClasses] 16 | 17 | 18 | def create_exchange(exchange_name): 19 | for ec in exchangeClasses: 20 | if ec.name == exchange_name: 21 | return ec(debug=True) 22 | return None 23 | 24 | -------------------------------------------------------------------------------- /exchange/okex/Client.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # encoding: utf-8 4 | #客户端调用,用于查看API返回结果 5 | 6 | from OkcoinSpotAPI import OKCoinSpot 7 | from OkcoinFutureAPI import OKCoinFuture 8 | import os 9 | 10 | #初始化apikey,secretkey,url 11 | apikey = os.environ.get('OKEX_API_KEY') 12 | secretkey = os.environ.get('OKEX_SECRET_KEY') 13 | okcoinRESTURL = 'www.okex.com' #请求注意:国内账号需要 修改为 www.okcoin.cn 14 | 15 | #现货API 16 | okcoinSpot = OKCoinSpot(okcoinRESTURL,apikey,secretkey) 17 | 18 | #期货API 19 | okcoinFuture = OKCoinFuture(okcoinRESTURL,apikey,secretkey) 20 | 21 | coin = 'dpy_eth' 22 | 23 | # print(len(okcoinSpot.get_kline(coin, '1day', 7))) 24 | # print (u' Spot ticker ') 25 | # print (okcoinSpot.ticker(coin)) 26 | # 27 | # print (u' Spot depth ') 28 | # print (okcoinSpot.depth(coin)) 29 | # 30 | # print (u' Spot trades history') 31 | # print (okcoinSpot.trades(coin)) 32 | 33 | #print (u' Spot user info ') 34 | #print (okcoinSpot.userinfo()) 35 | 36 | #print (u' Spot trade ') 37 | #print (okcoinSpot.trade(coin,'buy','0.1','0.2')) 38 | 39 | #print (u' 现货批量下单 ') 40 | #print (okcoinSpot.batchTrade(coin,'buy','[{price:0.1,amount:0.2},{price:0.1,amount:0.2}]')) 41 | 42 | #print (u' 现货取消订单 ') 43 | #print (okcoinSpot.cancelOrder(coin,'18243073')) 44 | 45 | #print (u' 现货订单信息查询 ') 46 | print (okcoinSpot.orderinfo(coin,'-1')) 47 | 48 | # print (u' 现货批量订单信息查询 ') 49 | # print (okcoinSpot.ordersinfo(coin,'18243800,18243801,18243644','0')) 50 | 51 | print (u' 现货历史订单信息查询 ') 52 | print (okcoinSpot.orderHistory(coin,'1','1','20')) 53 | 54 | #print (u' 期货行情信息') 55 | #print (okcoinFuture.future_ticker(coin,'this_week')) 56 | 57 | #print (u' 期货市场深度信息') 58 | #print (okcoinFuture.future_depth('btc_usd','this_week','6')) 59 | 60 | #print (u'期货交易记录信息') 61 | #print (okcoinFuture.future_trades(coin,'this_week')) 62 | 63 | #print (u'期货指数信息') 64 | #print (okcoinFuture.future_index(coin)) 65 | 66 | #print (u'美元人民币汇率') 67 | #print (okcoinFuture.exchange_rate()) 68 | 69 | #print (u'获取预估交割价') 70 | #print (okcoinFuture.future_estimated_price(coin)) 71 | 72 | #print (u'获取全仓账户信息') 73 | #print (okcoinFuture.future_userinfo()) 74 | 75 | #print (u'获取全仓持仓信息') 76 | #print (okcoinFuture.future_position(coin,'this_week')) 77 | 78 | #print (u'期货下单') 79 | #print (okcoinFuture.future_trade(coin,'this_week','0.1','1','1','0','20')) 80 | 81 | #print (u'期货批量下单') 82 | #print (okcoinFuture.future_batchTrade(coin,'this_week','[{price:0.1,amount:1,type:1,match_price:0},{price:0.1,amount:3,type:1,match_price:0}]','20')) 83 | 84 | #print (u'期货取消订单') 85 | #print (okcoinFuture.future_cancel(coin,'this_week','47231499')) 86 | 87 | #print (u'期货获取订单信息') 88 | #print (okcoinFuture.future_orderinfo(coin,'this_week','47231812','0','1','2')) 89 | 90 | #print (u'期货逐仓账户信息') 91 | #print (okcoinFuture.future_userinfo_4fix()) 92 | 93 | #print (u'期货逐仓持仓信息') 94 | #print (okcoinFuture.future_position_4fix(coin,'this_week',1)) 95 | 96 | 97 | 98 | 99 | -------------------------------------------------------------------------------- /exchange/okex/HttpMD5Util.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | #用于进行http请求,以及MD5加密,生成签名的工具类 4 | 5 | import http.client 6 | import urllib 7 | import json 8 | import hashlib 9 | import time 10 | 11 | def buildMySign(params,secretKey): 12 | sign = '' 13 | for key in sorted(params.keys()): 14 | sign += key + '=' + str(params[key]) +'&' 15 | data = sign+'secret_key='+secretKey 16 | return hashlib.md5(data.encode("utf8")).hexdigest().upper() 17 | 18 | def httpGet(url,resource,params=''): 19 | conn = http.client.HTTPSConnection(url, timeout=10) 20 | conn.request("GET",resource + '?' + params) 21 | response = conn.getresponse() 22 | data = response.read().decode('utf-8') 23 | return json.loads(data) 24 | 25 | def httpPost(url,resource,params): 26 | headers = { 27 | "Content-type" : "application/x-www-form-urlencoded", 28 | } 29 | conn = http.client.HTTPSConnection(url, timeout=10) 30 | temp_params = urllib.parse.urlencode(params) 31 | conn.request("POST", resource, temp_params, headers) 32 | response = conn.getresponse() 33 | data = response.read().decode('utf-8') 34 | params.clear() 35 | conn.close() 36 | return data 37 | 38 | 39 | 40 | 41 | -------------------------------------------------------------------------------- /exchange/okex/OkcoinFutureAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | #用于访问OKCOIN 期货REST API 4 | from HttpMD5Util import buildMySign,httpGet,httpPost 5 | 6 | class OKCoinFuture: 7 | 8 | def __init__(self,url,apikey,secretkey): 9 | self.__url = url 10 | self.__apikey = apikey 11 | self.__secretkey = secretkey 12 | 13 | #OKCOIN期货行情信息 14 | def future_ticker(self,symbol,contractType): 15 | FUTURE_TICKER_RESOURCE = "/api/v1/future_ticker.do" 16 | params = '' 17 | if symbol: 18 | params += '&symbol=' + symbol if params else 'symbol=' +symbol 19 | if contractType: 20 | params += '&contract_type=' + contractType if params else 'contract_type=' +symbol 21 | return httpGet(self.__url,FUTURE_TICKER_RESOURCE,params) 22 | 23 | #OKCoin期货市场深度信息 24 | def future_depth(self,symbol,contractType,size): 25 | FUTURE_DEPTH_RESOURCE = "/api/v1/future_depth.do" 26 | params = '' 27 | if symbol: 28 | params += '&symbol=' + symbol if params else 'symbol=' +symbol 29 | if contractType: 30 | params += '&contract_type=' + contractType if params else 'contract_type=' +symbol 31 | if size: 32 | params += '&size=' + size if params else 'size=' + size 33 | return httpGet(self.__url,FUTURE_DEPTH_RESOURCE,params) 34 | 35 | #OKCoin期货交易记录信息 36 | def future_trades(self,symbol,contractType): 37 | FUTURE_TRADES_RESOURCE = "/api/v1/future_trades.do" 38 | params = '' 39 | if symbol: 40 | params += '&symbol=' + symbol if params else 'symbol=' +symbol 41 | if contractType: 42 | params += '&contract_type=' + contractType if params else 'contract_type=' +symbol 43 | return httpGet(self.__url,FUTURE_TRADES_RESOURCE,params) 44 | 45 | #OKCoin期货指数 46 | def future_index(self,symbol): 47 | FUTURE_INDEX = "/api/v1/future_index.do" 48 | params='' 49 | if symbol: 50 | params = 'symbol=' +symbol 51 | return httpGet(self.__url,FUTURE_INDEX,params) 52 | 53 | #获取美元人民币汇率 54 | def exchange_rate(self): 55 | EXCHANGE_RATE = "/api/v1/exchange_rate.do" 56 | return httpGet(self.__url,EXCHANGE_RATE,'') 57 | 58 | #获取预估交割价 59 | def future_estimated_price(self,symbol): 60 | FUTURE_ESTIMATED_PRICE = "/api/v1/future_estimated_price.do" 61 | params='' 62 | if symbol: 63 | params = 'symbol=' +symbol 64 | return httpGet(self.__url,FUTURE_ESTIMATED_PRICE,params) 65 | 66 | #期货全仓账户信息 67 | def future_userinfo(self): 68 | FUTURE_USERINFO = "/api/v1/future_userinfo.do?" 69 | params ={} 70 | params['api_key'] = self.__apikey 71 | params['sign'] = buildMySign(params,self.__secretkey) 72 | return httpPost(self.__url,FUTURE_USERINFO,params) 73 | 74 | #期货全仓持仓信息 75 | def future_position(self,symbol,contractType): 76 | FUTURE_POSITION = "/api/v1/future_position.do?" 77 | params = { 78 | 'api_key':self.__apikey, 79 | 'symbol':symbol, 80 | 'contract_type':contractType 81 | } 82 | params['sign'] = buildMySign(params,self.__secretkey) 83 | return httpPost(self.__url,FUTURE_POSITION,params) 84 | 85 | #期货下单 86 | def future_trade(self,symbol,contractType,price='',amount='',tradeType='',matchPrice='',leverRate=''): 87 | FUTURE_TRADE = "/api/v1/future_trade.do?" 88 | params = { 89 | 'api_key':self.__apikey, 90 | 'symbol':symbol, 91 | 'contract_type':contractType, 92 | 'amount':amount, 93 | 'type':tradeType, 94 | 'match_price':matchPrice, 95 | 'lever_rate':leverRate 96 | } 97 | if price: 98 | params['price'] = price 99 | params['sign'] = buildMySign(params,self.__secretkey) 100 | return httpPost(self.__url,FUTURE_TRADE,params) 101 | 102 | #期货批量下单 103 | def future_batchTrade(self,symbol,contractType,orders_data,leverRate): 104 | FUTURE_BATCH_TRADE = "/api/v1/future_batch_trade.do?" 105 | params = { 106 | 'api_key':self.__apikey, 107 | 'symbol':symbol, 108 | 'contract_type':contractType, 109 | 'orders_data':orders_data, 110 | 'lever_rate':leverRate 111 | } 112 | params['sign'] = buildMySign(params,self.__secretkey) 113 | return httpPost(self.__url,FUTURE_BATCH_TRADE,params) 114 | 115 | #期货取消订单 116 | def future_cancel(self,symbol,contractType,orderId): 117 | FUTURE_CANCEL = "/api/v1/future_cancel.do?" 118 | params = { 119 | 'api_key':self.__apikey, 120 | 'symbol':symbol, 121 | 'contract_type':contractType, 122 | 'order_id':orderId 123 | } 124 | params['sign'] = buildMySign(params,self.__secretkey) 125 | return httpPost(self.__url,FUTURE_CANCEL,params) 126 | 127 | #期货获取订单信息 128 | def future_orderinfo(self,symbol,contractType,orderId,status,currentPage,pageLength): 129 | FUTURE_ORDERINFO = "/api/v1/future_order_info.do?" 130 | params = { 131 | 'api_key':self.__apikey, 132 | 'symbol':symbol, 133 | 'contract_type':contractType, 134 | 'order_id':orderId, 135 | 'status':status, 136 | 'current_page':currentPage, 137 | 'page_length':pageLength 138 | } 139 | params['sign'] = buildMySign(params,self.__secretkey) 140 | return httpPost(self.__url,FUTURE_ORDERINFO,params) 141 | 142 | #期货逐仓账户信息 143 | def future_userinfo_4fix(self): 144 | FUTURE_INFO_4FIX = "/api/v1/future_userinfo_4fix.do?" 145 | params = {'api_key':self.__apikey} 146 | params['sign'] = buildMySign(params,self.__secretkey) 147 | return httpPost(self.__url,FUTURE_INFO_4FIX,params) 148 | 149 | #期货逐仓持仓信息 150 | def future_position_4fix(self,symbol,contractType,type1): 151 | FUTURE_POSITION_4FIX = "/api/v1/future_position_4fix.do?" 152 | params = { 153 | 'api_key':self.__apikey, 154 | 'symbol':symbol, 155 | 'contract_type':contractType, 156 | 'type':type1 157 | } 158 | params['sign'] = buildMySign(params,self.__secretkey) 159 | return httpPost(self.__url,FUTURE_POSITION_4FIX,params) 160 | 161 | 162 | 163 | 164 | 165 | 166 | 167 | 168 | -------------------------------------------------------------------------------- /exchange/okex/OkcoinSpotAPI.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | # -*- coding: utf-8 -*- 3 | # 用于访问OKCOIN 现货REST API 4 | from .HttpMD5Util import buildMySign, httpGet, httpPost 5 | 6 | 7 | class OKCoinSpot: 8 | 9 | def __init__(self, url, apikey, secretkey): 10 | self.__url = url 11 | self.__apikey = apikey 12 | self.__secretkey = secretkey 13 | 14 | # 获取OKCOIN现货行情信息 15 | def ticker(self, symbol=''): 16 | TICKER_RESOURCE = "/api/v1/ticker.do" 17 | params = '' 18 | if symbol: 19 | params = 'symbol=%(symbol)s' % {'symbol': symbol} 20 | return httpGet(self.__url, TICKER_RESOURCE, params) 21 | 22 | # 获取OKCOIN现货市场深度信息 23 | def depth(self, symbol=''): 24 | DEPTH_RESOURCE = "/api/v1/depth.do" 25 | params = '' 26 | if symbol: 27 | params = 'symbol=%(symbol)s' % {'symbol': symbol} 28 | return httpGet(self.__url, DEPTH_RESOURCE, params) 29 | 30 | # 获取OKCOIN现货历史交易信息 31 | 32 | def trades(self, symbol=''): 33 | TRADES_RESOURCE = "/api/v1/trades.do" 34 | params = '' 35 | if symbol: 36 | params = 'symbol=%(symbol)s' % {'symbol': symbol} 37 | return httpGet(self.__url, TRADES_RESOURCE, params) 38 | 39 | # 获取用户现货账户信息 40 | def userinfo(self): 41 | USERINFO_RESOURCE = "/api/v1/userinfo.do" 42 | params = {} 43 | params['api_key'] = self.__apikey 44 | params['sign'] = buildMySign(params, self.__secretkey) 45 | return httpPost(self.__url, USERINFO_RESOURCE, params) 46 | 47 | # 现货交易 48 | def trade(self, symbol, tradeType, price='', amount=''): 49 | TRADE_RESOURCE = "/api/v1/trade.do" 50 | params = { 51 | 'api_key': self.__apikey, 52 | 'symbol': symbol, 53 | 'type': tradeType 54 | } 55 | if price: 56 | params['price'] = price 57 | if amount: 58 | params['amount'] = amount 59 | 60 | params['sign'] = buildMySign(params, self.__secretkey) 61 | return httpPost(self.__url, TRADE_RESOURCE, params) 62 | 63 | # 现货批量下单 64 | def batchTrade(self, symbol, tradeType, orders_data): 65 | BATCH_TRADE_RESOURCE = "/api/v1/batch_trade.do" 66 | params = { 67 | 'api_key': self.__apikey, 68 | 'symbol': symbol, 69 | 'type': tradeType, 70 | 'orders_data': orders_data 71 | } 72 | params['sign'] = buildMySign(params, self.__secretkey) 73 | return httpPost(self.__url, BATCH_TRADE_RESOURCE, params) 74 | 75 | # 现货取消订单 76 | def cancelOrder(self, symbol, orderId): 77 | CANCEL_ORDER_RESOURCE = "/api/v1/cancel_order.do" 78 | params = { 79 | 'api_key': self.__apikey, 80 | 'symbol': symbol, 81 | 'order_id': orderId 82 | } 83 | params['sign'] = buildMySign(params, self.__secretkey) 84 | return httpPost(self.__url, CANCEL_ORDER_RESOURCE, params) 85 | 86 | # 现货订单信息查询 87 | def orderinfo(self, symbol, orderId): 88 | ORDER_INFO_RESOURCE = "/api/v1/order_info.do" 89 | params = { 90 | 'api_key': self.__apikey, 91 | 'symbol': symbol, 92 | 'order_id': orderId 93 | } 94 | params['sign'] = buildMySign(params, self.__secretkey) 95 | return httpPost(self.__url, ORDER_INFO_RESOURCE, params) 96 | 97 | # 现货批量订单信息查询 98 | def ordersinfo(self, symbol, orderId, tradeType): 99 | ORDERS_INFO_RESOURCE = "/api/v1/orders_info.do" 100 | params = { 101 | 'api_key': self.__apikey, 102 | 'symbol': symbol, 103 | 'order_id': orderId, 104 | 'type': tradeType 105 | } 106 | params['sign'] = buildMySign(params, self.__secretkey) 107 | return httpPost(self.__url, ORDERS_INFO_RESOURCE, params) 108 | 109 | # 现货获得历史订单信息 110 | def orderHistory(self, symbol, status, currentPage, pageLength): 111 | ORDER_HISTORY_RESOURCE = "/api/v1/order_history.do" 112 | params = { 113 | 'api_key': self.__apikey, 114 | 'symbol': symbol, 115 | 'status': status, 116 | 'current_page': currentPage, 117 | 'page_length': pageLength 118 | } 119 | params['sign'] = buildMySign(params, self.__secretkey) 120 | return httpPost(self.__url, ORDER_HISTORY_RESOURCE, params) 121 | 122 | def get_kline(self, symbol, line_type, size=0, since=''): 123 | """ 124 | :param symbol: 125 | :param line_type: 126 | :param size: 127 | :param since: 128 | :return: 129 | list[0]: UTC 130 | list[1]: start price 131 | list[2]: top price 132 | list[3]: bottom price 133 | list[4]: close price 134 | list[5]: volume 135 | """ 136 | resource = "/api/v1/kline.do" 137 | if not symbol or not line_type: 138 | print('Error: symbol and line_type should be input') 139 | return [] 140 | params = 'symbol=%s' % symbol 141 | params += '&type=%s' % line_type 142 | if size: 143 | params += '&size=%s' % size 144 | if since: 145 | params += '&since=%s' % since 146 | print(params) 147 | return httpGet(self.__url, resource, params) 148 | -------------------------------------------------------------------------------- /exchange/okex/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/exchange/okex/__init__.py -------------------------------------------------------------------------------- /exchange/okexExchange.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | import common.xquant as xq 4 | from .okex.OkcoinSpotAPI import OKCoinSpot 5 | 6 | api_key = os.environ.get('OKEX_API_KEY') 7 | secret_key = os.environ.get('OKEX_SECRET_KEY') 8 | rest_url = 'www.okex.com' 9 | 10 | 11 | class OkexExchange: 12 | """docstring for OkexExchange""" 13 | name = "okex" 14 | 15 | def __init__(self, debug=False): 16 | self.client = OKCoinSpot(rest_url, api_key, secret_key) 17 | 18 | def __get_coinkey(self, coin): 19 | return coin.lower() 20 | 21 | def __trans_symbol(self, symbol): 22 | target_coin, base_coin = xq.get_symbol_coins(symbol) 23 | return '%s_%s' % (self.__get_coinkey(target_coin), self.__get_coinkey(base_coin)) 24 | 25 | def _trans_side(self, side): 26 | if side == xq.SIDE_BUY: 27 | return 'buy' 28 | elif side == xq.SIDE_SELL: 29 | return 'sell' 30 | else: 31 | return None 32 | 33 | @staticmethod 34 | def get_kline_column_names(): 35 | return ['open_time', 'open','high','low','close','volume','close_time'] 36 | 37 | def __get_klines(self, symbol, interval, size, since): 38 | exchange_symbol = __trans_symbol(symbol) 39 | klines = self.client.get_kline(symbol=exchange_symbol, interval=interval, size=size) 40 | return klines 41 | 42 | def get_klines_1day(self, symbol, size=300, since=''): 43 | return self.__get_klines(symbol, '1day', size, since) 44 | 45 | 46 | def get_balances(self, *coins): 47 | coin_balances = [] 48 | account = self.client.get_account() 49 | free = account['free'] 50 | frozen = account['frozen'] 51 | 52 | for coin in coins: 53 | coinKey = self.__get_coinkey(coin) 54 | balance = xq.create_balance(coin, free[coinKey], frozen[coinKey]) 55 | coin_balances.append(balance) 56 | 57 | if len(coin_balances) <= 0: 58 | return 59 | elif len(coin_balances) == 1: 60 | return coin_balances[0] 61 | else: 62 | return tuple(coin_balances) 63 | 64 | def send_order(self, side, type, symbol, price, amount, client_order_id=''): 65 | exchange_symbol = __trans_symbol(symbol) 66 | self.debug('send order: pair(%s), side(%s), price(%s), amount(%s)' % (exchange_symbol, side, price, amount)) 67 | 68 | okex_side = _trans_side(side) 69 | if okex_side is None: 70 | return 71 | 72 | if type != xq.ORDER_TYPE_LIMIT: 73 | return 74 | 75 | ret = json.loads(self.client.trade(exchange_symbol, okex_side, price=str(price), amount=str(amount))) 76 | # self.debug(ret) 77 | try: 78 | if ret['result']: 79 | # self.debug('Return buy order ID: %s' % ret['order_id']) 80 | return ret['order_id'] 81 | else: 82 | self.debug('Place order failed') 83 | return None 84 | except Exception: 85 | self.debug('Error result: %s' % ret) 86 | return None 87 | 88 | def cancel_order(self, symbol, order_id): 89 | exchange_symbol = self.__trans_symbol(symbol) 90 | self.client.cancel_order(symbol=exchange_symbol, orderId=order_id) 91 | -------------------------------------------------------------------------------- /importer/binance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import time 5 | from datetime import datetime, timedelta 6 | import db.mongodb as md 7 | import common.xquant as xq 8 | import common.kline as kl 9 | import exchange as ex 10 | from setup import * 11 | import pandas as pd 12 | from importer import add_common_arguments, split_time_range 13 | 14 | if __name__ == "__main__": 15 | parser = add_common_arguments('Binance Importer') 16 | args = parser.parse_args() 17 | # print(args) 18 | if not (args.s and args.k and args.m): 19 | parser.print_help() 20 | exit(1) 21 | 22 | symbol = args.s 23 | interval = timedelta(seconds=kl.get_interval_seconds(args.k)) 24 | 25 | collection = kl.get_kline_collection(symbol, args.k) 26 | #print("collection: ", collection) 27 | 28 | db = md.MongoDB(mongo_user, mongo_pwd, args.m, db_url) 29 | db.ensure_index(collection, [("open_time",1)], unique=True) 30 | 31 | exchange = ex.create_exchange(args.m) 32 | if not exchange: 33 | print("market data source error!") 34 | exit(1) 35 | exchange.connect() 36 | 37 | # 注意,下面代码有隐患,在上午8点前取1d、12h时,最后的一个是不完整的,后续再整改 38 | if args.r: 39 | start_time, end_time = split_time_range(args.r) 40 | else: 41 | # 续接db中最后一条记录,至今天之前 42 | klines = db.find_sort(collection, {}, 'open_time', -1, 1) 43 | if len(klines) > 0: 44 | start_time = (exchange.get_time_from_data_ts(klines[0]["open_time"]) + interval).replace(hour=0,minute=0,second=0,microsecond=0) 45 | else: 46 | start_time = None 47 | end_time = datetime.now().replace(hour=0,minute=0,second=0,microsecond=0) 48 | 49 | if not start_time: 50 | start_time = exchange.start_time 51 | 52 | size = 1000 53 | tmp_time = start_time 54 | 55 | while tmp_time < end_time: 56 | print(tmp_time, end=" ") 57 | if (tmp_time + size * interval) > end_time: 58 | batch = int((end_time - tmp_time)/interval) 59 | else: 60 | batch = size 61 | # print(batch) 62 | 63 | klines = exchange.get_klines(symbol, args.k, size=batch, since=exchange.get_data_ts_from_time(tmp_time)) 64 | 65 | klines_df = pd.DataFrame(klines, columns=exchange.get_kline_column_names()) 66 | 67 | klen = len(klines) 68 | print("klines len: ", klen) 69 | #print(klines) 70 | #print("klines[0]: ", klines.ix[0]) 71 | #print("klines[-1]: ", klines.ix[klen-1]) 72 | #print("records: ", klines.to_dict('records')) 73 | if not db.insert_many(collection, klines_df.to_dict('records')): 74 | for item in klines_df.to_dict('records'): 75 | db.insert_one(collection, item) 76 | tmp_time += batch * interval 77 | 78 | # df = db.get_kline(no_id=False) 79 | # pd.set_option('display.max_columns', None) 80 | # pd.set_option('display.max_rows', None) 81 | # pd.set_option('display.max_colwidth', -1) 82 | # print(df) 83 | -------------------------------------------------------------------------------- /importer/check.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | from datetime import datetime, timedelta 5 | import db.mongodb as md 6 | from setup import * 7 | import common.xquant as xq 8 | import common.kline as kl 9 | from importer import add_common_arguments, split_time_range 10 | 11 | if __name__ == "__main__": 12 | parser = add_common_arguments('check kline') 13 | parser.add_argument('-d', '--display', action = 'store_true', help='display info') 14 | args = parser.parse_args() 15 | # print(args) 16 | 17 | if not (args.s and args.r and args.k and args.m): 18 | parser.print_help() 19 | exit(1) 20 | 21 | start_time, end_time = split_time_range(args.r) 22 | print("range: [%s, %s)"%(start_time, end_time)) 23 | 24 | interval = args.k 25 | collection = kl.get_kline_collection(args.s, interval) 26 | td = kl.get_interval_timedelta(interval) 27 | period = kl.get_interval_seconds(interval) 28 | tick_time = kl.get_open_time(interval, start_time) 29 | if tick_time < start_time: 30 | tick_time = kl.get_open_time(interval, start_time+td) 31 | 32 | 33 | db = md.MongoDB(mongo_user, mongo_pwd, args.m, db_url) 34 | target_len = int((int(end_time.timestamp()) - int(start_time.timestamp())) / period) 35 | print("Target length:", target_len) 36 | 37 | klines = db.find_sort(collection, {"open_time": { 38 | "$gte": int(start_time.timestamp())*1000, 39 | "$lt": int(end_time.timestamp())*1000}}, 'open_time', 1) 40 | klines_len = len(klines) 41 | print("klines len: %d"% klines_len) 42 | 43 | i = 0 44 | repeat_count = 0 45 | wrong_count = 0 46 | miss_count = 0 47 | while tick_time < end_time: 48 | if i >= klines_len: 49 | miss_count += (end_time - tick_time).total_seconds()/td.total_seconds() 50 | print("miss tail %s~%s" % (tick_time, end_time) ) 51 | break 52 | 53 | next_tick_time = tick_time + td 54 | open_time = klines[i]["open_time"]/1000 55 | #print("tick_time %s, next_tick_time %s" % (tick_time, next_tick_time) ) 56 | #print("tick_time %s, open_time %s" % (tick_time.timestamp(), open_time) ) 57 | 58 | if open_time < tick_time.timestamp(): 59 | i += 1 60 | repeat_count += 1 61 | print("repeat %s" % (datetime.fromtimestamp(open_time))) 62 | elif open_time == tick_time.timestamp(): 63 | kline = klines[i] 64 | if args.display: 65 | open_time = datetime.fromtimestamp(int(kline["open_time"])/1000) 66 | close_time = datetime.fromtimestamp(int(kline["close_time"])/1000) 67 | print("(%s ~ %s) high: %s low: %s close: %s volume: %s" % 68 | (open_time, close_time, kline["high"], kline["low"], kline["close"], kline["volume"])) 69 | i += 1 70 | tick_time = next_tick_time 71 | elif open_time < next_tick_time.timestamp(): 72 | i += 1 73 | tick_time = next_tick_time 74 | wrong_count += 1 75 | print("tick_time %s, wrong 2 %s" % (tick_time, datetime.fromtimestamp(open_time))) 76 | else: 77 | miss_count += 1 78 | print("miss %s" % (tick_time) ) 79 | tick_time = next_tick_time 80 | 81 | print("repeat count: ", repeat_count) 82 | print("wrong count: ", wrong_count) 83 | print("miss count: ", miss_count) 84 | -------------------------------------------------------------------------------- /importer/download.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import time 5 | from datetime import datetime, timedelta 6 | import common.xquant as xq 7 | import common.kline as kl 8 | from exchange.exchange import create_exchange 9 | from db.mongodb import get_mongodb 10 | from setup import * 11 | import pandas as pd 12 | from importer import add_common_arguments, split_time_range 13 | 14 | if __name__ == "__main__": 15 | parser = add_common_arguments('Binance Importer') 16 | args = parser.parse_args() 17 | # print(args) 18 | if not (args.s and args.k and args.m): 19 | parser.print_help() 20 | exit(1) 21 | 22 | symbol = args.s 23 | interval = timedelta(seconds=kl.get_interval_seconds(args.k)) 24 | 25 | collection = kl.get_kline_collection(symbol, args.k) 26 | #print("collection: ", collection) 27 | 28 | db = get_mongodb(args.m) 29 | db.ensure_index(collection, [("open_time",1)], unique=True) 30 | 31 | exchange = create_exchange(args.m) 32 | if not exchange: 33 | print("market data source error!") 34 | exit(1) 35 | 36 | if args.r: 37 | start_time, end_time = split_time_range(args.r) 38 | else: 39 | # 续接db中最后一条记录,至今天之前 40 | klines = db.find_sort(collection, {}, 'open_time', -1, 1) 41 | if len(klines) > 0: 42 | start_time = (exchange.get_time_from_data_ts(klines[0]["open_time"]) + interval) 43 | else: 44 | start_time = None 45 | end_time = datetime.now() 46 | 47 | print("%s connecting..." % (args.m), end='') 48 | exchange.connect() 49 | print('ok!') 50 | 51 | open_hour = exchange.start_time.hour 52 | 53 | if start_time.hour != open_hour: 54 | print("open time(%s) hour error! %s open time hour: %s" % (start_time, args.m, open_hour)) 55 | exit(1) 56 | 57 | if end_time.hour < open_hour: 58 | end_time -= timedelta(days=1) 59 | end_time = end_time.replace(hour=open_hour, minute=0, second=0, microsecond=0) 60 | print("time range: %s ~ %s " % (start_time, end_time)) 61 | 62 | size = exchange.max_count_of_single_download_kl 63 | tmp_time = start_time 64 | while tmp_time < end_time: 65 | print(tmp_time, end=" ") 66 | 67 | size_interval = size * interval 68 | if (tmp_time + size_interval) > end_time: 69 | batch = int((end_time - tmp_time)/interval) 70 | else: 71 | batch = size 72 | # print(batch) 73 | 74 | klines = exchange.get_klines(symbol, args.k, size=batch, since=exchange.get_data_ts_from_time(tmp_time)) 75 | klines_df = pd.DataFrame(klines, columns=exchange.kline_column_names) 76 | klen = len(klines) 77 | print("klines len: ", klen) 78 | for i in range(klen-1, -1, -1): 79 | last_open_time = exchange.get_time_from_data_ts(klines_df["open_time"].values[i]) 80 | if last_open_time + interval <= end_time: 81 | break 82 | klines_df = klines_df.drop([i]) 83 | 84 | if not db.insert_many(collection, klines_df.to_dict('records')): 85 | for item in klines_df.to_dict('records'): 86 | db.insert_one(collection, item) 87 | 88 | last_time = exchange.get_time_from_data_ts(klines_df["open_time"].values[-1]) + interval 89 | if last_time > tmp_time + batch * interval: 90 | batch = int((last_time - tmp_time)/interval) 91 | tmp_time += batch * interval 92 | -------------------------------------------------------------------------------- /importer/download.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #source ~/.profile 4 | range=$1 5 | echo $range 6 | 7 | mds=(binance) 8 | echo ${mds[*]} 9 | 10 | symbles=(btc_usdt bnb_usdt eth_usdt btc_pax btc_usdc) 11 | echo ${symbles[*]} 12 | 13 | kline_types=(1m 5m 15m 30m 1h 2h 4h 6h 8h 12h 1d) 14 | echo ${kline_types[*]} 15 | 16 | for md in ${mds[*]} 17 | do 18 | for symble in ${symbles[*]} 19 | do 20 | #echo $symble 21 | 22 | for kline_type in ${kline_types[*]} 23 | do 24 | #echo $kline_type 25 | echo "downloading ${range} ${md} ${symble} ${kline_type}" 26 | if [ $range ] 27 | then 28 | python3 download.py -m $md -s $symble -k $kline_type -r $range 29 | else 30 | python3 download.py -m $md -s $symble -k $kline_type 31 | fi 32 | done 33 | done 34 | done -------------------------------------------------------------------------------- /importer/download2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import time 6 | from datetime import datetime, timedelta 7 | import db.mongodb as md 8 | import common.xquant as xq 9 | import common.kline as kl 10 | from exchange.exchange import create_exchange 11 | from db.mongodb import get_mongodb 12 | from setup import * 13 | import pandas as pd 14 | from importer import add_common_arguments, split_time_range 15 | 16 | 17 | def download_from_exchange(exchange, db, symbol, kline_type, time_range): 18 | print('%12s %6s ' % (' ', kline_type), end = '' ) 19 | collection = kl.get_kline_collection(symbol, kline_type) 20 | open_time_key = exchange.kline_key_open_time 21 | db.ensure_index(collection, [(open_time_key,1)], unique=True) 22 | 23 | interval = kl.get_interval_timedelta(kline_type) 24 | if time_range: 25 | start_time, end_time = split_time_range(time_range) 26 | else: 27 | # 续接db中最后一条记录,至今天之前 28 | klines = db.find_sort(collection, {}, open_time_key, -1, 1) 29 | if len(klines) > 0: 30 | start_time = (exchange.get_time_from_data_ts(klines[0][open_time_key]) + interval) 31 | else: 32 | start_time = exchange.start_time 33 | end_time = datetime.now() 34 | 35 | #print(kl.get_open_time(kline_type, end_time)) 36 | """ 37 | if start_time.hour != exchange.start_time.hour: 38 | print("open time(%s) hour error! %s open time hour: %s" % (start_time, exchange.name, exchange.start_time.hour)) 39 | exit(1) 40 | 41 | if end_time.hour < exchange.start_time.hour: 42 | end_time -= timedelta(days=1) 43 | end_time = end_time.replace(hour=exchange.start_time.hour, minute=0, second=0, microsecond=0) 44 | """ 45 | 46 | end_time = end_time.replace(minute=0, second=0, microsecond=0) 47 | end_time = kl.get_open_time(kline_type, end_time) 48 | print("time range: %s ~ %s " % (start_time, end_time)) 49 | 50 | size = exchange.max_count_of_single_download_kl 51 | tmp_time = start_time 52 | while tmp_time < end_time: 53 | size_interval = size * interval 54 | if (tmp_time + size_interval) > end_time: 55 | batch = int((end_time - tmp_time)/interval) 56 | else: 57 | batch = size 58 | # print(batch) 59 | 60 | if batch == 0: 61 | break 62 | 63 | klines = exchange.get_klines(symbol, kline_type, size=batch, since=exchange.get_data_ts_from_time(tmp_time)) 64 | klines_df = pd.DataFrame(klines, columns=exchange.kline_column_names) 65 | klen = len(klines) 66 | print(" %20s start time: %s %s" % (' ', tmp_time, klen)) 67 | for i in range(klen-1, -1, -1): 68 | last_open_time = exchange.get_time_from_data_ts(klines_df[open_time_key].values[i]) 69 | if last_open_time + interval <= end_time: 70 | break 71 | klines_df = klines_df.drop([i]) 72 | 73 | db_datalines = klines_df.to_dict('records') 74 | if len(db_datalines) == 0: 75 | break 76 | if not db.insert_many(collection, db_datalines): 77 | for item in db_datalines: 78 | db.insert_one(collection, item) 79 | 80 | last_time = exchange.get_time_from_data_ts(klines_df[open_time_key].values[-1]) + interval 81 | if last_time > tmp_time + batch * interval: 82 | batch = int((last_time - tmp_time)/interval) 83 | tmp_time += batch * interval 84 | 85 | 86 | if __name__ == "__main__": 87 | parser = argparse.ArgumentParser(description='klines print or check') 88 | parser.add_argument('-m', help='market data source') 89 | parser.add_argument('-r', help='time range (2018-7-1T8' + xq.time_range_split + '2018-8-1T8)') 90 | parser.add_argument('-ss', help='symbols: btc_usdt,eth_usdt') 91 | parser.add_argument('-kts', help='kline types: 1m,4h,1d') 92 | args = parser.parse_args() 93 | print(args) 94 | if not (args.ss and args.kts and args.m): 95 | parser.print_help() 96 | exit(1) 97 | 98 | exchange = create_exchange(args.m) 99 | if not exchange: 100 | print("market data source error!") 101 | exit(1) 102 | 103 | print("%s connecting..." % (args.m), end='') 104 | exchange.connect() 105 | print('ok!') 106 | 107 | db = get_mongodb(args.m) 108 | 109 | symbols = args.ss.split(',') 110 | kline_types = args.kts.split(',') 111 | for symbol in symbols: 112 | print('%12s ' % (symbol)) 113 | for kline_type in kline_types: 114 | download_from_exchange(exchange, db, symbol, kline_type, args.r) 115 | 116 | -------------------------------------------------------------------------------- /importer/download_binance.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #source ~/.profile 4 | range=$1 5 | echo $range 6 | 7 | mds=(binance) 8 | echo ${mds[*]} 9 | 10 | symbles=btc_usdt,eth_usdt,btc_busd,btc_usdc 11 | echo ${symbles} 12 | 13 | kline_types=1m,1h,2h,4h,1d 14 | echo ${kline_types} 15 | 16 | for md in ${mds[*]} 17 | do 18 | 19 | #echo $kline_type 20 | echo "downloading ${range} ${md}" 21 | if [ $range ] 22 | then 23 | python3 download2.py -m $md -ss $symbles -kts $kline_types -r $range 24 | else 25 | python3 download2.py -m $md -ss $symbles -kts $kline_types 26 | fi 27 | 28 | done -------------------------------------------------------------------------------- /importer/download_kuaiqi.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | #source ~/.profile 4 | range=$1 5 | echo $range 6 | 7 | mds=(kuaiqi) 8 | echo ${mds[*]} 9 | 10 | symbles=SHFE.cu_rmb 11 | echo ${symbles} 12 | 13 | kline_types=1m,5m,15m,1h,1d 14 | echo ${kline_types} 15 | 16 | for md in ${mds[*]} 17 | do 18 | 19 | #echo $kline_type 20 | echo "downloading ${range} ${md}" 21 | if [ $range ] 22 | then 23 | python3 download2.py -m $md -ss $symbles -kts $kline_types -r $range 24 | else 25 | python3 download2.py -m $md -ss $symbles -kts $kline_types 26 | fi 27 | 28 | done -------------------------------------------------------------------------------- /importer/fix.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | from datetime import datetime,timedelta 5 | import common.kline as kl 6 | import db.mongodb as md 7 | from setup import * 8 | from importer import add_common_arguments, split_time_range 9 | 10 | 11 | def fix_middle_kline(database, collection, start_kline, end_kline): 12 | print(start_kline) 13 | print(end_kline) 14 | mid_kline = { 15 | 'open_time': int((start_kline['open_time'] + end_kline['open_time']) / 2), 16 | 'number_of_trades': int((start_kline['number_of_trades'] + end_kline['number_of_trades']) / 2), 17 | 'close_time': int((start_kline['close_time'] + end_kline['close_time']) / 2), 18 | 'open': str((float(start_kline['open']) + float(end_kline['open'])) / 2), 19 | 'high': str((float(start_kline['high']) + float(end_kline['high'])) / 2), 20 | 'low': str((float(start_kline['low']) + float(end_kline['low'])) / 2), 21 | 'close': str((float(start_kline['close']) + float(end_kline['close'])) / 2), 22 | 'volume': str((float(start_kline['volume']) + float(end_kline['volume'])) / 2), 23 | 'quote_asset_volume': str((float(start_kline['quote_asset_volume']) + float(end_kline['quote_asset_volume'])) / 2), 24 | 'taker_buy_base_asset_volume': str((float(start_kline['taker_buy_base_asset_volume']) + float(end_kline['taker_buy_base_asset_volume'])) / 2), 25 | 'taker_buy_quote_asset_volume': str((float(start_kline['taker_buy_quote_asset_volume']) + float(end_kline['taker_buy_quote_asset_volume'])) / 2), 26 | 'ignore': str(int((float(start_kline['ignore']) + float(end_kline['ignore'])) / 2)), 27 | } 28 | print(mid_kline) 29 | database.insert_one(collection, mid_kline) 30 | return mid_kline 31 | 32 | 33 | if __name__ == "__main__": 34 | parser = add_common_arguments('fix') 35 | args = parser.parse_args() 36 | # print(args) 37 | 38 | if not (args.s and args.r and args.k and args.m): 39 | parser.print_help() 40 | exit(1) 41 | 42 | start_time, end_time = split_time_range(args.r) 43 | 44 | interval = args.k 45 | collection = kl.get_kline_collection(args.s, interval) 46 | td = kl.get_interval_timedelta(interval) 47 | period = kl.get_interval_seconds(interval) 48 | tick_time = kl.get_open_time(interval, start_time) 49 | if tick_time < start_time: 50 | tick_time = kl.get_open_time(interval, start_time+td) 51 | 52 | db = md.MongoDB(mongo_user, mongo_pwd, args.m, db_url) 53 | 54 | klines = db.find_sort(collection, {"open_time": { 55 | "$gte": int(start_time.timestamp())*1000, 56 | "$lt": int(end_time.timestamp())*1000}}, 'open_time', 1) 57 | 58 | i = 0 59 | miss_count = 0 60 | print(len(klines)) 61 | 62 | period_ms = period * 1000 63 | for kline in klines: 64 | rest = kline['open_time'] % period_ms 65 | if rest: 66 | print(kline) 67 | kline['open_time'] -= rest 68 | kline['close_time'] = kline['open_time'] + period_ms - 1 69 | print(kline) 70 | db.update_one(collection, kline['_id'], kline) 71 | 72 | rest = (kline['close_time'] + 1) % period_ms 73 | if rest: 74 | print(kline) 75 | kline['close_time'] = kline['open_time'] + period_ms - 1 76 | print(kline) 77 | db.update_one(collection, kline['_id'], kline) 78 | 79 | -------------------------------------------------------------------------------- /importer/fixKline.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | from datetime import datetime,timedelta 5 | import common.kline as kl 6 | import db.mongodb as md 7 | from setup import * 8 | from importer import add_common_arguments, split_time_range 9 | 10 | 11 | def fix_middle_kline(database, collection, start_kline, end_kline): 12 | print(start_kline) 13 | print(end_kline) 14 | mid_kline = { 15 | 'open_time': int((start_kline['open_time'] + end_kline['open_time']) / 2), 16 | 'number_of_trades': int((start_kline['number_of_trades'] + end_kline['number_of_trades']) / 2), 17 | 'close_time': int((start_kline['close_time'] + end_kline['close_time']) / 2), 18 | 'open': str((float(start_kline['open']) + float(end_kline['open'])) / 2), 19 | 'high': str((float(start_kline['high']) + float(end_kline['high'])) / 2), 20 | 'low': str((float(start_kline['low']) + float(end_kline['low'])) / 2), 21 | 'close': str((float(start_kline['close']) + float(end_kline['close'])) / 2), 22 | 'volume': str((float(start_kline['volume']) + float(end_kline['volume'])) / 2), 23 | 'quote_asset_volume': str((float(start_kline['quote_asset_volume']) + float(end_kline['quote_asset_volume'])) / 2), 24 | 'taker_buy_base_asset_volume': str((float(start_kline['taker_buy_base_asset_volume']) + float(end_kline['taker_buy_base_asset_volume'])) / 2), 25 | 'taker_buy_quote_asset_volume': str((float(start_kline['taker_buy_quote_asset_volume']) + float(end_kline['taker_buy_quote_asset_volume'])) / 2), 26 | 'ignore': str(int((float(start_kline['ignore']) + float(end_kline['ignore'])) / 2)), 27 | } 28 | print(mid_kline) 29 | database.insert_one(collection, mid_kline) 30 | return mid_kline 31 | 32 | 33 | if __name__ == "__main__": 34 | parser = add_common_arguments('fix kline') 35 | args = parser.parse_args() 36 | # print(args) 37 | 38 | if not (args.s and args.r and args.k and args.m): 39 | parser.print_help() 40 | exit(1) 41 | 42 | start_time, end_time = split_time_range(args.r) 43 | 44 | interval = args.k 45 | collection = kl.get_kline_collection(args.s, interval) 46 | td = kl.get_interval_timedelta(interval) 47 | period = kl.get_interval_seconds(interval) 48 | tick_time = kl.get_open_time(interval, start_time) 49 | if tick_time < start_time: 50 | tick_time = kl.get_open_time(interval, start_time+td) 51 | 52 | db = md.MongoDB(mongo_user, mongo_pwd, args.m, db_url) 53 | 54 | target_len = int((int(end_time.timestamp()) - int(start_time.timestamp())) / period) 55 | print("Target length:", target_len) 56 | 57 | length = db.count(collection, {"open_time": { 58 | "$gte": int(start_time.timestamp())*1000, 59 | "$lt": int(end_time.timestamp())*1000}}) 60 | 61 | print("Real length:", length) 62 | if length == target_len: 63 | print('No data lost. Everything is okay.') 64 | exit(0) 65 | 66 | klines = db.find_sort(collection, {"open_time": { 67 | "$gte": int(start_time.timestamp())*1000, 68 | "$lt": int(end_time.timestamp())*1000}}, 'open_time', 1) 69 | 70 | i = 0 71 | miss_count = 0 72 | print(len(klines)) 73 | 74 | while i < len(klines): 75 | ts = int(tick_time.timestamp()*1000) 76 | if ts != klines[i]["open_time"]: 77 | if i == 0: 78 | print("The first kline is lost. Cannot be fixed. Try a longer period") 79 | exit(1) 80 | print(ts, klines[i]["open_time"]) 81 | start = klines[i - 1] 82 | end = klines[i] 83 | # j = 1 84 | # while (end['open_time'] - start['open_time']) % (2 * period * 1000) != 0: 85 | # end = klines[i + j] 86 | # j += 1 87 | if (end['open_time'] - start['open_time']) % (2 * period * 1000) != 0: 88 | if i < 2: 89 | print("The second kline is lost. Cannot be fixed. Try a longer period") 90 | exit(1) 91 | else: 92 | start = klines[i - 2] 93 | if (end['open_time'] - start['open_time']) % (2 * period * 1000) != 0: 94 | print("Error: Something error. Please check") 95 | print("start kline:", start) 96 | print("end kline:", end) 97 | exit(1) 98 | mid_kline = fix_middle_kline(db, collection, start, end) 99 | klines.insert(i, mid_kline) 100 | continue 101 | 102 | tick_time += td 103 | i += 1 104 | 105 | -------------------------------------------------------------------------------- /importer/importer.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | from datetime import datetime 6 | import common.xquant as xq 7 | 8 | def split_time_range(range): 9 | time_range = range.split(xq.time_range_split) 10 | start_time = datetime.strptime(time_range[0], "%Y-%m-%dT%H") 11 | end_time = datetime.strptime(time_range[1], "%Y-%m-%dT%H") 12 | return start_time, end_time 13 | 14 | def add_common_arguments(description): 15 | parser = argparse.ArgumentParser(description='klines print or check') 16 | parser.add_argument('-m', help='market data source') 17 | parser.add_argument('-s', help='symbol (btc_usdt)') 18 | parser.add_argument('-r', help='time range (2018-7-1T8' + xq.time_range_split + '2018-8-1T8)') 19 | parser.add_argument('-k', help='kline type (1m、4h、1d...)') 20 | return parser 21 | 22 | -------------------------------------------------------------------------------- /md/exmd.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """实盘""" 3 | from datetime import datetime 4 | import utils.tools as ts 5 | import common.xquant as xq 6 | import common.kline as kl 7 | from .md import MarketingData 8 | 9 | 10 | class ExchangeMD(MarketingData): 11 | """交易所实盘数据""" 12 | 13 | def __init__(self, exchange, kline_data_type=kl.KLINE_DATA_TYPE_JSON): 14 | super().__init__(exchange, kline_data_type) 15 | 16 | 17 | def get_latest_pirce(self, symbol): 18 | kls = self.get_klines(symbol, kl.KLINE_INTERVAL_1MINUTE, 1) 19 | if len(kls) <= 0: 20 | return None, None 21 | latest_kl = kls[0] 22 | latest_price = float(latest_kl[self.kline_key_close]) 23 | latest_time = self.get_kline_close_time(latest_kl) 24 | return latest_price, latest_time 25 | 26 | 27 | def get_klines(self, symbol, interval, size): 28 | """ 获取日k线 """ 29 | kls = self._exchange.get_klines(symbol, interval, size) 30 | 31 | if self._exchange.kline_data_type == self.kline_data_type: 32 | return kls 33 | elif self._exchange.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 34 | return kl.trans_from_list_to_json(kls, self._exchange.kline_column_names) 35 | else: 36 | return kl.trans_from_json_to_list(kls, self._exchange.kline_column_names) 37 | 38 | def get_klines_1day(self, symbol, size): 39 | """ 获取日k线 """ 40 | return self.get_klines(symbol, kl.KLINE_INTERVAL_1DAY, size) 41 | 42 | 43 | -------------------------------------------------------------------------------- /md/md.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """marketing data""" 3 | from datetime import datetime, timedelta 4 | import common.kline as kl 5 | import exchange.exchange as ex 6 | 7 | 8 | class MarketingData: 9 | """市场数据""" 10 | 11 | def __init__(self, exchange, kline_data_type): 12 | self.kline_column_names = exchange.kline_column_names 13 | self.kline_key_open_time = exchange.kline_key_open_time 14 | self.kline_key_close_time = exchange.kline_key_close_time 15 | self.kline_key_open = exchange.kline_key_open 16 | self.kline_key_close = exchange.kline_key_close 17 | self.kline_key_high = exchange.kline_key_high 18 | self.kline_key_low = exchange.kline_key_low 19 | self.kline_key_volume = exchange.kline_key_volume 20 | 21 | self._exchange = exchange 22 | self.kline_data_type = kline_data_type 23 | 24 | self.kline_seat_open_time = self.get_kline_seat_open_time() 25 | self.kline_seat_close_time = self.get_kline_seat_close_time() 26 | self.kline_seat_open = self.get_kline_seat_open() 27 | self.kline_seat_close = self.get_kline_seat_close() 28 | self.kline_seat_high = self.get_kline_seat_high() 29 | self.kline_seat_low = self.get_kline_seat_low() 30 | self.kline_seat_volume = self.get_kline_seat_volume() 31 | 32 | 33 | def get_kline_column_names(self): 34 | return self._exchange.kline_column_names 35 | 36 | def get_kline_seat_open_time(self): 37 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 38 | return self._exchange.kline_idx_open_time 39 | else: 40 | return self._exchange.kline_key_open_time 41 | 42 | def get_kline_seat_close_time(self): 43 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 44 | return self._exchange.kline_idx_close_time 45 | else: 46 | return self._exchange.kline_key_close_time 47 | 48 | def get_kline_seat_open(self): 49 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 50 | return self._exchange.kline_idx_open 51 | else: 52 | return self._exchange.kline_key_open 53 | 54 | def get_kline_seat_close(self): 55 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 56 | return self._exchange.kline_idx_close 57 | else: 58 | return self._exchange.kline_key_close 59 | 60 | def get_kline_seat_high(self): 61 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 62 | return self._exchange.kline_idx_high 63 | else: 64 | return self._exchange.kline_key_high 65 | 66 | def get_kline_seat_low(self): 67 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 68 | return self._exchange.kline_idx_low 69 | else: 70 | return self._exchange.kline_key_low 71 | 72 | def get_kline_seat_volume(self): 73 | if self.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 74 | return self._exchange.kline_idx_volume 75 | else: 76 | return self._exchange.kline_key_volume 77 | 78 | def get_time_from_data_ts(self, ts): 79 | return self._exchange.get_time_from_data_ts(ts) 80 | 81 | def get_data_ts_from_time(self, t): 82 | return self._exchange.get_data_ts_from_time(t) 83 | 84 | def get_kline_open_time(self, kl): 85 | return self.get_time_from_data_ts(kl[self.kline_seat_open_time]) 86 | 87 | def get_kline_close_time(self, kl): 88 | return self.get_time_from_data_ts(kl[self.kline_seat_close_time]) 89 | 90 | def get_kline_open(self, kl): 91 | return float(kl[self.kline_seat_open]) 92 | 93 | def get_kline_close(self, kl): 94 | return float(kl[self.kline_seat_close]) 95 | 96 | def get_kline_high(self, kl): 97 | return float(kl[self.kline_seat_high]) 98 | 99 | def get_kline_low(self, kl): 100 | return float(kl[self.kline_seat_low]) 101 | 102 | def get_kline_volume(self, kl): 103 | return float(kl[self.kline_seat_volume]) 104 | -------------------------------------------------------------------------------- /monitor/daily_report.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | from jinja2 import Environment, FileSystemLoader 4 | import datetime 5 | import db.mongodb as md 6 | import re 7 | from utils.email_obj import EmailObj 8 | from setup import * 9 | 10 | db_name = trade_db_name 11 | 12 | if __name__ == "__main__": 13 | parser = argparse.ArgumentParser(description='Daily Report') 14 | parser.add_argument('-i', help='Instance') 15 | parser.add_argument('-d', help='Included Days') 16 | parser.add_argument('-f', help='Log file name') 17 | parser.add_argument('-r', help='Receiver Email Address') 18 | 19 | args = parser.parse_args() 20 | # print(args) 21 | if not (args.i and args.d and args.r and args.f): 22 | parser.print_help() 23 | exit(1) 24 | 25 | instance_id = args.i 26 | days = args.d 27 | email_addr = args.r 28 | file_name = args.f 29 | 30 | template_dir = location('monitor/template') 31 | print(template_dir) 32 | subject = 'Quant Daily Report - ' + instance_id 33 | 34 | ''' 35 | process = os.popen('ps aux | grep %s | grep instance_id | grep -v grep' % instance_id).read() 36 | ret = re.search('python', process) 37 | if ret is None: 38 | process_info = 'Error: Instance (%s) is dead. Please check it ASAP' % instance_id 39 | else: 40 | process_info = process[ret.span()[0]:] 41 | print(process_info) 42 | ''' 43 | process_info = '' 44 | with open(file_name, 'r') as f: 45 | off = 1800 46 | f.seek(0, os.SEEK_END) 47 | f.seek(f.tell() - off, os.SEEK_SET) 48 | lines = f.readlines() 49 | for line in lines: 50 | process_info += (line + "
") 51 | 52 | print(process_info) 53 | 54 | db = md.MongoDB(mongo_user, mongo_pwd, db_name, db_url) 55 | collection = 'orders' 56 | 57 | begin_time = datetime.datetime.now() - datetime.timedelta(days=int(days)) 58 | 59 | orders = db.find_sort(collection, {"instance_id": instance_id, 60 | "create_time": {"$gte": int(begin_time.timestamp())}}, 'create_time', -1) 61 | 62 | print('orders:', orders) 63 | for order in orders: 64 | del order['_id'] 65 | del order['instance_id'] 66 | order['create_time'] = datetime.datetime.fromtimestamp(order['create_time']).strftime("%Y-%m-%d %H:%M:%S") 67 | 68 | # construct html 69 | env = Environment( 70 | loader=FileSystemLoader(template_dir), 71 | ) 72 | template = env.get_template('template.html') 73 | html = template.render(orders=orders, process_info=process_info) 74 | # print(html) 75 | email_obj = EmailObj(email_srv, email_user, email_pwd, email_port) 76 | email_obj.send_mail(subject, html, email_user, to_addr=email_addr) 77 | -------------------------------------------------------------------------------- /monitor/daily_report.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | source ~/.profile 5 | base_dir=$(cd `dirname $0`; pwd) 6 | echo $1 7 | python3.6 $base_dir/daily_report.py -i gbtc -d 5 -r pkguowu@yahoo.com -f /home/gw/xq/gbtc.log 8 | -------------------------------------------------------------------------------- /monitor/insert_trade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3.6 2 | import argparse 3 | import time 4 | import db.mongodb as md 5 | import re 6 | from utils.email_obj import EmailObj 7 | from setup import * 8 | 9 | # Example: 10 | # python3.6 insert_trade.py -i gbtc -d buy -s btc_usdt -p 11000.23 -a 1.11 11 | 12 | db_name = trade_db_name 13 | collection = 'orders' 14 | record = { 15 | "create_time": time.time(), 16 | "instance_id": "", 17 | "symbol": "", 18 | "direction": "LONG", 19 | "action":"", 20 | "pst_rate": 0, 21 | "type": "LIMIT", 22 | "market_price": 0, 23 | "price": 0, 24 | "amount": 0, 25 | "status": "close", 26 | "order_id": 264988599, 27 | "cancle_amount": 0, 28 | "deal_amount": 0, 29 | "deal_value": 0, 30 | "rmk": " " 31 | } 32 | 33 | if __name__ == "__main__": 34 | parser = argparse.ArgumentParser(description='Insert trade') 35 | parser.add_argument('-i', help='Instance') 36 | parser.add_argument('-d', help='Buy or sell') 37 | parser.add_argument('-s', help='Symbol like btc_usdt') 38 | parser.add_argument('-p', help='Price', type=float) 39 | parser.add_argument('-a', help='Amount', type=float) 40 | 41 | args = parser.parse_args() 42 | print(args) 43 | if not (args.i and args.d and args.s and args.p and args.a): 44 | parser.print_help() 45 | exit(1) 46 | 47 | record['instance_id'] = args.i 48 | record['symbol'] = args.s.lower() 49 | side = args.d.upper() 50 | if side == 'BUY': 51 | record['action'] = 'OPEN' 52 | else: 53 | record['action'] = 'CLOSE' 54 | record['price'] = record['market_price'] = args.p 55 | record['amount'] = record['deal_amount'] = args.a 56 | record['deal_value'] = round(record['price'] * record['deal_amount'], 2) 57 | 58 | print('record:', record) 59 | db = md.MongoDB(mongo_user, mongo_pwd, db_name, db_url) 60 | db.insert_one(collection, record) 61 | -------------------------------------------------------------------------------- /monitor/monitor.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import argparse 3 | from jinja2 import Environment, FileSystemLoader 4 | import datetime 5 | import time 6 | import db.mongodb as md 7 | import re 8 | from utils.email_obj import EmailObj 9 | from setup import * 10 | 11 | 12 | if __name__ == "__main__": 13 | parser = argparse.ArgumentParser(description='Daily Report') 14 | # parser.add_argument('-i', help='Instance') 15 | # parser.add_argument('-d', help='Included Days') 16 | parser.add_argument('-f', help='Log file name') 17 | parser.add_argument('-r', help='Receiver Email Address') 18 | 19 | args = parser.parse_args() 20 | # print(args) 21 | if not (args.r and args.f): 22 | parser.print_help() 23 | exit(1) 24 | 25 | # instance_id = args.i 26 | # days = args.d 27 | email_addr = args.r 28 | file_name = args.f 29 | 30 | # template_dir = location('monitor/template') 31 | # print(template_dir) 32 | subject = 'Quant Alert' 33 | 34 | modified_time = datetime.datetime.fromtimestamp(os.stat(file_name).st_mtime) 35 | 36 | if datetime.datetime.now() < modified_time + datetime.timedelta(minutes=10): 37 | print("Everything is okey") 38 | exit(0) 39 | # construct html 40 | # env = Environment( 41 | # loader=FileSystemLoader(template_dir), 42 | # ) 43 | # template = env.get_template('alert.html') 44 | # html = template.render(orders=orders, process_info=process_info) 45 | html = "Quant is dead. Please check" 46 | print(html) 47 | email_obj = EmailObj(email_srv, email_user, email_pwd, email_port) 48 | email_obj.send_mail(subject, html, email_user, to_addr=email_addr) 49 | 50 | -------------------------------------------------------------------------------- /monitor/monitor.sh: -------------------------------------------------------------------------------- 1 | #!/bin/bash 2 | 3 | 4 | source ~/.profile 5 | base_dir=$(cd `dirname $0`; pwd) 6 | echo $1 7 | python3.6 $base_dir/monitor.py -r pkguowu@yahoo.com -f ~/xq/gbtc.log 8 | -------------------------------------------------------------------------------- /monitor/template/template.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | Title 6 | 20 | 21 | 22 | 23 | 24 | 25 | 26 |
Process Status
{{ process_info }}
27 |
28 | 29 | 30 | 31 | 32 | {% for item in orders[0] %} 33 | 34 | {% endfor %} 35 | 36 | 37 | {% for order in orders %} 38 | 39 | {% for item in order %} 40 | 41 | {% endfor %} 42 | 43 | {% endfor %} 44 |
Order Status
{{ item }}
{{ order[item] }}
45 | 46 | 47 | -------------------------------------------------------------------------------- /real.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import os 6 | import utils.tools as ts 7 | import common.xquant as xq 8 | import common.log as log 9 | from engine.realengine import RealEngine 10 | 11 | 12 | def real_run(config, instance_id, exchange_name, value, args): 13 | info = 'instance_id: %s, exchange_name: %s, value: %s ' % (instance_id, exchange_name, value) 14 | print(info) 15 | module_name = config["module_name"].replace("/", ".") 16 | class_name = config["class_name"] 17 | 18 | if args.log: 19 | logfilename = instance_id + ".log" 20 | print(logfilename) 21 | 22 | server_ip = os.environ.get('LOG_SERVER_IP') 23 | server_port = os.environ.get('LOG_SERVER_PORT') 24 | print('Log server IP: %s, Log server port: %s' % (server_ip, server_port)) 25 | 26 | log.init('real', logfilename, server_ip, server_port) 27 | log.info("%s" % (info)) 28 | log.info("strategy name: %s; config: %s" % (class_name, config)) 29 | 30 | engine = RealEngine(instance_id, exchange_name, config, value, args.log) 31 | strategy = ts.createInstance(module_name, class_name, config, engine) 32 | 33 | engine.run(strategy, args.debug) 34 | 35 | 36 | def real_view(config, instance_id, exchange_name, value): 37 | symbol = config['symbol'] 38 | 39 | realEngine = RealEngine(instance_id, exchange_name, config, value) 40 | orders = realEngine.get_orders(symbol) 41 | realEngine.view(symbol, orders) 42 | 43 | def real_analyze(config, instance_id, exchange_name, value, print_switch_hl, display_rmk, print_switch_deal, print_switch_lock): 44 | symbol = config['symbol'] 45 | 46 | realEngine = RealEngine(instance_id, exchange_name, config, value) 47 | orders = realEngine.get_orders(symbol) 48 | realEngine.analyze(symbol, orders, print_switch_hl, display_rmk, print_switch_deal, print_switch_lock) 49 | 50 | 51 | if __name__ == "__main__": 52 | parser = argparse.ArgumentParser(description='real') 53 | parser.add_argument('-e', help='exchange') 54 | parser.add_argument('-sc', help='strategy config') 55 | parser.add_argument('-sii', help='strategy instance id') 56 | parser.add_argument('-v', type=int, help='value') 57 | parser.add_argument('--debug', help='debug', action="store_true") 58 | parser.add_argument('--log', help='log', action="store_true") 59 | args = parser.parse_args() 60 | print(args) 61 | if not (args.e and args.sc and args.sii and args.v): 62 | parser.print_help() 63 | exit(1) 64 | 65 | config = xq.get_strategy_config(args.sc) 66 | real_run(config, args.sii, args.e, args.v, args) 67 | -------------------------------------------------------------------------------- /real2.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import common.xquant as xq 6 | import common.instance as si 7 | from real import real_run 8 | from real import real_view 9 | from real import real_analyze 10 | from engine.realengine import RealEngine 11 | from db.mongodb import get_mongodb 12 | import setup 13 | from pprint import pprint 14 | 15 | 16 | def real2_run(args): 17 | instance = si.get_strategy_instance(args.sii) 18 | config = xq.get_strategy_config(instance['config_path']) 19 | 20 | real_run(config, args.sii, instance['exchange'], instance['value'], args) 21 | 22 | def real2_view(args): 23 | instance = si.get_strategy_instance(args.sii) 24 | config = xq.get_strategy_config(instance['config_path']) 25 | 26 | real_view(config, args.sii, instance['exchange'], instance['value']) 27 | 28 | def real2_analyze(args): 29 | instance = si.get_strategy_instance(args.sii) 30 | config = xq.get_strategy_config(instance['config_path']) 31 | 32 | real_analyze(config, args.sii, instance['exchange'], instance['value'], args.hl, args.rmk, args.deal, args.lock) 33 | 34 | 35 | def real2_list(args): 36 | td_db = get_mongodb(setup.trade_db_name) 37 | ss = td_db.find(si.STRATEGY_INSTANCE_COLLECTION_NAME, {"user": args.user}) 38 | #pprint(ss) 39 | all_value = 0 40 | all_his_profit = 0 41 | all_flo_profit = 0 42 | all_commission = 0 43 | 44 | title_head_fmt = "%-30s %10s%12s" 45 | head_fmt = "%-30s %10s(%10.0f)" 46 | 47 | title_profit_fmt = "%21s %21s %12s" 48 | profit_fmt = "%12.2f(%6.2f%%) %12.2f(%6.2f%%) %12.2f" 49 | 50 | title_tail_fmt = " %-60s %-20s %10s" 51 | 52 | 53 | print(title_head_fmt % ("instance_id", "value", "") + title_profit_fmt % ("history_profit", "floating_profit", "commission") + title_tail_fmt % ("config_path", "exchange", "status")) 54 | for s in ss: 55 | instance_id = s["instance_id"] 56 | exchange_name = s["exchange"] 57 | value = s["value"] 58 | config_path = s["config_path"] 59 | if "status" in s: 60 | status = s["status"] 61 | else: 62 | status = "" 63 | if status != args.status and status != "": 64 | continue 65 | 66 | all_value += value 67 | profit_info = "" 68 | try: 69 | config = xq.get_strategy_config(config_path) 70 | symbol = config['symbol'] 71 | realEngine = RealEngine(instance_id, exchange_name, config, value) 72 | orders = realEngine.get_orders(symbol) 73 | pst_info = realEngine.get_pst_by_orders(orders) 74 | history_profit, history_profit_rate, history_commission = realEngine.get_history(pst_info) 75 | all_his_profit += history_profit 76 | floating_profit, floating_profit_rate, floating_commission, cur_price = realEngine.get_floating(symbol, pst_info) 77 | all_flo_profit += floating_profit 78 | commission = history_commission + floating_commission 79 | all_commission += commission 80 | profit_info = profit_fmt % (history_profit, history_profit_rate*100, floating_profit, floating_profit_rate*100, commission) 81 | 82 | except Exception as ept: 83 | profit_info = "error: %s" % (ept) 84 | 85 | print(head_fmt % (instance_id, value, (value+history_profit+floating_profit)) + profit_info + title_tail_fmt % (config_path, exchange_name, status)) 86 | 87 | if args.stat: 88 | print(head_fmt % ("all", all_value, all_value+all_his_profit+all_flo_profit) + profit_fmt % (all_his_profit, all_his_profit/all_value*100, all_flo_profit, all_flo_profit/all_value*100, all_commission)) 89 | 90 | 91 | def real2_update(args): 92 | record = {} 93 | if args.user: 94 | record["user"] = args.user 95 | if args.instance_id: 96 | record["instance_id"] = args.instance_id 97 | if args.config_path: 98 | record["config_path"] = args.config_path 99 | if args.exchange: 100 | record["exchange"] = args.exchange 101 | if args.value: 102 | instance = si.get_strategy_instance(args.sii) 103 | instance_id = instance["instance_id"] 104 | exchange_name = instance["exchange"] 105 | if not exchange_name: 106 | exchange_name = record["exchange_name"] 107 | config_path = instance["config_path"] 108 | if not config_path: 109 | config_path = record["config_path"] 110 | value = instance["value"] 111 | config = xq.get_strategy_config(config_path) 112 | symbol = config['symbol'] 113 | realEngine = RealEngine(instance_id, exchange_name, config, value) 114 | orders = realEngine.get_orders(symbol) 115 | if orders: 116 | return 117 | 118 | record["value"] = args.value 119 | if args.status: 120 | record["status"] = args.status 121 | 122 | if record: 123 | si.update_strategy_instance({"instance_id": args.sii}, record) 124 | 125 | 126 | def real2_add(args): 127 | record = { 128 | "user": args.user, 129 | "instance_id": args.sii, 130 | "config_path": args.config_path, 131 | "exchange": args.exchange, 132 | "value": args.value, 133 | "status": args.status, 134 | } 135 | si.add_strategy_instance(record) 136 | 137 | 138 | def real2_delete(args): 139 | si.delete_strategy_instance({"instance_id": args.sii}) 140 | 141 | 142 | if __name__ == "__main__": 143 | parser = argparse.ArgumentParser(description='real') 144 | parser.add_argument('-sii', help='strategy instance id') 145 | parser.add_argument('--debug', help='debug', action="store_true") 146 | parser.add_argument('--log', help='log', action="store_true") 147 | 148 | subparsers = parser.add_subparsers(help='sub-command help') 149 | 150 | parser_view = subparsers.add_parser('view', help='view strategy instance') 151 | parser_view.add_argument('-sii', required=True, help='strategy instance id') 152 | parser_view.set_defaults(func=real2_view) 153 | 154 | parser_analyze = subparsers.add_parser('analyze', help='analyze strategy instance') 155 | parser_analyze.add_argument('-sii', required=True, help='strategy instance id') 156 | parser_analyze.add_argument('--hl', help='high low', action="store_true") 157 | parser_analyze.add_argument('--lock', help='lock info', action="store_true") 158 | parser_analyze.add_argument('--rmk', help='remark', action="store_true") 159 | parser_analyze.add_argument('--deal', help='deal amount', action="store_true") 160 | parser_analyze.set_defaults(func=real2_analyze) 161 | 162 | parser_list = subparsers.add_parser('list', help='list of strategy instance') 163 | parser_list.add_argument('-user', help='user name') 164 | parser_list.add_argument('--status', default=si.STRATEGY_INSTANCE_STATUS_START, choices=si.strategy_instance_statuses, help='strategy instance status') 165 | parser_list.add_argument('--stat', help='stat all', action="store_true") 166 | parser_list.set_defaults(func=real2_list) 167 | 168 | parser_update = subparsers.add_parser('update', help='update strategy instance') 169 | parser_update.add_argument('-sii', required=True, help='strategy instance id') 170 | parser_update.add_argument('--user', help='user name') 171 | parser_update.add_argument('--instance_id', help='new strategy instance id') 172 | parser_update.add_argument('--config_path', help='strategy config path') 173 | parser_update.add_argument('--exchange', help='strategy instance exchange') 174 | parser_update.add_argument('--value', type=int, help='strategy instance value') 175 | parser_update.add_argument('--status', choices=si.strategy_instance_statuses, help='strategy instance status') 176 | parser_update.set_defaults(func=real2_update) 177 | 178 | parser_add = subparsers.add_parser('add', help='add new strategy instance') 179 | parser_add.add_argument('-user', required=True, help='user name') 180 | parser_add.add_argument('-sii', required=True, help='strategy instance id') 181 | parser_add.add_argument('-config_path', required=True, help='strategy config path') 182 | parser_add.add_argument('-exchange', required=True, help='strategy instance exchange') 183 | parser_add.add_argument('-value', required=True, type=int, help='strategy instance value') 184 | parser_add.add_argument('-status', choices=si.strategy_instance_statuses, default=si.STRATEGY_INSTANCE_STATUS_START, help='strategy instance status') 185 | parser_add.set_defaults(func=real2_add) 186 | 187 | parser_delete = subparsers.add_parser('delete', help='delete strategy instance') 188 | parser_delete.add_argument('-sii', required=True, help='strategy instance id') 189 | parser_delete.set_defaults(func=real2_delete) 190 | 191 | args = parser.parse_args() 192 | #print(args) 193 | 194 | if hasattr(args, 'func'): 195 | args.func(args) 196 | else: 197 | real2_run(args) 198 | -------------------------------------------------------------------------------- /scripts/auto_repay.sh: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env bash 2 | 3 | OS=`uname -s` 4 | if [ ${OS} == "Darwin" ];then 5 | source ~/.bash_profile 6 | elif [ ${OS} == "Linux" ]; then 7 | source ~/.profile 8 | fi 9 | 10 | base_dir=$(cd `dirname $0`; pwd) 11 | echo $1 12 | PYTHONPATH=$base_dir/.. python3 $base_dir/../tools/auto_repay.py -e binance >> $base_dir/auto_repay.log 2>&1 13 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import os 3 | 4 | location = lambda x: os.path.join( 5 | os.path.dirname(os.path.realpath(__file__)), x) 6 | 7 | #db_name = location('database/main.db') 8 | trade_db_name = "xquant" 9 | mongo_user = os.environ.get('MONGO_USER') 10 | mongo_pwd = os.environ.get('MONGO_PWD') 11 | mongo_port = os.environ.get('MONGO_PORT') 12 | mongo_port = '%s' % (mongo_port) if mongo_port else '27017' 13 | db_url = "mongodb://localhost:%s/" % (mongo_port) 14 | 15 | email_srv = os.environ.get('EMAIL_SMTP') 16 | email_user = os.environ.get('EMAIL_FROM') 17 | email_pwd = os.environ.get('EMAIL_PWD') 18 | email_port = os.environ.get('EMAIL_PORT') 19 | -------------------------------------------------------------------------------- /strategy/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/strategy/__init__.py -------------------------------------------------------------------------------- /strategy/kd/kd.py: -------------------------------------------------------------------------------- 1 | import random 2 | import common.bill as bl 3 | import utils.indicator as ic 4 | import utils.ti as ti 5 | from strategy.strategy import Strategy 6 | 7 | 8 | class KDStrategy(Strategy): 9 | """ simple KD stragegy""" 10 | 11 | def __init__(self, strategy_config, engine): 12 | super().__init__(strategy_config, engine) 13 | self.kline = strategy_config["kline"] 14 | self.kd_offset = strategy_config["kd_offset"] 15 | 16 | def search_init(self): 17 | kd_offset_cfg = self.config["search"]["kd_offset"] 18 | 19 | kd_offset = random.uniform(kd_offset_cfg["min"], kd_offset_cfg["max"]) 20 | self.kd_offset = round(kd_offset, kd_offset_cfg["digits"]) 21 | 22 | return self.kd_offset 23 | 24 | def check(self, symbol, klines): 25 | """ kdj指标,金叉全买入,下降趋势部分卖出,死叉全卖出 """ 26 | ''' 27 | kdj_arr = ic.py_kdj(klines, self.highseat, self.lowseat, self.closeseat) 28 | cur_k = kdj_arr[-1][1] 29 | cur_d = kdj_arr[-1][2] 30 | ''' 31 | kkey, dkey = ti.KD(klines, self.closekey, self.highkey, self.lowkey) 32 | cur_k = klines[-1][kkey] 33 | cur_d = klines[-1][dkey] 34 | 35 | signal_info = ( 36 | "(%6.2f) k(%6.2f) d(%6.2f)" 37 | % ( 38 | cur_k - cur_d, 39 | cur_k, 40 | cur_d, 41 | ) 42 | ) 43 | 44 | if cur_k > cur_d + self.kd_offset: 45 | # 金叉 46 | return bl.open_long_bill(1, "买:", signal_info) 47 | 48 | elif cur_k < cur_d - self.kd_offset: 49 | # 死叉 50 | return bl.close_long_bill(0, "卖:", signal_info) 51 | 52 | 53 | return None 54 | 55 | 56 | def on_tick(self, klines=None): 57 | """ tick处理接口 """ 58 | symbol = self.config["symbol"] 59 | # 之前的挂单全撤掉 60 | self.engine.cancle_orders(symbol) 61 | 62 | if not klines: 63 | klines = self.engine.md.get_klines(symbol, self.kline["interval"], self.kline["size"]) 64 | self.cur_price = float(klines[-1][self.closeseat]) 65 | 66 | check_signals = [] 67 | signal = self.check(symbol, klines) 68 | if signal: 69 | check_signals.append(signal) 70 | position_info = self.engine.get_position(symbol, self.cur_price) 71 | self.engine.handle_order(symbol, position_info, self.cur_price, check_signals) 72 | -------------------------------------------------------------------------------- /strategy/kd/kd_btc_usdt.jsn: -------------------------------------------------------------------------------- 1 | { 2 | "module_name": "strategy/kd/kd", 3 | "class_name": "KDStrategy", 4 | "commission_rate": 0.001, 5 | "symbol": "btc_usdt", 6 | "digits": {"btc": 6, "usdt": 2}, 7 | "risk_control": { 8 | "stop_loss": { 9 | "condition": [], 10 | "defalut": {"r": -0.08} 11 | }, 12 | "take_profit": { 13 | "base_open": [] 14 | } 15 | }, 16 | "sec": 60, 17 | "kline": {"interval": "1d", "size": 180}, 18 | "kd_offset": 1.5, 19 | "mode": 1, 20 | "limit_price_rate": {"open": 0.01, "close": 0.1}, 21 | "search": { 22 | "kd_offset": {"max": 5, "min": 0, "digits": 2} 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /strategy/strategy.py: -------------------------------------------------------------------------------- 1 | """strategy""" 2 | from datetime import timedelta, datetime 3 | import common.bill as bl 4 | from engine.order import pst_is_lock 5 | 6 | class Strategy: 7 | """Strategy""" 8 | 9 | def __init__(self, strategy_config, engine): 10 | self.name = "" 11 | self.config = strategy_config 12 | self.engine = engine 13 | 14 | self.instance_id = engine.instance_id 15 | 16 | md = engine.md 17 | self.md = md 18 | 19 | self.highkey = md.kline_key_high 20 | self.lowkey = md.kline_key_low 21 | self.openkey = md.kline_key_open 22 | self.closekey = md.kline_key_close 23 | self.volumekey = md.kline_key_volume 24 | self.opentimekey = md.kline_key_open_time 25 | self.closetimekey = md.kline_key_close_time 26 | 27 | self.highseat = md.get_kline_seat_high() 28 | self.lowseat = md.get_kline_seat_low() 29 | self.openseat = md.get_kline_seat_open() 30 | self.closeseat = md.get_kline_seat_close() 31 | self.volumeseat = md.get_kline_seat_volume() 32 | self.opentimeseat = md.get_kline_seat_open_time() 33 | self.closetimeseat = md.get_kline_seat_close_time() 34 | 35 | self.aligning_log = "\n%4s" % " " 36 | self.aligning_info = "\n%68s" % " " 37 | 38 | def log_info(self, info): 39 | self.engine.log_info(info) 40 | 41 | def log_warning(self, info): 42 | self.engine.log_warning(info) 43 | 44 | def log_error(self, info): 45 | self.engine.log_error(info) 46 | 47 | def log_critical(self, info): 48 | self.engine.log_critical(info) 49 | 50 | def log_debug(self, info): 51 | self.engine.log_debug(info) 52 | 53 | 54 | class SignalStrategy(Strategy): 55 | def __init__(self, strategy_config, engine): 56 | super().__init__(strategy_config, engine) 57 | self.open_time = None 58 | self.micro_open_time = None 59 | 60 | def is_open(self): 61 | open_time = self.md.get_kline_open_time(self.kls[-1]) 62 | if not self.open_time or self.open_time < open_time: 63 | self.open_time = open_time 64 | return True 65 | return False 66 | 67 | def is_micro_open(self): 68 | micro_open_time = self.md.get_kline_open_time(self.micro_kls[-1]) 69 | if not self.micro_open_time or self.micro_open_time < micro_open_time: 70 | self.micro_open_time = micro_open_time 71 | return True 72 | return False 73 | 74 | def merge_infos(self, infos, aligning): 75 | info = "" 76 | for info_tmp in infos: 77 | info += aligning + info_tmp 78 | return info 79 | 80 | def on_tick(self, master_kls=None, micro_kls=None): 81 | """ tick处理接口 """ 82 | symbol = self.config["symbol"] 83 | kl_cfg = self.config["kline"] 84 | if not master_kls: 85 | master_kls = self.md.get_klines(symbol, kl_cfg["interval"], kl_cfg["size"]) 86 | if len(master_kls) <= 0: 87 | return 88 | self.kls = master_kls 89 | 90 | if "micro_interval" in kl_cfg: 91 | if not micro_kls: 92 | micro_kls = self.md.get_klines(symbol, kl_cfg["micro_interval"], kl_cfg["size"]) 93 | if len(micro_kls) <= 0: 94 | return 95 | self.micro_kls = micro_kls 96 | 97 | if self.is_open(): 98 | self.on_open() 99 | 100 | #if self.is_micro_open(): 101 | # self.on_micro_open() 102 | 103 | self.cur_price = self.md.get_kline_close(master_kls[-1]) 104 | cur_close_time = self.md.get_kline_close_time(master_kls[-1]) 105 | self.engine.handle(symbol, self, self.cur_price, cur_close_time, "") 106 | 107 | def check_bill(self, symbol, position_info): 108 | bill, info = self.check(symbol, position_info) 109 | self.log_info(info) 110 | if not bill: 111 | return [] 112 | return [bill] 113 | 114 | def calc_ti(self): 115 | pass 116 | 117 | def check_signal(self): 118 | symbol = self.config["symbol"] 119 | signals = [] 120 | infos = [] 121 | 122 | if not self.calc_ti(): 123 | return signals, infos 124 | 125 | s, tmp_infos = self.signal_long_close() 126 | infos += tmp_infos 127 | if s: 128 | signals.append(s) 129 | 130 | if "long_lock" in self.config: 131 | s, tmp_infos = self.signal_long_lock(symbol) 132 | infos += tmp_infos 133 | if s: 134 | signals.append(s) 135 | 136 | s, tmp_infos = self.signal_long_open() 137 | infos += tmp_infos 138 | if s: 139 | signals.append(s) 140 | 141 | s, tmp_infos = self.signal_short_close() 142 | infos += tmp_infos 143 | if s: 144 | signals.append(s) 145 | 146 | if "short_lock" in self.config: 147 | s, tmp_infos = self.signal_short_lock(symbol) 148 | infos += tmp_infos 149 | if s: 150 | signals.append(s) 151 | 152 | s, tmp_infos = self.signal_short_open() 153 | infos += tmp_infos 154 | if s: 155 | signals.append(s) 156 | 157 | return signals, infos 158 | 159 | 160 | def check_signal_single(self): 161 | symbol = self.config["symbol"] 162 | if not self.calc_ti(): 163 | return [], [] 164 | 165 | infos = [] 166 | s, tmp_infos = self.signal_long_close() 167 | infos += tmp_infos 168 | if s: 169 | return [s], infos 170 | if "long_lock" in self.config: 171 | s, tmp_infos = self.signal_long_lock(symbol) 172 | infos += tmp_infos 173 | if s: 174 | return [s], infos 175 | s, tmp_infos = self.signal_long_open() 176 | infos += tmp_infos 177 | if s: 178 | return [s], infos 179 | 180 | s, tmp_infos = self.signal_short_close() 181 | infos += tmp_infos 182 | if s: 183 | return [s], infos 184 | if "short_lock" in self.config: 185 | s, tmp_infos = self.signal_short_lock(symbol) 186 | infos += tmp_infos 187 | if s: 188 | return [s], infos 189 | s, tmp_infos = self.signal_short_open() 190 | infos += tmp_infos 191 | if s: 192 | return [s], infos 193 | 194 | return [], infos 195 | 196 | 197 | def check(self, symbol, position_info): 198 | info = "" 199 | if not self.calc_ti(): 200 | return None, info 201 | bill, infos = self.create_bill(symbol, position_info) 202 | return bill, self.merge_infos(infos, self.aligning_log) 203 | 204 | 205 | def create_bill(self, symbol, position_info): 206 | log_infos = [] 207 | pst_amount = position_info["amount"] 208 | 209 | if pst_amount == 0 or (pst_amount > 0 and position_info["direction"] == bl.DIRECTION_LONG): 210 | signal, tmp_infos = self.signal_long_close() 211 | log_infos += tmp_infos 212 | if signal: 213 | log_infos.append("long close signal: %s" % (signal["name"])) 214 | if pst_amount > 0: 215 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 216 | return bl.close_long_bill(0, signal["name"], rmk), log_infos 217 | else: 218 | if "long_lock" in self.config: 219 | signal, tmp_infos = self.signal_long_lock(symbol) 220 | log_infos += tmp_infos 221 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 222 | 223 | if signal: 224 | log_infos.append("long lock signal: %s" % (signal["name"])) 225 | if pst_amount > 0 and not pst_is_lock(position_info): 226 | return bl.lock_long_bill(position_info["pst_rate"], signal["name"], rmk), log_infos 227 | else: 228 | if pst_is_lock(position_info): 229 | return bl.unlock_long_bill(position_info["pst_rate"], "unlock", rmk), log_infos 230 | 231 | if not signal: 232 | signal, tmp_infos = self.signal_long_open() 233 | log_infos += tmp_infos 234 | if signal: 235 | log_infos.append("long open signal: %s" % (signal["name"])) 236 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 237 | return bl.open_long_bill(1, signal["name"], rmk), log_infos 238 | 239 | if pst_amount == 0 or (pst_amount > 0 and position_info["direction"] == bl.DIRECTION_SHORT): 240 | signal, tmp_infos = self.signal_short_close() 241 | log_infos += tmp_infos 242 | if signal: 243 | log_infos.append("short close signal: %s" % (signal["name"])) 244 | if pst_amount > 0: 245 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 246 | return bl.close_short_bill(0, signal["name"], rmk), log_infos 247 | else: 248 | if "short_lock" in self.config: 249 | signal, tmp_infos = self.signal_short_lock(symbol) 250 | log_infos += tmp_infos 251 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 252 | if signal: 253 | log_infos.append("short lock signal: %s" % (signal["name"])) 254 | if pst_amount > 0 and not pst_is_lock(position_info): 255 | return bl.lock_short_bill(position_info["pst_rate"], signal["name"], rmk), log_infos 256 | else: 257 | if pst_is_lock(position_info): 258 | return bl.unlock_short_bill(position_info["pst_rate"], "unlock", rmk), log_infos 259 | 260 | if not signal: 261 | signal, tmp_infos = self.signal_short_open() 262 | log_infos += tmp_infos 263 | if signal: 264 | log_infos.append("short open signal: %s" % (signal["name"])) 265 | rmk = self.merge_infos(tmp_infos, self.aligning_info) 266 | return bl.open_short_bill(1, signal["name"], rmk), log_infos 267 | 268 | return None, log_infos 269 | 270 | -------------------------------------------------------------------------------- /tests/binance/bnFuture_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | from exchange.binanceFuture import BinanceFuture 4 | # from exchange.binance.enums import * 5 | 6 | api_key = os.environ.get('BINANCE_API_KEY') 7 | secret_key = os.environ.get('BINANCE_SECRET_KEY') 8 | 9 | if __name__ == "__main__": 10 | client = BinanceFuture(debug=True) 11 | # ret = client.get_account() 12 | # ret = client.get_balances("USDT") 13 | # ret = client.get_all_balances() 14 | # ret = client.get_trades("BTC_USDT") 15 | # ret = client.get_deals("BTC_USDT") 16 | # ret = client.send_order(direction='SHORT', action='OPEN', type='LIMIT', symbol='ETH_USDT', amount=0.1, price=450) 17 | # ret = client.send_order(direction='LONG', action='OPEN', type='LIMIT', symbol='ETH_USDT', amount=0.1, price=350) 18 | # ret = client.get_open_orders("ETH_USDT") 19 | # ret = client.get_open_order_ids(symbol='ETH_USDT') 20 | # ret = client.cancel_order(symbol='ETH_USDT', order_id='2892556980') 21 | # ret = client.cancel_orders(symbol='ETH_USDT', order_ids=[2892581474, 2892581810]) 22 | # ret = client.get_order_book(symbol='ETH_USDT', limit=20) 23 | # ret = client.order_status_is_close(symbol='ETH_USDT', order_id=2892581810) 24 | # ret = client.transfer_to_future(asset='USDT', amount=5) 25 | # ret = client.transfer_from_future(asset='USDT', amount=5) 26 | # ret = client.get_klines_1hour(symbol="BTC_USDT", size=10) 27 | ret = client.get_klines_1day(symbol="BTC_USDT", size=10) 28 | 29 | print(ret) 30 | -------------------------------------------------------------------------------- /tests/binance/bnMargin_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | from exchange.binanceMargin import BinanceMargin 4 | # from exchange.binance.enums import * 5 | 6 | api_key = os.environ.get('BINANCE_API_KEY') 7 | secret_key = os.environ.get('BINANCE_SECRET_KEY') 8 | 9 | if __name__ == "__main__": 10 | client = BinanceMargin(debug=True) 11 | # ret = client.get_account() 12 | # ret = client.transfer_to_margin(asset='BTC', amount=0.01) 13 | # ret = client.transfer_from_margin(asset='BTC', amount=0.01) 14 | # ret = client.loan(asset='USDT', amount='0.01') 15 | # ret = client.repay(asset='USDT', amount='0.01') 16 | # ret = client.get_loan(asset='USDT', startTime=1570700000000) 17 | # ret = client.get_repay(asset='USDT', startTime=1570700000000) 18 | # ret = client.get_klines_1hour(symbol="BTC_USDT", size=10) 19 | # ret = client.get_klines_1day(symbol="BTC_USDT", size=10) 20 | # ret = client.get_balances("USDT") 21 | # ret = client.get_trades("BTC_USDT") 22 | # ret = client.get_deals("BTC_USDT") 23 | # ret = client.get_open_orders("BTC_USDT") 24 | # ret = client.get_open_order_ids(symbol='ETH_USDT') 25 | # ret = client.get_order_book(symbol='ETH_USDT', limit=20) 26 | # ret = client.order_status_is_close(symbol='BTC_USDT', order_id=726519783) 27 | # ret = client.send_order(direction='SHORT', action='OPEN', type='LIMIT', symbol='ETH_USDT', amount=0.234123, price=200) 28 | ret = client.send_order(direction='LONG', action='OPEN', type='LIMIT', symbol='ETH_USDT', amount=10000.12234, price=80.213) 29 | # ret = client.cancel_order(symbol='ETH_USDT', order_id='498421194') 30 | # ret = client.cancel_orders(symbol='ETH_USDT', order_ids=[498425779, 498425781, 498426026, 498426027, 498424836]) 31 | 32 | print(ret) 33 | -------------------------------------------------------------------------------- /tests/binance/future_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | from exchange.binance.future import Client 4 | from exchange.binance.enums import * 5 | 6 | api_key = os.environ.get('BINANCE_API_KEY') 7 | secret_key = os.environ.get('BINANCE_SECRET_KEY') 8 | 9 | if __name__ == "__main__": 10 | client = Client(api_key, secret_key) 11 | # ret = client.get_symbol_info(symbol='BTCUSDT') 12 | # ret = client.get_order_book(symbol='BTCUSDT', limit=10) 13 | # ret = client.get_recent_trades(symbol='BTCUSDT', limit=10) 14 | # ret = client.get_historical_trades(symbol='BTCUSDT', limit=10) 15 | # ret = client.get_klines(symbol='BTCUSDT', interval=KLINE_INTERVAL_1DAY ,limit=10) 16 | # ret = client.get_mark_price(symbol='BTCUSDT') 17 | # ret = client.get_ticker(symbol='BTCUSDT') 18 | # ret = client.get_symbol_ticker(symbol='BTCUSDT') 19 | # ret = client.get_orderbook_ticker(symbol='BTCUSDT') 20 | # ret = client.transfer(asset='USDT', amount='20', type=1) 21 | # ret = client.transfer(asset='USDT', amount='10', type=2) 22 | # ret = client.get_transfer_history(asset='USDT', startTime=1598696422) 23 | # ret = client.order_limit_buy(symbol='ETHUSDT', quantity=0.1, price=380) 24 | # ret = client.order_limit_sell(symbol='ETHUSDT', quantity=0.1, price=450) 25 | # ret = client.get_order(symbol='ETHUSDT', orderId='2891778466') 26 | ret = client.cancel_order(symbol='ETHUSDT', orderId='2891900482') 27 | # ret = client.get_open_orders() 28 | # ret = client.get_all_orders(symbol='ETHUSDT') 29 | # ret = client.get_balance() 30 | # ret = client.get_account() 31 | # ret = client.get_position_risk() 32 | # ret = client.get_my_trades(symbol='ETHUSDT') 33 | # ret = client.stream_get_listen_key() 34 | # ret = client.stream_keepalive(listenKey='xkSWWmFJRj0yl1uCdZhfdulBPlRqeCbc5KKmfUJix8aPhy8MCnVOjbwIhJoydxbb') 35 | # ret = client.stream_close(listenKey='xkSWWmFJRj0yl1uCdZhfdulBPlRqeCbc5KKmfUJix8aPhy8MCnVOjbwIhJoydxbb') 36 | 37 | print(ret) 38 | -------------------------------------------------------------------------------- /tests/binance/margin_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | from exchange.binance.margin import Client 4 | from exchange.binance.enums import * 5 | 6 | api_key = os.environ.get('BINANCE_API_KEY') 7 | secret_key = os.environ.get('BINANCE_SECRET_KEY') 8 | 9 | if __name__ == "__main__": 10 | client = Client(api_key, secret_key) 11 | # ret = client.transfer(asset='USDT', amount='10', type=1) 12 | # ret = client.transfer(asset='USDT', amount='0.1', type=2) 13 | # ret = client.loan(asset='USDT', amount='0.01') 14 | # ret = client.repay(asset='USDT', amount='0.01') 15 | # ret = client.order_limit_buy(symbol='ETHUSDT', quantity=0.1, price=180) 16 | # ret = client.transfer(asset='ETH', amount='0.1', type=1) 17 | # ret = client.order_limit_sell(symbol='ETHUSDT', quantity=0.1, price=200) 18 | # print(ret) 19 | # ret = client.cancel_order(symbol='ETHUSDT', orderId='445856389') 20 | # ret = client.get_order(symbol='ETHUSDT', orderId='445862537') 21 | # ret = client.get_open_orders() 22 | # ret = client.get_all_orders(symbol='ETHUSDT') 23 | # ret = client.get_loan(asset='USDT', startTime=1570700000000) 24 | # ret = client.get_repay(asset='USDT', startTime=1570700000000) 25 | # ret = client.get_account() 26 | # ret = client.get_asset(asset='ETH') 27 | # ret = client.get_pair(symbol='ETHUSDT') 28 | # ret = client.get_all_assets() 29 | ret = client.get_all_pairs() 30 | # ret = client.get_price_index(symbol='ETHUSDT') 31 | # ret = client.get_transfer_history(asset='ETH', type='ROLL_IN') 32 | # ret = client.get_transfer_history(type='ROLL_OUT') 33 | # ret = client.get_interest_history() 34 | # ret = client.get_force_liquidation_record() 35 | # ret = client.get_my_trades(symbol='ETHUSDT') 36 | # ret = client.get_max_borrowable(asset='ETH') 37 | # ret = client.get_max_transferable(asset='USDT') 38 | # ret = client.stream_get_listen_key() 39 | # ret = client.stream_keepalive(listenKey='BpJlFdbgdqQm9n7WQHG2URKSeu8qF1NZxFY02YgcyWzXNSMvHS065qNdJZqq') 40 | # ret = client.stream_close(listenKey='BpJlFdbgdqQm9n7WQHG2URKSeu8qF1NZxFY02YgcyWzXNSMvHS065qNdJZqq') 41 | 42 | print(ret) 43 | -------------------------------------------------------------------------------- /tests/binance/spot_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import os 3 | from exchange.binance.client import Client 4 | from exchange.binance.enums import * 5 | 6 | api_key = os.environ.get('BINANCE_API_KEY') 7 | secret_key = os.environ.get('BINANCE_SECRET_KEY') 8 | 9 | if __name__ == "__main__": 10 | client = Client(api_key, secret_key) 11 | 12 | # ret = client.create_order(symbol='ETHUSDT', side=SIDE_BUY, type=ORDER_TYPE_LIMIT, 13 | # timeInForce=TIME_IN_FORCE_GTC, price=180, quantity=0.1) 14 | # ret = client.create_order(symbol='ETHUSDT', side=SIDE_SELL, type=ORDER_TYPE_LIMIT, 15 | # timeInForce=TIME_IN_FORCE_GTC, price=210, quantity=0.1) 16 | # ret = client.get_all_orders(symbol='ETHUSDT') 17 | # ret = client.get_order(symbol='ETHUSDT', orderId='391540562') 18 | # ret = client.get_open_orders(symbol='ETHUSDT') 19 | # ret = client.get_my_trades(symbol='ETHUSDT') 20 | # ret = client.get_account() 21 | ret = client.cancel_order(symbol='ETHUSDT', orderId='391571598') 22 | print(ret) 23 | 24 | -------------------------------------------------------------------------------- /tests/md_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | sys.path.append('../') 4 | 5 | from datetime import datetime 6 | import unittest 7 | from pprint import pprint 8 | 9 | import numpy as np 10 | import pandas as pd 11 | import talib 12 | import utils.ti as ti 13 | 14 | import common.xquant as xq 15 | import common.kline as kl 16 | from md.dbmd import DBMD 17 | 18 | class TestDBMD(unittest.TestCase): 19 | symbol = "eth_usdt" 20 | digits = 4 21 | 22 | interval = kl.KLINE_INTERVAL_1DAY 23 | interval_td = kl.get_interval_timedelta(interval) 24 | pre_count = 150 25 | display_count = 10 26 | 27 | tick_interval = kl.KLINE_INTERVAL_1MINUTE 28 | tick_collection = kl.get_kline_collection(symbol, tick_interval) 29 | tick_td = kl.get_interval_timedelta(tick_interval) 30 | 31 | start_time = datetime(2017, 9, 1) 32 | end_time = datetime(2020, 8, 1) 33 | md = DBMD("binance") 34 | 35 | def setUp(self): 36 | self.md.tick_time = self.start_time 37 | tick = (self.end_time - self.start_time) / self.tick_td 38 | print("tick td=%s, tick=%d, time rang: %s ~ %s" % (self.tick_td, tick, self.start_time, self.end_time)) 39 | 40 | def tearDown(self): 41 | pass 42 | 43 | def perf_get_json_klines(self): 44 | while self.md.tick_time < self.end_time: 45 | klines = self.md.get_json_klines(self.symbol, self.interval, self.pre_count + self.display_count) 46 | self.md.tick_time += self.tick_td 47 | 48 | def perf_get_klines(self): 49 | while self.md.tick_time < self.end_time: 50 | klines = self.md.get_klines(self.symbol, self.interval, self.pre_count + self.display_count) 51 | self.md.tick_time += self.tick_td 52 | 53 | 54 | def perf_get_klines_adv(self): 55 | total_interval_count = int((self.end_time - self.start_time) / self.interval_td) + self.pre_count 56 | print("total_interval_count: %s" % (total_interval_count)) 57 | interval_collection = kl.get_kline_collection(self.symbol, self.interval) 58 | interval_klines = self.md.get_original_klines(interval_collection, self.start_time - self.interval_td * self.pre_count, self.end_time) 59 | kl = interval_klines[0] 60 | print("open_time: %s" % (self.md.get_kline_open_time(kl))) 61 | #print("json: %s" % (kl)) 62 | 63 | ti.EMA(interval_klines, "close", 13) 64 | ti.EMA(interval_klines, "close", 21) 65 | ti.EMA(interval_klines, "close", 55) 66 | ti.BIAS_EMA(interval_klines, 13, 21) 67 | ti.BIAS_EMA(interval_klines, 21, 55) 68 | ti.RSI(interval_klines, "close") 69 | #print(interval_klines[self.pre_count:self.pre_count+2]) 70 | #print(interval_klines[-2:]) 71 | #pprint(interval_klines[self.pre_count]) 72 | #pprint(interval_klines[-1]) 73 | 74 | for i in range(self.pre_count+1): 75 | if self.md.get_kline_open_time(interval_klines[i]) >= self.start_time: 76 | break 77 | interval_idx = i 78 | kl = interval_klines[interval_idx] 79 | print("interval_idx: %s" % (interval_idx)) 80 | print("open time: %s" % (self.md.get_kline_open_time(kl))) 81 | #print("json: %s" % (kl)) 82 | 83 | for i in range(interval_idx, len(interval_klines)): 84 | start_i = i - self.pre_count 85 | if start_i < 0: 86 | start_i = 0 87 | history_kls = interval_klines[start_i:i] 88 | #print(len(history_kls)) 89 | 90 | interval_open_time = self.md.get_kline_open_time(interval_klines[i]) 91 | #print(interval_open_time) 92 | 93 | tick_klines = self.md.get_original_klines(self.tick_collection, interval_open_time, interval_open_time+self.interval_td) 94 | new_interval_kl = tick_klines[0] 95 | for tick_kl in tick_klines[1:]: 96 | tick_open_time = self.md.get_kline_open_time(tick_kl) 97 | #print(tick_open_time) 98 | new_interval_kl["close"] = tick_kl["close"] 99 | new_interval_kl["close_time"] = tick_kl["close_time"] 100 | 101 | if new_interval_kl["high"] < tick_kl["high"]: 102 | new_interval_kl["high"] = tick_kl["high"] 103 | if new_interval_kl["low"] > tick_kl["low"]: 104 | new_interval_kl["low"] = tick_kl["low"] 105 | 106 | cur_kls = history_kls + [new_interval_kl] 107 | ti.EMA(cur_kls, "close", 13) 108 | ti.EMA(cur_kls, "close", 21) 109 | ti.EMA(cur_kls, "close", 55) 110 | ti.BIAS_EMA(cur_kls, 13, 21) 111 | ti.BIAS_EMA(cur_kls, 21, 55) 112 | ti.RSI(cur_kls, "close") 113 | #pprint(cur_kls[-2]) 114 | #pprint(cur_kls[-1]) 115 | #return 116 | 117 | 118 | 119 | if __name__ == '__main__': 120 | unittest.main() 121 | -------------------------------------------------------------------------------- /tools/account.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | from exchange.exchange import get_exchange_names, create_exchange 6 | from exchange.binanceExchange import BinanceExchange 7 | from tabulate import tabulate as tb 8 | import pprint 9 | 10 | 11 | if __name__ == "__main__": 12 | parser = argparse.ArgumentParser(description='account infomation') 13 | parser.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange name') 14 | args = parser.parse_args() 15 | # print(args) 16 | if not (args.exchange): 17 | parser.print_help() 18 | exit(1) 19 | 20 | exchange = create_exchange(args.exchange) 21 | if not exchange: 22 | print("exchange name error!") 23 | exit(1) 24 | exchange.connect() 25 | 26 | account = exchange.get_account() 27 | print("account info:" ) 28 | pprint.pprint(account) 29 | -------------------------------------------------------------------------------- /tools/auto_repay.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | from exchange.binanceMargin import BinanceMargin 6 | from decimal import Decimal 7 | import time 8 | 9 | if __name__ == "__main__": 10 | parser = argparse.ArgumentParser(description='account') 11 | parser.add_argument('-e', help='exchange') 12 | args = parser.parse_args() 13 | # print(args) 14 | if not (args.e): 15 | parser.print_help() 16 | exit(1) 17 | 18 | if args.e == "binance": 19 | exchange = BinanceMargin(debug=True) 20 | else: 21 | print("exchange error!") 22 | exit(1) 23 | 24 | account = exchange.get_account() 25 | # print("account info: %s" % account) 26 | 27 | for i in account['balances']: 28 | debt = Decimal(i['interest']) + Decimal(i['borrowed']) 29 | asset = Decimal(i['free']) 30 | if debt > 0 and asset > 0: 31 | repay = min(debt, asset) 32 | now = time.strftime("%Y-%m-%d %H:%M:%S", time.localtime()) 33 | print("%s Repay %s: %s" % (now, i['asset'], repay)) 34 | exchange.repay(i['asset'], repay) 35 | -------------------------------------------------------------------------------- /tools/balance.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import common.kline as kl 6 | from exchange.exchange import get_exchange_names, create_exchange 7 | from exchange.binanceExchange import BinanceExchange 8 | from tabulate import tabulate as tb 9 | from datetime import datetime 10 | import pprint 11 | 12 | from common.xquant import creat_symbol, get_balance_coin, get_balance_free, get_balance_frozen 13 | 14 | 15 | if __name__ == "__main__": 16 | parser = argparse.ArgumentParser(description='account infomation') 17 | parser.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange name') 18 | parser.add_argument('-basecoin', default='usdt', help='assert sum by base coin') 19 | args = parser.parse_args() 20 | # print(args) 21 | if not (args.exchange): 22 | parser.print_help() 23 | exit(1) 24 | 25 | exchange = create_exchange(args.exchange) 26 | if not exchange: 27 | print("exchange name error!") 28 | exit(1) 29 | exchange.connect() 30 | 31 | balances = exchange.get_all_balances() 32 | print(" %s %s balances info:" % (datetime.now(), args.exchange) ) 33 | #print(tb(balances)) 34 | 35 | if exchange.kline_data_type == kl.KLINE_DATA_TYPE_LIST: 36 | closeseat = exchange.kline_idx_close 37 | else: 38 | closeseat = exchange.kline_key_close 39 | 40 | total_value = 0 41 | for item in balances: 42 | amount = max(get_balance_free(item), get_balance_frozen(item)) 43 | if amount < 0: 44 | continue 45 | 46 | coin = get_balance_coin(item) 47 | if coin.upper() == args.basecoin.upper(): 48 | value = amount 49 | else: 50 | #print(coin) 51 | symbol = creat_symbol(coin, args.basecoin) 52 | klines = exchange.get_klines_1min(symbol, size=1) 53 | price = float(klines[-1][closeseat]) 54 | value = price * amount 55 | 56 | total_value += value 57 | item['value'] = value 58 | 59 | print(tb(balances)) 60 | print("total value: %s %s" % (total_value, args.basecoin)) 61 | -------------------------------------------------------------------------------- /tools/order.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import common.bill as bl 6 | import common.xquant as xq 7 | from exchange.exchange import get_exchange_names, create_exchange 8 | from exchange.binanceExchange import BinanceExchange 9 | 10 | 11 | def send_order(args): 12 | exchange = create_exchange(args.exchange) 13 | if not exchange: 14 | print("exchange name error!") 15 | exit(1) 16 | exchange.connect() 17 | exchange.send_order(args.direction, args.action, args.type, args.symbol, args.price, args.amount) 18 | 19 | 20 | def cancel_order(args): 21 | exchange = create_exchange(args.exchange) 22 | if not exchange: 23 | print("exchange name error!") 24 | exit(1) 25 | exchange.connect() 26 | exchange.cancel_order(args.symbol, args.orderid) 27 | 28 | 29 | def query_orders(args): 30 | exchange = create_exchange(args.exchange) 31 | if not exchange: 32 | print("exchange name error!") 33 | exit(1) 34 | exchange.connect() 35 | orders = exchange.get_open_orders(args.symbol) 36 | print(orders) 37 | 38 | 39 | if __name__ == "__main__": 40 | parser = argparse.ArgumentParser(description='account') 41 | 42 | subparsers = parser.add_subparsers(help='sub-command help') 43 | 44 | parser_send = subparsers.add_parser('send', help='send order') 45 | parser_send.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange') 46 | parser_send.add_argument('-symbol', required=True, help='symbol (btc_usdt)') 47 | parser_send.add_argument('-price', required=True, type=float, help='price') 48 | parser_send.add_argument('-amount', required=True, type=float, help='amount') 49 | parser_send.add_argument('-direction', required=True, choices=bl.directions, help='direction') 50 | parser_send.add_argument('-action', choices=bl.actions, help='action') 51 | parser_send.add_argument('-type', choices=xq.ordertypes, default=xq.ORDER_TYPE_LIMIT, help='order type') 52 | parser_send.set_defaults(func=send_order) 53 | 54 | parser_cancel = subparsers.add_parser('cancel', help='cancel order') 55 | parser_cancel.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange') 56 | parser_cancel.add_argument('-symbol', required=True, help='symbol (btc_usdt)') 57 | parser_cancel.add_argument('-orderid', required=True, help='order id') 58 | parser_cancel.set_defaults(func=cancel_order) 59 | 60 | parser_query = subparsers.add_parser('query', help='query open orders') 61 | parser_query.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange') 62 | parser_query.add_argument('-symbol', required=True, help='symbol (btc_usdt)') 63 | parser_query.set_defaults(func=query_orders) 64 | 65 | args = parser.parse_args() 66 | # print(args) 67 | if hasattr(args, 'func'): 68 | args.func(args) 69 | -------------------------------------------------------------------------------- /tools/position.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | import common.xquant as xq 6 | import common.bill as bl 7 | from common.instance import get_strategy_instance 8 | from engine.realengine import RealEngine 9 | 10 | if __name__ == "__main__": 11 | parser = argparse.ArgumentParser(description='close postion') 12 | parser.add_argument('-sii', help='strategy instance id') 13 | parser.add_argument('-direction', choices=bl.directions, help='direction') 14 | parser.add_argument('-action', choices=bl.actions, help='action') 15 | #parser.add_argument('-pr', type=float, default=0, help='postion rate') 16 | parser.add_argument('-rmk', help='remark') 17 | args = parser.parse_args() 18 | print(args) 19 | if not (args.sii and args.action): 20 | parser.print_help() 21 | exit(1) 22 | 23 | instance = get_strategy_instance(args.sii) 24 | config = xq.get_strategy_config(instance['config_path']) 25 | re = RealEngine(args.sii, instance['exchange'], config, instance['value']) 26 | 27 | symbol = config['symbol'] 28 | klines = re.md.get_klines(symbol, config["kline"]["interval"], 1) 29 | if len(klines) <= 0: 30 | exit(1) 31 | 32 | cur_price = float(klines[-1][re.md.get_kline_seat_close()]) 33 | pst_info = re.get_position(symbol, cur_price) 34 | if args.action in [bl.OPEN_POSITION, bl.UNLOCK_POSITION]: 35 | pst_rate = 1 36 | else: 37 | pst_rate = 0 38 | 39 | print('before position info: %s' % (pst_info)) 40 | print('args.action: %s, pst_rate: %g' % (args.action, pst_rate)) 41 | if args.rmk: 42 | rmk = args.rmk 43 | else: 44 | rmk = args.action 45 | rmk += ": " 46 | re.send_order(symbol, pst_info, cur_price, args.direction, args.action, pst_rate, None, rmk) 47 | 48 | after_pst_info = re.get_position(symbol, cur_price) 49 | print('after position info: %s' % (after_pst_info)) 50 | -------------------------------------------------------------------------------- /tools/slippage.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | from exchange.exchange import get_exchange_names, create_exchange 6 | from exchange.binanceExchange import BinanceExchange 7 | 8 | directions = ["buy", "sell"] 9 | 10 | 11 | if __name__ == "__main__": 12 | parser = argparse.ArgumentParser(description='Slippage Calculation') 13 | parser.add_argument('-exchange', default=BinanceExchange.name, choices=get_exchange_names(), help='exchange name') 14 | parser.add_argument('-symbol', required=True, help='symbol, eg: btc_usdt') 15 | parser.add_argument('-direction', required=True, choices=directions, help='direction') 16 | parser.add_argument('-amount', type=float, required=True, help='amount') 17 | 18 | args = parser.parse_args() 19 | # print(args) 20 | 21 | symbol = args.symbol 22 | direction = args.direction 23 | amount = args.amount 24 | 25 | exchange = create_exchange(args.exchange) 26 | if not exchange: 27 | print("exchange name error!") 28 | exit(1) 29 | print("%s" % (args.exchange) ) 30 | exchange.connect() 31 | 32 | klines = exchange.get_klines_1day(symbol, 1) 33 | cur_price = float(klines[-1][4]) 34 | 35 | book = exchange.get_order_book(symbol, 1000) 36 | # print(book) 37 | 38 | if direction == 'buy': 39 | orders = book['asks'] 40 | else: 41 | orders = book['bids'] 42 | 43 | deal = 0 44 | cost = 0 45 | for order in orders: 46 | order_amount = float(order[1]) 47 | order_price = float(order[0]) 48 | if amount - deal > order_amount: 49 | deal += order_amount 50 | cost += order_amount * order_price 51 | else: 52 | cost += (amount - deal) * order_price 53 | deal = amount 54 | break 55 | 56 | if deal < amount: 57 | print("The order book depth is not enough. Please get more orders") 58 | exit(1) 59 | average_price = cost / amount 60 | if direction == 'buy': 61 | slippage = (average_price - cur_price) * 100 / cur_price 62 | else: 63 | slippage = (cur_price - average_price) * 100 / cur_price 64 | print("Current price: ", cur_price) 65 | print("Average deal price:", average_price) 66 | print("Deal amount: ", amount) 67 | print("Total cost: ", cost) 68 | print("Slippage: ", slippage, "%") 69 | -------------------------------------------------------------------------------- /tools/strategy.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import json 3 | import argparse 4 | import sys 5 | sys.path.append('../') 6 | from common.instance import add_strategy_instance, update_strategy_instance, delete_strategy_instance, get_strategy_instance 7 | 8 | """ 9 | python3 strategy.py -m add -sii gwEma2BtcUsdt -p '{"user":"gw", "exchange" : "binance_margin","config_path" : "/home/gw/margin/xq/config/ema/ema2_btc_usdt_1d.jsn","value" : 10000}' 10 | python3 strategy.py -m update -sii gwEma2BtcUsdt -p '{"value" : 18000}' 11 | python3 strategy.py -m delete -sii gwEma2BtcUsdt 12 | python3 strategy.py -m show -sii gwEma2BtcUsdt 13 | """ 14 | if __name__ == "__main__": 15 | parser = argparse.ArgumentParser(description='Strategy Operation') 16 | parser.add_argument('-m', help='Mode: add, delete, update, show') 17 | parser.add_argument('-sii', help='Instance ID') 18 | parser.add_argument('-p', help='Parameters') 19 | args = parser.parse_args() 20 | 21 | print(args) 22 | 23 | if not args.m and not args.sii: 24 | parser.print_help() 25 | exit(1) 26 | 27 | if args.m == 'add' and not args.p: 28 | parser.print_help() 29 | exit(1) 30 | 31 | if args.m == 'udpate' and not args.p: 32 | parser.print_help() 33 | exit(1) 34 | 35 | if args.m == 'add': 36 | add_strategy_instance({**{"instance_id": args.sii}, **json.loads(args.p)}) 37 | elif args.m == 'update': 38 | update_strategy_instance({"instance_id": args.sii}, json.loads(args.p)) 39 | elif args.m == 'delete': 40 | delete_strategy_instance({"instance_id": args.sii}) 41 | elif args.m == 'show': 42 | print(get_strategy_instance(args.sii)) 43 | else: 44 | parser.print_help() 45 | exit(1) 46 | 47 | exit(1) 48 | -------------------------------------------------------------------------------- /tools/trade.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python3 2 | import sys 3 | sys.path.append('../') 4 | import argparse 5 | from exchange.exchange import create_exchange 6 | 7 | if __name__ == "__main__": 8 | parser = argparse.ArgumentParser(description='trade') 9 | parser.add_argument('-e', help='exchange') 10 | parser.add_argument('-s', help='symbol (btc_usdt)') 11 | args = parser.parse_args() 12 | # print(args) 13 | if not (args.e and args.s): 14 | parser.print_help() 15 | exit(1) 16 | 17 | exchange = create_exchange(args.e) 18 | if not exchange: 19 | print("exchange error!") 20 | exit(1) 21 | exchange.connect() 22 | 23 | trades = exchange.get_trades(args.s) 24 | print("trades: %s" % trades) 25 | -------------------------------------------------------------------------------- /utils/__init__.py: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/xiebing77/xquant/fa0b7afeca292326259ee7e64693e4501de2a735/utils/__init__.py -------------------------------------------------------------------------------- /utils/email_obj.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import mimetypes 3 | from email import encoders 4 | 5 | import smtplib 6 | from email.mime.multipart import MIMEMultipart 7 | from email.mime.text import MIMEText 8 | from email.mime.base import MIMEBase 9 | 10 | 11 | def attachment(filename): 12 | """ 13 | build attachment object 14 | """ 15 | fd = open(filename, 'rb') 16 | mime_type, mime_encoding = mimetypes.guess_type(filename) 17 | if mime_encoding or (mime_type is None): 18 | mime_type = 'application/octet-stream' 19 | 20 | maintype, subtype = mime_type.split('/') 21 | 22 | if maintype == 'text': 23 | ret_val = MIMEText(fd.read(), _subtype=subtype) 24 | else: 25 | ret_val = MIMEBase(maintype, subtype) 26 | ret_val.set_payload(fd.read()) 27 | encoders.encode_base64(ret_val) 28 | 29 | ret_val.add_header('Content-Disposition', 'attachment', 30 | filename=filename.split('/')[-1]) 31 | fd.close() 32 | return ret_val 33 | 34 | 35 | class EmailObj(object): 36 | def __init__(self, server, user, pwd, port): 37 | self.server = server 38 | self.user = user 39 | self.pwd = pwd 40 | self.port = port 41 | 42 | def send_mail(self, subject, msg_body, from_addr, to_addr, cc_addr='', attachments=None): 43 | msg = MIMEMultipart() 44 | msg['To'] = to_addr 45 | msg['From'] = from_addr 46 | msg['Cc'] = cc_addr 47 | msg['Subject'] = subject 48 | 49 | body = MIMEText(msg_body, _subtype='html', _charset='utf-8') 50 | msg.attach(body) 51 | 52 | if attachments is not None: 53 | for filename in attachments: 54 | msg.attach(attachment(filename)) 55 | 56 | s = smtplib.SMTP(self.server + ":" + self.port) 57 | #s = smtplib.SMTP_SSL(self.server + ":587") 58 | # s.set_debuglevel(True) 59 | s.ehlo() 60 | s.starttls() 61 | s.login(self.user, self.pwd) 62 | return s.sendmail(from_addr, cc_addr.split(",") + [to_addr], msg.as_string()) 63 | -------------------------------------------------------------------------------- /utils/indicator_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | sys.path.append('../') 4 | 5 | from datetime import datetime 6 | import unittest 7 | import pandas as pd 8 | import talib 9 | 10 | from md.dbmd import DBMD 11 | import utils.indicator as ic 12 | import xlib.ti as xti 13 | import common.xquant as xq 14 | import common.kline as kl 15 | import exchange.exchange as ex 16 | 17 | class TestIndicator(unittest.TestCase): 18 | 19 | def setUp(self): 20 | self.symbol = "eth_usdt" 21 | self.digits = 4 22 | self.interval = kl.KLINE_INTERVAL_1DAY 23 | self.display_count = 10 24 | 25 | exchange_name = ex.BINANCE_SPOT_EXCHANGE_NAME 26 | self.md = DBMD(exchange_name) 27 | self.md.tick_time = datetime(2019, 1, 1, 8) 28 | 29 | self.klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 30 | 31 | self.klines_df = pd.DataFrame(self.klines, columns=self.md.get_kline_column_names()) 32 | 33 | self.closeseat = self.md.get_kline_seat_close() 34 | self.closekey = ex.get_kline_key_close(exchange_name) 35 | 36 | self.count = 5000 37 | self.steps = 1 38 | self.td = kl.get_interval_timedelta(kl.KLINE_INTERVAL_1MINUTE) * self.steps 39 | 40 | 41 | def tearDown(self): 42 | pass 43 | 44 | 45 | def _test_rsi(self): 46 | print("") 47 | py_rsis = ic.py_rsis(self.klines, self.closeseat) 48 | ta_rsis = talib.RSI(self.klines_df[self.closekey]) 49 | #print(" X-Lib rsis: ", [round(a, 6) for a in py_rsis][-self.display_count:]) 50 | #print("TA-Lib rsis: ", [round(a, 6) for a in ta_rsis][-self.display_count:]) 51 | 52 | for i in range(-self.display_count, 0): 53 | #self.assertEqual(py_rsis[i], ta_rsis[i]) 54 | self.assertTrue(abs(py_rsis[i] - ta_rsis.values[i]) < 0.01) 55 | 56 | 57 | def test_ema(self): 58 | print("test ema") 59 | period = 55 60 | 61 | pys = ic.py_emas(self.klines, self.closeseat, period) 62 | tas = talib.EMA(self.klines_df[self.closekey], period) 63 | 64 | closes = [c for c in pd.to_numeric(self.klines_df[self.closekey])] 65 | xtis = xti.EMA(closes, period) 66 | 67 | #kl_xtis = xti.EMA_KL(self.klines, self.closeseat, period) 68 | 69 | print(" ic emas: ", [round(a, 6) for a in pys][-self.display_count:]) 70 | print("TA-Lib emas: ", [round(a, 6) for a in tas][-self.display_count:]) 71 | print(" X-Lib emas: ", [round(a, 6) for a in xtis][-self.display_count:]) 72 | #print(" X-Lib kl emas: ", [round(a, 6) for a in kl_xtis][-self.display_count:]) 73 | 74 | for i in range(-self.display_count, 0): 75 | #self.assertEqual(pys[i], tas[i]) 76 | self.assertTrue(abs(pys[i] - tas.values[i]) < 0.01) 77 | self.assertTrue(abs(xtis[i] - tas.values[i]) < 0.01) 78 | #self.assertTrue(abs(kl_xtis[i] - tas.values[i]) < 0.01) 79 | 80 | 81 | def _test_bias(self): 82 | print("") 83 | period_s = 21 84 | period_l = 55 85 | 86 | semas = ic.py_emas(self.klines, self.closeseat, period_s) 87 | ta_semas = talib.EMA(self.klines_df[self.closekey], period_s) 88 | print(" X-Lib semas(%d): %s" % (period_s, [round(a, self.digits) for a in semas][-self.display_count:])) 89 | print("TA-Lib semas(%d): %s" % (period_s, [round(a, self.digits) for a in ta_semas][-self.display_count:])) 90 | for i in range(-self.display_count, 0): 91 | self.assertTrue(abs(semas[i] - ta_semas.values[i]) < 0.01) 92 | 93 | lemas = ic.py_emas(self.klines, self.closeseat, period_l) 94 | ta_lemas = talib.EMA(self.klines_df[self.closekey], period_l) 95 | print(" X-Lib lemas(%d): %s" % (period_l, [round(a, self.digits) for a in lemas][-self.display_count:])) 96 | print("TA-Lib lemas(%d): %s" % (period_l, [round(a, self.digits) for a in ta_lemas][-self.display_count:])) 97 | for i in range(-self.display_count, 0): 98 | self.assertTrue(abs(lemas[i] - ta_lemas.values[i]) < 0.01) 99 | 100 | biases = ic.py_biases(semas, lemas) 101 | ta_biases = ic.pd_biases(ta_semas, ta_lemas) 102 | print(" X-Lib biases(%d, %d): %s" % (period_s, period_l, [round(a, 4) for a in biases][-self.display_count:])) 103 | print("TA-Lib biases(%d, %d): %s" % (period_s, period_l, [round(a, 4) for a in ta_biases][-self.display_count:])) 104 | for i in range(-self.display_count, 0): 105 | self.assertTrue(abs(biases[i] - ta_biases.values[i]) < 0.01) 106 | 107 | 108 | 109 | def _test_nbias(self): 110 | print("") 111 | period = 55 112 | 113 | closes = [float(k[self.closeseat]) for k in self.klines] 114 | print(" closes: ", [round(a, self.digits) for a in closes][-self.display_count:]) 115 | 116 | emas = ic.py_emas(self.klines, self.closeseat, period) 117 | ta_emas = talib.EMA(self.klines_df[self.closekey], period) 118 | print(" X-Lib emas(%d): %s" % (period, [round(a, self.digits) for a in emas][-self.display_count:])) 119 | print("TA-Lib emas(%d): %s" % (period, [round(a, self.digits) for a in ta_emas][-self.display_count:])) 120 | for i in range(-self.display_count, 0): 121 | self.assertTrue(abs(emas[i] - ta_emas.values[i]) < 0.01) 122 | 123 | nbiases = ic.py_biases(closes, emas) 124 | ta_nbiases = ic.pd_biases(pd.to_numeric(self.klines_df[self.closekey]), ta_emas) 125 | print(" X-Lib nbiases(%d): %s" % (period, [round(a, 4) for a in nbiases][-self.display_count:])) 126 | print("TA-Lib nbiases(%d): %s" % (period, [round(a, 4) for a in ta_nbiases][-self.display_count:])) 127 | for i in range(-self.display_count, 0): 128 | self.assertTrue(abs(nbiases[i] - ta_nbiases.values[i]) < 0.01) 129 | 130 | 131 | def _test_perf_xlib_rsi(self): 132 | for i in range(self.count): 133 | klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 134 | 135 | py_rsis = ic.py_rsis(klines, self.closeseat) 136 | py_rsis = [round(a, 3) for a in py_rsis][-self.display_count:] 137 | 138 | self.md.tick_time += self.td 139 | 140 | def _test_perf_talib_rsi(self): 141 | for i in range(self.count): 142 | klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 143 | klines_df = pd.DataFrame(klines, columns=self.md.get_kline_column_names()) 144 | 145 | ta_rsis = talib.RSI(klines_df[self.closekey]) 146 | ta_rsis = [round(a, 3) for a in ta_rsis][-self.display_count:] 147 | 148 | self.md.tick_time += self.td 149 | 150 | def perf_py_ema(self): 151 | period = 55 152 | for i in range(self.count): 153 | klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 154 | 155 | emas = ic.py_emas(self.klines, self.closeseat, period) 156 | 157 | self.md.tick_time += self.td 158 | 159 | def perf_xlib_ema(self): 160 | period = 55 161 | for i in range(self.count): 162 | klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 163 | 164 | closes = [float(k[self.closeseat]) for k in klines] 165 | emas = xti.EMA(closes, period) 166 | 167 | self.md.tick_time += self.td 168 | 169 | def perf_xlib_ema_kl(self): 170 | period = 55 171 | for i in range(self.count): 172 | klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 173 | 174 | #emas = xti.EMA_KL(self.klines, self.closeseat, period) 175 | 176 | self.md.tick_time += self.td 177 | 178 | if __name__ == '__main__': 179 | unittest.main() 180 | -------------------------------------------------------------------------------- /utils/tal.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """各种指标""" 3 | import talib 4 | import utils.tools as ts 5 | 6 | def STOCHRSI(klines_df, timeperiod=14): 7 | fastk, fastd = talib.STOCHRSI(klines_df["close"], timeperiod=14, fastk_period=5, fastd_period=3, fastd_matype=0) 8 | return [ts.reserve_float(a, 6) for a in fastk], [ts.reserve_float(a, 6) for a in fastd] 9 | 10 | def APO(klines_df, timeperiod=14): 11 | real = talib.APO(klines_df["close"], fastperiod=12, slowperiod=26, matype=0) 12 | return [ts.reserve_float(a, 6) for a in real] 13 | 14 | def DX(klines_df, timeperiod=14): 15 | real = talib.DX(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 16 | return [ts.reserve_float(a, 6) for a in real] 17 | 18 | def ADX(klines_df, timeperiod=14): 19 | real = talib.ADX(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 20 | return [ts.reserve_float(a, 6) for a in real] 21 | 22 | def ADXR(klines_df, timeperiod=14): 23 | real = talib.ADXR(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 24 | return [ts.reserve_float(a, 6) for a in real] 25 | 26 | def CCI(klines_df, timeperiod=14): 27 | real = talib.CCI(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 28 | return [ts.reserve_float(a, 6) for a in real] 29 | 30 | def MFI(klines_df, timeperiod=14): 31 | real = talib.MFI(klines_df["high"], klines_df["low"], klines_df["close"], klines_df["volume"], timeperiod=14) 32 | return [ts.reserve_float(a, 6) for a in real] 33 | 34 | def PLUS_DM(klines_df, timeperiod=14): 35 | real = talib.PLUS_DM(klines_df["high"], klines_df["low"], timeperiod=14) 36 | return [ts.reserve_float(a, 6) for a in real] 37 | 38 | def MINUS_DM(klines_df, timeperiod=14): 39 | real = talib.MINUS_DM(klines_df["high"], klines_df["low"], timeperiod=14) 40 | return [ts.reserve_float(a, 6) for a in real] 41 | 42 | def TRIX(klines_df, timeperiod=30): 43 | real = talib.TRIX(klines_df["close"], timeperiod=timeperiod) 44 | return [ts.reserve_float(a, 6) for a in real] 45 | 46 | def WILLR(klines_df, timeperiod=14): 47 | real = talib.WILLR(klines_df["high"], klines_df["low"], klines_df["close"], timeperiod=14) 48 | return [ts.reserve_float(a, 6) for a in real] 49 | -------------------------------------------------------------------------------- /utils/ti_test.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | import sys 3 | sys.path.append('../') 4 | 5 | from datetime import datetime 6 | import unittest 7 | import pandas as pd 8 | import talib 9 | 10 | from md.dbmd import DBMD 11 | import utils.indicator as ic 12 | import utils.ti as ti 13 | import common.xquant as xq 14 | import common.kline as kl 15 | import exchange.exchange as ex 16 | 17 | def print_test_title(name): 18 | print("\n", "-"*8, "test", name, "-"*100) 19 | 20 | 21 | class TestIndicator(unittest.TestCase): 22 | 23 | def setUp(self): 24 | self.symbol = "eth_usdt" 25 | self.digits = 6 26 | self.interval = kl.KLINE_INTERVAL_1DAY 27 | self.display_count = 10 28 | 29 | exchange_name = ex.BINANCE_SPOT_EXCHANGE_NAME 30 | self.md = DBMD(exchange_name) 31 | self.md.tick_time = datetime(2019, 1, 1, 8) 32 | 33 | self.klines = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 34 | 35 | self.klines_df = pd.DataFrame(self.klines, columns=self.md.get_kline_column_names()) 36 | 37 | self.closeseat = self.md.get_kline_seat_close() 38 | self.highseat = self.md.get_kline_seat_high() 39 | self.lowseat = self.md.get_kline_seat_low() 40 | 41 | self.closekey = ex.get_kline_key_close(exchange_name) 42 | self.highkey = ex.get_kline_key_high(exchange_name) 43 | self.lowkey = ex.get_kline_key_low(exchange_name) 44 | 45 | self.count = 5000 46 | self.steps = 1 47 | self.td = kl.get_interval_timedelta(kl.KLINE_INTERVAL_1MINUTE) * self.steps 48 | 49 | 50 | def tearDown(self): 51 | pass 52 | 53 | def test_ma(self): 54 | print_test_title("ma") 55 | period = 55 56 | 57 | kls =self.klines 58 | ma_key = ti.MA(kls, self.closekey, period) 59 | print(" ti mas: ", [round(kl[ma_key], self.digits) for kl in kls[-self.display_count:]]) 60 | 61 | tas = talib.MA(self.klines_df[self.closekey], period) 62 | print("TA-Lib mas: ", [round(a, self.digits) for a in tas][-self.display_count:]) 63 | 64 | for i in range(-self.display_count, 0): 65 | self.assertTrue(abs(kls[i][ma_key] - tas.values[i]) < 0.01) 66 | 67 | 68 | def test_ema(self): 69 | print_test_title("ema") 70 | period = 55 71 | 72 | kls =self.klines 73 | ema_key = ti.EMA(kls, self.closekey, period) 74 | print(" ti emas: ", [round(kl[ema_key], self.digits) for kl in kls[-self.display_count:]]) 75 | 76 | tas = talib.EMA(self.klines_df[self.closekey], period) 77 | print("TA-Lib emas: ", [round(a, self.digits) for a in tas][-self.display_count:]) 78 | 79 | for i in range(-self.display_count, 0): 80 | self.assertTrue(abs(kls[i][ema_key] - tas.values[i]) < 0.01) 81 | 82 | 83 | def test_bias(self): 84 | print_test_title("bias") 85 | period_s = 21 86 | period_l = 55 87 | 88 | kls =self.klines 89 | ema_s_key = ti.EMA(kls, self.closekey, period_s) 90 | ta_semas = talib.EMA(self.klines_df[self.closekey], period_s) 91 | print(" ti semas(%d): %s" % (period_s, [round(kl[ema_s_key], self.digits) for kl in kls[-self.display_count:]])) 92 | print("TA-Lib semas(%d): %s" % (period_s, [round(a, self.digits) for a in ta_semas][-self.display_count:])) 93 | for i in range(-self.display_count, 0): 94 | self.assertTrue(abs(kls[i][ema_s_key] - ta_semas.values[i]) < 0.01) 95 | 96 | ema_l_key = ti.EMA(kls, self.closekey, period_l) 97 | ta_lemas = talib.EMA(self.klines_df[self.closekey], period_l) 98 | print(" ti lemas(%d): %s" % (period_l, [round(kl[ema_l_key], self.digits) for kl in kls[-self.display_count:]])) 99 | print("TA-Lib lemas(%d): %s" % (period_l, [round(a, self.digits) for a in ta_lemas][-self.display_count:])) 100 | for i in range(-self.display_count, 0): 101 | self.assertTrue(abs(kls[i][ema_l_key] - ta_lemas.values[i]) < 0.01) 102 | 103 | bias_sl_key = ti.BIAS_EMA(kls, self.closekey, period_s, period_l) 104 | ta_biases = ic.pd_biases(ta_semas, ta_lemas) 105 | print(" ti biases(%d, %d): %s" % (period_s, period_l, [round(kl[bias_sl_key], self.digits) for kl in kls[-self.display_count:]])) 106 | print("TA-Lib biases(%d, %d): %s" % (period_s, period_l, [round(a, self.digits) for a in ta_biases][-self.display_count:])) 107 | for i in range(-self.display_count, 0): 108 | self.assertTrue(abs(kls[i][bias_sl_key] - ta_biases.values[i]) < 0.01) 109 | 110 | 111 | def test_rsi(self): 112 | print_test_title("rsi") 113 | 114 | kls =self.klines 115 | rsi_key = ti.RSI(kls, self.closekey) 116 | ta_rsis = talib.RSI(self.klines_df[self.closekey]) 117 | print(" ti rsis: ", [round(kl[rsi_key], self.digits) for kl in kls[-self.display_count:]]) 118 | print("TA-Lib rsis: ", [round(a, self.digits) for a in ta_rsis][-self.display_count:]) 119 | 120 | for i in range(-self.display_count, 0): 121 | self.assertTrue(abs(kls[i][rsi_key] - ta_rsis.values[i]) < 0.01) 122 | 123 | def test_macd(self): 124 | print_test_title("macd") 125 | 126 | kls =self.klines 127 | dif_key, signal_key, hist_key = ti.MACD(kls, self.closekey) 128 | difs, signals, hists = talib.MACD(self.klines_df[self.closekey]) 129 | print(" ti macd difs: ", [round(kl[dif_key], self.digits) for kl in kls[-self.display_count:]]) 130 | print("TA-Lib macd difs: ", [round(a, self.digits) for a in difs][-self.display_count:]) 131 | 132 | print(" ti macd signals: ", [round(kl[signal_key], self.digits) for kl in kls[-self.display_count:]]) 133 | print("TA-Lib macd signals: ", [round(a, self.digits) for a in signals][-self.display_count:]) 134 | 135 | print(" ti macd hists: ", [round(kl[hist_key], self.digits) for kl in kls[-self.display_count:]]) 136 | print("TA-Lib macd hists: ", [round(a, self.digits) for a in hists][-self.display_count:]) 137 | 138 | for i in range(-self.display_count, 0): 139 | self.assertTrue(abs(kls[i][dif_key] - difs.values[i]) < 0.01) 140 | self.assertTrue(abs(kls[i][signal_key] - signals.values[i]) < 0.01) 141 | self.assertTrue(abs(kls[i][hist_key] - hists.values[i]) < 0.01) 142 | 143 | def perf_macd(self): 144 | for i in range(self.count): 145 | kls = self.md.get_klines(self.symbol, self.interval, 150 + self.display_count) 146 | dif_key, signal_key, hist_key = ti.MACD(kls, self.closekey) 147 | 148 | kls_df = pd.DataFrame(kls, columns=self.md.get_kline_column_names()) 149 | difs, signals, hists = talib.MACD(kls_df[self.closekey]) 150 | 151 | i = -1 152 | self.assertTrue(abs(kls[i][dif_key] - difs.values[i]) < 0.01) 153 | self.assertTrue(abs(kls[i][signal_key] - signals.values[i]) < 0.01) 154 | self.assertTrue(abs(kls[i][hist_key] - hists.values[i]) < 0.01) 155 | 156 | self.md.tick_time += self.td 157 | 158 | def test_kd(self): 159 | print_test_title("kd") 160 | period = 9 161 | kls =self.klines 162 | kkey, dkey = ti.KD(kls, self.closekey, self.highkey, self.lowkey, period) 163 | ''' 164 | kls_df = self.klines_df 165 | slowk, slowd = talib.STOCH(high=kls_df[self.highkey], low=kls_df[self.lowkey], close=kls_df[self.closekey], 166 | fastk_period=period, slowk_period=3, slowk_matype=0, slowd_period=3, slowd_matype=0) 167 | ''' 168 | kds = ic.py_kdj(kls, self.highseat, self.lowseat, self.closeseat) 169 | 170 | 171 | print(" ti kd ks: ", [round(kl[kkey], self.digits) for kl in kls[-self.display_count:]]) 172 | #print("TA-Lib kd ks: ", [round(k, self.digits) for k in slowk][-self.display_count:]) 173 | print("indicator kd ks: ", [round(kd[1], self.digits) for kd in kds[-self.display_count:]]) 174 | 175 | print(" ti kd ds: ", [round(kl[dkey], self.digits) for kl in kls[-self.display_count:]]) 176 | #print("TA-Lib kd ds: ", [round(d, self.digits) for d in slowd][-self.display_count:]) 177 | print("indicator kd ds: ", [round(kd[2], self.digits) for kd in kds[-self.display_count:]]) 178 | 179 | for i in range(-self.display_count, 0): 180 | #self.assertTrue(abs(kls[i][kkey] - slowk.values[i]) < 0.01) 181 | #self.assertTrue(abs(kls[i][dkey] - slowd.values[i]) < 0.01) 182 | self.assertTrue(abs(kls[i][kkey] - kds[i][1]) < 0.01) 183 | self.assertTrue(abs(kls[i][dkey] - kds[i][2]) < 0.01) 184 | 185 | def _test_atr(self): 186 | name = "atr" 187 | period = 14 188 | print_test_title(name) 189 | 190 | kls =self.klines 191 | ti_key = ti._ATR(kls, self.highkey, self.lowkey, self.closekey, period) 192 | tas = talib.ATR(self.klines_df[self.highkey], self.klines_df[self.lowkey], self.klines_df[self.closekey], timeperiod=period) 193 | print(" ti %ss: %s" % (name, [round(kl[ti_key], self.digits) for kl in kls[-self.display_count:]])) 194 | print("TA-Lib %ss: %s" % (name, [round(a, self.digits) for a in tas][-self.display_count:])) 195 | 196 | for i in range(-self.display_count, 0): 197 | self.assertTrue(abs(kls[i][ti_key] - tas.values[i]) < 0.01) 198 | 199 | def test_trix(self): 200 | name = "trix" 201 | print_test_title(name) 202 | period = 30 203 | 204 | kls =self.klines 205 | ti_key = ti.TRIX(kls, self.closekey, period) 206 | tas = talib.TRIX(self.klines_df[self.closekey], timeperiod=period) 207 | print(" ti %ss: %s" % (name, [round(kl[ti_key], self.digits) for kl in kls[-self.display_count:]])) 208 | print("TA-Lib %ss: %s" % (name, [round(a, self.digits) for a in tas][-self.display_count:])) 209 | 210 | for i in range(-self.display_count, 0): 211 | self.assertTrue(abs(kls[i][ti_key] - tas.values[i]) < 0.01) 212 | 213 | 214 | if __name__ == '__main__': 215 | unittest.main() 216 | -------------------------------------------------------------------------------- /utils/tools.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """小工具函数""" 3 | import math 4 | from datetime import datetime, timedelta, time 5 | import logging 6 | import pandas as pd 7 | from decimal import Decimal 8 | import numpy as np 9 | 10 | MATH_FLOOR = 0 # 向下,舍去多余 11 | MATH_CEIL = 1 # 向上, 12 | MATH_ROUND = 2 # 四舍五入 13 | 14 | def parse_date_range(date_range): 15 | #print("time range: [ %s )" % date_range) 16 | dates = date_range.split("~") 17 | 18 | start_time = datetime.strptime(dates[0], "%Y-%m-%d") 19 | end_time = datetime.strptime(dates[1], "%Y-%m-%d") 20 | return start_time, end_time 21 | 22 | def parse_ic_keys(ss): 23 | print("keys: [ %s )" % ss) 24 | keys = {} 25 | 26 | if not ss: 27 | return keys 28 | for key in ss.split(","): 29 | keys[key] = True 30 | return keys 31 | 32 | def ax(ax, ic_key, x, y, c, drawstyle="default"): 33 | ax.set_ylabel(ic_key) 34 | ax.grid(True) 35 | ax.plot(x, y, c, label=ic_key, drawstyle=drawstyle) 36 | 37 | def createInstance(module_name, class_name, *args, **kwargs): 38 | # print("args :", args) 39 | # print("kwargs:", kwargs) 40 | module_meta = __import__(module_name, globals(), locals(), [class_name]) 41 | class_meta = getattr(module_meta, class_name) 42 | obj = class_meta(*args, **kwargs) 43 | return obj 44 | 45 | def get_decimal(number): 46 | return len(str(Decimal(str(number))-Decimal(number).to_integral()).split('.')[1]) 47 | 48 | def reserve_float_ceil(flo, float_digits=0): 49 | return reserve_float(flo, float_digits, MATH_CEIL) 50 | 51 | def multiply_ceil(var, const): 52 | decimal = get_decimal(var) 53 | return reserve_float_ceil(var * const, decimal) 54 | 55 | def multiply_floor(var, const): 56 | decimal = get_decimal(var) 57 | return reserve_float(var * const, decimal) 58 | 59 | def reserve_float(flo, float_digits=0, flag=MATH_FLOOR): 60 | """调整精度""" 61 | value_str = "%.11f" % flo 62 | return str_to_float(value_str, float_digits, flag) 63 | 64 | 65 | def str_to_float(string, float_digits=0, flag=MATH_FLOOR): 66 | """字符转浮点,并调整精度""" 67 | value_list = string.split(".") 68 | if len(value_list) == 1: 69 | return float(value_list[0]) 70 | 71 | elif len(value_list) == 2: 72 | new_value_str = ".".join([value_list[0], value_list[1][0:float_digits]]) 73 | new_value = float(new_value_str) 74 | if flag == MATH_FLOOR: 75 | pass 76 | elif flag == MATH_CEIL: 77 | if float(value_list[1][float_digits:]) > 0: 78 | new_value += math.pow(10, -float_digits) 79 | else: 80 | return None 81 | 82 | return new_value 83 | else: 84 | return None 85 | 86 | 87 | def cacl_today_fall_rate(klines, cur_price): 88 | """ 计算当天最高价的回落比例 """ 89 | today_high_price = pd.to_numeric(klines["high"].values[-1]) 90 | today_fall_rate = 1 - cur_price / today_high_price 91 | logging.info( 92 | "today high price(%f); fall rate(%f)", today_high_price, today_fall_rate 93 | ) 94 | return today_fall_rate 95 | 96 | 97 | def cacl_period_fall_rate(md, klines, start_time, cur_price): 98 | """ 计算开仓日期到现在最高价的回落比例 """ 99 | if start_time is None: 100 | return 101 | 102 | period_df = klines[ 103 | klines["open_time"].map(lambda x: int(x)) > md.get_data_ts_from_time(start_time) 104 | ] 105 | period_high_price = period_df["high"].apply(pd.to_numeric).max() 106 | 107 | period_fall_rate = 1 - cur_price / period_high_price 108 | logging.info( 109 | "period high price(%f), fall rate(%f), start time(%s)" 110 | % (period_high_price, period_fall_rate, start_time) 111 | ) 112 | return period_fall_rate 113 | 114 | 115 | def get_min_seat(arr): 116 | i_min = -len(arr) 117 | v_min = arr[i_min] 118 | for i in range(i_min+1, -1): 119 | v = arr[i] 120 | if v_min > v: 121 | v_min = v 122 | i_min = i 123 | return i_min 124 | 125 | def get_tops(arr, c): 126 | tops = [] 127 | #print(arr) 128 | for i in range(-len(arr), 0): 129 | bi = i-c 130 | if bi < -len(arr): 131 | bi = -len(arr) 132 | 133 | ei = i + 1 + c 134 | if ei >= 0: 135 | sub_arr = arr[bi:] 136 | else: 137 | sub_arr = arr[bi:ei] 138 | if arr[i] == max(sub_arr): 139 | tops.append(i) 140 | return tops 141 | 142 | def get_bottoms(arr, c): 143 | bottoms = [] 144 | #print(arr) 145 | for i in range(-len(arr), 0): 146 | bi = i-c 147 | if bi < -len(arr): 148 | bi = -len(arr) 149 | 150 | ei = i + 1 + c 151 | if ei >= 0: 152 | sub_arr = arr[bi:] 153 | else: 154 | sub_arr = arr[bi:ei] 155 | 156 | if arr[i] == min(sub_arr): 157 | bottoms.append(i) 158 | return bottoms 159 | 160 | def get_macd_tops(arr): 161 | tops = [] 162 | bi = -len(arr) 163 | for i in range(bi+1, 0): 164 | v = arr[i] 165 | if v > 0: 166 | if arr[bi] < v: 167 | bi = i 168 | else: 169 | if arr[bi] > 0: 170 | tops.append(bi) 171 | bi = i 172 | if arr[bi] > 0: 173 | tops.append(bi) 174 | 175 | return tops 176 | 177 | def get_macd_bottoms(arr): 178 | bottoms = [] 179 | bi = -len(arr) 180 | for i in range(bi+1, 0): 181 | v = arr[i] 182 | if v < 0: 183 | if arr[bi] > v: 184 | bi = i 185 | else: 186 | if arr[bi] < 0: 187 | bottoms.append(bi) 188 | bi = i 189 | if arr[bi] < 0: 190 | bottoms.append(bi) 191 | 192 | return bottoms 193 | 194 | def get_trend(ema1, ema2, std_diff_threshold=0.01, start=-5, end=0): 195 | if end == 0: 196 | diff_data = ema1[start:] + ema2[start:] 197 | else: 198 | diff_data = ema1[start:end] + ema2[start:end] 199 | 200 | std_diff = np.std(diff_data)/sum(diff_data) * len(diff_data) 201 | if std_diff < std_diff_threshold: 202 | ret = 0 203 | elif ema1[-1] < ema2[-1]: 204 | ret = -1 205 | else: 206 | ret = 1 207 | 208 | return ret, std_diff 209 | -------------------------------------------------------------------------------- /xlib/step.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/python 2 | """ """ 3 | 4 | 5 | def is_increment(arr): 6 | for i in range(1, len(arr)): 7 | if arr[i-1] >= arr[i]: 8 | return False 9 | return True 10 | 11 | def is_decrement(arr): 12 | for i in range(1, len(arr)): 13 | if arr[i-1] <= arr[i]: 14 | return False 15 | return True 16 | 17 | def get_inc_step(arr): 18 | i = -1 19 | while i >= -len(arr)+1: 20 | if arr[i-1] > arr[i]: 21 | break 22 | i -= 1 23 | return -i-1 24 | 25 | def get_dec_step(arr): 26 | i = -1 27 | while i >= -len(arr)+1: 28 | if arr[i-1] < arr[i]: 29 | break 30 | i -= 1 31 | return -i-1 32 | 33 | def get_over_step(vs, v): 34 | i = -1 35 | while i >= -len(vs)+1: 36 | if vs[i] > v: 37 | break 38 | i -= 1 39 | return -i-1 40 | 41 | def get_below_step(vs, v): 42 | i = -1 43 | while i >= -len(vs)+1: 44 | if vs[i] < v: 45 | break 46 | i -= 1 47 | return -i-1 48 | 49 | def is_more(arr, c=2): 50 | for i in range(c, len(arr)): 51 | if min(arr[i-c:i]) > arr[i]: 52 | return False 53 | return True 54 | 55 | def is_less(arr, c=2): 56 | for i in range(c, len(arr)): 57 | if max(arr[i-c:i]) < arr[i]: 58 | return False 59 | return True 60 | 61 | def get_more_step(arr, c=2): 62 | i = -1 63 | while i >= -len(arr)+c: 64 | if min(arr[i-c:i]) > arr[i]: 65 | break 66 | i -= 1 67 | 68 | if i == -1: 69 | return 0 70 | 71 | return -1-i 72 | 73 | def get_less_step(arr, c=2): 74 | i = -1 75 | while i >= -len(arr)+c: 76 | if max(arr[i-c:i]) < arr[i]: 77 | break 78 | i -= 1 79 | 80 | if i == -1: 81 | return 0 82 | 83 | return -1-i 84 | 85 | --------------------------------------------------------------------------------