├── .gitattributes ├── .gitignore ├── .npmignore ├── .prettierrc ├── .travis.yml ├── .vscode └── launch.json ├── LICENSE ├── README.md ├── assets ├── a-b-c.png ├── diagram.png ├── running-result.png ├── ta-chart.png └── webui.png ├── config └── default.org.toml ├── db └── db.md ├── package-lock.json ├── package.json ├── public ├── favicon.ico ├── image │ └── coins │ │ ├── act.png │ │ ├── ada.png │ │ ├── adx.png │ │ ├── ae.png │ │ ├── agi.png │ │ ├── aion.png │ │ ├── amb.png │ │ ├── amp.png │ │ ├── ant.png │ │ ├── appc.png │ │ ├── ardr.png │ │ ├── ark.png │ │ ├── arn.png │ │ ├── ast.png │ │ ├── atm.png │ │ ├── bat.png │ │ ├── bay.png │ │ ├── bcc.png │ │ ├── bcd.png │ │ ├── bch.png │ │ ├── bcn.png │ │ ├── bco.png │ │ ├── bcpt.png │ │ ├── bdl.png │ │ ├── bela.png │ │ ├── blcn.png │ │ ├── blk.png │ │ ├── block.png │ │ ├── bnb.png │ │ ├── bnt.png │ │ ├── bnty.png │ │ ├── bpt.png │ │ ├── bq.png │ │ ├── bqx.png │ │ ├── brd.png │ │ ├── btc.png │ │ ├── btcd.png │ │ ├── btcz.png │ │ ├── btg.png │ │ ├── btm.png │ │ ├── bts.png │ │ ├── btx.png │ │ ├── burst.png │ │ ├── cdn.png │ │ ├── cdt.png │ │ ├── clam.png │ │ ├── cloak.png │ │ ├── cmt.png │ │ ├── cnd.png │ │ ├── cnx.png │ │ ├── cny.png │ │ ├── cred.png │ │ ├── crpt.png │ │ ├── cvc.png │ │ ├── dash.png │ │ ├── dat.png │ │ ├── data.png │ │ ├── dbc.png │ │ ├── dcn.png │ │ ├── dcr.png │ │ ├── dent.png │ │ ├── dgb.png │ │ ├── dgd.png │ │ ├── dlt.png │ │ ├── dnt.png │ │ ├── doge.png │ │ ├── drgn.png │ │ ├── ebst.png │ │ ├── edg.png │ │ ├── edoge.png │ │ ├── elf.png │ │ ├── elix.png │ │ ├── ella.png │ │ ├── emc.png │ │ ├── emc2.png │ │ ├── eng.png │ │ ├── enj.png │ │ ├── eos.png │ │ ├── etc.png │ │ ├── eth.png │ │ ├── ethos.png │ │ ├── etn.png │ │ ├── etp.png │ │ ├── eur.png │ │ ├── evx.png │ │ ├── exmo.png │ │ ├── exp.png │ │ ├── fair.png │ │ ├── fct.png │ │ ├── fil.png │ │ ├── fldc.png │ │ ├── flo.png │ │ ├── ftc.png │ │ ├── fuel.png │ │ ├── fun.png │ │ ├── game.png │ │ ├── gas.png │ │ ├── gbp.png │ │ ├── gbx.png │ │ ├── gbyte.png │ │ ├── gno.png │ │ ├── gnt.png │ │ ├── grc.png │ │ ├── grs.png │ │ ├── gto.png │ │ ├── gup.png │ │ ├── gvt.png │ │ ├── gxs.png │ │ ├── hpb.png │ │ ├── hsr.png │ │ ├── huc.png │ │ ├── hush.png │ │ ├── icn.png │ │ ├── icx.png │ │ ├── ignis.png │ │ ├── ins.png │ │ ├── iop.png │ │ ├── iost.png │ │ ├── iota.png │ │ ├── jpy.png │ │ ├── kcs.png │ │ ├── kin.png │ │ ├── kmd.png │ │ ├── knc.png │ │ ├── krb.png │ │ ├── lbc.png │ │ ├── lend.png │ │ ├── link.png │ │ ├── lkk.png │ │ ├── lrc.png │ │ ├── lsk.png │ │ ├── ltc.png │ │ ├── lun.png │ │ ├── maid.png │ │ ├── mana.png │ │ ├── mcap.png │ │ ├── mco.png │ │ ├── med.png │ │ ├── miota.png │ │ ├── mkr.png │ │ ├── mln.png │ │ ├── mnx.png │ │ ├── mona.png │ │ ├── mth.png │ │ ├── mtl.png │ │ ├── music.png │ │ ├── nano.png │ │ ├── nas.png │ │ ├── nav.png │ │ ├── ncash.png │ │ ├── ndz.png │ │ ├── nebl.png │ │ ├── neo.png │ │ ├── neos.png │ │ ├── ngc.png │ │ ├── nlc2.png │ │ ├── nlg.png │ │ ├── nmc.png │ │ ├── nuls.png │ │ ├── nxs.png │ │ ├── nxt.png │ │ ├── oax.png │ │ ├── omg.png │ │ ├── omni.png │ │ ├── ost.png │ │ ├── ox.png │ │ ├── pac.png │ │ ├── part.png │ │ ├── pasl.png │ │ ├── pay.png │ │ ├── pink.png │ │ ├── pirl.png │ │ ├── pivx.png │ │ ├── plr.png │ │ ├── poe.png │ │ ├── poly.png │ │ ├── pot.png │ │ ├── powr.png │ │ ├── ppc.png │ │ ├── ppp.png │ │ ├── ppt.png │ │ ├── prl.png │ │ ├── pura.png │ │ ├── qash.png │ │ ├── qiwi.png │ │ ├── qlc.png │ │ ├── qsp.png │ │ ├── qtum.png │ │ ├── r.png │ │ ├── rads.png │ │ ├── rcn.png │ │ ├── rdd.png │ │ ├── rdn.png │ │ ├── rep.png │ │ ├── req.png │ │ ├── rhoc.png │ │ ├── ric.png │ │ ├── rise.png │ │ ├── rlc.png │ │ ├── rpx.png │ │ ├── rub.png │ │ ├── salt.png │ │ ├── san.png │ │ ├── sbd.png │ │ ├── sberbank.png │ │ ├── sc.png │ │ ├── sky.png │ │ ├── smart.png │ │ ├── sngls.png │ │ ├── snt.png │ │ ├── sonm.png │ │ ├── sphtx.png │ │ ├── srn.png │ │ ├── start.png │ │ ├── steem.png │ │ ├── storj.png │ │ ├── storm.png │ │ ├── strat.png │ │ ├── sub.png │ │ ├── sys.png │ │ ├── taas.png │ │ ├── tau.png │ │ ├── tix.png │ │ ├── tkn.png │ │ ├── tnb.png │ │ ├── tnc.png │ │ ├── tnt.png │ │ ├── trig.png │ │ ├── trx.png │ │ ├── tzc.png │ │ ├── ubq.png │ │ ├── usd.png │ │ ├── usdt.png │ │ ├── ven.png │ │ ├── veri.png │ │ ├── via.png │ │ ├── vib.png │ │ ├── vibe.png │ │ ├── vivo.png │ │ ├── vrc.png │ │ ├── vtc.png │ │ ├── wabi.png │ │ ├── waves.png │ │ ├── wax.png │ │ ├── wtc.png │ │ ├── xbc.png │ │ ├── xcp.png │ │ ├── xdn.png │ │ ├── xem.png │ │ ├── xlm.png │ │ ├── xmg.png │ │ ├── xmr.png │ │ ├── xmy.png │ │ ├── xp.png │ │ ├── xpa.png │ │ ├── xpm.png │ │ ├── xrp.png │ │ ├── xtz.png │ │ ├── xuc.png │ │ ├── xvc.png │ │ ├── xvg.png │ │ ├── xzc.png │ │ ├── yoyow.png │ │ ├── zcl.png │ │ ├── zec.png │ │ ├── zen.png │ │ ├── zil.png │ │ └── zrx.png ├── index.html └── pubsub.js ├── runner.js ├── src ├── index.ts └── lib │ ├── aggregator.ts │ ├── api-handler.ts │ ├── arbitrage.ts │ ├── bin │ ├── start.ts │ └── ws.ts │ ├── common │ ├── helper.ts │ ├── index.ts │ └── logger.ts │ ├── engine.ts │ ├── event.ts │ ├── rate.ts │ ├── storage │ ├── base.ts │ ├── index.ts │ ├── queue.ts │ ├── rank.ts │ └── trade.ts │ ├── trading │ ├── daemon.ts │ ├── index.ts │ ├── mocker.ts │ └── order.ts │ ├── type │ ├── account.ts │ ├── api-handler.ts │ ├── binance │ │ └── index.ts │ ├── exchange.ts │ ├── index.ts │ ├── storage.ts │ └── trading.ts │ └── webservice.ts ├── test ├── api-handler.test.ts ├── engine.test.ts ├── order.test.ts ├── pouchdb │ ├── clie.ts │ └── serv.ts ├── storage.test.ts └── trading.test.ts ├── tsconfig.development.json ├── tsconfig.json └── tslint.json /.gitattributes: -------------------------------------------------------------------------------- 1 | # Set the default behavior, in case people don't have core.autocrlf set. 2 | * text=auto 3 | 4 | # Explicitly declare text files you want to always be normalized and converted 5 | # to native line endings on checkout. 6 | *.c text 7 | *.h text 8 | 9 | # Declare files that will always have CRLF line endings on checkout. 10 | *.org text eol=crlf 11 | 12 | # Denote all files that are truly binary and should not be modified. 13 | *.png binary 14 | *.jpg binary 15 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Logs 2 | logs 3 | *.log 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Runtime data 9 | pids 10 | *.pid 11 | *.seed 12 | *.pid.lock 13 | 14 | # Directory for instrumented libs generated by jscoverage/JSCover 15 | lib-cov 16 | 17 | # Coverage directory used by tools like istanbul 18 | coverage 19 | 20 | # nyc test coverage 21 | .nyc_output 22 | 23 | # Grunt intermediate storage (http://gruntjs.com/creating-plugins#storing-task-files) 24 | .grunt 25 | 26 | # Bower dependency directory (https://bower.io/) 27 | bower_components 28 | 29 | # node-waf configuration 30 | .lock-wscript 31 | 32 | # Compiled binary addons (http://nodejs.org/api/addons.html) 33 | build/Release 34 | 35 | # Dependency directories 36 | node_modules/ 37 | jspm_packages/ 38 | 39 | # Typescript v1 declaration files 40 | typings/ 41 | 42 | # Optional npm cache directory 43 | .npm 44 | 45 | # Optional eslint cache 46 | .eslintcache 47 | 48 | # Optional REPL history 49 | .node_repl_history 50 | 51 | # Output of 'npm pack' 52 | *.tgz 53 | 54 | # Yarn Integrity file 55 | .yarn-integrity 56 | 57 | # dotenv environment variables file 58 | .env 59 | 60 | dist 61 | .history 62 | config/**/*.toml 63 | db/* 64 | -------------------------------------------------------------------------------- /.npmignore: -------------------------------------------------------------------------------- 1 | /src 2 | /log 3 | /logs 4 | /node_modules 5 | /test 6 | /dist/**/*.test.js 7 | /dist/**/*.test.d.ts 8 | /config/development.toml 9 | /config/test.toml 10 | npm-debug.log 11 | .idea 12 | .vscode 13 | .npmignore 14 | .gitignore 15 | .git 16 | .travis.yml 17 | .history 18 | .prettierrc 19 | appveyor.yml 20 | package-lock.json 21 | tsconfig.development.json 22 | tsconfig.json 23 | tslint.json 24 | -------------------------------------------------------------------------------- /.prettierrc: -------------------------------------------------------------------------------- 1 | { 2 | "printWidth": 140, 3 | "tabWidth": 2, 4 | "useTabs": false, 5 | "semi": true, 6 | "singleQuote": true, 7 | "trailingComma": "all", 8 | "bracketSpacing": true, 9 | "arrowParens": "always", 10 | "parser": "typescript" 11 | } 12 | -------------------------------------------------------------------------------- /.travis.yml: -------------------------------------------------------------------------------- 1 | language: node_js 2 | 3 | node_js: 4 | - "8" 5 | -------------------------------------------------------------------------------- /.vscode/launch.json: -------------------------------------------------------------------------------- 1 | { 2 | // 使用 IntelliSense 了解相关属性。 3 | // 悬停以查看现有属性的描述。 4 | // 欲了解更多信息,请访问: https://go.microsoft.com/fwlink/?linkid=830387 5 | "version": "0.2.0", 6 | "configurations": [ 7 | { 8 | "type": "node", 9 | "request": "launch", 10 | "name": "Mocha Tests", 11 | "program": "${workspaceFolder}/node_modules/mocha/bin/_mocha", 12 | "args": [ 13 | "-u", 14 | "tdd", 15 | "--timeout", 16 | "999999", 17 | "--colors", 18 | "${workspaceFolder}/dist/**/trading.test.js" 19 | ], 20 | "env": { 21 | "NODE_ENV": "test" 22 | }, 23 | "outFiles": [ 24 | "${workspaceFolder}/dist/**/*.js" 25 | ], 26 | "internalConsoleOptions": "openOnSessionStart" 27 | }, 28 | { 29 | "type": "node", 30 | "request": "launch", 31 | "name": "Launch Program", 32 | "runtimeArgs": [ 33 | "-r", 34 | "ts-node/register" 35 | ], 36 | "args": [ 37 | "${workspaceFolder}//src//lib//bin//start.ts" 38 | ] 39 | }, 40 | { 41 | "type": "node", 42 | "request": "launch", 43 | "name": "Launch WS", 44 | "program": "${workspaceFolder}//dist//src//lib//bin//ws.js", 45 | "outFiles": [ 46 | "${workspaceFolder}/dist/**/*.js" 47 | ] 48 | }, 49 | { 50 | "type": "node", 51 | "request": "launch", 52 | "name": "launode", 53 | "program": "${workspaceFolder}//dist//test//serv.js", 54 | "outFiles": [ 55 | "${workspaceFolder}/dist/test/**/*.js" 56 | ] 57 | } 58 | ] 59 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | GNU GENERAL PUBLIC LICENSE 2 | Version 3, 29 June 2007 3 | 4 | Copyright (C) 2007 Free Software Foundation, Inc. 5 | Everyone is permitted to copy and distribute verbatim copies 6 | of this license document, but changing it is not allowed. 7 | 8 | Preamble 9 | 10 | The GNU General Public License is a free, copyleft license for 11 | software and other kinds of works. 12 | 13 | The licenses for most software and other practical works are designed 14 | to take away your freedom to share and change the works. By contrast, 15 | the GNU General Public License is intended to guarantee your freedom to 16 | share and change all versions of a program--to make sure it remains free 17 | software for all its users. We, the Free Software Foundation, use the 18 | GNU General Public License for most of our software; it applies also to 19 | any other work released this way by its authors. You can apply it to 20 | your programs, too. 21 | 22 | When we speak of free software, we are referring to freedom, not 23 | price. Our General Public Licenses are designed to make sure that you 24 | have the freedom to distribute copies of free software (and charge for 25 | them if you wish), that you receive source code or can get it if you 26 | want it, that you can change the software or use pieces of it in new 27 | free programs, and that you know you can do these things. 28 | 29 | To protect your rights, we need to prevent others from denying you 30 | these rights or asking you to surrender the rights. Therefore, you have 31 | certain responsibilities if you distribute copies of the software, or if 32 | you modify it: responsibilities to respect the freedom of others. 33 | 34 | For example, if you distribute copies of such a program, whether 35 | gratis or for a fee, you must pass on to the recipients the same 36 | freedoms that you received. You must make sure that they, too, receive 37 | or can get the source code. And you must show them these terms so they 38 | know their rights. 39 | 40 | Developers that use the GNU GPL protect your rights with two steps: 41 | (1) assert copyright on the software, and (2) offer you this License 42 | giving you legal permission to copy, distribute and/or modify it. 43 | 44 | For the developers' and authors' protection, the GPL clearly explains 45 | that there is no warranty for this free software. For both users' and 46 | authors' sake, the GPL requires that modified versions be marked as 47 | changed, so that their problems will not be attributed erroneously to 48 | authors of previous versions. 49 | 50 | Some devices are designed to deny users access to install or run 51 | modified versions of the software inside them, although the manufacturer 52 | can do so. This is fundamentally incompatible with the aim of 53 | protecting users' freedom to change the software. The systematic 54 | pattern of such abuse occurs in the area of products for individuals to 55 | use, which is precisely where it is most unacceptable. Therefore, we 56 | have designed this version of the GPL to prohibit the practice for those 57 | products. If such problems arise substantially in other domains, we 58 | stand ready to extend this provision to those domains in future versions 59 | of the GPL, as needed to protect the freedom of users. 60 | 61 | Finally, every program is threatened constantly by software patents. 62 | States should not allow patents to restrict development and use of 63 | software on general-purpose computers, but in those that do, we wish to 64 | avoid the special danger that patents applied to a free program could 65 | make it effectively proprietary. To prevent this, the GPL assures that 66 | patents cannot be used to render the program non-free. 67 | 68 | The precise terms and conditions for copying, distribution and 69 | modification follow. 70 | 71 | TERMS AND CONDITIONS 72 | 73 | 0. Definitions. 74 | 75 | "This License" refers to version 3 of the GNU General Public License. 76 | 77 | "Copyright" also means copyright-like laws that apply to other kinds of 78 | works, such as semiconductor masks. 79 | 80 | "The Program" refers to any copyrightable work licensed under this 81 | License. Each licensee is addressed as "you". "Licensees" and 82 | "recipients" may be individuals or organizations. 83 | 84 | To "modify" a work means to copy from or adapt all or part of the work 85 | in a fashion requiring copyright permission, other than the making of an 86 | exact copy. The resulting work is called a "modified version" of the 87 | earlier work or a work "based on" the earlier work. 88 | 89 | A "covered work" means either the unmodified Program or a work based 90 | on the Program. 91 | 92 | To "propagate" a work means to do anything with it that, without 93 | permission, would make you directly or secondarily liable for 94 | infringement under applicable copyright law, except executing it on a 95 | computer or modifying a private copy. Propagation includes copying, 96 | distribution (with or without modification), making available to the 97 | public, and in some countries other activities as well. 98 | 99 | To "convey" a work means any kind of propagation that enables other 100 | parties to make or receive copies. Mere interaction with a user through 101 | a computer network, with no transfer of a copy, is not conveying. 102 | 103 | An interactive user interface displays "Appropriate Legal Notices" 104 | to the extent that it includes a convenient and prominently visible 105 | feature that (1) displays an appropriate copyright notice, and (2) 106 | tells the user that there is no warranty for the work (except to the 107 | extent that warranties are provided), that licensees may convey the 108 | work under this License, and how to view a copy of this License. If 109 | the interface presents a list of user commands or options, such as a 110 | menu, a prominent item in the list meets this criterion. 111 | 112 | 1. Source Code. 113 | 114 | The "source code" for a work means the preferred form of the work 115 | for making modifications to it. "Object code" means any non-source 116 | form of a work. 117 | 118 | A "Standard Interface" means an interface that either is an official 119 | standard defined by a recognized standards body, or, in the case of 120 | interfaces specified for a particular programming language, one that 121 | is widely used among developers working in that language. 122 | 123 | The "System Libraries" of an executable work include anything, other 124 | than the work as a whole, that (a) is included in the normal form of 125 | packaging a Major Component, but which is not part of that Major 126 | Component, and (b) serves only to enable use of the work with that 127 | Major Component, or to implement a Standard Interface for which an 128 | implementation is available to the public in source code form. A 129 | "Major Component", in this context, means a major essential component 130 | (kernel, window system, and so on) of the specific operating system 131 | (if any) on which the executable work runs, or a compiler used to 132 | produce the work, or an object code interpreter used to run it. 133 | 134 | The "Corresponding Source" for a work in object code form means all 135 | the source code needed to generate, install, and (for an executable 136 | work) run the object code and to modify the work, including scripts to 137 | control those activities. However, it does not include the work's 138 | System Libraries, or general-purpose tools or generally available free 139 | programs which are used unmodified in performing those activities but 140 | which are not part of the work. For example, Corresponding Source 141 | includes interface definition files associated with source files for 142 | the work, and the source code for shared libraries and dynamically 143 | linked subprograms that the work is specifically designed to require, 144 | such as by intimate data communication or control flow between those 145 | subprograms and other parts of the work. 146 | 147 | The Corresponding Source need not include anything that users 148 | can regenerate automatically from other parts of the Corresponding 149 | Source. 150 | 151 | The Corresponding Source for a work in source code form is that 152 | same work. 153 | 154 | 2. Basic Permissions. 155 | 156 | All rights granted under this License are granted for the term of 157 | copyright on the Program, and are irrevocable provided the stated 158 | conditions are met. This License explicitly affirms your unlimited 159 | permission to run the unmodified Program. The output from running a 160 | covered work is covered by this License only if the output, given its 161 | content, constitutes a covered work. This License acknowledges your 162 | rights of fair use or other equivalent, as provided by copyright law. 163 | 164 | You may make, run and propagate covered works that you do not 165 | convey, without conditions so long as your license otherwise remains 166 | in force. You may convey covered works to others for the sole purpose 167 | of having them make modifications exclusively for you, or provide you 168 | with facilities for running those works, provided that you comply with 169 | the terms of this License in conveying all material for which you do 170 | not control copyright. Those thus making or running the covered works 171 | for you must do so exclusively on your behalf, under your direction 172 | and control, on terms that prohibit them from making any copies of 173 | your copyrighted material outside their relationship with you. 174 | 175 | Conveying under any other circumstances is permitted solely under 176 | the conditions stated below. Sublicensing is not allowed; section 10 177 | makes it unnecessary. 178 | 179 | 3. Protecting Users' Legal Rights From Anti-Circumvention Law. 180 | 181 | No covered work shall be deemed part of an effective technological 182 | measure under any applicable law fulfilling obligations under article 183 | 11 of the WIPO copyright treaty adopted on 20 December 1996, or 184 | similar laws prohibiting or restricting circumvention of such 185 | measures. 186 | 187 | When you convey a covered work, you waive any legal power to forbid 188 | circumvention of technological measures to the extent such circumvention 189 | is effected by exercising rights under this License with respect to 190 | the covered work, and you disclaim any intention to limit operation or 191 | modification of the work as a means of enforcing, against the work's 192 | users, your or third parties' legal rights to forbid circumvention of 193 | technological measures. 194 | 195 | 4. Conveying Verbatim Copies. 196 | 197 | You may convey verbatim copies of the Program's source code as you 198 | receive it, in any medium, provided that you conspicuously and 199 | appropriately publish on each copy an appropriate copyright notice; 200 | keep intact all notices stating that this License and any 201 | non-permissive terms added in accord with section 7 apply to the code; 202 | keep intact all notices of the absence of any warranty; and give all 203 | recipients a copy of this License along with the Program. 204 | 205 | You may charge any price or no price for each copy that you convey, 206 | and you may offer support or warranty protection for a fee. 207 | 208 | 5. Conveying Modified Source Versions. 209 | 210 | You may convey a work based on the Program, or the modifications to 211 | produce it from the Program, in the form of source code under the 212 | terms of section 4, provided that you also meet all of these conditions: 213 | 214 | a) The work must carry prominent notices stating that you modified 215 | it, and giving a relevant date. 216 | 217 | b) The work must carry prominent notices stating that it is 218 | released under this License and any conditions added under section 219 | 7. This requirement modifies the requirement in section 4 to 220 | "keep intact all notices". 221 | 222 | c) You must license the entire work, as a whole, under this 223 | License to anyone who comes into possession of a copy. This 224 | License will therefore apply, along with any applicable section 7 225 | additional terms, to the whole of the work, and all its parts, 226 | regardless of how they are packaged. This License gives no 227 | permission to license the work in any other way, but it does not 228 | invalidate such permission if you have separately received it. 229 | 230 | d) If the work has interactive user interfaces, each must display 231 | Appropriate Legal Notices; however, if the Program has interactive 232 | interfaces that do not display Appropriate Legal Notices, your 233 | work need not make them do so. 234 | 235 | A compilation of a covered work with other separate and independent 236 | works, which are not by their nature extensions of the covered work, 237 | and which are not combined with it such as to form a larger program, 238 | in or on a volume of a storage or distribution medium, is called an 239 | "aggregate" if the compilation and its resulting copyright are not 240 | used to limit the access or legal rights of the compilation's users 241 | beyond what the individual works permit. Inclusion of a covered work 242 | in an aggregate does not cause this License to apply to the other 243 | parts of the aggregate. 244 | 245 | 6. Conveying Non-Source Forms. 246 | 247 | You may convey a covered work in object code form under the terms 248 | of sections 4 and 5, provided that you also convey the 249 | machine-readable Corresponding Source under the terms of this License, 250 | in one of these ways: 251 | 252 | a) Convey the object code in, or embodied in, a physical product 253 | (including a physical distribution medium), accompanied by the 254 | Corresponding Source fixed on a durable physical medium 255 | customarily used for software interchange. 256 | 257 | b) Convey the object code in, or embodied in, a physical product 258 | (including a physical distribution medium), accompanied by a 259 | written offer, valid for at least three years and valid for as 260 | long as you offer spare parts or customer support for that product 261 | model, to give anyone who possesses the object code either (1) a 262 | copy of the Corresponding Source for all the software in the 263 | product that is covered by this License, on a durable physical 264 | medium customarily used for software interchange, for a price no 265 | more than your reasonable cost of physically performing this 266 | conveying of source, or (2) access to copy the 267 | Corresponding Source from a network server at no charge. 268 | 269 | c) Convey individual copies of the object code with a copy of the 270 | written offer to provide the Corresponding Source. This 271 | alternative is allowed only occasionally and noncommercially, and 272 | only if you received the object code with such an offer, in accord 273 | with subsection 6b. 274 | 275 | d) Convey the object code by offering access from a designated 276 | place (gratis or for a charge), and offer equivalent access to the 277 | Corresponding Source in the same way through the same place at no 278 | further charge. You need not require recipients to copy the 279 | Corresponding Source along with the object code. If the place to 280 | copy the object code is a network server, the Corresponding Source 281 | may be on a different server (operated by you or a third party) 282 | that supports equivalent copying facilities, provided you maintain 283 | clear directions next to the object code saying where to find the 284 | Corresponding Source. Regardless of what server hosts the 285 | Corresponding Source, you remain obligated to ensure that it is 286 | available for as long as needed to satisfy these requirements. 287 | 288 | e) Convey the object code using peer-to-peer transmission, provided 289 | you inform other peers where the object code and Corresponding 290 | Source of the work are being offered to the general public at no 291 | charge under subsection 6d. 292 | 293 | A separable portion of the object code, whose source code is excluded 294 | from the Corresponding Source as a System Library, need not be 295 | included in conveying the object code work. 296 | 297 | A "User Product" is either (1) a "consumer product", which means any 298 | tangible personal property which is normally used for personal, family, 299 | or household purposes, or (2) anything designed or sold for incorporation 300 | into a dwelling. In determining whether a product is a consumer product, 301 | doubtful cases shall be resolved in favor of coverage. For a particular 302 | product received by a particular user, "normally used" refers to a 303 | typical or common use of that class of product, regardless of the status 304 | of the particular user or of the way in which the particular user 305 | actually uses, or expects or is expected to use, the product. A product 306 | is a consumer product regardless of whether the product has substantial 307 | commercial, industrial or non-consumer uses, unless such uses represent 308 | the only significant mode of use of the product. 309 | 310 | "Installation Information" for a User Product means any methods, 311 | procedures, authorization keys, or other information required to install 312 | and execute modified versions of a covered work in that User Product from 313 | a modified version of its Corresponding Source. The information must 314 | suffice to ensure that the continued functioning of the modified object 315 | code is in no case prevented or interfered with solely because 316 | modification has been made. 317 | 318 | If you convey an object code work under this section in, or with, or 319 | specifically for use in, a User Product, and the conveying occurs as 320 | part of a transaction in which the right of possession and use of the 321 | User Product is transferred to the recipient in perpetuity or for a 322 | fixed term (regardless of how the transaction is characterized), the 323 | Corresponding Source conveyed under this section must be accompanied 324 | by the Installation Information. But this requirement does not apply 325 | if neither you nor any third party retains the ability to install 326 | modified object code on the User Product (for example, the work has 327 | been installed in ROM). 328 | 329 | The requirement to provide Installation Information does not include a 330 | requirement to continue to provide support service, warranty, or updates 331 | for a work that has been modified or installed by the recipient, or for 332 | the User Product in which it has been modified or installed. Access to a 333 | network may be denied when the modification itself materially and 334 | adversely affects the operation of the network or violates the rules and 335 | protocols for communication across the network. 336 | 337 | Corresponding Source conveyed, and Installation Information provided, 338 | in accord with this section must be in a format that is publicly 339 | documented (and with an implementation available to the public in 340 | source code form), and must require no special password or key for 341 | unpacking, reading or copying. 342 | 343 | 7. Additional Terms. 344 | 345 | "Additional permissions" are terms that supplement the terms of this 346 | License by making exceptions from one or more of its conditions. 347 | Additional permissions that are applicable to the entire Program shall 348 | be treated as though they were included in this License, to the extent 349 | that they are valid under applicable law. If additional permissions 350 | apply only to part of the Program, that part may be used separately 351 | under those permissions, but the entire Program remains governed by 352 | this License without regard to the additional permissions. 353 | 354 | When you convey a copy of a covered work, you may at your option 355 | remove any additional permissions from that copy, or from any part of 356 | it. (Additional permissions may be written to require their own 357 | removal in certain cases when you modify the work.) You may place 358 | additional permissions on material, added by you to a covered work, 359 | for which you have or can give appropriate copyright permission. 360 | 361 | Notwithstanding any other provision of this License, for material you 362 | add to a covered work, you may (if authorized by the copyright holders of 363 | that material) supplement the terms of this License with terms: 364 | 365 | a) Disclaiming warranty or limiting liability differently from the 366 | terms of sections 15 and 16 of this License; or 367 | 368 | b) Requiring preservation of specified reasonable legal notices or 369 | author attributions in that material or in the Appropriate Legal 370 | Notices displayed by works containing it; or 371 | 372 | c) Prohibiting misrepresentation of the origin of that material, or 373 | requiring that modified versions of such material be marked in 374 | reasonable ways as different from the original version; or 375 | 376 | d) Limiting the use for publicity purposes of names of licensors or 377 | authors of the material; or 378 | 379 | e) Declining to grant rights under trademark law for use of some 380 | trade names, trademarks, or service marks; or 381 | 382 | f) Requiring indemnification of licensors and authors of that 383 | material by anyone who conveys the material (or modified versions of 384 | it) with contractual assumptions of liability to the recipient, for 385 | any liability that these contractual assumptions directly impose on 386 | those licensors and authors. 387 | 388 | All other non-permissive additional terms are considered "further 389 | restrictions" within the meaning of section 10. If the Program as you 390 | received it, or any part of it, contains a notice stating that it is 391 | governed by this License along with a term that is a further 392 | restriction, you may remove that term. If a license document contains 393 | a further restriction but permits relicensing or conveying under this 394 | License, you may add to a covered work material governed by the terms 395 | of that license document, provided that the further restriction does 396 | not survive such relicensing or conveying. 397 | 398 | If you add terms to a covered work in accord with this section, you 399 | must place, in the relevant source files, a statement of the 400 | additional terms that apply to those files, or a notice indicating 401 | where to find the applicable terms. 402 | 403 | Additional terms, permissive or non-permissive, may be stated in the 404 | form of a separately written license, or stated as exceptions; 405 | the above requirements apply either way. 406 | 407 | 8. Termination. 408 | 409 | You may not propagate or modify a covered work except as expressly 410 | provided under this License. Any attempt otherwise to propagate or 411 | modify it is void, and will automatically terminate your rights under 412 | this License (including any patent licenses granted under the third 413 | paragraph of section 11). 414 | 415 | However, if you cease all violation of this License, then your 416 | license from a particular copyright holder is reinstated (a) 417 | provisionally, unless and until the copyright holder explicitly and 418 | finally terminates your license, and (b) permanently, if the copyright 419 | holder fails to notify you of the violation by some reasonable means 420 | prior to 60 days after the cessation. 421 | 422 | Moreover, your license from a particular copyright holder is 423 | reinstated permanently if the copyright holder notifies you of the 424 | violation by some reasonable means, this is the first time you have 425 | received notice of violation of this License (for any work) from that 426 | copyright holder, and you cure the violation prior to 30 days after 427 | your receipt of the notice. 428 | 429 | Termination of your rights under this section does not terminate the 430 | licenses of parties who have received copies or rights from you under 431 | this License. If your rights have been terminated and not permanently 432 | reinstated, you do not qualify to receive new licenses for the same 433 | material under section 10. 434 | 435 | 9. Acceptance Not Required for Having Copies. 436 | 437 | You are not required to accept this License in order to receive or 438 | run a copy of the Program. Ancillary propagation of a covered work 439 | occurring solely as a consequence of using peer-to-peer transmission 440 | to receive a copy likewise does not require acceptance. However, 441 | nothing other than this License grants you permission to propagate or 442 | modify any covered work. These actions infringe copyright if you do 443 | not accept this License. Therefore, by modifying or propagating a 444 | covered work, you indicate your acceptance of this License to do so. 445 | 446 | 10. Automatic Licensing of Downstream Recipients. 447 | 448 | Each time you convey a covered work, the recipient automatically 449 | receives a license from the original licensors, to run, modify and 450 | propagate that work, subject to this License. You are not responsible 451 | for enforcing compliance by third parties with this License. 452 | 453 | An "entity transaction" is a transaction transferring control of an 454 | organization, or substantially all assets of one, or subdividing an 455 | organization, or merging organizations. If propagation of a covered 456 | work results from an entity transaction, each party to that 457 | transaction who receives a copy of the work also receives whatever 458 | licenses to the work the party's predecessor in interest had or could 459 | give under the previous paragraph, plus a right to possession of the 460 | Corresponding Source of the work from the predecessor in interest, if 461 | the predecessor has it or can get it with reasonable efforts. 462 | 463 | You may not impose any further restrictions on the exercise of the 464 | rights granted or affirmed under this License. For example, you may 465 | not impose a license fee, royalty, or other charge for exercise of 466 | rights granted under this License, and you may not initiate litigation 467 | (including a cross-claim or counterclaim in a lawsuit) alleging that 468 | any patent claim is infringed by making, using, selling, offering for 469 | sale, or importing the Program or any portion of it. 470 | 471 | 11. Patents. 472 | 473 | A "contributor" is a copyright holder who authorizes use under this 474 | License of the Program or a work on which the Program is based. The 475 | work thus licensed is called the contributor's "contributor version". 476 | 477 | A contributor's "essential patent claims" are all patent claims 478 | owned or controlled by the contributor, whether already acquired or 479 | hereafter acquired, that would be infringed by some manner, permitted 480 | by this License, of making, using, or selling its contributor version, 481 | but do not include claims that would be infringed only as a 482 | consequence of further modification of the contributor version. For 483 | purposes of this definition, "control" includes the right to grant 484 | patent sublicenses in a manner consistent with the requirements of 485 | this License. 486 | 487 | Each contributor grants you a non-exclusive, worldwide, royalty-free 488 | patent license under the contributor's essential patent claims, to 489 | make, use, sell, offer for sale, import and otherwise run, modify and 490 | propagate the contents of its contributor version. 491 | 492 | In the following three paragraphs, a "patent license" is any express 493 | agreement or commitment, however denominated, not to enforce a patent 494 | (such as an express permission to practice a patent or covenant not to 495 | sue for patent infringement). To "grant" such a patent license to a 496 | party means to make such an agreement or commitment not to enforce a 497 | patent against the party. 498 | 499 | If you convey a covered work, knowingly relying on a patent license, 500 | and the Corresponding Source of the work is not available for anyone 501 | to copy, free of charge and under the terms of this License, through a 502 | publicly available network server or other readily accessible means, 503 | then you must either (1) cause the Corresponding Source to be so 504 | available, or (2) arrange to deprive yourself of the benefit of the 505 | patent license for this particular work, or (3) arrange, in a manner 506 | consistent with the requirements of this License, to extend the patent 507 | license to downstream recipients. "Knowingly relying" means you have 508 | actual knowledge that, but for the patent license, your conveying the 509 | covered work in a country, or your recipient's use of the covered work 510 | in a country, would infringe one or more identifiable patents in that 511 | country that you have reason to believe are valid. 512 | 513 | If, pursuant to or in connection with a single transaction or 514 | arrangement, you convey, or propagate by procuring conveyance of, a 515 | covered work, and grant a patent license to some of the parties 516 | receiving the covered work authorizing them to use, propagate, modify 517 | or convey a specific copy of the covered work, then the patent license 518 | you grant is automatically extended to all recipients of the covered 519 | work and works based on it. 520 | 521 | A patent license is "discriminatory" if it does not include within 522 | the scope of its coverage, prohibits the exercise of, or is 523 | conditioned on the non-exercise of one or more of the rights that are 524 | specifically granted under this License. You may not convey a covered 525 | work if you are a party to an arrangement with a third party that is 526 | in the business of distributing software, under which you make payment 527 | to the third party based on the extent of your activity of conveying 528 | the work, and under which the third party grants, to any of the 529 | parties who would receive the covered work from you, a discriminatory 530 | patent license (a) in connection with copies of the covered work 531 | conveyed by you (or copies made from those copies), or (b) primarily 532 | for and in connection with specific products or compilations that 533 | contain the covered work, unless you entered into that arrangement, 534 | or that patent license was granted, prior to 28 March 2007. 535 | 536 | Nothing in this License shall be construed as excluding or limiting 537 | any implied license or other defenses to infringement that may 538 | otherwise be available to you under applicable patent law. 539 | 540 | 12. No Surrender of Others' Freedom. 541 | 542 | If conditions are imposed on you (whether by court order, agreement or 543 | otherwise) that contradict the conditions of this License, they do not 544 | excuse you from the conditions of this License. If you cannot convey a 545 | covered work so as to satisfy simultaneously your obligations under this 546 | License and any other pertinent obligations, then as a consequence you may 547 | not convey it at all. For example, if you agree to terms that obligate you 548 | to collect a royalty for further conveying from those to whom you convey 549 | the Program, the only way you could satisfy both those terms and this 550 | License would be to refrain entirely from conveying the Program. 551 | 552 | 13. Use with the GNU Affero General Public License. 553 | 554 | Notwithstanding any other provision of this License, you have 555 | permission to link or combine any covered work with a work licensed 556 | under version 3 of the GNU Affero General Public License into a single 557 | combined work, and to convey the resulting work. The terms of this 558 | License will continue to apply to the part which is the covered work, 559 | but the special requirements of the GNU Affero General Public License, 560 | section 13, concerning interaction through a network will apply to the 561 | combination as such. 562 | 563 | 14. Revised Versions of this License. 564 | 565 | The Free Software Foundation may publish revised and/or new versions of 566 | the GNU General Public License from time to time. Such new versions will 567 | be similar in spirit to the present version, but may differ in detail to 568 | address new problems or concerns. 569 | 570 | Each version is given a distinguishing version number. If the 571 | Program specifies that a certain numbered version of the GNU General 572 | Public License "or any later version" applies to it, you have the 573 | option of following the terms and conditions either of that numbered 574 | version or of any later version published by the Free Software 575 | Foundation. If the Program does not specify a version number of the 576 | GNU General Public License, you may choose any version ever published 577 | by the Free Software Foundation. 578 | 579 | If the Program specifies that a proxy can decide which future 580 | versions of the GNU General Public License can be used, that proxy's 581 | public statement of acceptance of a version permanently authorizes you 582 | to choose that version for the Program. 583 | 584 | Later license versions may give you additional or different 585 | permissions. However, no additional obligations are imposed on any 586 | author or copyright holder as a result of your choosing to follow a 587 | later version. 588 | 589 | 15. Disclaimer of Warranty. 590 | 591 | THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY 592 | APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT 593 | HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY 594 | OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, 595 | THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR 596 | PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM 597 | IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF 598 | ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 599 | 600 | 16. Limitation of Liability. 601 | 602 | IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING 603 | WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS 604 | THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY 605 | GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE 606 | USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF 607 | DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD 608 | PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), 609 | EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF 610 | SUCH DAMAGES. 611 | 612 | 17. Interpretation of Sections 15 and 16. 613 | 614 | If the disclaimer of warranty and limitation of liability provided 615 | above cannot be given local legal effect according to their terms, 616 | reviewing courts shall apply local law that most closely approximates 617 | an absolute waiver of all civil liability in connection with the 618 | Program, unless a warranty or assumption of liability accompanies a 619 | copy of the Program in return for a fee. 620 | 621 | END OF TERMS AND CONDITIONS 622 | 623 | How to Apply These Terms to Your New Programs 624 | 625 | If you develop a new program, and you want it to be of the greatest 626 | possible use to the public, the best way to achieve this is to make it 627 | free software which everyone can redistribute and change under these terms. 628 | 629 | To do so, attach the following notices to the program. It is safest 630 | to attach them to the start of each source file to most effectively 631 | state the exclusion of warranty; and each file should have at least 632 | the "copyright" line and a pointer to where the full notice is found. 633 | 634 | 635 | Copyright (C) 636 | 637 | This program is free software: you can redistribute it and/or modify 638 | it under the terms of the GNU General Public License as published by 639 | the Free Software Foundation, either version 3 of the License, or 640 | (at your option) any later version. 641 | 642 | This program is distributed in the hope that it will be useful, 643 | but WITHOUT ANY WARRANTY; without even the implied warranty of 644 | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the 645 | GNU General Public License for more details. 646 | 647 | You should have received a copy of the GNU General Public License 648 | along with this program. If not, see . 649 | 650 | Also add information on how to contact you by electronic and paper mail. 651 | 652 | If the program does terminal interaction, make it output a short 653 | notice like this when it starts in an interactive mode: 654 | 655 | Copyright (C) 656 | This program comes with ABSOLUTELY NO WARRANTY; for details type `show w'. 657 | This is free software, and you are welcome to redistribute it 658 | under certain conditions; type `show c' for details. 659 | 660 | The hypothetical commands `show w' and `show c' should show the appropriate 661 | parts of the General Public License. Of course, your program's commands 662 | might be different; for a GUI interface, you would use an "about box". 663 | 664 | You should also get your employer (if you work as a programmer) or school, 665 | if any, to sign a "copyright disclaimer" for the program, if necessary. 666 | For more information on this, and how to apply and follow the GNU GPL, see 667 | . 668 | 669 | The GNU General Public License does not permit incorporating your program 670 | into proprietary programs. If your program is a subroutine library, you 671 | may consider it more useful to permit linking proprietary applications with 672 | the library. If this is what you want to do, use the GNU Lesser General 673 | Public License instead of this License. But first, please read 674 | . 675 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # triangular-arbitrage 2 | 数字货币-三角套利机器人 3 | 4 | ## 重要(important) 5 | 6 | 成程序已经很长时间不维护,且今后也将不在维护。新程序请点击[此链接](https://github.com/zlq4863947/triangular-arbitrage2) 7 | 8 | This program has not been maintained for a long time, and will not be maintained in the future. 9 | Click [this link](https://github.com/zlq4863947/triangular-arbitrage2) for the new program 10 | 11 | ## 配置 12 | 1、config/default.org.toml 改为 config/default.toml 13 | 14 | 2、config/default.toml文件中配置修改,例如:币安apikey 15 | 16 | ## 启动步骤 17 | 启动自动套利程序步骤 18 | 19 | ```js 20 | // 只在第一次安装程序时需要运行 21 | npm install 22 | // 启动自动套利主程序 23 | npm start 24 | ``` 25 | 26 | ## 控制台运行效果 27 |

28 | 29 | 30 | ## web服务启动步骤 31 | 32 | 非必须启动项,只对想看的排行页面的同学需要如下配置 33 | 34 | - 下载并安装CouchDB数据库 35 | https://couchdb.apache.org/#download 36 | 37 | - default.toml配置 38 | 39 | 找到storage项目中的`url = ""`改为`url = "http://127.0.0.1:5984"` 40 | 41 | - CMD切换到项目路径,执行如下命令 42 | ```js 43 | npm run ws 44 | ``` 45 | 46 | ## 页面显示 47 | 打开`127.0.0.1:3000`后,显示如下: 48 |

49 | 50 | ## 系统构成图 51 |

52 | 53 | ## 概念图 54 |

55 |

56 | 57 | ## 疑难解答 58 | 59 | Q:toml配置如何改为json配置? 60 | 61 | A:可以把toml后缀改成json,然后通过[这个地址](https://toml-to-json.matiaskorhonen.fi/),把toml格式配置转换成json格式。 62 | -------------------------------------------------------------------------------- /assets/a-b-c.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/assets/a-b-c.png -------------------------------------------------------------------------------- /assets/diagram.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/assets/diagram.png -------------------------------------------------------------------------------- /assets/running-result.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/assets/running-result.png -------------------------------------------------------------------------------- /assets/ta-chart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/assets/ta-chart.png -------------------------------------------------------------------------------- /assets/webui.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/assets/webui.png -------------------------------------------------------------------------------- /config/default.org.toml: -------------------------------------------------------------------------------- 1 | # 配置文件 2 | 3 | ### 4 | ### [display] 5 | ### 6 | ### 画面配置 7 | ### 8 | [display] 9 | maxRows = 40 10 | 11 | ### 12 | ### [exchange] 13 | ### 14 | ### 交易所相关配置 15 | ### 16 | [exchange] 17 | # 初始默认交易所[kucoin/binance/bitbank], 之后可以通过参数变更 18 | active = "binance" 19 | 20 | ### 21 | ### [arbitrage] 22 | ### 23 | ### 三角套利相关配置 24 | ### 25 | [arbitrage] 26 | # 套利监视间隔(秒) 27 | # 币安时无视此项,tickers取得间隔固定(1秒) 28 | interval = 1 29 | # 最小盈利率 30 | minRateProfit = 0.05 31 | 32 | ### 33 | ### [log] 34 | ### 日志配置 35 | ### 36 | [log] 37 | # 是否开启debug信息 38 | debug = false 39 | 40 | ### 41 | ### [storage] 42 | ### 存储配置 43 | ### 44 | [storage] 45 | # PouchDB数据库URL(没有可以填空字符串) 46 | #url = "http://127.0.0.1:5984" 47 | url = "" 48 | # 是否存储用以显示的tick排行数据 49 | tickRank = false 50 | 51 | ### 52 | ### [trading] 53 | ### 交易相关配置 54 | ### 55 | [trading] 56 | # 是否只进行模拟交易 57 | mock = false 58 | # 最大同时进行的交易会话数 59 | limit = 1 60 | 61 | ### 62 | ### [account] 63 | ### 账户相关配置 64 | ### apiKey与secret在获取行情api中并不是必须项目,只有交易相关api中为必须项目 65 | ### 66 | [account] 67 | # 库币 68 | [account.kucoin] 69 | apiKey = "c" 70 | secret = "x" 71 | 72 | # binance 73 | [account.binance] 74 | # 账号 75 | apiKey = "cc" 76 | secret = "xx" 77 | 78 | # bitbank 79 | [account.bitbank] 80 | # 账号 81 | apiKey = "xxx" 82 | secret = "ccc" -------------------------------------------------------------------------------- /db/db.md: -------------------------------------------------------------------------------- 1 | 此文件夹存储本地数据库文件 -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "triangular-arbitrage", 3 | "version": "0.1.1", 4 | "description": "triangular arbitrage auto trader", 5 | "repository": { 6 | "type": "git", 7 | "url": "https://github.com/zlq4863947/triangular-arbitrage.git" 8 | }, 9 | "license": "GPL3", 10 | "main": "dist/src/index.js", 11 | "typings": "dist/src/index.d.ts", 12 | "scripts": { 13 | "rm": "rimraf dist", 14 | "format": "prettier --config .prettierrc --write \"src/**/*.ts\" \"test/**/*.ts\"", 15 | "tsc": "npm run rm && tsc -p tsconfig.development.json", 16 | "start": "npm run prepare && node runner.js", 17 | "start:dev": "node runner.js", 18 | "ws": "node runner.js ws", 19 | "tslint": "tslint --project ./tslint.json", 20 | "db:serv": "node dist/test/pouchdb/serv.js", 21 | "db:clie": "node dist/test/pouchdb/clie.js", 22 | "test": "cross-env NODE_ENV=test mocha dist/**/*.test.js --timeout 5000 --require intelli-espower-loader", 23 | "test:m": "cross-env NODE_ENV=test mocha dist/**/trading.test.js --timeout 5000 --require intelli-espower-loader", 24 | "test:db": "cross-env NODE_ENV=test mocha dist/**/storage.test.js --timeout 5000 --require intelli-espower-loader", 25 | "test:api": "cross-env NODE_ENV=test mocha dist/**/api-handler.test.js --timeout 5000 --require intelli-espower-loader", 26 | "prepare": "npm run rm && tsc -p tsconfig.json" 27 | }, 28 | "dependencies": { 29 | "bignumber.js": "^6.0.0", 30 | "binance": "^1.3.3", 31 | "bitbank-handler": "0.0.1-beta.2", 32 | "ccxt": "^1.10.1258", 33 | "cli-color": "^1.2.0", 34 | "cross-env": "^5.1.3", 35 | "execution-time": "^1.2.0", 36 | "express": "^4.16.2", 37 | "glob": "^7.1.2", 38 | "moment": "^2.20.1", 39 | "pouchdb": "^6.4.3", 40 | "pouchdb-find": "^6.4.3", 41 | "rimraf": "^2.6.2", 42 | "socket.io": "^2.0.4", 43 | "toml": "^2.3.3", 44 | "winston": "^3.0.0-rc1" 45 | }, 46 | "devDependencies": { 47 | "@types/config": "0.0.33", 48 | "@types/express": "^4.11.1", 49 | "@types/mocha": "^2.2.41", 50 | "@types/node": "^9.3.0", 51 | "@types/power-assert": "^1.4.29", 52 | "@types/socket.io": "^1.4.31", 53 | "@types/pouchdb": "^6.3.2", 54 | "@types/pouchdb-find": "^6.3.2", 55 | "config": "^1.26.1", 56 | "intelli-espower-loader": "^1.0.1", 57 | "mocha": "^5.0.0", 58 | "power-assert": "^1.4.4", 59 | "prettier": "^1.10.2", 60 | "ts-node": "^4.1.0", 61 | "tslint": "^5.6.0", 62 | "typescript": "^2.6.1" 63 | } 64 | } -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/favicon.ico -------------------------------------------------------------------------------- /public/image/coins/act.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/act.png -------------------------------------------------------------------------------- /public/image/coins/ada.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ada.png -------------------------------------------------------------------------------- /public/image/coins/adx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/adx.png -------------------------------------------------------------------------------- /public/image/coins/ae.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ae.png -------------------------------------------------------------------------------- /public/image/coins/agi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/agi.png -------------------------------------------------------------------------------- /public/image/coins/aion.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/aion.png -------------------------------------------------------------------------------- /public/image/coins/amb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/amb.png -------------------------------------------------------------------------------- /public/image/coins/amp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/amp.png -------------------------------------------------------------------------------- /public/image/coins/ant.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ant.png -------------------------------------------------------------------------------- /public/image/coins/appc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/appc.png -------------------------------------------------------------------------------- /public/image/coins/ardr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ardr.png -------------------------------------------------------------------------------- /public/image/coins/ark.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ark.png -------------------------------------------------------------------------------- /public/image/coins/arn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/arn.png -------------------------------------------------------------------------------- /public/image/coins/ast.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ast.png -------------------------------------------------------------------------------- /public/image/coins/atm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/atm.png -------------------------------------------------------------------------------- /public/image/coins/bat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bat.png -------------------------------------------------------------------------------- /public/image/coins/bay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bay.png -------------------------------------------------------------------------------- /public/image/coins/bcc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bcc.png -------------------------------------------------------------------------------- /public/image/coins/bcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bcd.png -------------------------------------------------------------------------------- /public/image/coins/bch.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bch.png -------------------------------------------------------------------------------- /public/image/coins/bcn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bcn.png -------------------------------------------------------------------------------- /public/image/coins/bco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bco.png -------------------------------------------------------------------------------- /public/image/coins/bcpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bcpt.png -------------------------------------------------------------------------------- /public/image/coins/bdl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bdl.png -------------------------------------------------------------------------------- /public/image/coins/bela.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bela.png -------------------------------------------------------------------------------- /public/image/coins/blcn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/blcn.png -------------------------------------------------------------------------------- /public/image/coins/blk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/blk.png -------------------------------------------------------------------------------- /public/image/coins/block.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/block.png -------------------------------------------------------------------------------- /public/image/coins/bnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bnb.png -------------------------------------------------------------------------------- /public/image/coins/bnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bnt.png -------------------------------------------------------------------------------- /public/image/coins/bnty.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bnty.png -------------------------------------------------------------------------------- /public/image/coins/bpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bpt.png -------------------------------------------------------------------------------- /public/image/coins/bq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bq.png -------------------------------------------------------------------------------- /public/image/coins/bqx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bqx.png -------------------------------------------------------------------------------- /public/image/coins/brd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/brd.png -------------------------------------------------------------------------------- /public/image/coins/btc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btc.png -------------------------------------------------------------------------------- /public/image/coins/btcd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btcd.png -------------------------------------------------------------------------------- /public/image/coins/btcz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btcz.png -------------------------------------------------------------------------------- /public/image/coins/btg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btg.png -------------------------------------------------------------------------------- /public/image/coins/btm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btm.png -------------------------------------------------------------------------------- /public/image/coins/bts.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/bts.png -------------------------------------------------------------------------------- /public/image/coins/btx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/btx.png -------------------------------------------------------------------------------- /public/image/coins/burst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/burst.png -------------------------------------------------------------------------------- /public/image/coins/cdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cdn.png -------------------------------------------------------------------------------- /public/image/coins/cdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cdt.png -------------------------------------------------------------------------------- /public/image/coins/clam.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/clam.png -------------------------------------------------------------------------------- /public/image/coins/cloak.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cloak.png -------------------------------------------------------------------------------- /public/image/coins/cmt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cmt.png -------------------------------------------------------------------------------- /public/image/coins/cnd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cnd.png -------------------------------------------------------------------------------- /public/image/coins/cnx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cnx.png -------------------------------------------------------------------------------- /public/image/coins/cny.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cny.png -------------------------------------------------------------------------------- /public/image/coins/cred.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cred.png -------------------------------------------------------------------------------- /public/image/coins/crpt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/crpt.png -------------------------------------------------------------------------------- /public/image/coins/cvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/cvc.png -------------------------------------------------------------------------------- /public/image/coins/dash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dash.png -------------------------------------------------------------------------------- /public/image/coins/dat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dat.png -------------------------------------------------------------------------------- /public/image/coins/data.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/data.png -------------------------------------------------------------------------------- /public/image/coins/dbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dbc.png -------------------------------------------------------------------------------- /public/image/coins/dcn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dcn.png -------------------------------------------------------------------------------- /public/image/coins/dcr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dcr.png -------------------------------------------------------------------------------- /public/image/coins/dent.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dent.png -------------------------------------------------------------------------------- /public/image/coins/dgb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dgb.png -------------------------------------------------------------------------------- /public/image/coins/dgd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dgd.png -------------------------------------------------------------------------------- /public/image/coins/dlt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dlt.png -------------------------------------------------------------------------------- /public/image/coins/dnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/dnt.png -------------------------------------------------------------------------------- /public/image/coins/doge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/doge.png -------------------------------------------------------------------------------- /public/image/coins/drgn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/drgn.png -------------------------------------------------------------------------------- /public/image/coins/ebst.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ebst.png -------------------------------------------------------------------------------- /public/image/coins/edg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/edg.png -------------------------------------------------------------------------------- /public/image/coins/edoge.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/edoge.png -------------------------------------------------------------------------------- /public/image/coins/elf.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/elf.png -------------------------------------------------------------------------------- /public/image/coins/elix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/elix.png -------------------------------------------------------------------------------- /public/image/coins/ella.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ella.png -------------------------------------------------------------------------------- /public/image/coins/emc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/emc.png -------------------------------------------------------------------------------- /public/image/coins/emc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/emc2.png -------------------------------------------------------------------------------- /public/image/coins/eng.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/eng.png -------------------------------------------------------------------------------- /public/image/coins/enj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/enj.png -------------------------------------------------------------------------------- /public/image/coins/eos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/eos.png -------------------------------------------------------------------------------- /public/image/coins/etc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/etc.png -------------------------------------------------------------------------------- /public/image/coins/eth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/eth.png -------------------------------------------------------------------------------- /public/image/coins/ethos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ethos.png -------------------------------------------------------------------------------- /public/image/coins/etn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/etn.png -------------------------------------------------------------------------------- /public/image/coins/etp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/etp.png -------------------------------------------------------------------------------- /public/image/coins/eur.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/eur.png -------------------------------------------------------------------------------- /public/image/coins/evx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/evx.png -------------------------------------------------------------------------------- /public/image/coins/exmo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/exmo.png -------------------------------------------------------------------------------- /public/image/coins/exp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/exp.png -------------------------------------------------------------------------------- /public/image/coins/fair.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fair.png -------------------------------------------------------------------------------- /public/image/coins/fct.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fct.png -------------------------------------------------------------------------------- /public/image/coins/fil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fil.png -------------------------------------------------------------------------------- /public/image/coins/fldc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fldc.png -------------------------------------------------------------------------------- /public/image/coins/flo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/flo.png -------------------------------------------------------------------------------- /public/image/coins/ftc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ftc.png -------------------------------------------------------------------------------- /public/image/coins/fuel.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fuel.png -------------------------------------------------------------------------------- /public/image/coins/fun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/fun.png -------------------------------------------------------------------------------- /public/image/coins/game.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/game.png -------------------------------------------------------------------------------- /public/image/coins/gas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gas.png -------------------------------------------------------------------------------- /public/image/coins/gbp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gbp.png -------------------------------------------------------------------------------- /public/image/coins/gbx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gbx.png -------------------------------------------------------------------------------- /public/image/coins/gbyte.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gbyte.png -------------------------------------------------------------------------------- /public/image/coins/gno.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gno.png -------------------------------------------------------------------------------- /public/image/coins/gnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gnt.png -------------------------------------------------------------------------------- /public/image/coins/grc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/grc.png -------------------------------------------------------------------------------- /public/image/coins/grs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/grs.png -------------------------------------------------------------------------------- /public/image/coins/gto.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gto.png -------------------------------------------------------------------------------- /public/image/coins/gup.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gup.png -------------------------------------------------------------------------------- /public/image/coins/gvt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gvt.png -------------------------------------------------------------------------------- /public/image/coins/gxs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/gxs.png -------------------------------------------------------------------------------- /public/image/coins/hpb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/hpb.png -------------------------------------------------------------------------------- /public/image/coins/hsr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/hsr.png -------------------------------------------------------------------------------- /public/image/coins/huc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/huc.png -------------------------------------------------------------------------------- /public/image/coins/hush.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/hush.png -------------------------------------------------------------------------------- /public/image/coins/icn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/icn.png -------------------------------------------------------------------------------- /public/image/coins/icx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/icx.png -------------------------------------------------------------------------------- /public/image/coins/ignis.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ignis.png -------------------------------------------------------------------------------- /public/image/coins/ins.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ins.png -------------------------------------------------------------------------------- /public/image/coins/iop.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/iop.png -------------------------------------------------------------------------------- /public/image/coins/iost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/iost.png -------------------------------------------------------------------------------- /public/image/coins/iota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/iota.png -------------------------------------------------------------------------------- /public/image/coins/jpy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/jpy.png -------------------------------------------------------------------------------- /public/image/coins/kcs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/kcs.png -------------------------------------------------------------------------------- /public/image/coins/kin.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/kin.png -------------------------------------------------------------------------------- /public/image/coins/kmd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/kmd.png -------------------------------------------------------------------------------- /public/image/coins/knc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/knc.png -------------------------------------------------------------------------------- /public/image/coins/krb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/krb.png -------------------------------------------------------------------------------- /public/image/coins/lbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lbc.png -------------------------------------------------------------------------------- /public/image/coins/lend.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lend.png -------------------------------------------------------------------------------- /public/image/coins/link.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/link.png -------------------------------------------------------------------------------- /public/image/coins/lkk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lkk.png -------------------------------------------------------------------------------- /public/image/coins/lrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lrc.png -------------------------------------------------------------------------------- /public/image/coins/lsk.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lsk.png -------------------------------------------------------------------------------- /public/image/coins/ltc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ltc.png -------------------------------------------------------------------------------- /public/image/coins/lun.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/lun.png -------------------------------------------------------------------------------- /public/image/coins/maid.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/maid.png -------------------------------------------------------------------------------- /public/image/coins/mana.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mana.png -------------------------------------------------------------------------------- /public/image/coins/mcap.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mcap.png -------------------------------------------------------------------------------- /public/image/coins/mco.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mco.png -------------------------------------------------------------------------------- /public/image/coins/med.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/med.png -------------------------------------------------------------------------------- /public/image/coins/miota.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/miota.png -------------------------------------------------------------------------------- /public/image/coins/mkr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mkr.png -------------------------------------------------------------------------------- /public/image/coins/mln.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mln.png -------------------------------------------------------------------------------- /public/image/coins/mnx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mnx.png -------------------------------------------------------------------------------- /public/image/coins/mona.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mona.png -------------------------------------------------------------------------------- /public/image/coins/mth.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mth.png -------------------------------------------------------------------------------- /public/image/coins/mtl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/mtl.png -------------------------------------------------------------------------------- /public/image/coins/music.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/music.png -------------------------------------------------------------------------------- /public/image/coins/nano.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nano.png -------------------------------------------------------------------------------- /public/image/coins/nas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nas.png -------------------------------------------------------------------------------- /public/image/coins/nav.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nav.png -------------------------------------------------------------------------------- /public/image/coins/ncash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ncash.png -------------------------------------------------------------------------------- /public/image/coins/ndz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ndz.png -------------------------------------------------------------------------------- /public/image/coins/nebl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nebl.png -------------------------------------------------------------------------------- /public/image/coins/neo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/neo.png -------------------------------------------------------------------------------- /public/image/coins/neos.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/neos.png -------------------------------------------------------------------------------- /public/image/coins/ngc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ngc.png -------------------------------------------------------------------------------- /public/image/coins/nlc2.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nlc2.png -------------------------------------------------------------------------------- /public/image/coins/nlg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nlg.png -------------------------------------------------------------------------------- /public/image/coins/nmc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nmc.png -------------------------------------------------------------------------------- /public/image/coins/nuls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nuls.png -------------------------------------------------------------------------------- /public/image/coins/nxs.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nxs.png -------------------------------------------------------------------------------- /public/image/coins/nxt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/nxt.png -------------------------------------------------------------------------------- /public/image/coins/oax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/oax.png -------------------------------------------------------------------------------- /public/image/coins/omg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/omg.png -------------------------------------------------------------------------------- /public/image/coins/omni.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/omni.png -------------------------------------------------------------------------------- /public/image/coins/ost.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ost.png -------------------------------------------------------------------------------- /public/image/coins/ox.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ox.png -------------------------------------------------------------------------------- /public/image/coins/pac.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pac.png -------------------------------------------------------------------------------- /public/image/coins/part.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/part.png -------------------------------------------------------------------------------- /public/image/coins/pasl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pasl.png -------------------------------------------------------------------------------- /public/image/coins/pay.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pay.png -------------------------------------------------------------------------------- /public/image/coins/pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pink.png -------------------------------------------------------------------------------- /public/image/coins/pirl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pirl.png -------------------------------------------------------------------------------- /public/image/coins/pivx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pivx.png -------------------------------------------------------------------------------- /public/image/coins/plr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/plr.png -------------------------------------------------------------------------------- /public/image/coins/poe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/poe.png -------------------------------------------------------------------------------- /public/image/coins/poly.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/poly.png -------------------------------------------------------------------------------- /public/image/coins/pot.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pot.png -------------------------------------------------------------------------------- /public/image/coins/powr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/powr.png -------------------------------------------------------------------------------- /public/image/coins/ppc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ppc.png -------------------------------------------------------------------------------- /public/image/coins/ppp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ppp.png -------------------------------------------------------------------------------- /public/image/coins/ppt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ppt.png -------------------------------------------------------------------------------- /public/image/coins/prl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/prl.png -------------------------------------------------------------------------------- /public/image/coins/pura.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/pura.png -------------------------------------------------------------------------------- /public/image/coins/qash.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/qash.png -------------------------------------------------------------------------------- /public/image/coins/qiwi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/qiwi.png -------------------------------------------------------------------------------- /public/image/coins/qlc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/qlc.png -------------------------------------------------------------------------------- /public/image/coins/qsp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/qsp.png -------------------------------------------------------------------------------- /public/image/coins/qtum.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/qtum.png -------------------------------------------------------------------------------- /public/image/coins/r.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/r.png -------------------------------------------------------------------------------- /public/image/coins/rads.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rads.png -------------------------------------------------------------------------------- /public/image/coins/rcn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rcn.png -------------------------------------------------------------------------------- /public/image/coins/rdd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rdd.png -------------------------------------------------------------------------------- /public/image/coins/rdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rdn.png -------------------------------------------------------------------------------- /public/image/coins/rep.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rep.png -------------------------------------------------------------------------------- /public/image/coins/req.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/req.png -------------------------------------------------------------------------------- /public/image/coins/rhoc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rhoc.png -------------------------------------------------------------------------------- /public/image/coins/ric.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ric.png -------------------------------------------------------------------------------- /public/image/coins/rise.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rise.png -------------------------------------------------------------------------------- /public/image/coins/rlc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rlc.png -------------------------------------------------------------------------------- /public/image/coins/rpx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rpx.png -------------------------------------------------------------------------------- /public/image/coins/rub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/rub.png -------------------------------------------------------------------------------- /public/image/coins/salt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/salt.png -------------------------------------------------------------------------------- /public/image/coins/san.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/san.png -------------------------------------------------------------------------------- /public/image/coins/sbd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sbd.png -------------------------------------------------------------------------------- /public/image/coins/sberbank.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sberbank.png -------------------------------------------------------------------------------- /public/image/coins/sc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sc.png -------------------------------------------------------------------------------- /public/image/coins/sky.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sky.png -------------------------------------------------------------------------------- /public/image/coins/smart.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/smart.png -------------------------------------------------------------------------------- /public/image/coins/sngls.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sngls.png -------------------------------------------------------------------------------- /public/image/coins/snt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/snt.png -------------------------------------------------------------------------------- /public/image/coins/sonm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sonm.png -------------------------------------------------------------------------------- /public/image/coins/sphtx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sphtx.png -------------------------------------------------------------------------------- /public/image/coins/srn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/srn.png -------------------------------------------------------------------------------- /public/image/coins/start.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/start.png -------------------------------------------------------------------------------- /public/image/coins/steem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/steem.png -------------------------------------------------------------------------------- /public/image/coins/storj.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/storj.png -------------------------------------------------------------------------------- /public/image/coins/storm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/storm.png -------------------------------------------------------------------------------- /public/image/coins/strat.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/strat.png -------------------------------------------------------------------------------- /public/image/coins/sub.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sub.png -------------------------------------------------------------------------------- /public/image/coins/sys.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/sys.png -------------------------------------------------------------------------------- /public/image/coins/taas.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/taas.png -------------------------------------------------------------------------------- /public/image/coins/tau.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tau.png -------------------------------------------------------------------------------- /public/image/coins/tix.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tix.png -------------------------------------------------------------------------------- /public/image/coins/tkn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tkn.png -------------------------------------------------------------------------------- /public/image/coins/tnb.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tnb.png -------------------------------------------------------------------------------- /public/image/coins/tnc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tnc.png -------------------------------------------------------------------------------- /public/image/coins/tnt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tnt.png -------------------------------------------------------------------------------- /public/image/coins/trig.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/trig.png -------------------------------------------------------------------------------- /public/image/coins/trx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/trx.png -------------------------------------------------------------------------------- /public/image/coins/tzc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/tzc.png -------------------------------------------------------------------------------- /public/image/coins/ubq.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ubq.png -------------------------------------------------------------------------------- /public/image/coins/usd.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/usd.png -------------------------------------------------------------------------------- /public/image/coins/usdt.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/usdt.png -------------------------------------------------------------------------------- /public/image/coins/ven.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/ven.png -------------------------------------------------------------------------------- /public/image/coins/veri.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/veri.png -------------------------------------------------------------------------------- /public/image/coins/via.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/via.png -------------------------------------------------------------------------------- /public/image/coins/vib.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/vib.png -------------------------------------------------------------------------------- /public/image/coins/vibe.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/vibe.png -------------------------------------------------------------------------------- /public/image/coins/vivo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/vivo.png -------------------------------------------------------------------------------- /public/image/coins/vrc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/vrc.png -------------------------------------------------------------------------------- /public/image/coins/vtc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/vtc.png -------------------------------------------------------------------------------- /public/image/coins/wabi.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/wabi.png -------------------------------------------------------------------------------- /public/image/coins/waves.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/waves.png -------------------------------------------------------------------------------- /public/image/coins/wax.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/wax.png -------------------------------------------------------------------------------- /public/image/coins/wtc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/wtc.png -------------------------------------------------------------------------------- /public/image/coins/xbc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xbc.png -------------------------------------------------------------------------------- /public/image/coins/xcp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xcp.png -------------------------------------------------------------------------------- /public/image/coins/xdn.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xdn.png -------------------------------------------------------------------------------- /public/image/coins/xem.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xem.png -------------------------------------------------------------------------------- /public/image/coins/xlm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xlm.png -------------------------------------------------------------------------------- /public/image/coins/xmg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xmg.png -------------------------------------------------------------------------------- /public/image/coins/xmr.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xmr.png -------------------------------------------------------------------------------- /public/image/coins/xmy.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xmy.png -------------------------------------------------------------------------------- /public/image/coins/xp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xp.png -------------------------------------------------------------------------------- /public/image/coins/xpa.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xpa.png -------------------------------------------------------------------------------- /public/image/coins/xpm.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xpm.png -------------------------------------------------------------------------------- /public/image/coins/xrp.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xrp.png -------------------------------------------------------------------------------- /public/image/coins/xtz.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xtz.png -------------------------------------------------------------------------------- /public/image/coins/xuc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xuc.png -------------------------------------------------------------------------------- /public/image/coins/xvc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xvc.png -------------------------------------------------------------------------------- /public/image/coins/xvg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xvg.png -------------------------------------------------------------------------------- /public/image/coins/xzc.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/xzc.png -------------------------------------------------------------------------------- /public/image/coins/yoyow.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/yoyow.png -------------------------------------------------------------------------------- /public/image/coins/zcl.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/zcl.png -------------------------------------------------------------------------------- /public/image/coins/zec.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/zec.png -------------------------------------------------------------------------------- /public/image/coins/zen.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/zen.png -------------------------------------------------------------------------------- /public/image/coins/zil.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/zil.png -------------------------------------------------------------------------------- /public/image/coins/zrx.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/zlq4863947/triangular-arbitrage/c8e58a7f3c14b5e9af6577c492cb57f873a58ef7/public/image/coins/zrx.png -------------------------------------------------------------------------------- /public/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 三角套利机器人 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 |
16 |
17 |
18 |

三角套利

19 |
20 |
21 |
22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 43 | 47 | 51 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | -------------------------------------------------------------------------------- /public/pubsub.js: -------------------------------------------------------------------------------- 1 | angular.module('pubSub', []) 2 | .controller('pubSubController', function ($scope) { 3 | 4 | var socket = io(); // connect to the socket on this server 5 | socket.on('updateArbitage', function (data) { // whenever server send 'updateArbitage' 6 | $scope.ranks = data; // update 7 | $scope.$apply(); // send to frontend 8 | }); 9 | }); -------------------------------------------------------------------------------- /runner.js: -------------------------------------------------------------------------------- 1 | const glob = require("glob"); 2 | const path = require( 'path' ); 3 | 4 | var args = process.argv.splice(2); 5 | let name = 'start'; 6 | if(args.length != 0) { 7 | name = args[0]; 8 | } 9 | 10 | glob.sync(`dist/**/${name}.js`).forEach( function( file ) { 11 | require(path.resolve(file)); 12 | }); -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | export * from './lib/arbitrage'; 2 | -------------------------------------------------------------------------------- /src/lib/aggregator.ts: -------------------------------------------------------------------------------- 1 | import * as types from './type'; 2 | import { logger, Helper } from './common'; 3 | import { Bitbank } from 'bitbank-handler'; 4 | 5 | export class Aggregator { 6 | async getMarkets(exchange: types.IExchange): Promise { 7 | const api = exchange.endpoint.public || exchange.endpoint.private; 8 | if (!api) { 9 | return; 10 | } 11 | switch (exchange.id) { 12 | case types.ExchangeId.Bitbank: 13 | return { 14 | 'BCC/BTC': { id: 'bcc_btc', symbol: 'BCC/BTC', base: 'BCC', quote: 'BTC', baseId: 'BCC' }, 15 | 'BCC/JPY': { id: 'bcc_jpy', symbol: 'BCC/JPY', base: 'BCC', quote: 'JPY', baseId: 'BCC' }, 16 | 'MONA/BTC': { id: 'mona_btc', symbol: 'MONA/BTC', base: 'MONA', quote: 'BTC' }, 17 | 'MONA/JPY': { id: 'mona_jpy', symbol: 'MONA/JPY', base: 'MONA', quote: 'JPY' }, 18 | 'ETH/BTC': { id: 'eth_btc', symbol: 'ETH/BTC', base: 'ETH', quote: 'BTC' }, 19 | 'LTC/BTC': { id: 'ltc_btc', symbol: 'LTC/BTC', base: 'LTC', quote: 'BTC' }, 20 | 'XRP/JPY': { id: 'xrp_jpy', symbol: 'XRP/JPY', base: 'XRP', quote: 'JPY' }, 21 | 'BTC/JPY': { id: 'btc_jpy', symbol: 'BTC/JPY', base: 'BTC', quote: 'JPY' }, 22 | }; 23 | default: 24 | return await api.loadMarkets(); 25 | } 26 | } 27 | 28 | async getAllTickers(exchange: types.IExchange, extTickers?: types.Binance24HrTicker[]): Promise { 29 | try { 30 | const api = exchange.endpoint.public || exchange.endpoint.private; 31 | if (!api || !exchange.pairs) { 32 | return; 33 | } 34 | switch (exchange.id) { 35 | case types.ExchangeId.Binance: 36 | if (!extTickers) { 37 | logger.error(`getAllTickers:[${exchange.id}], 未传递extTickers数据!`); 38 | return; 39 | } 40 | return Helper.changeBinanceTickers(extTickers, exchange.pairs); 41 | case types.ExchangeId.Bitbank: 42 | const symbols = Object.keys(exchange.pairs); 43 | const bitbank = api; 44 | const tickers: types.ITickers = {}; 45 | for (const symbol of symbols) { 46 | const tickId = symbol.replace('/', '_').toLowerCase(); 47 | const extDepth = await bitbank.getDepth(tickId).toPromise(); 48 | tickers[symbol] = { 49 | ask: +extDepth.asks[0][0], 50 | askVolume: +extDepth.asks[0][1], 51 | bid: +extDepth.bids[0][0], 52 | bidVolume: +extDepth.bids[0][1], 53 | symbol, 54 | timestamp: Date.now(), 55 | datetime: '', 56 | high: 0, 57 | low: 0, 58 | info: {}, 59 | }; 60 | } 61 | return tickers; 62 | default: 63 | return await api.fetchTickers(); 64 | } 65 | } catch (err) { 66 | logger.error(`getAllTickers出错: ${err.message ? err.message : err.msg}`); 67 | } 68 | } 69 | } 70 | -------------------------------------------------------------------------------- /src/lib/api-handler.ts: -------------------------------------------------------------------------------- 1 | import * as ccxt from 'ccxt'; 2 | import * as types from './type'; 3 | import { Bitbank } from 'bitbank-handler'; 4 | import { logger, Helper } from './common'; 5 | 6 | export { ccxt }; 7 | export class ApiHandler { 8 | async getBalance(exchange: types.IExchange): Promise { 9 | const api = exchange.endpoint.private; 10 | if (!api) { 11 | return; 12 | } 13 | switch (exchange.id) { 14 | case types.ExchangeId.Bitbank: 15 | const bitbank = api; 16 | // TODO 17 | return await bitbank.getAssets().toPromise(); 18 | default: 19 | return await api.fetchBalance(); 20 | } 21 | } 22 | 23 | async getFreeAmount(exchange: types.IExchange, coin: string) { 24 | const balances = await this.getBalance(exchange); 25 | if (!balances) { 26 | return; 27 | } 28 | const asset = balances[coin]; 29 | if (!asset) { 30 | logger.debug(`未查找到持有${coin}!!`); 31 | return; 32 | } 33 | return asset.free; 34 | } 35 | 36 | async createOrder(exchange: types.IExchange, order: types.IOrder): Promise { 37 | const api = exchange.endpoint.private; 38 | if (!api) { 39 | return; 40 | } 41 | return await api.createOrder(order.symbol, order.type, order.side, String(order.amount), String(order.price)); 42 | } 43 | 44 | async queryOrder(exchange: types.IExchange, orderId: string, symbol: string): Promise { 45 | const api = exchange.endpoint.private; 46 | if (!api) { 47 | return; 48 | } 49 | return await api.fetchOrder(orderId, symbol); 50 | } 51 | 52 | async queryOrderStatus(exchange: types.IExchange, orderId: string, symbol: string) { 53 | const api = exchange.endpoint.private; 54 | if (!api) { 55 | return; 56 | } 57 | return await api.fetchOrderStatus(orderId, symbol); 58 | } 59 | } 60 | -------------------------------------------------------------------------------- /src/lib/arbitrage.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import { logger, Helper } from './common'; 3 | import { Event } from './event'; 4 | import { Engine } from './engine'; 5 | import { Aggregator } from './aggregator'; 6 | import * as types from './type'; 7 | 8 | const clc = require('cli-color'); 9 | const config = require('config'); 10 | 11 | export class TriangularArbitrage extends Event { 12 | exchanges: Map = new Map(); 13 | activeExchangeId: types.ExchangeId; 14 | // 机器人id 15 | worker = 0; 16 | // 匹配引擎 17 | engine: Engine; 18 | // 集计数据提供 19 | aggregator: Aggregator; 20 | 21 | constructor() { 22 | super(); 23 | this.activeExchangeId = config.exchange.active; 24 | this.engine = new Engine(); 25 | this.aggregator = new Aggregator(); 26 | } 27 | 28 | async start(activeExchangeId?: types.ExchangeId) { 29 | const timer = Helper.getTimer(); 30 | logger.debug('启动三角套利机器人[开始]'); 31 | if (activeExchangeId) { 32 | this.activeExchangeId = activeExchangeId; 33 | } 34 | 35 | try { 36 | // 初始化交易所 37 | await this.initExchange(this.activeExchangeId); 38 | if (types.ExchangeId.Binance === this.activeExchangeId) { 39 | const exchange = this.exchanges.get(this.activeExchangeId); 40 | if (!exchange) { 41 | return; 42 | } 43 | exchange.endpoint.ws.onAllTickers(this.estimate.bind(this)); 44 | } else { 45 | this.worker = setInterval(this.estimate.bind(this), config.arbitrage.interval * 1000); 46 | } 47 | 48 | logger.info('----- 机器人启动完成 -----'); 49 | } catch (err) { 50 | logger.error(`机器人运行出错(${Helper.endTimer(timer)}): ${err}`); 51 | } 52 | logger.debug(`启动三角套利机器人[终了] ${Helper.endTimer(timer)}`); 53 | } 54 | 55 | destroy() { 56 | if (this.worker) { 57 | clearInterval(this.worker); 58 | } 59 | } 60 | 61 | public async initExchange(exchangeId: types.ExchangeId) { 62 | const timer = Helper.getTimer(); 63 | logger.debug('初始化交易所[启动]'); 64 | try { 65 | // 查看是否已初始化api 66 | if (this.exchanges.get(exchangeId)) { 67 | return; 68 | } 69 | 70 | const exchange = Helper.getExchange(exchangeId); 71 | if (!exchange) { 72 | return; 73 | } 74 | const api = exchange.endpoint.public || exchange.endpoint.private; 75 | if (api) { 76 | exchange.pairs = await this.aggregator.getMarkets(exchange); 77 | if (!exchange.pairs) { 78 | return; 79 | } 80 | const markets: { 81 | [coin: string]: types.IMarket[]; 82 | } = {}; 83 | const baseCoins = Helper.getMarketCoins(Object.keys(exchange.pairs)); 84 | for (const baseCoin of baseCoins) { 85 | if (!markets[baseCoin]) { 86 | markets[baseCoin] = []; 87 | } 88 | const pairKeys = Object.keys(exchange.pairs).filter((pair: string) => pair.includes(baseCoin)); 89 | for (const key of pairKeys) { 90 | markets[baseCoin].push(exchange.pairs[key]); 91 | } 92 | exchange.markets = markets; 93 | } 94 | } 95 | this.exchanges.set(exchangeId, exchange); 96 | logger.debug(`初始化交易所[终了] ${Helper.endTimer(timer)}`); 97 | } catch (err) { 98 | logger.error(`初始化交易所[异常](${Helper.endTimer(timer)}): ${err}`); 99 | } 100 | } 101 | 102 | // 套利测算 103 | async estimate(tickers?: types.Binance24HrTicker[]) { 104 | const timer = Helper.getTimer(); 105 | logger.debug('监视行情[开始]'); 106 | try { 107 | // logger.info(clc.magentaBright('----- 套利测算 -----')); 108 | const exchange = this.exchanges.get(this.activeExchangeId); 109 | if (!exchange) { 110 | return; 111 | } 112 | const allTickers = await this.aggregator.getAllTickers(exchange, tickers); 113 | if (!allTickers) { 114 | return; 115 | } 116 | // 匹配候选者 117 | const candidates = await this.engine.getCandidates(exchange, allTickers); 118 | if (!candidates || candidates.length === 0) { 119 | return; 120 | } 121 | 122 | const ranks = Helper.getRanks(exchange.id, candidates); 123 | if (config.storage.tickRank && ranks.length > 0) { 124 | // 更新套利数据 125 | this.emit('updateArbitage', ranks); 126 | } 127 | // 更新套利数据 128 | if (ranks[0]) { 129 | // logger.info(`选出套利组合第一名:${candidates[0].id}, 预测利率(扣除手续费): ${ranks[0].profitRate[0]}`); 130 | // 执行三角套利 131 | this.emit('placeOrder', exchange, candidates[0]); 132 | } 133 | 134 | /*const output = candidates.length > 5 ? candidates.slice(0, 5) : candidates.slice(0, candidates.length); 135 | for (const candidate of output) { 136 | const clcRate = candidate.rate < 0 ? clc.redBright(candidate.rate) : clc.greenBright(candidate.rate); 137 | const path = candidate.id.length < 15 ? candidate.id + ' '.repeat(15 - candidate.id.length) : candidate.id; 138 | logger.info(`路径:${clc.cyanBright(path)} 利率: ${clcRate}`); 139 | }*/ 140 | logger.debug(`监视行情[终了] ${Helper.endTimer(timer)}`); 141 | } catch (err) { 142 | logger.error(`监视行情[异常](${Helper.endTimer(timer)}): ${JSON.stringify(err)}`); 143 | } 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /src/lib/bin/start.ts: -------------------------------------------------------------------------------- 1 | import { TriangularArbitrage } from '../arbitrage'; 2 | 3 | (async function bootstrap() { 4 | const app = new TriangularArbitrage(); 5 | await app.start(); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/lib/bin/ws.ts: -------------------------------------------------------------------------------- 1 | import { WebService } from '../webservice'; 2 | 3 | (async function bootstrap() { 4 | const webapp = new WebService(); 5 | webapp.start(); 6 | })(); 7 | -------------------------------------------------------------------------------- /src/lib/common/helper.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { Rate } from '../rate'; 3 | import { Queue } from '../storage/queue'; 4 | import { BigNumber } from 'bignumber.js'; 5 | import * as bitbank from 'bitbank-handler'; 6 | 7 | const ccxt = require('ccxt'); 8 | const config = require('config'); 9 | const excTime = require('execution-time'); 10 | const binance = require('binance'); 11 | 12 | export class Helper { 13 | static getPrivateKey(exchangeId: types.ExchangeId) { 14 | if (config.account[exchangeId] && config.account[exchangeId].apiKey && config.account[exchangeId].secret) { 15 | return { 16 | apiKey: config.account[exchangeId].apiKey, 17 | secret: config.account[exchangeId].secret, 18 | }; 19 | } 20 | } 21 | 22 | static getExchange(exchangeId: types.ExchangeId): types.IExchange | undefined { 23 | const privateKey = Helper.getPrivateKey(exchangeId); 24 | switch (exchangeId) { 25 | case types.ExchangeId.KuCoin: 26 | case types.ExchangeId.Binance: 27 | let ws, rest; 28 | if (exchangeId === types.ExchangeId.Binance) { 29 | ws = new binance.BinanceWS(); 30 | rest = new binance.BinanceRest({ 31 | key: privateKey ? privateKey.apiKey : '', 32 | secret: privateKey ? privateKey.secret : '', 33 | timeout: 15000, 34 | recvWindow: 10000, 35 | disableBeautification: false, 36 | handleDrift: false, 37 | }); 38 | } 39 | if (privateKey) { 40 | return { 41 | id: exchangeId, 42 | endpoint: { 43 | private: new ccxt[exchangeId](privateKey), 44 | ws, 45 | rest, 46 | }, 47 | }; 48 | } 49 | return { 50 | id: exchangeId, 51 | endpoint: { 52 | public: new ccxt[exchangeId](), 53 | ws, 54 | rest, 55 | }, 56 | }; 57 | case types.ExchangeId.Bitbank: 58 | if (privateKey) { 59 | return { 60 | id: exchangeId, 61 | endpoint: { 62 | private: new bitbank.Bitbank({ 63 | apiKey: privateKey.apiKey, 64 | apiSecret: privateKey.secret, 65 | }), 66 | }, 67 | }; 68 | } 69 | return { 70 | id: exchangeId, 71 | endpoint: { 72 | public: new bitbank.Bitbank({}), 73 | }, 74 | }; 75 | } 76 | } 77 | 78 | static getMarketCoins(pairs: string[]) { 79 | const markets: string[] = []; 80 | pairs.reduce( 81 | (pre, pair) => { 82 | const market = pair.substr(pair.indexOf('/') + 1); 83 | if (market && !markets.includes(market)) { 84 | markets.push(market); 85 | } 86 | }, 87 | {}, 88 | ); 89 | return markets; 90 | } 91 | 92 | static changeBinanceTickers(tickers: types.Binance24HrTicker[], pairs: types.IPairs) { 93 | const allTickers: types.ITickers = {}; 94 | const pairKeys = Object.keys(pairs); 95 | for (const pair of pairKeys) { 96 | const oTicker = tickers.find((ticker) => ticker.symbol === pair.replace('/', '')); 97 | if (oTicker) { 98 | allTickers[pair] = { 99 | ask: +oTicker.bestAskPrice, 100 | askVolume: +oTicker.bestAskQuantity, 101 | bid: +oTicker.bestBid, 102 | bidVolume: +oTicker.bestBidQuantity, 103 | symbol: pair, 104 | timestamp: oTicker.eventTime, 105 | datetime: '', 106 | high: +oTicker.high, 107 | low: +oTicker.low, 108 | info: {}, 109 | }; 110 | } 111 | } 112 | return allTickers; 113 | } 114 | 115 | /** 116 | * 获取排行数据 117 | * @param triangles 三角套利数组 118 | */ 119 | static getRanks(exchangeId: types.ExchangeId, triangles: types.ITriangle[]) { 120 | const ranks: types.IRank[] = []; 121 | triangles.reduce( 122 | (pre, tri) => { 123 | if (tri.rate <= 0) { 124 | return; 125 | } 126 | const rate = new BigNumber(tri.rate); 127 | let fee = [0, 0]; 128 | if (exchangeId === types.ExchangeId.Binance) { 129 | fee = [rate.times(0.1).toNumber(), rate.times(0.05).toNumber()]; 130 | } 131 | const profitRate = [rate.minus(fee[0]), rate.minus(fee[1])]; 132 | if (profitRate[0].isLessThan(config.arbitrage.minRateProfit)) { 133 | return; 134 | } 135 | const rank: types.IRank = { 136 | stepA: tri.a.coinFrom, 137 | stepB: tri.b.coinFrom, 138 | stepC: tri.c.coinFrom, 139 | rate: rate.toNumber(), 140 | fee: [fee[0], fee[1]], 141 | profitRate: [profitRate[0].toNumber(), profitRate[1].toNumber()], 142 | ts: tri.ts, 143 | }; 144 | ranks.push(rank); 145 | }, 146 | {}, 147 | ); 148 | return ranks; 149 | } 150 | 151 | static toFixed(val: BigNumber, precision: number = 8) { 152 | return val.toFixed(precision); 153 | } 154 | 155 | static getTriangleRate(a: types.IEdge, b: types.IEdge, c: types.IEdge) { 156 | // 利率 = (1/priceA/priceB*priceC-1)-1 157 | // 资本金 158 | const capital = new BigNumber(1); 159 | let step1Rate = new BigNumber(a.price); 160 | if (a.side === 'buy') { 161 | step1Rate = capital.div(a.price); 162 | } 163 | 164 | let step2Rate = step1Rate.times(b.price); 165 | if (b.side === 'buy') { 166 | step2Rate = step1Rate.div(b.price); 167 | } 168 | 169 | let step3Rate = step2Rate.times(c.price); 170 | if (c.side === 'buy') { 171 | step3Rate = step2Rate.div(c.price); 172 | } 173 | 174 | return +step3Rate 175 | .minus(1) 176 | .times(100) 177 | .toFixed(8); 178 | } 179 | 180 | static getTimer() { 181 | const timer = excTime(); 182 | timer.start(); 183 | return timer; 184 | } 185 | 186 | static endTimer(timer: any) { 187 | return timer.stop().words; 188 | } 189 | 190 | /** 191 | * 获取价格精度 192 | */ 193 | static getPriceScale(pairs: types.IPairs, pairName: string): types.IPrecision | undefined { 194 | const symbol = pairs[pairName]; 195 | if (!symbol) { 196 | return; 197 | } 198 | return { 199 | amount: symbol.precision.amount, 200 | price: symbol.precision.price, 201 | cost: symbol.limits.cost ? symbol.limits.cost.min : undefined, 202 | }; 203 | } 204 | 205 | /** 206 | * 获取基础货币交易额度 207 | */ 208 | static getBaseTradeAmount(tradeAmount: BigNumber, freeAmount: BigNumber) { 209 | // 如果A点交易额 x 50% < 该资产可用额度 210 | if (tradeAmount.times(0.5).isLessThan(freeAmount)) { 211 | // 返回交易额 x 50% 212 | return tradeAmount.times(0.5); 213 | } 214 | // 返回可用额度 x 50% 215 | return freeAmount.times(0.5); 216 | } 217 | 218 | /** 219 | * 获取基础货币交易额度 220 | */ 221 | static getBaseAmountByBC(triangle: types.ITriangle, freeAmount: BigNumber, minAmount: BigNumber) { 222 | const { a, b, c } = triangle; 223 | // B点的数量 224 | const bAmount = Helper.convertAmount(b.price, b.quantity, b.side); 225 | 226 | // 换回A点的数量 227 | const b2aAmount = Helper.convertAmount(a.price, bAmount.toNumber(), a.side); 228 | // c点数量 229 | const c2aAmount = Helper.getConvertedAmount({ 230 | side: triangle.c.side, 231 | exchangeRate: triangle.c.price, 232 | amount: triangle.c.quantity, 233 | }) 234 | 235 | // 选取数量最大的 236 | const thanAmount = b2aAmount.isGreaterThan(c2aAmount) ? b2aAmount : c2aAmount; 237 | // 选取数量 > 最小交易量 && 选取数量 < 可用余额 238 | if (thanAmount.isGreaterThan(minAmount) && thanAmount.isLessThan(freeAmount)) { 239 | return thanAmount; 240 | } 241 | return minAmount; 242 | } 243 | 244 | /** 245 | * 获取转换后的数量 246 | */ 247 | static getConvertedAmount(rateQuote: types.IRateQuote) { 248 | return Rate.convert(rateQuote); 249 | } 250 | 251 | /** 252 | * 转换获取指定总价(标的货币数量)时,需要的交易数量 253 | * @param price 价格 254 | * @param cost 总价 255 | * @param side 方向 256 | */ 257 | static convertAmount(price: number, cost: number, side: 'sell' | 'buy') { 258 | return Rate.convertAmount(price, cost, side); 259 | } 260 | 261 | /** 262 | * 检查交易队列是否超过限制 263 | */ 264 | static async checkQueueLimit(queue: Queue) { 265 | const res = await queue.info(); 266 | if (res && res.doc_count < config.trading.limit) { 267 | return true; 268 | } 269 | return false; 270 | } 271 | } 272 | -------------------------------------------------------------------------------- /src/lib/common/index.ts: -------------------------------------------------------------------------------- 1 | export * from './helper'; 2 | export * from './logger'; 3 | -------------------------------------------------------------------------------- /src/lib/common/logger.ts: -------------------------------------------------------------------------------- 1 | const moment = require('moment'); 2 | const config = require('config'); 3 | import * as fs from 'fs'; 4 | 5 | // 准备日志 6 | const { createLogger, format, transports } = require('winston'); 7 | const { combine, timestamp, label, printf, colorize } = format; 8 | timestamp(); 9 | const logDir = 'log'; 10 | // 如果目录不存在 11 | if (!fs.existsSync(logDir)) { 12 | // 创建日志目录 13 | fs.mkdirSync(logDir); 14 | } 15 | 16 | const tsFormat = () => moment().format(); 17 | const myFormat = printf((info: any) => { 18 | return `${info.timestamp} [${info.level}] ${info.message}`; 19 | }); 20 | let myTransports = [ 21 | // 将输出着色到控制台 22 | new transports.File({ 23 | filename: `${logDir}/error.log`, 24 | level: 'error', 25 | }), 26 | new transports.File({ 27 | filename: `${logDir}/combined.log`, 28 | level: 'info', 29 | }), 30 | new transports.Console({ 31 | format: combine(colorize(), myFormat), 32 | level: 'info', 33 | }), 34 | ]; 35 | 36 | if (config.log.debug) { 37 | myTransports = myTransports.concat([ 38 | new transports.File({ 39 | filename: `${logDir}/debug.log`, 40 | level: 'debug', 41 | }), 42 | new transports.Console({ 43 | format: combine(colorize(), myFormat), 44 | level: 'debug', 45 | }), 46 | ]); 47 | } 48 | 49 | export const logger = createLogger({ 50 | format: combine( 51 | label({ label: '' }), 52 | timestamp({ 53 | format: tsFormat, 54 | }), 55 | myFormat, 56 | ), 57 | transports: myTransports, 58 | }); 59 | -------------------------------------------------------------------------------- /src/lib/engine.ts: -------------------------------------------------------------------------------- 1 | import * as types from './type'; 2 | import { logger, Helper } from './common'; 3 | 4 | const config = require('config'); 5 | 6 | export class Engine { 7 | // 获取组合的边 8 | getEdge(tickers: types.ITickers, coinFrom: string, coinTo: string) { 9 | if ((!tickers && Object.keys(tickers).length === 0) || !coinFrom || !coinTo) { 10 | return; 11 | } 12 | 13 | // 查找匹配的ticker 14 | const buyTicker = tickers[coinTo + '/' + coinFrom]; 15 | 16 | const edge = { coinFrom, coinTo }; 17 | if (buyTicker) { 18 | edge.pair = buyTicker.symbol; 19 | edge.side = 'buy'; 20 | edge.price = buyTicker.ask; 21 | edge.quantity = buyTicker.askVolume; 22 | } else { 23 | // 查找匹配的ticker 24 | const sellTicker = tickers[coinFrom + '/' + coinTo]; 25 | if (!sellTicker) { 26 | return; 27 | } 28 | edge.pair = sellTicker.symbol; 29 | edge.side = 'sell'; 30 | edge.price = sellTicker.bid; 31 | edge.quantity = sellTicker.bidVolume; 32 | } 33 | return edge; 34 | } 35 | 36 | // 获取三角套利信息 37 | private getTriangle(tickers: types.ITickers, abc: { a: string; b: string; c: string }) { 38 | if ((!tickers && Object.keys(tickers).length === 0) || !abc || !abc.a || !abc.b || !abc.c) { 39 | return; 40 | } 41 | const a = this.getEdge(tickers, abc.a, abc.b); 42 | const b = this.getEdge(tickers, abc.b, abc.c); 43 | const c = this.getEdge(tickers, abc.c, abc.a); 44 | if (!a || !b || !c) { 45 | return; 46 | } 47 | const rate = Helper.getTriangleRate(a, b, c); 48 | return { 49 | id: a.coinFrom + '-' + b.coinFrom + '-' + c.coinFrom, 50 | a, 51 | b, 52 | c, 53 | rate, 54 | ts: Date.now(), 55 | }; 56 | } 57 | 58 | private findCandidates(exchange: types.IExchange, tickers: types.ITickers, aCoinfrom: string, aCoinTo: string) { 59 | if (!exchange.markets) { 60 | return; 61 | } 62 | const abc = { 63 | a: aCoinfrom.toUpperCase(), 64 | b: aCoinTo.toUpperCase(), 65 | c: 'findme'.toUpperCase(), 66 | }; 67 | 68 | const aPairs = exchange.markets[abc.a]; 69 | const bPairs = exchange.markets[abc.b]; 70 | 71 | const aCoinToSet: { [coin: string]: types.IMarket } = {}; 72 | aPairs.map((market: types.IMarket) => { 73 | aCoinToSet[market.base] = market; 74 | }); 75 | 76 | // 去掉b点coin 77 | delete aCoinToSet[abc.b]; 78 | 79 | /* 80 | 通过BPair配对 81 | */ 82 | const triangles: types.ITriangle[] = []; 83 | for (let i = 0; i < bPairs.length; i++) { 84 | const bPairMarket = bPairs[i]; 85 | 86 | if (aCoinToSet[bPairMarket.base]) { 87 | const stepC = this.getEdge(tickers, bPairMarket.base, abc.a); 88 | 89 | // 匹配到路径C 90 | if (stepC) { 91 | abc.c = stepC.coinFrom; 92 | 93 | const triangle = this.getTriangle(tickers, abc); 94 | if (!triangle) { 95 | continue; 96 | } 97 | 98 | triangles.push(triangle); 99 | } 100 | } 101 | } 102 | return triangles; 103 | } 104 | 105 | async getCandidates(exchange: types.IExchange, tickers: types.ITickers) { 106 | let candidates: types.ITriangle[] = []; 107 | if (!exchange.markets) { 108 | return; 109 | } 110 | const marketPairs = Object.keys(exchange.markets); 111 | const api = exchange.endpoint.public || exchange.endpoint.private; 112 | if (!api || marketPairs.length === 0) { 113 | return; 114 | } 115 | const timer = Helper.getTimer(); 116 | logger.debug('getCandidates:获取全市场候选者[开始]'); 117 | for (const [index, marketPair] of marketPairs.entries()) { 118 | const paths = marketPairs.slice(0); 119 | // 删除起始路径 120 | paths.splice(index, 1); 121 | 122 | for (const path of paths) { 123 | const foundCandidates = this.findCandidates(exchange, tickers, marketPair, path); 124 | if (foundCandidates && foundCandidates.length > 0) { 125 | candidates = candidates.concat(foundCandidates); 126 | } 127 | } 128 | } 129 | if (candidates.length) { 130 | candidates.sort((a, b) => { 131 | return b.rate - a.rate; 132 | }); 133 | } 134 | 135 | // 淘汰落选者 136 | if (candidates.length > config.display.maxRows) { 137 | candidates = candidates.slice(0, config.display.maxRows); 138 | } 139 | 140 | logger.debug(`getCandidates:获取全市场候选者[终了] ${Helper.endTimer(timer)}`); 141 | return candidates; 142 | } 143 | } 144 | -------------------------------------------------------------------------------- /src/lib/event.ts: -------------------------------------------------------------------------------- 1 | import { EventEmitter } from 'events'; 2 | import { Trading } from './trading'; 3 | import * as types from './type'; 4 | import { logger, Helper } from './common'; 5 | 6 | /** 7 | * 通用事件处理器 8 | */ 9 | export class Event extends EventEmitter { 10 | trading: Trading; 11 | 12 | constructor() { 13 | super(); 14 | this.trading = new Trading(); 15 | this.on('placeOrder', this.onPlaceOrder); 16 | this.on('updateArbitage', this.onUpdateArbitage); 17 | } 18 | 19 | async onPlaceOrder(exchange: types.IExchange, triangle: types.ITriangle) { 20 | const timer = Helper.getTimer(); 21 | logger.debug('执行订单事件[开始]'); 22 | await this.trading.placeOrder(exchange, triangle); 23 | } 24 | 25 | async onUpdateArbitage(ranks: types.IRank[]) { 26 | if (ranks.length > 0) { 27 | await this.trading.storage.rank.putRanks(ranks); 28 | } 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /src/lib/rate.ts: -------------------------------------------------------------------------------- 1 | import * as types from './type'; 2 | import { BigNumber } from 'bignumber.js'; 3 | 4 | export class Rate { 5 | /** 6 | * 汇率对象转换(返回可购买数量) 7 | * 8 | * @param rateQuote 转换对象 9 | */ 10 | static convert(rateQuote: types.IRateQuote): BigNumber { 11 | const bigAmount = new BigNumber(rateQuote.amount); 12 | if (rateQuote.side === 'buy') { 13 | return bigAmount.div(rateQuote.exchangeRate); 14 | } 15 | return bigAmount.times(rateQuote.exchangeRate); 16 | } 17 | 18 | /** 19 | * 换算需使用的数量 20 | * 21 | * @param price 价格 22 | * @param cost 数量*价格=总价 23 | */ 24 | static convertAmount(price: number, cost: number, side: 'sell' | 'buy') { 25 | const bigCost = new BigNumber(cost); 26 | if (side === 'buy') { 27 | // amount / price = cost 28 | return bigCost.times(price); 29 | } 30 | // amount * price = cost 31 | return bigCost.div(price); 32 | } 33 | } 34 | -------------------------------------------------------------------------------- /src/lib/storage/base.ts: -------------------------------------------------------------------------------- 1 | import * as PouchDB from 'pouchdb'; 2 | import { logger } from '../common'; 3 | 4 | export class StorageBase extends PouchDB { 5 | constructor(url: string) { 6 | super(url, { 7 | prefix: './db/', 8 | }); 9 | } 10 | 11 | /** 12 | * 存储数据 13 | * @todo 存储放到待修改 14 | * @param rows 15 | */ 16 | async putRows(rows: { [attr: string]: any }[]) { 17 | try { 18 | const docs = await this.allDocs({ 19 | include_docs: true, 20 | attachments: true, 21 | }); 22 | for (const [i, row] of rows.entries()) { 23 | if (docs.rows[i]) { 24 | rows[i] = Object.assign({}, docs.rows[i].doc, row); 25 | } 26 | } 27 | return await this.bulkDocs(rows); 28 | } catch (err) { 29 | logger.error(`存储数据集出错: ${err.message}`); 30 | } 31 | } 32 | 33 | async putRow(row: { [attr: string]: any }) { 34 | try { 35 | const docs = await this.allDocs({ 36 | include_docs: true, 37 | attachments: true, 38 | }); 39 | if (!docs) { 40 | return; 41 | } 42 | const dbRow = Object.assign({}, docs.rows.find((o) => o.id === row.id), row); 43 | return await this.put(dbRow); 44 | } catch (err) { 45 | logger.error(`存储数据出错: ${err.message}`); 46 | } 47 | } 48 | 49 | async getAllDocs() { 50 | try { 51 | const docsInfo = await this.allDocs({ 52 | include_docs: true, 53 | attachments: true, 54 | }); 55 | const docs: any[] = []; 56 | if (docsInfo && docsInfo.rows && docsInfo.rows.length > 0) { 57 | const rows: any[] = docsInfo.rows; 58 | rows.reduce( 59 | (pre, row) => { 60 | if (row && row.doc) { 61 | docs.push(row.doc); 62 | } 63 | }, 64 | {}, 65 | ); 66 | } 67 | return docs; 68 | } catch (err) { 69 | logger.error(`获取数据出错: ${err.message}`); 70 | } 71 | } 72 | 73 | async removeAllDocs() { 74 | try { 75 | const docsInfo = await this.allDocs({ 76 | include_docs: true, 77 | attachments: true, 78 | }); 79 | if (docsInfo && docsInfo.rows && docsInfo.rows.length > 0) { 80 | const docs: any[] = []; 81 | if (docsInfo && docsInfo.rows && docsInfo.rows.length > 0) { 82 | const rows: any[] = docsInfo.rows; 83 | rows.reduce( 84 | (pre, row) => { 85 | if (row && row.doc) { 86 | row.doc._deleted = true; 87 | docs.push(row.doc); 88 | } 89 | }, 90 | {}, 91 | ); 92 | } 93 | return await this.bulkDocs(docs); 94 | } 95 | } catch (err) { 96 | logger.error(`删除数据出错: ${err.message}`); 97 | } 98 | } 99 | 100 | onChanged(fn: (change: any) => {}) { 101 | this.changes({ 102 | live: true, 103 | include_docs: true, 104 | }).on('change', fn); 105 | } 106 | } 107 | -------------------------------------------------------------------------------- /src/lib/storage/index.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { logger } from '../common'; 3 | import { Rank } from './rank'; 4 | import { Trade } from './trade'; 5 | import { Queue } from './queue'; 6 | const config = require('config'); 7 | 8 | export class Storage { 9 | url = ''; 10 | rank: Rank; 11 | trade: Trade; 12 | queue: Queue; 13 | 14 | constructor() { 15 | if (config.storage.url) { 16 | this.url = config.storage.url; 17 | } 18 | this.rank = new Rank(this.url); 19 | this.trade = new Trade(this.url); 20 | this.queue = new Queue(this.url); 21 | } 22 | 23 | private async getQueue(tradeId: string, exchange: string) { 24 | const queueRes = await this.queue.findQueue(tradeId, exchange); 25 | // 队列中triangleId和exchange组合key是唯一的 26 | if (!queueRes || !queueRes.doc) { 27 | return; 28 | } 29 | return queueRes.doc; 30 | } 31 | 32 | /** 33 | * 打开交易会话 34 | */ 35 | async openTradingSession(trade: types.ITrade) { 36 | if (!trade.mock.id || !trade.mock.exchange) { 37 | return; 38 | } 39 | const queueInfo = await this.queue.addQueue({ 40 | triangleId: trade.mock.id, 41 | exchange: trade.mock.exchange, 42 | step: 0, 43 | }); 44 | if (!queueInfo) { 45 | return; 46 | } 47 | trade._id = queueInfo.id; 48 | trade.mock.queueId = queueInfo.id; 49 | if (trade.real) { 50 | trade.real.queueId = queueInfo.id; 51 | } 52 | await this.trade.put(trade); 53 | return trade._id; 54 | } 55 | 56 | async updateTradingSession(trade: types.ITradeTriangle, step: types.tradeStep) { 57 | const queue = await this.getQueue(trade.id, trade.exchange); 58 | if (!queue || !queue._id) { 59 | return; 60 | } 61 | queue.step = step; 62 | await this.queue.put(queue); 63 | const oldTrade: types.ITrade = await this.trade.get(queue._id); 64 | if (oldTrade.real) { 65 | switch (step) { 66 | case 0: 67 | oldTrade.real.a = trade.a; 68 | break; 69 | case 1: 70 | oldTrade.real.b = trade.b; 71 | break; 72 | case 2: 73 | oldTrade.real.c = trade.c; 74 | break; 75 | } 76 | return await this.trade.put(oldTrade); 77 | } 78 | } 79 | 80 | async closeTradingSession(trade: types.ITradeTriangle) { 81 | const queue = await this.getQueue(trade.id, trade.exchange); 82 | if (!queue || !queue._id || !queue._rev) { 83 | return; 84 | } 85 | await this.queue.remove(queue._id, queue._rev); 86 | const oldTrade: types.ITrade = await this.trade.get(queue._id); 87 | if (oldTrade.real) { 88 | oldTrade.real.c = trade.c; 89 | return await this.trade.put(oldTrade); 90 | } 91 | } 92 | 93 | async clearQueue(tradeId: string, exchange: string) { 94 | const queue = await this.getQueue(tradeId, exchange); 95 | if (!queue || !queue._id || !queue._rev) { 96 | return; 97 | } 98 | await this.queue.remove(queue._id, queue._rev); 99 | } 100 | } 101 | -------------------------------------------------------------------------------- /src/lib/storage/queue.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { logger } from '../common'; 3 | import { StorageBase } from './base'; 4 | const moment = require('moment'); 5 | const config = require('config'); 6 | 7 | export class Queue extends StorageBase { 8 | static id = 'queue'; 9 | 10 | constructor(url: string) { 11 | super(url + Queue.id); 12 | } 13 | 14 | async addQueue(queue: types.IQueue) { 15 | try { 16 | if (!queue.ts) { 17 | queue.ts = Date.now(); 18 | } 19 | logger.debug('存入队列数据:' + JSON.stringify(queue)); 20 | return await this.post(queue); 21 | } catch (err) { 22 | logger.error(`存储队列数据出错: ${err.message}`); 23 | } 24 | } 25 | 26 | async updateQueue(queue: types.IQueue) { 27 | return await this.put(queue); 28 | } 29 | 30 | async getQueue(trade: types.ITradeTriangle) { 31 | const queueRes = await this.findQueue(trade.id, trade.exchange); 32 | // 队列中triangleId和exchange组合key是唯一的 33 | if (!queueRes || !queueRes.doc) { 34 | return; 35 | } 36 | return queueRes.doc; 37 | } 38 | 39 | async findQueue(triangleId: string, exchangeId: string) { 40 | /*await this.createIndex({ 41 | index: { 42 | fields: ['triangleId'] 43 | } 44 | })*/ 45 | const docs = await this.allDocs({ 46 | include_docs: true, 47 | attachments: true, 48 | }); 49 | if (docs.rows.length > 0) { 50 | return docs.rows.find(o => { 51 | if (o.doc) { 52 | const queue = o.doc; 53 | return queue.triangleId === triangleId && queue.exchange === exchangeId; 54 | } 55 | return false; 56 | }) 57 | } 58 | } 59 | 60 | async clearQueue() { 61 | const docs = await this.allDocs({ 62 | include_docs: true, 63 | attachments: true, 64 | }); 65 | for (const row of docs.rows) { 66 | if (!row.doc) { 67 | return; 68 | } 69 | const queue = row.doc; 70 | const timelimit = Date.now() - moment.duration(15, 'm').asMilliseconds(); 71 | // 队列中数据超过15分钟时删除 72 | if (queue._id && queue.ts && timelimit > queue.ts) { 73 | logger.error(`删除超过15分钟的队列: ${queue._id}`); 74 | await this.removeQueue(queue._id); 75 | } 76 | } 77 | } 78 | 79 | async removeQueue(id: string) { 80 | const doc = await this.get(id); 81 | return await this.remove(doc); 82 | } 83 | } 84 | -------------------------------------------------------------------------------- /src/lib/storage/rank.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { logger } from '../common'; 3 | import { StorageBase } from './base'; 4 | const config = require('config'); 5 | 6 | export class Rank extends StorageBase { 7 | static id = 'rank'; 8 | 9 | constructor(url: string) { 10 | super(url + Rank.id); 11 | } 12 | 13 | async putRanks(ranks: types.IRank[]) { 14 | try { 15 | logger.info('存入排行数据,大小:' + ranks.length); 16 | const docs = await this.allDocs({ 17 | include_docs: true, 18 | attachments: true, 19 | }); 20 | if (docs.rows.length > config.display.maxRows) { 21 | // 超过最大显示数时,清空数据库 22 | await this.removeAllDocs(); 23 | } 24 | 25 | const removeList = []; 26 | for (const [i, row] of docs.rows.entries()) { 27 | if (ranks[i]) { 28 | ranks[i] = Object.assign({}, row.doc, ranks[i]); 29 | } else { 30 | removeList.push(row.doc); 31 | } 32 | } 33 | 34 | for (const doc of removeList) { 35 | if (doc) { 36 | await this.remove(doc); 37 | } 38 | } 39 | return await this.bulkDocs(ranks); 40 | } catch (err) { 41 | logger.error(`存储排行数据出错: ${err.message}`); 42 | } 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /src/lib/storage/trade.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { logger } from '../common'; 3 | import { StorageBase } from './base'; 4 | const config = require('config'); 5 | 6 | export class Trade extends StorageBase { 7 | static id = 'trade'; 8 | 9 | constructor(url: string) { 10 | super(url + Trade.id); 11 | } 12 | 13 | async putTrades(trades: types.ITrade[]) { 14 | try { 15 | logger.debug('存入交易数据,大小:' + trades.length); 16 | return await this.bulkDocs(trades); 17 | } catch (err) { 18 | logger.error(`存储交易数据出错: ${err.message}`); 19 | } 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /src/lib/trading/daemon.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { Storage } from '../storage'; 3 | import { Order } from './order'; 4 | import { logger } from '../common'; 5 | const clc = require('cli-color'); 6 | 7 | export class Daemon { 8 | 9 | storage: Storage; 10 | order: Order; 11 | 12 | constructor(storage: Storage) { 13 | this.storage = storage; 14 | this.order = new Order(this.storage); 15 | } 16 | 17 | private async clearError(queueId: string) { 18 | const res: types.IQueue = await this.storage.queue.get(queueId); 19 | res.error = ''; 20 | await this.storage.queue.updateQueue(res); 21 | } 22 | 23 | // 重启套利流程 24 | private async reboot(exchange: types.IExchange, trade: types.ITradeTriangle, queue: types.IQueue) { 25 | logger.info('----- 继续前次套利 -----'); 26 | logger.info(`路径:${clc.cyanBright(trade.id)} 利率: ${trade.rate}`); 27 | // 第一步 28 | if (queue.step === 0) { 29 | if (!trade.a.orderId) { 30 | logger.info('A点订单为空,退出交易队列!'); 31 | // 退出交易队列 32 | await this.storage.clearQueue(trade.id, exchange.id); 33 | } else { 34 | if (queue._id) { 35 | await this.clearError(queue._id); 36 | this.order.orderA(exchange, trade) 37 | } 38 | } 39 | } else if (queue.step === 1) { 40 | if (queue._id) { 41 | await this.clearError(queue._id); 42 | } 43 | await this.order.orderB(exchange, trade); 44 | } else if (queue.step === 2) { 45 | if (queue._id) { 46 | await this.clearError(queue._id); 47 | } 48 | await this.order.orderC(exchange, trade); 49 | } 50 | } 51 | 52 | // 继续处理失败的队列 53 | async continueTrade(exchange: types.IExchange) { 54 | const res = await this.storage.queue.allDocs({ 55 | include_docs: true, 56 | attachments: true, 57 | }); 58 | 59 | for (const row of res.rows) { 60 | if (!row.doc || !row.doc._id) { 61 | continue; 62 | } 63 | const queue = row.doc; 64 | if (!queue.error) { 65 | continue; 66 | } 67 | const trade: types.ITrade = await this.storage.trade.get(row.doc._id); 68 | if (!trade.real) { 69 | continue; 70 | } 71 | await this.reboot(exchange, trade.real, queue); 72 | } 73 | } 74 | } 75 | -------------------------------------------------------------------------------- /src/lib/trading/index.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import * as ccxt from 'ccxt'; 3 | import * as types from '../type'; 4 | import { logger, Helper } from '../common'; 5 | import { Storage } from '../storage'; 6 | import { Mocker } from './mocker'; 7 | import { Order } from './order'; 8 | import { Daemon } from './daemon'; 9 | 10 | const clc = require('cli-color'); 11 | const config = require('config'); 12 | 13 | export class Trading { 14 | mocker: Mocker; 15 | order: Order; 16 | storage: Storage; 17 | daemon: Daemon; 18 | 19 | constructor() { 20 | this.mocker = new Mocker(); 21 | this.storage = new Storage(); 22 | this.order = new Order(this.storage); 23 | this.daemon = new Daemon(this.storage); 24 | } 25 | async testOrder(exchange: types.IExchange, triangle: types.ITriangle) { 26 | return await this.mocker.testOrder(exchange, triangle); 27 | } 28 | // 下单 29 | async placeOrder(exchange: types.IExchange, triangle: types.ITriangle) { 30 | try { 31 | // 清理超时数据 32 | await this.storage.queue.clearQueue(); 33 | // 继续处理失败的队列 34 | await this.daemon.continueTrade(exchange); 35 | 36 | const limitCheck = await Helper.checkQueueLimit(this.storage.queue) 37 | if (!limitCheck) { 38 | return; 39 | } 40 | const testTrade = await this.testOrder(exchange, triangle); 41 | // 未通过检查时返回 42 | if (!testTrade || !testTrade.id) { 43 | // logger.info(`套利组合未通过可行性检测!!`); 44 | return; 45 | } 46 | 47 | if (config.trading.mock) { 48 | logger.info('配置为模拟交易,终止真实交易!'); 49 | return; 50 | } 51 | 52 | logger.info('----- 套利开始 -----'); 53 | logger.info(`路径:${clc.cyanBright(triangle.id)} 利率: ${triangle.rate}`); 54 | 55 | // 放入交易队列 56 | const queueId = await this.storage.openTradingSession({ 57 | mock: testTrade, 58 | real: testTrade 59 | }); 60 | if (!queueId) { 61 | return; 62 | } 63 | testTrade.queueId = queueId; 64 | // 执行a点订单 65 | await this.order.orderA(exchange, testTrade); 66 | } catch (err) { 67 | logger.error(`处理订单出错: ${err.message ? err.message : err.msg}`); 68 | // 退出交易队列 69 | // await this.storage.clearQueue(triangle.id, exchange.id); 70 | } 71 | } 72 | } 73 | -------------------------------------------------------------------------------- /src/lib/trading/mocker.ts: -------------------------------------------------------------------------------- 1 | import { BigNumber } from 'bignumber.js'; 2 | import * as ccxt from 'ccxt'; 3 | import { logger, Helper } from '../common'; 4 | import { ApiHandler } from '../api-handler'; 5 | import * as types from '../type'; 6 | 7 | const clc = require('cli-color'); 8 | const config = require('config'); 9 | 10 | export class Mocker extends ApiHandler { 11 | constructor() { 12 | super(); 13 | } 14 | 15 | /** 16 | * 模拟每个边的交易信息 17 | * 18 | * @param pairs 全市场交易对 19 | * @param edge 组合边 20 | * @param amount 待交易数量 21 | */ 22 | getMockTradeEdge(pairs: types.IPairs, edge: types.IEdge, amount: BigNumber) { 23 | const tradeEdge = { 24 | pair: edge.pair, 25 | side: edge.side, 26 | }; 27 | const timer = Helper.getTimer(); 28 | 29 | // 获取交易精度 30 | const priceScale = Helper.getPriceScale(pairs, edge.pair); 31 | if (!priceScale) { 32 | logger.debug(`未取得交易精度!!`); 33 | return; 34 | } 35 | // 获取格式化精度(买->价格精度、卖->数量精度) 36 | const precision = edge.side.toLowerCase() === 'buy' ? priceScale.price : priceScale.amount; 37 | // 格式化购买数量(多余小数位舍弃) 38 | const fmAmount = new BigNumber(amount.toFixed(precision, 1)); 39 | if (fmAmount.isZero()) { 40 | logger.debug(`格式化购买数量后结果为0!!`); 41 | return; 42 | } 43 | // 查询交易对手续费 44 | const feeRate = pairs[edge.pair].maker; 45 | if (!feeRate || feeRate <= 0) { 46 | logger.debug(`未取得交易对的手续费!!`); 47 | return; 48 | } 49 | tradeEdge.amount = +amount.toFixed(priceScale.amount, 1); 50 | tradeEdge.price = edge.price; 51 | tradeEdge.fee = Helper.getConvertedAmount({ 52 | side: edge.side, 53 | exchangeRate: edge.price, 54 | amount: tradeEdge.amount 55 | }).times(feeRate).toFixed(8) + ' ' + edge.coinTo; 56 | tradeEdge.timecost = Helper.endTimer(timer); 57 | return tradeEdge; 58 | } 59 | 60 | // 订单执行前,可行性检查 61 | async testOrder(exchange: types.IExchange, triangle: types.ITriangle) { 62 | // logger.info(`三角套利组合:${triangle.id}, 订单可行性检测...`); 63 | if (!exchange.endpoint.private || !exchange.pairs) { 64 | logger.error('交易所相关参数出错!!'); 65 | return; 66 | } 67 | 68 | // 查询资产 69 | const balances = await this.getBalance(exchange); 70 | if (!balances) { 71 | logger.debug('未查找到持有资产!!'); 72 | return; 73 | } 74 | 75 | const tradeTriangle = { 76 | coin: triangle.a.coinFrom, 77 | exchange: config.exchange.active, 78 | }; 79 | 80 | const asset = balances[tradeTriangle.coin]; 81 | if (!asset) { 82 | logger.debug(`未查找到持有${tradeTriangle.coin}!!`); 83 | return; 84 | } 85 | const free = new BigNumber(asset.free); 86 | if (free.isZero()) { 87 | logger.debug(`未查找到持有${tradeTriangle.coin}!!`); 88 | return; 89 | } 90 | // 获取交易精度 91 | const priceScale = Helper.getPriceScale(exchange.pairs, triangle.a.pair); 92 | if (!priceScale || !priceScale.cost) { 93 | return; 94 | } 95 | // 检查最小交易数量 96 | let minAmount; 97 | if (triangle.a.coinFrom.toUpperCase() !== 'BTC') { 98 | minAmount = Helper.convertAmount(triangle.a.price, priceScale.cost, triangle.a.side).times(1.1); 99 | } else { 100 | minAmount = Helper.getConvertedAmount({ 101 | side: triangle.a.side, 102 | exchangeRate: triangle.a.price, 103 | amount: priceScale.cost 104 | }).times(1.1); 105 | } 106 | 107 | if (triangle.a.side === 'sell' && free.isLessThanOrEqualTo(minAmount)) { 108 | // logger.debug(`持有${free + ' ' + triangle.a.coinFrom},小于最低交易数量(${minAmount})!!`); 109 | return; 110 | } 111 | // 查找最佳交易量 112 | const tradeAmount = Helper.getBaseAmountByBC(triangle, free, minAmount); 113 | 114 | // ---------------------- A点开始------------------------ 115 | const tradeEdgeA = this.getMockTradeEdge(exchange.pairs, triangle.a, tradeAmount); 116 | if (!tradeEdgeA) { 117 | return; 118 | } 119 | tradeTriangle.a = tradeEdgeA; 120 | tradeTriangle.before = tradeEdgeA.amount; 121 | 122 | // ---------------------- B点开始------------------------ 123 | let aAmount = tradeEdgeA.amount; 124 | if (tradeEdgeA.side === 'sell') { 125 | tradeTriangle.before = tradeEdgeA.amount; 126 | aAmount = +Helper.getConvertedAmount({ 127 | side: tradeEdgeA.side, 128 | exchangeRate: tradeEdgeA.price, 129 | amount: tradeEdgeA.amount 130 | }).toFixed(8); 131 | } else { 132 | tradeTriangle.before = +Helper.convertAmount(tradeEdgeA.price, tradeEdgeA.amount, tradeEdgeA.side).toFixed(8); 133 | } 134 | const bAmount = Helper.getConvertedAmount({ 135 | side: triangle.b.side, 136 | exchangeRate: triangle.b.price, 137 | amount: +aAmount.toFixed(8) 138 | }); 139 | const tradeEdgeB = this.getMockTradeEdge(exchange.pairs, triangle.b, bAmount); 140 | if (!tradeEdgeB) { 141 | return; 142 | } 143 | tradeTriangle.b = tradeEdgeB; 144 | 145 | // ---------------------- C点开始------------------------ 146 | let cAmount = bAmount; 147 | if (triangle.c.side === 'buy') { 148 | cAmount = Helper.getConvertedAmount({ 149 | side: triangle.c.side, 150 | exchangeRate: triangle.c.price, 151 | amount: tradeEdgeB.amount 152 | }); 153 | } 154 | const tradeEdgeC = this.getMockTradeEdge(exchange.pairs, triangle.c, cAmount); 155 | if (!tradeEdgeC) { 156 | return; 157 | } 158 | tradeTriangle.c = tradeEdgeC; 159 | 160 | // const after = tradeTriangle.c.amount; 161 | const after = Helper.getConvertedAmount({ 162 | side: tradeTriangle.c.side, 163 | exchangeRate: tradeTriangle.c.price, 164 | amount: tradeTriangle.c.amount 165 | }) 166 | tradeTriangle.after = +after.toFixed(8); 167 | 168 | const profit = new BigNumber(after).minus(tradeTriangle.before); 169 | // 利润 170 | tradeTriangle.profit = profit.toFixed(8); 171 | if (profit.isLessThanOrEqualTo(0)) { 172 | // logger.info(`订单可行性检测结果,利润(${clc.redBright(tradeTriangle.profit)})为负数,终止下单!`); 173 | return tradeTriangle; 174 | } 175 | tradeTriangle.id = triangle.id; 176 | // 利率 177 | tradeTriangle.rate = 178 | profit 179 | .div(tradeTriangle.before) 180 | .times(100) 181 | .toFixed(3) + '%'; 182 | tradeTriangle.ts = Date.now(); 183 | logger.info(clc.yellowBright('----- 模拟交易结果 -----')); 184 | logger.info(`套利货币:${tradeTriangle.coin}`); 185 | logger.info(`套利前资产:${tradeTriangle.before}, 套利后资产:${tradeTriangle.after}`); 186 | logger.info(`利润:${clc.greenBright(tradeTriangle.profit)}, 利率:${clc.greenBright(tradeTriangle.rate)}`); 187 | return tradeTriangle; 188 | } 189 | } 190 | -------------------------------------------------------------------------------- /src/lib/trading/order.ts: -------------------------------------------------------------------------------- 1 | import * as types from '../type'; 2 | import { ApiHandler } from '../api-handler'; 3 | import { logger, Helper } from '../common'; 4 | import { Storage } from '../storage'; 5 | 6 | const clc = require('cli-color'); 7 | export class Order extends ApiHandler { 8 | 9 | private worker = 0; 10 | storage: Storage; 11 | 12 | constructor(storage: Storage) { 13 | super(); 14 | this.storage = storage; 15 | } 16 | 17 | async orderA(exchange: types.IExchange, testTrade: types.ITradeTriangle) { 18 | try { 19 | const timer = Helper.getTimer(); 20 | // 已下单时跳过 21 | if (!testTrade.a.orderId) { 22 | // 获取交易额度 23 | logger.info(`第一步:${clc.blueBright(testTrade.a.pair)}`); 24 | testTrade.a.timecost = ''; 25 | logger.info(`限价:${testTrade.a.price}, 数量:${testTrade.a.amount}, 方向:${testTrade.a.side}`); 26 | const order = { 27 | symbol: testTrade.a.pair, 28 | side: testTrade.a.side.toLowerCase(), 29 | type: 'limit', 30 | price: testTrade.a.price, 31 | amount: testTrade.a.amount, 32 | }; 33 | const orderInfo = await this.createOrder(exchange, order); 34 | if (!orderInfo) { 35 | return; 36 | } 37 | logger.debug(`下单返回值: ${JSON.stringify(orderInfo, null, 2)}`); 38 | 39 | testTrade.a.status = orderInfo.status; 40 | testTrade.a.orderId = orderInfo.id; 41 | 42 | // 更新队列 43 | await this.storage.updateTradingSession(testTrade, 0); 44 | } 45 | const nextB = async () => { 46 | logger.info('执行nextB...'); 47 | const orderRes = await this.queryOrder(exchange, testTrade.a.orderId, testTrade.a.pair); 48 | if (!orderRes) { 49 | return false; 50 | } 51 | logger.info(`查询订单状态: ${orderRes.status}`); 52 | // 交易成功时 53 | if (orderRes.status === 'closed') { 54 | testTrade.a.timecost = Helper.endTimer(timer); 55 | // 修正数量 56 | testTrade.a.amount = orderRes.amount; 57 | testTrade.a.status = orderRes.status; 58 | // 更新队列 59 | await this.storage.updateTradingSession(testTrade, 0); 60 | 61 | if (this.worker) { 62 | clearInterval(this.worker); 63 | } 64 | await this.orderB(exchange, testTrade); 65 | return true; 66 | } 67 | return false; 68 | }; 69 | 70 | // 订单未成交时 71 | if (!await nextB()) { 72 | logger.info('订单未成交,每秒循环执行。'); 73 | this.worker = setInterval(nextB.bind(this), 1000); 74 | } 75 | } catch (err) { 76 | const errMsg = err.message ? err.message : err.msg; 77 | logger.error(`订单A出错: ${errMsg}`); 78 | await this.errorHandle(testTrade.queueId, errMsg); 79 | } 80 | } 81 | 82 | async orderB(exchange: types.IExchange, trade: types.ITradeTriangle) { 83 | try { 84 | const timer = Helper.getTimer(); 85 | const tradeB = trade.b; 86 | // 已下单时跳过 87 | if (!tradeB.orderId) { 88 | 89 | logger.info(`第二步:${clc.blueBright(trade.b.pair)}`); 90 | logger.info(`限价:${tradeB.price}, 数量:${tradeB.amount}, 方向:${tradeB.side}`); 91 | const order = { 92 | symbol: tradeB.pair, 93 | side: tradeB.side.toLowerCase(), 94 | type: 'limit', 95 | price: tradeB.price, 96 | amount: tradeB.amount, 97 | }; 98 | const orderInfo = await this.createOrder(exchange, order); 99 | if (!orderInfo) { 100 | return; 101 | } 102 | logger.debug(`下单返回值: ${JSON.stringify(orderInfo, null, 2)}`); 103 | 104 | trade.b.status = orderInfo.status; 105 | trade.b.orderId = orderInfo.id; 106 | // 更新队列 107 | await this.storage.updateTradingSession(trade, 1); 108 | } 109 | const nextC = async () => { 110 | logger.info('执行nextC...'); 111 | 112 | const orderRes = await this.queryOrder(exchange, tradeB.orderId, tradeB.pair); 113 | if (!orderRes) { 114 | return false; 115 | } 116 | logger.info(`查询订单状态: ${orderRes.status}`); 117 | // 交易成功时 118 | if (orderRes.status === 'closed') { 119 | if (this.worker) { 120 | clearInterval(this.worker); 121 | } 122 | trade.b.timecost = Helper.endTimer(timer); 123 | // 修正数量 124 | trade.b.amount = orderRes.amount; 125 | trade.b.status = orderRes.status; 126 | // 更新队列 127 | await this.storage.updateTradingSession(trade, 1); 128 | await this.orderC(exchange, trade); 129 | return true; 130 | } 131 | return false; 132 | }; 133 | 134 | // 订单未成交时 135 | if (!await nextC()) { 136 | logger.info('订单未成交,每秒循环执行。'); 137 | this.worker = setInterval(nextC.bind(this), 1000); 138 | } 139 | } catch (err) { 140 | const errMsg = err.message ? err.message : err.msg; 141 | logger.error(`订单B出错: ${errMsg}`); 142 | await this.errorHandle(trade.queueId, errMsg); 143 | } 144 | } 145 | 146 | async orderC(exchange: types.IExchange, trade: types.ITradeTriangle) { 147 | try { 148 | const timer = Helper.getTimer(); 149 | const tradeC = trade.c; 150 | // 已下单时跳过 151 | if (!tradeC.orderId) { 152 | logger.info(`第三步:${clc.blueBright(trade.c.pair)}`); 153 | logger.info(`限价:${tradeC.price}, 数量:${tradeC.amount}, 方向:${tradeC.side}`); 154 | if (tradeC.side.toLowerCase() === 'sell' && tradeC.amount > trade.b.amount) { 155 | tradeC.amount = trade.b.amount; 156 | } 157 | const order = { 158 | symbol: tradeC.pair, 159 | side: tradeC.side.toLowerCase(), 160 | type: 'limit', 161 | price: tradeC.price, 162 | amount: tradeC.amount, 163 | }; 164 | const orderInfo = await this.createOrder(exchange, order); 165 | if (!orderInfo) { 166 | return; 167 | } 168 | logger.debug(`下单返回值: ${JSON.stringify(orderInfo, null, 2)}`); 169 | 170 | trade.c.status = orderInfo.status; 171 | trade.c.orderId = orderInfo.id; 172 | // 更新队列 173 | await this.storage.updateTradingSession(trade, 2); 174 | } 175 | const completedC = async () => { 176 | logger.info('completedC...'); 177 | const orderRes = await this.queryOrder(exchange, tradeC.orderId, tradeC.pair); 178 | if (!orderRes) { 179 | return false; 180 | } 181 | logger.info(`查询订单状态: ${orderRes.status}`); 182 | // 交易成功时 183 | if (orderRes.status === 'closed') { 184 | if (this.worker) { 185 | clearInterval(this.worker); 186 | } 187 | logger.info(`三角套利完成,最终获得:${orderRes.amount}...`); 188 | trade.c.timecost = Helper.endTimer(timer); 189 | // 修正数量 190 | trade.c.amount = orderRes.amount; 191 | trade.c.status = orderRes.status; 192 | // 在交易队列中清除这条数据 193 | await this.storage.closeTradingSession(trade); 194 | } 195 | return false; 196 | }; 197 | 198 | // 订单未成交时 199 | if (!await completedC()) { 200 | logger.info('订单未成交,每秒循环执行。'); 201 | this.worker = setInterval(completedC.bind(this), 1000); 202 | } 203 | } catch (err) { 204 | const errMsg = err.message ? err.message : err.msg; 205 | logger.error(`订单C出错: ${errMsg}`); 206 | await this.errorHandle(trade.queueId, errMsg); 207 | } 208 | } 209 | 210 | private async errorHandle(queueId: string, error: string) { 211 | const res: types.IQueue = await this.storage.queue.get(queueId); 212 | res.error = error; 213 | await this.storage.queue.updateQueue(res); 214 | } 215 | } 216 | -------------------------------------------------------------------------------- /src/lib/type/account.ts: -------------------------------------------------------------------------------- 1 | import { Balance as IBalance, Balances as IBalances } from 'ccxt'; 2 | 3 | export { IBalance, IBalances }; 4 | -------------------------------------------------------------------------------- /src/lib/type/api-handler.ts: -------------------------------------------------------------------------------- 1 | export interface IOrder { 2 | symbol: string; // symbol in CCXT format 3 | amount: number; // amount of base currency 4 | price: number; // float price in quote currency 5 | type: 'market' | 'limit'; // order type, 'market', 'limit' or undefined/None/null 6 | side: 'buy' | 'sell'; 7 | } 8 | -------------------------------------------------------------------------------- /src/lib/type/binance/index.ts: -------------------------------------------------------------------------------- 1 | export interface Binance24HrTicker { 2 | eventType: string; // "24hrTicker", Event type 3 | eventTime: number; // 123456789, Event time 4 | symbol: string; // "BNBBTC", Symbol 5 | priceChange: string; // "0.0015", Price change 6 | priceChangePercent: string; // "250.00", Price change percent 7 | weightedAveragePrice: string; // "0.0018", Weighted average price 8 | previousClose: string; // "0.0009", Previous day's close price 9 | currentClose: string; // "0.0025", Current day's close price 10 | closeQuantity: string; // "10", Close trade's quantity 11 | bestBid: string; // "0.0024", Best bid price 12 | bestBidQuantity: string; // "10", Bid bid quantity 13 | bestAskPrice: string; // "0.0026", Best ask price 14 | bestAskQuantity: string; // "100", Best ask quantity 15 | open: string; // "0.0010", Open price 16 | high: string; // "0.0025", High price 17 | low: string; // "0.0010", Low price 18 | baseAssetVolume: string; // "10000", Total traded base asset volume 19 | quoteAssetVolume: string; // "18", Total traded quote asset volume 20 | openTime: number; // 0, Statistics open time 21 | closeTime: number; // 86400000, Statistics close time 22 | firstTradeId: number; // 0, First trade ID 23 | lastTradeId: number; // 18150, Last trade Id 24 | trades: number; // 18151 Total number of trades 25 | } 26 | -------------------------------------------------------------------------------- /src/lib/type/exchange.ts: -------------------------------------------------------------------------------- 1 | import { Market as IMarket } from 'ccxt'; 2 | 3 | export { IMarket }; 4 | 5 | export interface ISupportExchange { 6 | id: string; 7 | name: string; 8 | } 9 | 10 | export interface IExchange { 11 | id: ExchangeId; 12 | endpoint: { 13 | public?: any; 14 | private?: any; 15 | ws?: any; 16 | rest?: any; 17 | }; 18 | markets?: IMarkets; 19 | pairs?: IPairs; 20 | } 21 | 22 | export interface IMarkets { 23 | [baseCoin: string]: IMarket[]; 24 | } 25 | 26 | export interface IPairs { 27 | [pair: string]: IMarket; 28 | } 29 | 30 | export enum ExchangeId { 31 | KuCoin = 'kucoin', 32 | Binance = 'binance', 33 | Bitbank = 'bitbank', 34 | } 35 | 36 | export const SupportExchanges = [ 37 | { 38 | id: ExchangeId.KuCoin, 39 | name: '库币', 40 | }, 41 | { 42 | id: ExchangeId.Binance, 43 | name: '币安', 44 | }, 45 | { 46 | id: ExchangeId.Bitbank, 47 | name: 'Bitbank', 48 | }, 49 | ]; 50 | 51 | export interface ICredentials { 52 | apiKey: string; 53 | secret: string; 54 | } 55 | -------------------------------------------------------------------------------- /src/lib/type/index.ts: -------------------------------------------------------------------------------- 1 | export * from './binance'; 2 | export * from './exchange'; 3 | export * from './storage'; 4 | export * from './trading'; 5 | export * from './account'; 6 | export * from './api-handler'; 7 | -------------------------------------------------------------------------------- /src/lib/type/storage.ts: -------------------------------------------------------------------------------- 1 | import { ITriangle } from './trading'; 2 | 3 | export interface IRank { 4 | stepA: string; 5 | stepB: string; 6 | stepC: string; 7 | rate: number; 8 | fee: number[]; 9 | profitRate: number[]; 10 | ts: number; 11 | } 12 | 13 | /** 14 | * 三角组合边的成交记录 15 | */ 16 | export interface ITradeEdge { 17 | pair: string; 18 | side: 'buy' | 'sell'; 19 | // 成交价格 20 | price: number; 21 | // 报单数量 22 | amount: number; 23 | // 手续费(未折扣) 24 | fee: string; 25 | orderId: string; 26 | status: 'open' | 'closed' | 'canceled'; 27 | // 用时 28 | timecost: string; 29 | } 30 | 31 | export interface ITradeTriangle { 32 | // 三角组合唯一id(例:btc-bnb-bcd) 33 | id: string; 34 | // 队列id 35 | queueId: string; 36 | a: ITradeEdge; 37 | b: ITradeEdge; 38 | c: ITradeEdge; 39 | // 套利货币 40 | coin: string; 41 | exchange: string; 42 | // 起始买入资金 43 | before: number; 44 | // 套利后获得资金 45 | after: number; 46 | profit: string; 47 | rate: string; 48 | ts: number; 49 | } 50 | 51 | export type tradeStep = 0 | 1 | 2; 52 | 53 | export interface ITrade { 54 | _id?: string; 55 | real?: ITradeTriangle; 56 | mock: ITradeTriangle; 57 | } 58 | 59 | export interface IQueue { 60 | _id?: string; 61 | _rev?: string; 62 | triangleId: string; 63 | exchange: string; 64 | step: tradeStep; 65 | error?: string; 66 | ts?: number; 67 | } 68 | -------------------------------------------------------------------------------- /src/lib/type/trading.ts: -------------------------------------------------------------------------------- 1 | import { Ticker } from 'ccxt'; 2 | 3 | export interface ITicker extends Ticker { 4 | askVolume: number; 5 | bidVolume: number; 6 | } 7 | 8 | export interface ITickers { 9 | [pair: string]: ITicker; 10 | } 11 | 12 | /** 13 | * 三角组合的边 14 | */ 15 | export interface IEdge { 16 | pair: string; 17 | coinFrom: string; 18 | coinTo: string; 19 | // 交易方向 20 | side: 'sell' | 'buy'; 21 | // 最佳价格 22 | price: number; 23 | // 最佳数量 24 | quantity: number; 25 | } 26 | 27 | /** 28 | * 三角组合 29 | */ 30 | export interface ITriangle { 31 | // 三角组合唯一id(例:btc-bnb-bcd) 32 | id: string; 33 | a: IEdge; 34 | b: IEdge; 35 | c: IEdge; 36 | // 利率 37 | rate: number; 38 | // 时间戳 39 | ts: number; 40 | } 41 | 42 | export interface IPrecision { 43 | // 数量精度 44 | amount: number; 45 | // 价格精度 46 | price: number; 47 | // 最小成交金额 48 | cost?: number; 49 | } 50 | 51 | /** 52 | * 兑换报价对象 53 | */ 54 | export interface IRateQuote { 55 | side: 'sell' | 'buy'; 56 | // 兑换率 57 | exchangeRate: number; 58 | // 数量 59 | amount: number; 60 | } 61 | -------------------------------------------------------------------------------- /src/lib/webservice.ts: -------------------------------------------------------------------------------- 1 | import * as express from 'express'; 2 | import * as socketIO from 'socket.io'; 3 | import { logger } from './common'; 4 | import { Storage } from './storage'; 5 | 6 | const path = require('path'); 7 | const app = express(); 8 | const server = require('http').createServer(app); 9 | const io = socketIO(server); 10 | const port = process.env.PORT || 3000; 11 | 12 | export class WebService { 13 | storage: Storage; 14 | // 已连接ws数组 15 | connected: any[] = []; 16 | 17 | constructor() { 18 | this.storage = new Storage(); 19 | } 20 | 21 | start() { 22 | try { 23 | const that = this; 24 | io.on('connection', (socket: any) => { 25 | logger.info('客户端已连接 !'); 26 | 27 | that.connected.push(socket); 28 | socket.on('disconnect', (client: any) => { 29 | // when client disconnects 30 | logger.info('客户端断开连接 !'); 31 | const index = that.connected.indexOf(client); 32 | that.connected.splice(index, 1); // remove client from the list of connected clients 33 | }); 34 | }); 35 | 36 | // express logic 37 | app.get('/', (req, res) => { 38 | res.sendFile(path.resolve(__dirname, '../../..') + '/public/index.html'); // serve index.html 39 | }); 40 | app.use('/', express.static(path.resolve(__dirname, '../../..') + '/public')); // serve js and css static files in public 41 | 42 | server.listen(port, () => { 43 | logger.info('服务已开启!'); 44 | logger.info('请使用浏览器打开: http://127.0.0.1:' + port); 45 | that.storage.rank.onChanged(async (change: any) => { 46 | const docs = await that.storage.rank.getAllDocs(); 47 | for (const ws of that.connected) { 48 | if (ws) { 49 | ws.emit('updateArbitage', docs); 50 | } 51 | } 52 | }); 53 | }); 54 | } catch (err) { 55 | logger.error(`启动web服务出错: ${err}`); 56 | } 57 | } 58 | } 59 | -------------------------------------------------------------------------------- /test/api-handler.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'power-assert'; 2 | import { Helper } from '../src/lib/common'; 3 | import * as types from '../src/lib/type'; 4 | import { ApiHandler } from '../src/lib/api-handler'; 5 | 6 | const testCreateOrder = async () => { 7 | const exId = types.ExchangeId.Binance; 8 | const exchange = Helper.getExchange(types.ExchangeId.Binance); 9 | const api = new ApiHandler(); 10 | const order: types.IOrder = { 11 | symbol: 'ETH/BTC', // symbol in CCXT format 12 | amount: 0.014, // amount of base currency 13 | price: 0.077845, // float price in quote currency 14 | type: 'limit', // order type, 'market', 'limit' or undefined/None/null 15 | side: 'buy', 16 | }; 17 | const res = await api.createOrder(exchange, order); 18 | console.log(res); 19 | }; 20 | 21 | const testQueryOrder = async () => { 22 | const exId = types.ExchangeId.Binance; 23 | const exchange = Helper.getExchange(types.ExchangeId.Binance); 24 | const api = new ApiHandler(); 25 | const res = await api.queryOrder(exchange, '98162639', 'ETH/BTC'); 26 | console.log(res); 27 | }; 28 | 29 | const testQueryOrderStatus = async () => { 30 | const exId = types.ExchangeId.Binance; 31 | const exchange = Helper.getExchange(types.ExchangeId.Binance); 32 | const api = new ApiHandler(); 33 | const res = await api.queryOrderStatus(exchange, '98162639', 'ETH/BTC'); 34 | console.log(res); 35 | }; 36 | 37 | describe('API测试', () => { 38 | // it('测试下单', testCreateOrder); 39 | // it('测试订单查询', testQueryOrder); 40 | it('测试订单状态查询', testQueryOrderStatus); 41 | }); 42 | -------------------------------------------------------------------------------- /test/engine.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'power-assert'; 2 | import { Helper } from '../src/lib/common'; 3 | import { TriangularArbitrage } from '../src/lib/arbitrage'; 4 | import * as types from '../src/lib/type'; 5 | import * as PouchDB from 'pouchdb'; 6 | import * as find from 'pouchdb-find'; 7 | PouchDB.plugin(find); 8 | 9 | const exInfo = async () => { 10 | const ex = Helper.getExchange(types.ExchangeId.Binance); 11 | if (ex && ex.endpoint.private) { 12 | const api = ex.endpoint.private; 13 | const res = await api.loadMarkets(); 14 | const res2 = await api.fetchTickers(); 15 | console.log(res); 16 | } 17 | // assert(symbolInfo); 18 | }; 19 | 20 | const initRobot = async () => { 21 | const robot = new TriangularArbitrage(); 22 | await robot.start(); 23 | console.log(1); 24 | }; 25 | 26 | const testPouchDb = async () => { 27 | const movies = new PouchDB('db'); 28 | const doc = { 29 | _id: 'mittens2', 30 | name: 'Mittens', 31 | occupation: 'kitten', 32 | age: 3, 33 | hobbies: ['playing with balls of yarn', 'chasing laser pointers', 'lookin hella cute'], 34 | }; 35 | 36 | // const res = await movies.put(doc) 37 | // console.log('res: ', res) 38 | /* const info = await movies.info(); 39 | console.log(info) 40 | const info2 = await movies.get('mittens'); 41 | console.log('info2: ', info2) 42 | info2.age = 2012 43 | await movies.put(info2); 44 | const info3 = await movies.get('mittens'); 45 | console.log('info3: ', info3)*/ 46 | /* 47 | const findData = await movies.find({ 48 | selector: { 49 | name: 'Mittens' 50 | } 51 | });*/ 52 | const findData = await movies.find({ 53 | selector: { 54 | name: { $gt: null }, 55 | }, 56 | limit: 10, 57 | }); 58 | console.log('findData: ', findData.docs); 59 | }; 60 | 61 | describe('引擎测试', () => { 62 | // it('查询交易所信息', exInfo); 63 | // it('初始化套利机器人', initRobot); 64 | it('测试pouchdb', testPouchDb); 65 | }); 66 | -------------------------------------------------------------------------------- /test/order.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'power-assert'; 2 | const config = require('config'); 3 | const api = require('binance'); 4 | const exchangeAPI = new api.BinanceRest({ 5 | key: config.binance.apiKey, 6 | secret: config.binance.secret, 7 | timeout: parseInt(config.restTimeout, 10), // 可选,默认为15000,请求超时为毫秒 8 | recvWindow: parseInt(config.restRecvWindow, 10), // 可选,默认为5000,如果您收到时间戳错误,则增加 9 | disableBeautification: !config.restBeautify, 10 | }); 11 | const testOrder = async () => { 12 | const unit = 0.0001; 13 | const orderInfo = { 14 | symbol: 'BNBBTC', 15 | side: 'BUY', 16 | type: 'MARKET', 17 | // timeInForce: 'GTC', 18 | quantity: unit, 19 | timestamp: Date.now(), 20 | }; 21 | /*const account = await exchangeAPI.account(); 22 | if (account) { 23 | const btcAsset = (>account.balances).find((o: any, index: number, arr: any[]) => { 24 | return o.asset === 'BTC'; 25 | }) 26 | console.log(btcAsset) 27 | }*/ 28 | const res = await exchangeAPI.testOrder(orderInfo); 29 | // assert(symbolInfo); 30 | }; 31 | const exInfo = async () => { 32 | /*const account = await exchangeAPI.account(); 33 | if (account) { 34 | const btcAsset = (>account.balances).find((o: any, index: number, arr: any[]) => { 35 | return o.asset === 'BTC'; 36 | }) 37 | console.log(btcAsset) 38 | }*/ 39 | const res = await exchangeAPI.exchangeInfo(); 40 | console.log(res); 41 | // assert(symbolInfo); 42 | }; 43 | 44 | describe('订单测试', () => { 45 | // it('下达测试单', testOrder); 46 | it('查询交易所信息', exInfo); 47 | }); 48 | -------------------------------------------------------------------------------- /test/pouchdb/clie.ts: -------------------------------------------------------------------------------- 1 | import * as PouchDB from 'pouchdb'; 2 | import { Storage } from '../../src/lib/storage'; 3 | 4 | const storage = new Storage(); 5 | 6 | (async () => { 7 | storage.rank.onChanged(async (change: any) => { 8 | const docs = await storage.rank.allDocs({ 9 | include_docs: true, 10 | attachments: true, 11 | }); 12 | console.log('to change: ', JSON.stringify(docs, null, 2)); 13 | }); 14 | })(); 15 | -------------------------------------------------------------------------------- /test/pouchdb/serv.ts: -------------------------------------------------------------------------------- 1 | import * as PouchDB from 'pouchdb'; 2 | import { Storage } from '../../src/lib/storage'; 3 | 4 | const storage = new Storage(); 5 | let z_index = 0; 6 | 7 | setInterval(async () => { 8 | try { 9 | z_index++; 10 | const rows = [{ z_index, title: 'Lisa Says' }, { z_index, title: 'Space Oddity' }, { z_index, title: '23tl' }]; 11 | const docs = await storage.rank.allDocs({ 12 | include_docs: true, 13 | attachments: true, 14 | }); 15 | for (const [i, row] of rows.entries()) { 16 | if (docs.rows[i]) { 17 | rows[i] = Object.assign({}, docs.rows[i].doc, row); 18 | } 19 | } 20 | const res = await storage.rank.bulkDocs(rows); 21 | console.log(z_index, res); 22 | } catch (err) { 23 | console.error(err); 24 | } 25 | }, 3000); 26 | -------------------------------------------------------------------------------- /test/storage.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'power-assert'; 2 | import { Helper } from '../src/lib/common'; 3 | import { Storage } from '../src/lib/storage'; 4 | import { BigNumber } from 'bignumber.js'; 5 | import * as types from '../src/lib/type'; 6 | import * as PouchDB from 'pouchdb'; 7 | import * as find from 'pouchdb-find'; 8 | PouchDB.plugin(find); 9 | 10 | const testPutDb = async () => { 11 | const storage = new Storage(); 12 | const doc = { 13 | _id: 'mittens2', 14 | name: 'Mittens', 15 | occupation: 'kitten', 16 | age: 3, 17 | hobbies: ['playing with balls of yarn', 'chasing laser pointers', 'lookin hella cute'], 18 | }; 19 | const res = await storage.rank.removeAllDocs(); 20 | console.log(res); 21 | // const res = await storage.pouchDB.put(doc) 22 | // console.log('res: ', res) 23 | /* const info = await movies.info(); 24 | console.log(info) 25 | const info2 = await movies.get('mittens'); 26 | console.log('info2: ', info2) 27 | info2.age = 2012 28 | await movies.put(info2); 29 | const info3 = await movies.get('mittens'); 30 | console.log('info3: ', info3)*/ 31 | /* 32 | const findData = await movies.find({ 33 | selector: { 34 | name: 'Mittens' 35 | } 36 | });*/ 37 | /*const findData = await movies.find({ 38 | selector: { 39 | name: { '$gt': null } 40 | }, 41 | limit: 10 42 | }); 43 | console.log('findData: ', findData.docs);*/ 44 | }; 45 | 46 | const testRank = async () => { 47 | const storage = new Storage(); 48 | const ranks: types.IRank[] = []; 49 | const rank: types.IRank = { 50 | stepA: 'ETH/BTC', 51 | stepB: 'DLT/ETH', 52 | stepC: 'DLT/BTC', 53 | rate: 0.05, 54 | fee: [0.005, 0.0025], 55 | profitRate: [0.045, 0.0475], 56 | ts: 1520516680000, 57 | }; 58 | ranks.push(rank); 59 | const putRes = await storage.rank.post(rank); 60 | assert(putRes.id); 61 | // const putRes = await storage.rank.putRanks(ranks); 62 | console.log('putRes: ', JSON.stringify(putRes, null, 2)); 63 | const res = await storage.rank.getAllDocs(); 64 | console.log(res); 65 | }; 66 | 67 | const testQueue = async () => { 68 | const storage = new Storage(); 69 | const queue: types.IQueue = { 70 | triangleId: 'BNB-BTC-IOsTA', 71 | exchange: 'binance', 72 | step: 0, 73 | }; 74 | // const res2 = await storage.queue.addQueue(queue); 75 | // const res = await storage.queue.findQueue(queue.triangleId, queue.exchange); 76 | const res: types.IQueue = await storage.queue.get('5edc408001f0ccb7080b7fe63d00c1c0'); 77 | res.error = 'xxx'; 78 | await storage.queue.updateQueue(res); 79 | // const res = await storage.trade.get('5edc408001f0ccb7080b7fe63d00c1c0'); 80 | /* 81 | const res = await storage.queue.allDocs({ 82 | include_docs: true, 83 | attachments: true, 84 | });*/ 85 | assert(res); 86 | // const putRes = await storage.rank.putRanks(ranks); 87 | console.log('putRes: ', JSON.stringify(res, null, 2)); 88 | }; 89 | 90 | const testTrade = async () => { 91 | const storage = new Storage(); 92 | const trades: types.ITradeTriangle[] = []; 93 | /*const trade: types.ITradeTriangle = { 94 | "_id": "1520516680001", 95 | "coin": "BTC2", 96 | "a": { 97 | "pair": "1ETH/BTC", 98 | "side": "buy", 99 | "fee": "0.00001001 ETH", 100 | "amount": 0.01000962, 101 | "bigAmount": new BigNumber("0.010009615"), 102 | "price": 0.078203, 103 | "timecost": "2.84 ms" 104 | }, 105 | "b": { 106 | "pair": "OMG/ETH", 107 | "side": "buy", 108 | "fee": "0.00051117 OMG", 109 | "amount": 0.51117393, 110 | "bigAmount": new BigNumber("0.511173928"), 111 | "price": 0.0196, 112 | "timecost": "549 μs" 113 | }, 114 | "c": { 115 | "pair": "OMG/BTC", 116 | "side": "sell", 117 | "fee": "0.00000078 BTC", 118 | "amount": 0.00078156, 119 | "bigAmount": new BigNumber("0.0078155766"), 120 | "price": 0.001534, 121 | "timecost": "716 μs" 122 | }, 123 | "before": 0.00078278, 124 | "after": 0.00078156, 125 | "profit": "-0.00000122", 126 | "rate": "-0.156%", 127 | ts: 1520516680000, 128 | }; 129 | trades.push(trade); 130 | await storage.trade.putTrades(trades); 131 | const res = await storage.trade.getAllDocs(); 132 | console.log(JSON.stringify(res, null, 2));*/ 133 | }; 134 | 135 | describe('存储测试', () => { 136 | // it('测试pouchdb', testPutDb); 137 | // it('测试Rank', testRank); 138 | it('测试queue', testQueue); 139 | // it('测试Trade', testTrade); 140 | }); 141 | -------------------------------------------------------------------------------- /test/trading.test.ts: -------------------------------------------------------------------------------- 1 | import * as assert from 'power-assert'; 2 | import { Helper } from '../src/lib/common'; 3 | import { Trading } from '../src/lib/trading'; 4 | import * as types from '../src/lib/type'; 5 | import { TriangularArbitrage } from '../src/lib/arbitrage'; 6 | 7 | const ccxt = require('ccxt'); 8 | 9 | const testFetchBalance = async () => { 10 | const exId = types.ExchangeId.Binance; 11 | const exchange = Helper.getExchange(types.ExchangeId.Binance); 12 | const trading = new Trading(); 13 | const res = await trading.order.getBalance(exchange); 14 | console.log(res); 15 | }; 16 | 17 | const testMockOrder = async () => { 18 | const exId = types.ExchangeId.Binance; 19 | const exchange = Helper.getExchange(types.ExchangeId.Binance); 20 | const trading = new Trading(); 21 | const t = 22 | '{"id":"BTC-ETH-DLT","a":{"coinFrom":"BTC","coinTo":"ETH","pair":"ETH/BTC","side":"BUY","conversionRate":0.075077,"price":0.075077,"quantity":0.538},"b":{"coinFrom":"ETH","coinTo":"DLT","pair":"DLT/ETH","side":"BUY","conversionRate":0.00039193,"price":0.00039193,"quantity":97},"c":{"coinFrom":"DLT","coinTo":"BTC","pair":"DLT/BTC","side":"SELL","price":0.00002908,"quantity":121,"conversionRate":34387.8954607978},"rate":1.0118613689821185,"ts":1520099319732}'; 23 | const triangle: types.ITriangle = JSON.parse(t); 24 | const res = await trading.testOrder(exchange, triangle); 25 | console.log(res); 26 | }; 27 | 28 | const testPlaceOrder = async () => { 29 | const exId = types.ExchangeId.Binance; 30 | const arbitrage = new TriangularArbitrage(); 31 | await arbitrage.initExchange(exId); 32 | const exchange = arbitrage.exchanges.get(exId); 33 | if (!exchange) { 34 | return; 35 | } 36 | const trading = new Trading(); 37 | const t1 = 38 | '{"id":"ETH-BTC-ADA","a":{"coinFrom":"ETH","coinTo":"BTC","pair":"ETH/BTC","side":"sell","price":0.076252,"quantity":0.102,"conversionRate":13.11441011383308},"b":{"coinFrom":"BTC","coinTo":"ADA","pair":"ADA/BTC","side":"buy","conversionRate":0.00002348,"price":0.00002348,"quantity":591},"c":{"coinFrom":"ADA","coinTo":"ETH","pair":"ADA/ETH","side":"sell","price":0.00030802,"quantity":6147,"conversionRate":3246.5424323095904},"rate":0.03041329,"ts":1520536682458}'; 39 | const t2 = 40 | '{"id":"BTC-ETH-SALT","a":{"coinFrom":"BTC","coinTo":"ETH","pair":"ETH/BTC","side":"buy","conversionRate":0.078563,"price":0.078563,"quantity":1.67},"b":{"coinFrom":"ETH","coinTo":"SALT","pair":"SALT/ETH","side":"buy","conversionRate":0.004515,"price":0.004515,"quantity":0.01},"c":{"coinFrom":"SALT","coinTo":"BTC","pair":"SALT/BTC","side":"sell","price":0.000355,"quantity":100.75,"conversionRate":2816.9014084507044},"rate":0.08120815,"ts":1520659933180}'; 41 | const t3 = 42 | '{"id":"BTC-BNB-ZIL","a":{"coinFrom":"BTC","coinTo":"BNB","pair":"BNB/BTC","side":"buy","price":0.0008819,"quantity":8.94},"b":{"coinFrom":"BNB","coinTo":"ZIL","pair":"ZIL/BNB","side":"buy","price":0.00594,"quantity":0.8},"c":{"coinFrom":"ZIL","coinTo":"BTC","pair":"ZIL/BTC","side":"sell","price":0.00000525,"quantity":4534},"rate":0.21979633,"ts":1520682679007}'; 43 | const t4 = 44 | '{"id":"BTC-ETH-OMG","a":{"coinFrom":"BTC","coinTo":"ETH","pair":"ETH/BTC","side":"buy","price":0.078203,"quantity":0.59},"b":{"coinFrom":"ETH","coinTo":"OMG","pair":"OMG/ETH","side":"buy","price":0.0196,"quantity":0.01},"c":{"coinFrom":"OMG","coinTo":"BTC","pair":"OMG/BTC","side":"sell","price":0.001534,"quantity":1.62},"rate":0.07967229,"ts":1520688553746}'; 45 | const t5 = 46 | '{"id":"BTC-BNB-WABI","a":{"coinFrom":"BTC","coinTo":"BNB","pair":"BNB/BTC","side":"buy","price":0.0008758,"quantity":9.45},"b":{"coinFrom":"BNB","coinTo":"WABI","pair":"WABI/BNB","side":"buy","price":0.12955,"quantity":0.19},"c":{"coinFrom":"WABI","coinTo":"BTC","pair":"WABI/BTC","side":"sell","price":0.00011373,"quantity":688},"rate":123,"ts":1520688553746}'; 47 | const t6 = 48 | '{"id":"ETH-BTC-PPT","a":{"coinFrom":"ETH","coinTo":"BTC","pair":"ETH/BTC","side":"sell","price":0.078001,"quantity":37.585},"b":{"coinFrom":"BTC","coinTo":"PPT","pair":"PPT/BTC","side":"buy","price":0.001782,"quantity":4.99},"c":{"coinFrom":"PPT","coinTo":"ETH","pair":"PPT/ETH","side":"sell","price":0.022907,"quantity":1.1},"rate":0.26761543,"ts":1520705623932}'; 49 | const t7 = 50 | '{"id":"BTC-ETH-ICX","a":{"coinFrom":"BTC","coinTo":"ETH","pair":"ETH/BTC","side":"buy","price":0.079221,"quantity":0.19},"b":{"coinFrom":"ETH","coinTo":"ICX","pair":"ICX/ETH","side":"buy","price":0.003662,"quantity":70.9},"c":{"coinFrom":"ICX","coinTo":"BTC","pair":"ICX/BTC","side":"sell","price":0.0002903,"quantity":67.56},"rate":0.06642301,"ts":1520765253834}'; 51 | const t8 = 52 | '{"id":"ETH-BNB-ONT","a":{"coinFrom":"ETH","coinTo":"BNB","pair":"BNB/ETH","side":"buy","price":0.01126,"quantity":0.41},"b":{"coinFrom":"BNB","coinTo":"ONT","pair":"ONT/BNB","side":"buy","price":0.1966,"quantity":5.85},"c":{"coinFrom":"ONT","coinTo":"ETH","pair":"ONT/ETH","side":"sell","price":0.0022154,"quantity":220},"rate":0.07607119,"ts":1520782545989}'; 53 | const t9 = '{"id":"BNB-BTC-WABI","a":{"coinFrom":"BNB","coinTo":"BTC","pair":"BNB/BTC","side":"sell","price":0.0010959,"quantity":29.81},"b":{"coinFrom":"BTC","coinTo":"WABI","pair":"WABI/BTC","side":"buy","price":0.00009205,"quantity":3},"c":{"coinFrom":"WABI","coinTo":"BNB","pair":"WABI/BNB","side":"sell","price":0.08427,"quantity":0.72},"rate":0.32753178,"ts":1521204333027}'; 54 | const t10 = '{"id":"BTC-ETH-QSP","a":{"coinFrom":"BTC","coinTo":"ETH","pair":"ETH/BTC","side":"buy","price":0.062865,"quantity":0.017},"b":{"coinFrom":"ETH","coinTo":"QSP","pair":"QSP/ETH","side":"buy","price":0.00022745,"quantity":1188},"c":{"coinFrom":"QSP","coinTo":"BTC","pair":"QSP/BTC","side":"sell","price":0.00001435,"quantity":220},"rate":0.07607119,"ts":1520782545989}'; 55 | const t11 = '{"id":"BNB-ETH-RPX","a":{"coinFrom":"BNB","coinTo":"ETH","pair":"BNB/ETH","side":"sell","price":0.016023,"quantity":30},"b":{"coinFrom":"ETH","coinTo":"RPX","pair":"RPX/ETH","side":"buy","price":0.0001505,"quantity":1188},"c":{"coinFrom":"RPX","coinTo":"BNB","pair":"RPX/BNB","side":"sell","price":0.00943,"quantity":220},"rate":0.07607119,"ts":1520782545989}'; 56 | const list = [t11] // [t1, t2, t3, t4, t5, t6, t7, t8, t9]; 57 | for (const t of list) { 58 | const triangle: types.ITriangle = JSON.parse(t); 59 | // for (let i = 0; i < 10; i++) { 60 | try { 61 | const res = await trading.placeOrder(exchange, triangle); 62 | console.log('下单结果:', res); 63 | } catch (e) { 64 | console.log('下单异常:', e.stack); 65 | } 66 | // } 67 | } 68 | }; 69 | 70 | describe('交易测试', () => { 71 | // it('测试获取资产', testFetchBalance) 72 | // it('测试模拟下单', testMockOrder); 73 | it('测试下单', testPlaceOrder); 74 | }); 75 | -------------------------------------------------------------------------------- /tsconfig.development.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "./tsconfig.json", 3 | "compilerOptions": { 4 | "watch": true, 5 | "declaration": false, 6 | "sourceMap": true 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "target": "ES2016", 4 | "module": "commonjs", 5 | "moduleResolution": "node", 6 | "strict": true, 7 | "types": [ 8 | "node", 9 | "mocha" 10 | ], 11 | "typeRoots": [ 12 | "./node_modules/@types" 13 | ], 14 | "outDir": "./dist", 15 | "declaration": true, 16 | "experimentalDecorators": true 17 | }, 18 | "include": [ 19 | "./src/**/*.ts", 20 | "./test/**/*.ts" 21 | ], 22 | "exclude": [ 23 | "node_modules" 24 | ] 25 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "rules": { 3 | "arrow-return-shorthand": true, 4 | "callable-types": true, 5 | "class-name": true, 6 | "comment-format": [ 7 | true, 8 | "check-space" 9 | ], 10 | "curly": true, 11 | "eofline": true, 12 | "forin": true, 13 | "import-spacing": true, 14 | "indent": [ 15 | true, 16 | "spaces" 17 | ], 18 | "interface-over-type-literal": true, 19 | "label-position": true, 20 | "max-line-length": [ 21 | true, 22 | 140 23 | ], 24 | "member-access": false, 25 | "member-ordering": [ 26 | true, 27 | "static-before-instance", 28 | "variables-before-functions" 29 | ], 30 | "no-arg": true, 31 | "no-bitwise": true, 32 | "no-console": [ 33 | true, 34 | "debug", 35 | "info", 36 | "time", 37 | "timeEnd", 38 | "trace" 39 | ], 40 | "no-construct": true, 41 | "no-debugger": true, 42 | "no-duplicate-super": true, 43 | "no-empty": false, 44 | "no-empty-interface": true, 45 | "no-eval": true, 46 | "no-inferrable-types": [ 47 | true, 48 | "ignore-params" 49 | ], 50 | "no-misused-new": true, 51 | "no-non-null-assertion": true, 52 | "no-shadowed-variable": true, 53 | "no-string-literal": false, 54 | "no-string-throw": true, 55 | "no-switch-case-fall-through": true, 56 | "no-trailing-whitespace": true, 57 | "no-unnecessary-initializer": true, 58 | "no-unused-expression": true, 59 | "no-use-before-declare": true, 60 | "no-var-keyword": true, 61 | "object-literal-sort-keys": false, 62 | "one-line": [ 63 | true, 64 | "check-open-brace", 65 | "check-catch", 66 | "check-else", 67 | "check-whitespace" 68 | ], 69 | "prefer-const": true, 70 | "quotemark": [ 71 | true, 72 | "single" 73 | ], 74 | "radix": true, 75 | "semicolon": [ 76 | "always" 77 | ], 78 | "triple-equals": [ 79 | true, 80 | "allow-null-check" 81 | ], 82 | "typedef-whitespace": [ 83 | true, 84 | { 85 | "call-signature": "nospace", 86 | "index-signature": "nospace", 87 | "parameter": "nospace", 88 | "property-declaration": "nospace", 89 | "variable-declaration": "nospace" 90 | } 91 | ], 92 | "typeof-compare": true, 93 | "unified-signatures": true, 94 | "variable-name": false, 95 | "whitespace": [ 96 | true, 97 | "check-branch", 98 | "check-decl", 99 | "check-operator", 100 | "check-separator", 101 | "check-type" 102 | ] 103 | } 104 | } 105 | --------------------------------------------------------------------------------
数据时间 A B C Rate正常手续费(Rate - 手续费)BnB手续费(Rate - BnB手续费)
{{rank.ts | date:'HH:mm:ss'}} 40 | 41 |
{{rank.stepA}} 42 |
44 | 45 |
{{rank.stepB}} 46 |
48 | 49 |
{{rank.stepC}} 50 |
{{rank.rate.toFixed(3) + '%'}}{{rank.fee[0].toFixed(4) + '%'}}{{rank.profitRate[0].toFixed(4) + '%'}}{{rank.fee[1].toFixed(4) + '%'}}{{rank.profitRate[1].toFixed(4) + '%'}}