├── .gitignore ├── CODE_OF_CONDUCT.md ├── LICENSE ├── README.md ├── babel.config.js ├── dist ├── Bot.d.ts ├── Bot.js ├── Bot.js.map ├── algorithms │ ├── Algorithm.d.ts │ ├── Algorithm.js │ ├── Algorithm.js.map │ ├── ArbitrageBetweenExchanges.d.ts │ ├── ArbitrageBetweenExchanges.js │ ├── ArbitrageBetweenExchanges.js.map │ ├── ArbitrageTriangleWithinExchange.d.ts │ ├── ArbitrageTriangleWithinExchange.js │ ├── ArbitrageTriangleWithinExchange.js.map │ ├── ArbitrageTriangularBetweenExchanges.d.ts │ ├── ArbitrageTriangularBetweenExchanges.js │ └── ArbitrageTriangularBetweenExchanges.js.map ├── common │ ├── config.d.ts │ ├── config.js │ ├── config.js.map │ ├── constants.d.ts │ ├── constants.js │ ├── constants.js.map │ ├── errors.d.ts │ ├── errors.js │ ├── errors.js.map │ ├── helpers.d.ts │ ├── helpers.js │ ├── helpers.js.map │ ├── interfaces.d.ts │ ├── interfaces.js │ ├── interfaces.js.map │ ├── types.d.ts │ ├── types.js │ └── types.js.map ├── config.json ├── config.local.example.json ├── example.d.ts ├── example.js ├── example.js.map ├── index.d.ts ├── index.js ├── index.js.map └── misc │ ├── Telegram.d.ts │ ├── Telegram.js │ └── Telegram.js.map ├── docs └── example.png ├── jest.config.js ├── misc └── proxy.js ├── nodemon.json ├── package-lock.json ├── package.json ├── src ├── Bot.ts ├── algorithms │ ├── Algorithm.ts │ ├── ArbitrageBetweenExchanges.ts │ ├── ArbitrageTriangleWithinExchange.ts │ └── ArbitrageTriangularBetweenExchanges.ts ├── common │ ├── config.ts │ ├── constants.ts │ ├── errors.ts │ ├── helpers.ts │ ├── interfaces.ts │ └── types.ts ├── config.json ├── config.local.example.json ├── example.ts ├── index.ts └── misc │ └── Telegram.ts ├── test ├── ArbitrageTriangleWithinExchange.spec.ts └── common │ └── helpers.ts ├── tsconfig.json └── tslint.json /.gitignore: -------------------------------------------------------------------------------- 1 | # config 2 | config.local.json 3 | src/config.local.json 4 | 5 | # Logs 6 | logs 7 | *.log 8 | npm-debug.log* 9 | yarn-debug.log* 10 | yarn-error.log* 11 | lerna-debug.log* 12 | 13 | # Diagnostic reports (https://nodejs.org/api/report.html) 14 | report.[0-9]*.[0-9]*.[0-9]*.[0-9]*.json 15 | 16 | # Runtime data 17 | pids 18 | *.pid 19 | *.seed 20 | *.pid.lock 21 | 22 | # Directory for instrumented libs generated by jscoverage/JSCover 23 | lib-cov 24 | 25 | # Coverage directory used by tools like istanbul 26 | coverage 27 | *.lcov 28 | 29 | # nyc test coverage 30 | .nyc_output 31 | 32 | # Grunt intermediate storage (https://gruntjs.com/creating-plugins#storing-task-files) 33 | .grunt 34 | 35 | # Bower dependency directory (https://bower.io/) 36 | bower_components 37 | 38 | # node-waf configuration 39 | .lock-wscript 40 | 41 | # Compiled binary addons (https://nodejs.org/api/addons.html) 42 | build/Release 43 | 44 | # Dependency directories 45 | node_modules/ 46 | jspm_packages/ 47 | 48 | # Snowpack dependency directory (https://snowpack.dev/) 49 | web_modules/ 50 | 51 | # TypeScript cache 52 | *.tsbuildinfo 53 | 54 | # Optional npm cache directory 55 | .npm 56 | 57 | # Optional eslint cache 58 | .eslintcache 59 | 60 | # Microbundle cache 61 | .rpt2_cache/ 62 | .rts2_cache_cjs/ 63 | .rts2_cache_es/ 64 | .rts2_cache_umd/ 65 | 66 | # Optional REPL history 67 | .node_repl_history 68 | 69 | # Output of 'npm pack' 70 | *.tgz 71 | 72 | # Yarn Integrity file 73 | .yarn-integrity 74 | 75 | # dotenv environment variables file 76 | .env 77 | .env.test 78 | 79 | # parcel-bundler cache (https://parceljs.org/) 80 | .cache 81 | .parcel-cache 82 | 83 | # Next.js build output 84 | .next 85 | out 86 | 87 | # Nuxt.js build / generate output 88 | .nuxt 89 | 90 | # Gatsby files 91 | .cache/ 92 | # Comment in the public line in if your project uses Gatsby and not Next.js 93 | # https://nextjs.org/blog/next-9-1#public-directory-support 94 | # public 95 | 96 | # vuepress build output 97 | .vuepress/dist 98 | 99 | # Serverless directories 100 | .serverless/ 101 | 102 | # FuseBox cache 103 | .fusebox/ 104 | 105 | # DynamoDB Local files 106 | .dynamodb/ 107 | 108 | # TernJS port file 109 | .tern-port 110 | 111 | # Stores VSCode versions used for testing VSCode extensions 112 | .vscode-test 113 | 114 | # yarn v2 115 | .yarn/cache 116 | .yarn/unplugged 117 | .yarn/build-state.yml 118 | .yarn/install-state.gz 119 | .pnp.* 120 | 121 | # Other 122 | notes -------------------------------------------------------------------------------- /CODE_OF_CONDUCT.md: -------------------------------------------------------------------------------- 1 | 2 | # Contributor Covenant Code of Conduct 3 | 4 | ## Our Pledge 5 | 6 | We as members, contributors, and leaders pledge to make participation in our 7 | community a harassment-free experience for everyone, regardless of age, body 8 | size, visible or invisible disability, ethnicity, sex characteristics, gender 9 | identity and expression, level of experience, education, socio-economic status, 10 | nationality, personal appearance, race, religion, or sexual identity 11 | and orientation. 12 | 13 | We pledge to act and interact in ways that contribute to an open, welcoming, 14 | diverse, inclusive, and healthy community. 15 | 16 | ## Our Standards 17 | 18 | Examples of behavior that contributes to a positive environment for our 19 | community include: 20 | 21 | * Demonstrating empathy and kindness toward other people 22 | * Being respectful of differing opinions, viewpoints, and experiences 23 | * Giving and gracefully accepting constructive feedback 24 | * Accepting responsibility and apologizing to those affected by our mistakes, 25 | and learning from the experience 26 | * Focusing on what is best not just for us as individuals, but for the 27 | overall community 28 | 29 | Examples of unacceptable behavior include: 30 | 31 | * The use of sexualized language or imagery, and sexual attention or 32 | advances of any kind 33 | * Trolling, insulting or derogatory comments, and personal or political attacks 34 | * Public or private harassment 35 | * Publishing others' private information, such as a physical or email 36 | address, without their explicit permission 37 | * Other conduct which could reasonably be considered inappropriate in a 38 | professional setting 39 | 40 | ## Enforcement Responsibilities 41 | 42 | Community leaders are responsible for clarifying and enforcing our standards of 43 | acceptable behavior and will take appropriate and fair corrective action in 44 | response to any behavior that they deem inappropriate, threatening, offensive, 45 | or harmful. 46 | 47 | Community leaders have the right and responsibility to remove, edit, or reject 48 | comments, commits, code, wiki edits, issues, and other contributions that are 49 | not aligned to this Code of Conduct, and will communicate reasons for moderation 50 | decisions when appropriate. 51 | 52 | ## Scope 53 | 54 | This Code of Conduct applies within all community spaces, and also applies when 55 | an individual is officially representing the community in public spaces. 56 | Examples of representing our community include using an official e-mail address, 57 | posting via an official social media account, or acting as an appointed 58 | representative at an online or offline event. 59 | 60 | ## Enforcement 61 | 62 | Instances of abusive, harassing, or otherwise unacceptable behavior may be 63 | reported to the community leaders responsible for enforcement at 64 | zvvolinski.g@gmail.com. 65 | All complaints will be reviewed and investigated promptly and fairly. 66 | 67 | All community leaders are obligated to respect the privacy and security of the 68 | reporter of any incident. 69 | 70 | ## Enforcement Guidelines 71 | 72 | Community leaders will follow these Community Impact Guidelines in determining 73 | the consequences for any action they deem in violation of this Code of Conduct: 74 | 75 | ### 1. Correction 76 | 77 | **Community Impact**: Use of inappropriate language or other behavior deemed 78 | unprofessional or unwelcome in the community. 79 | 80 | **Consequence**: A private, written warning from community leaders, providing 81 | clarity around the nature of the violation and an explanation of why the 82 | behavior was inappropriate. A public apology may be requested. 83 | 84 | ### 2. Warning 85 | 86 | **Community Impact**: A violation through a single incident or series 87 | of actions. 88 | 89 | **Consequence**: A warning with consequences for continued behavior. No 90 | interaction with the people involved, including unsolicited interaction with 91 | those enforcing the Code of Conduct, for a specified period of time. This 92 | includes avoiding interactions in community spaces as well as external channels 93 | like social media. Violating these terms may lead to a temporary or 94 | permanent ban. 95 | 96 | ### 3. Temporary Ban 97 | 98 | **Community Impact**: A serious violation of community standards, including 99 | sustained inappropriate behavior. 100 | 101 | **Consequence**: A temporary ban from any sort of interaction or public 102 | communication with the community for a specified period of time. No public or 103 | private interaction with the people involved, including unsolicited interaction 104 | with those enforcing the Code of Conduct, is allowed during this period. 105 | Violating these terms may lead to a permanent ban. 106 | 107 | ### 4. Permanent Ban 108 | 109 | **Community Impact**: Demonstrating a pattern of violation of community 110 | standards, including sustained inappropriate behavior, harassment of an 111 | individual, or aggression toward or disparagement of classes of individuals. 112 | 113 | **Consequence**: A permanent ban from any sort of public interaction within 114 | the community. 115 | 116 | ## Attribution 117 | 118 | This Code of Conduct is adapted from the [Contributor Covenant][homepage], 119 | version 2.0, available at 120 | [https://www.contributor-covenant.org/version/2/0/code_of_conduct.html][v2.0]. 121 | 122 | Community Impact Guidelines were inspired by 123 | [Mozilla's code of conduct enforcement ladder][Mozilla CoC]. 124 | 125 | For answers to common questions about this code of conduct, see the FAQ at 126 | [https://www.contributor-covenant.org/faq][FAQ]. Translations are available 127 | at [https://www.contributor-covenant.org/translations][translations]. 128 | 129 | [homepage]: https://www.contributor-covenant.org 130 | [v2.0]: https://www.contributor-covenant.org/version/2/0/code_of_conduct.html 131 | [Mozilla CoC]: https://github.com/mozilla/diversity 132 | [FAQ]: https://www.contributor-covenant.org/faq 133 | [translations]: https://www.contributor-covenant.org/translations 134 | 135 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | Looks like there's already a license file for this project. 2 | [?25l? Do you want to replace it? » (y/N)√ Do you want to replace it? ... no 3 | [?25hExiting... 4 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Arbitrage Algorithms Framework Prototype 2 | For making trading bots on top of [CCXT](https://github.com/ccxt/ccxt/), with simple lifecycle. In TypeScript. 3 | 4 |
5 | example 6 |
7 | 8 | Feel free to contribute. 9 | 10 | Configuration 11 | =================================== 12 | ``` 13 | { 14 | keys: { 15 | [key: string /* exchange */]: { 16 | apiKey: string; 17 | secret: string; 18 | } 19 | }; 20 | exchangesToWatch: string[]; 21 | orderBookLimit: number; 22 | exchangeOptions: { 23 | [key: string /* exchange */]: any 24 | }; 25 | defaultExchangeOptions: any; 26 | currenciesToWatch: string[]; 27 | 28 | makeOrders: boolean; 29 | parallelOrders: boolean; 30 | 31 | profile: boolean; 32 | logDetails: boolean; 33 | logAdditionalDetails: boolean; 34 | logWarnings: boolean; 35 | logAdditionalWarnings: boolean; 36 | logError: boolean; 37 | logErrorDetails: boolean; 38 | 39 | // ccxt calculate_fees correction 40 | feesRate: number; 41 | zeroesFeesCorrection: boolean; 42 | correctAllFees: boolean; 43 | feesRoundType: 'ceil' | 'floor' |'round'; 44 | 45 | orderOptionsByExchange: { 46 | [key: string /* exchange */]: any 47 | }; 48 | defaultOrderOptions: any; 49 | 50 | enableProxy: boolean; 51 | changeProxyAfterEveryOrder: boolean; 52 | changeProxyAfterAnyNetworkError: boolean; 53 | proxies: string[]; 54 | 55 | telegram?: { 56 | token: string; 57 | startPhrase: string; 58 | stopPhrase: string; 59 | chats: string[]; 60 | logErrors: boolean; 61 | } 62 | } 63 | ``` 64 | 65 | Usage 66 | =================================== 67 | ``` 68 | import Bot from "arbitrage-algorithms-framework"; 69 | import { config, errorLogTemplate, log } from "arbitrage-algorithms-framework"; 70 | 71 | const bot = new Bot(config(require("path/to/config.local.json"))); 72 | const toIterate = []; 73 | bot.init().then( 74 | () => { 75 | bot.printProfileTime(); 76 | bot.cycle( 77 | toIterate, 78 | (params) => { 79 | // class extending Algorithm base class to be used in cycle 80 | return new CustomAlgorithm(params) 81 | }, iteratedElement => ({ 82 | // function for element adaptation 83 | ...iteratedElement 84 | } as CustomAlgorithmParams), 85 | () => { 86 | log('Algorithm cycle stared'); 87 | bot.printProfileTime(); 88 | bot.telegram.sendMessage('Algorithm cycle stared'); 89 | } 90 | ); 91 | }, 92 | err => log(errorLogTemplate(err)) 93 | ); 94 | ``` 95 | Check `src/example.ts` for more details. 96 | 97 | Todo 98 | =================================== 99 | - "fluid" fees (depends on fees.currency, other than default) 100 | - use prepared error handler 101 | - bot method for placing orders with retries 102 | - WS adapter class compatible with ccxt (for updating iterating elements in rl time, making orders, use with proxy?) 103 | - limits, precisions exceptions (per exchange, coin, exchange + coin and order side) 104 | - make triangle orders at once via proxy or one by one 105 | - fill order at once or chunk (depends on balance and/or agresivness) 106 | - ArbitrageBetweenExchanges 107 | - ArbitrageTriangularBetweenExchanges 108 | - handle fees in BNB on binance 109 | - handle other ordersand fees types (eg. oco, precision rounding types) 110 | - ignore (exchange, market, coin) lists 111 | - readme, docs 112 | 113 | Proxy 114 | =================================== 115 | To start proxy on another server: 116 | ``` 117 | npm i arbitrage-algorithms-framework 118 | npm i -g cors-anywhere 119 | node node_modules/arbitrage-algorithms-framework/misc/proxy.js PORT HOST 120 | ``` 121 | 122 | Then add HOST:PORT in config proxies list. 123 | 124 | ArbitrageTriangleWithinExchange 125 | =================================== 126 | ``` 127 | MARKET: BASE/QUOTE 128 | 129 | M0 130 | /\ 131 | / \ 132 | D0 |/_ \_ 133 | / |\ 134 | _/ \ 135 | /| _\| D1 136 | / \ 137 | /____\____/____\ 138 | M2 / \ M1 139 | 140 | D - DIRECTION, 141 | M - MARKET, 142 | C - COIN 143 | 144 | M0 C0_C1 145 | M1 C1_C2 146 | M2 C0_C2 147 | 148 | Direction 0: 149 | M0 BUY (C0 for C1) | +C0 -C1 150 | M1 BUY (C1 for C2) | +C1 -C2 151 | M2 SELL (C0 for C2) | -C0 +C2 152 | 153 | Direction 1: 154 | M0 SELL (C0 for C1) | -C0 +C1 155 | M1 SELL (C1 for C2) | -C1 +C2 156 | M2 BUY (C0 for C2) | +C0 -C2 157 | ``` 158 | Temporary assumptions: 159 | - don't use BNB for fees on Binance 160 | - every exchange has limit orders 161 | 162 | Known issues: 163 | - handle minimum fees on Bleutrade 164 | 165 | 166 | ArbitrageBetweenExchanges 167 | =================================== 168 | TBD 169 | 170 | BUY C0 FOR C1 -> TRANSFER C0 -> SELL C0 FOR C1 -> TRANSFER C1 171 | SELL C0 FOR C1 -> TRANSFER C1 -> BUY C0 FOR C1 -> TRANSFER C0 172 | 173 | 174 | ArbitrageTriangularBetweenExchanges 175 | =================================== 176 | TBD 177 | 178 | ``` 179 | MARKET: BASE/QUOTE 180 | 181 | M0E0 182 | /\ 183 | / \ 184 | |/_ \_ 185 | / |\ 186 | _/ \ 187 | /| _\| 188 | / \ 189 | /____\____/____\ 190 | M2E1 / \ M1E0 191 | 192 | D - DIRECTION, 193 | M - MARKET, 194 | C - COIN, 195 | E - EXCHANGE 196 | 197 | M0 C0_C1 198 | M1 C1_C2 199 | M2 C0_C2 200 | 201 | Direction 0: 202 | M0E0 BUY (C0 for C1) | +C0 -C1 | E0 203 | M2E0 SELL (C0 for C2) | -C0 +C2 | E0 204 | TRANSFER C2 FROM E0 TO E1 | -E0 +E1 205 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 206 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 207 | 208 | Direction 1: 209 | M0E0 SELL (C0 for C1) | -C0 +C1 | E0 210 | M1E0 BUY (C2 for C1) | +C2 -C1 | E0 211 | TRANSFER C2 FROM E0 TO E1 | -E0 +E1 212 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 213 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 214 | 215 | Direction 2: 216 | M0E0 BUY (C0 for C1) | +C0 -C1 | E0 217 | TRANSFER C0 FROM E0 TO E1 | -E0 +E1 218 | M2E1 SELL (C0 for C2) | -C0 +C2 | E1 219 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 220 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 221 | 222 | Direction 3: 223 | M0E0 SELL (C0 for C1) | -C0 +C1 | E0 224 | TRANSFER C1 FROM E0 TO E1 | -E0 +E1 225 | M1E1 BUY (C2 for C1) | +C2 -C1 | E1 226 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 227 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 228 | 229 | Direction 4: 230 | M0E0 BUY (C0 for C1) | +C0 -C1 | E0 231 | TRANSFER C0 FROM E0 TO E1 | -E0 +E1 232 | M2E1 SELL (C0 for C2) | -C0 +C2 | E1 233 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 234 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 235 | 236 | Direction 5: 237 | M0E0 SELL (C0 for C1) | -C0 +C1 | E0 238 | TRANSFER C1 FROM E0 TO E1 | -E0 +E1 239 | M1E1 BUY (C2 for C1) | +C2 -C1 | E1 240 | M2E1 BUY (C0 for C2) | +C0 -C2 | E1 241 | TRANSFER C0 FROM E1 TO E0 | -E1 +E0 242 | ``` 243 | 244 | Direction 0 is exemplary. Transfers can be between any cone, cones can be vary within three markets (not every occures in every direction). 245 | ``` 246 | m m m t t 247 | m t t m m 248 | t m m m t 249 | m m t t m 250 | t t m m m * 251 | ``` 252 | @TODO: GET EVERY POSSIBLE DIRECTION (* take into account bids/asks variation) -------------------------------------------------------------------------------- /babel.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | presets: [ 3 | ['@babel/preset-env', {targets: {node: 'current'}}], 4 | '@babel/preset-typescript', 5 | ], 6 | }; -------------------------------------------------------------------------------- /dist/Bot.d.ts: -------------------------------------------------------------------------------- 1 | import ccxt, { Balances, Exchange } from 'ccxt'; 2 | import Algorithm from './algorithms/Algorithm'; 3 | import Telegram, { TelegramParams } from './misc/Telegram'; 4 | export interface BotConfig { 5 | keys: { 6 | [key: string]: { 7 | apiKey: string; 8 | secret: string; 9 | }; 10 | }; 11 | exchangesToWatch: string[]; 12 | orderBookLimit: number; 13 | exchangeOptions: { 14 | [key: string]: any; 15 | }; 16 | defaultExchangeOptions: any; 17 | currenciesToWatch: string[]; 18 | makeOrders: boolean; 19 | parallelOrders: boolean; 20 | profile: boolean; 21 | logDetails: boolean; 22 | logAdditionalDetails: boolean; 23 | logWarnings: boolean; 24 | logAdditionalWarnings: boolean; 25 | logError: boolean; 26 | logErrorDetails: boolean; 27 | feesRate: number; 28 | zeroesFeesCorrection: boolean; 29 | correctAllFees: boolean; 30 | feesRoundType: 'ceil' | 'floor' | 'round'; 31 | orderOptionsByExchange: { 32 | [key: string]: any; 33 | }; 34 | defaultOrderOptions: any; 35 | enableProxy: boolean; 36 | changeProxyAfterEveryOrder: boolean; 37 | changeProxyAfterAnyNetworkError: boolean; 38 | proxies: string[]; 39 | telegram?: TelegramParams; 40 | } 41 | export default class Bot { 42 | config: BotConfig; 43 | exchanges: { 44 | [key: string]: Exchange; 45 | }; 46 | balances: { 47 | [key: string]: Balances; 48 | }; 49 | cycleIndex: number; 50 | proxyIndex: number; 51 | telegram: Telegram; 52 | constructor(config: BotConfig); 53 | printProfileTime(): void; 54 | init(): Promise; 55 | runAlgorithm(algorithm: Algorithm): Promise; 56 | runAlgorithmOnIteratedElement(elementsArray: any, algorithm: any, paramsFromElement: any): Promise; 57 | cycle: (toIterate: any[], algorithm: (params: any) => Algorithm, paramsFromElement: (element: any) => any, onCycleRun: () => any) => void; 58 | validateExchange(exchange: string): void; 59 | fetchBalanceAsync(exchange: Exchange): Promise; 60 | fetchBalance(exchange: Exchange): Promise; 61 | setExchangeProxy(exchange: Exchange, index?: number | null): void; 62 | makeOrder(exchange: Exchange, market: any, side: any, amount: any, price: any, additionalParams?: {}): any; 63 | } 64 | -------------------------------------------------------------------------------- /dist/Bot.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const helpers_1 = require("./common/helpers"); 16 | const ccxt_1 = __importDefault(require("ccxt")); 17 | const Telegram_1 = __importDefault(require("./misc/Telegram")); 18 | const constants_1 = require("./common/constants"); 19 | class Bot { 20 | constructor(config) { 21 | this.exchanges = {}; 22 | this.balances = {}; 23 | this.cycleIndex = 0; 24 | this.proxyIndex = 0; 25 | this.cycle = (toIterate, algorithm, paramsFromElement, onCycleRun = () => null) => __awaiter(this, void 0, void 0, function* () { 26 | this.cycleIndex = 0; 27 | const cycleMaxIndex = toIterate.length; 28 | onCycleRun(); 29 | do { 30 | yield this.runAlgorithmOnIteratedElement(toIterate, algorithm, paramsFromElement); 31 | } while (this.cycleIndex < cycleMaxIndex); 32 | process.nextTick(() => { 33 | this.cycle(toIterate, algorithm, paramsFromElement, onCycleRun); 34 | }); 35 | }); 36 | this.config = config; 37 | this.telegram = new Telegram_1.default(this.config.telegram); 38 | this.telegram.sendMessage('Bot started'); 39 | } 40 | printProfileTime() { 41 | if (!this.config.profile) { 42 | return; 43 | } 44 | console.log("\x1b[47m\x1b[30m%s\x1b[0m", new Date().toLocaleString().padEnd(100, ' ')); 45 | } 46 | init() { 47 | return __awaiter(this, void 0, void 0, function* () { 48 | const self = this; 49 | return new Promise((resolve, reject) => __awaiter(this, void 0, void 0, function* () { 50 | yield Promise.all(ccxt_1.default.exchanges.map((exchange) => (function () { 51 | return __awaiter(this, void 0, void 0, function* () { 52 | if (self.config.exchangesToWatch.includes(exchange)) { 53 | self.validateExchange(exchange); 54 | self.exchanges[exchange] = new (ccxt_1.default)[exchange](Object.assign(Object.assign({}, self.config.defaultExchangeOptions), self.config.exchangeOptions[exchange])); 55 | self.exchanges[exchange].apiKey = self.config.keys[exchange].apiKey; 56 | self.exchanges[exchange].secret = self.config.keys[exchange].secret; 57 | try { 58 | self.balances[exchange] = yield self.fetchBalance(self.exchanges[exchange]); 59 | } 60 | catch (err) { 61 | helpers_1.log(helpers_1.errorLogTemplate(err)); 62 | } 63 | } 64 | }); 65 | })())); 66 | resolve(); 67 | })); 68 | }); 69 | } 70 | runAlgorithm(algorithm) { 71 | return __awaiter(this, void 0, void 0, function* () { 72 | return new Promise(resolve => { 73 | algorithm.run().then(res => resolve(res)); 74 | }); 75 | }); 76 | } 77 | runAlgorithmOnIteratedElement(elementsArray, algorithm, paramsFromElement) { 78 | return __awaiter(this, void 0, void 0, function* () { 79 | const element = elementsArray[this.cycleIndex]; 80 | console.log('\x1b[45m%s\x1b[0m', `RUNNING: ${element}`.padEnd(100, ' ')); 81 | let runningAlgorithm; 82 | try { 83 | runningAlgorithm = algorithm(paramsFromElement(element)); 84 | } 85 | catch (err) { 86 | helpers_1.log(helpers_1.errorLogTemplate(err)); 87 | } 88 | let result = runningAlgorithm ? yield this.runAlgorithm(runningAlgorithm) : false; 89 | if (!result) { 90 | this.cycleIndex = this.cycleIndex + 1; 91 | } 92 | }); 93 | } 94 | validateExchange(exchange) { 95 | // @TODO: add validaiton (check if exchange has every required method) 96 | if (!this.config.exchangesToWatch.includes(exchange)) { 97 | helpers_1.validationException('EXCHANGE', `${exchange} IS IGNORED`); 98 | } 99 | } 100 | fetchBalanceAsync(exchange) { 101 | return __awaiter(this, void 0, void 0, function* () { 102 | let balance; 103 | try { 104 | balance = yield exchange.fetchBalance(); 105 | } 106 | catch (err) { 107 | helpers_1.log(helpers_1.errorLogTemplate(err)); 108 | yield exports.fetchBalance(exchange); 109 | } 110 | return balance; 111 | }); 112 | } 113 | fetchBalance(exchange) { 114 | return exchange.fetchBalance(); 115 | } 116 | setExchangeProxy(exchange, index = null) { 117 | this.proxyIndex = index === null ? this.proxyIndex + 1 : index; 118 | this.proxyIndex = this.proxyIndex === this.config.proxies.length ? 0 : this.proxyIndex; 119 | exchange.proxy = this.config.proxies[this.proxyIndex]; 120 | } 121 | makeOrder(exchange, market, side, amount, price, additionalParams = {}) { 122 | // @wip 123 | if (!this.config.makeOrders) 124 | false; 125 | // @TODO: send Telegram notification after order 126 | // @TODO: setExchangeProxy if config.changeProxyAfterEveryOrder 127 | if (side === constants_1.BUY) { 128 | return exchange.createLimitBuyOrder(market, amount, price, Object.assign(Object.assign(Object.assign({}, additionalParams), this.config.defaultOrderOptions), this.config.orderOptionsByExchange[exchange.id])); 129 | } 130 | if (side === constants_1.SELL) { 131 | return exchange.createLimitSellOrder(market, amount, price, Object.assign(Object.assign(Object.assign({}, additionalParams), this.config.defaultOrderOptions), this.config.orderOptionsByExchange[exchange.id])); 132 | } 133 | return false; 134 | } 135 | } 136 | exports.default = Bot; 137 | //# sourceMappingURL=Bot.js.map -------------------------------------------------------------------------------- /dist/Bot.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Bot.js","sourceRoot":"","sources":["../src/Bot.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/algorithms/Algorithm.d.ts: -------------------------------------------------------------------------------- 1 | import { Balances, Market } from "ccxt"; 2 | import Bot from "../Bot"; 3 | export interface AlgorithmCommonParams { 4 | bot: Bot; 5 | markets: Market[]; 6 | balances: Balances; 7 | logWarnings: boolean; 8 | } 9 | export default class Algorithm { 10 | onRun: (params: any) => Promise; 11 | run: (params?: any) => Promise; 12 | } 13 | -------------------------------------------------------------------------------- /dist/algorithms/Algorithm.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | Object.defineProperty(exports, "__esModule", { value: true }); 12 | class Algorithm { 13 | constructor() { 14 | this.onRun = () => new Promise(() => false); 15 | this.run = (params) => __awaiter(this, void 0, void 0, function* () { 16 | return yield this.onRun(params); 17 | }); 18 | } 19 | } 20 | exports.default = Algorithm; 21 | //# sourceMappingURL=Algorithm.js.map -------------------------------------------------------------------------------- /dist/algorithms/Algorithm.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Algorithm.js","sourceRoot":"","sources":["../../src/algorithms/Algorithm.ts"],"names":[],"mappings":";;;;;;;;;;;AAUA,MAAqB,SAAS;IAA9B;QACC,UAAK,GAAkC,GAAG,EAAE,CAAC,IAAI,OAAO,CAAC,GAAG,EAAE,CAAC,KAAK,CAAC,CAAC;QAEtE,QAAG,GAAG,CAAO,MAAY,EAAoB,EAAE;YAC9C,OAAO,MAAM,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,CAAC;QACjC,CAAC,CAAA,CAAA;IACF,CAAC;CAAA;AAND,4BAMC"} -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageBetweenExchanges.d.ts: -------------------------------------------------------------------------------- 1 | import Algorithm from './Algorithm'; 2 | export default class ArbitrageBetweenExchanges extends Algorithm { 3 | } 4 | -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageBetweenExchanges.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Algorithm_1 = __importDefault(require("./Algorithm")); 7 | class ArbitrageBetweenExchanges extends Algorithm_1.default { 8 | } 9 | exports.default = ArbitrageBetweenExchanges; 10 | //# sourceMappingURL=ArbitrageBetweenExchanges.js.map -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageBetweenExchanges.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ArbitrageBetweenExchanges.js","sourceRoot":"","sources":["../../src/algorithms/ArbitrageBetweenExchanges.ts"],"names":[],"mappings":";;;;;AAAA,4DAAoC;AAEpC,MAAqB,yBAA0B,SAAQ,mBAAS;CAK/D;AALD,4CAKC"} -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangleWithinExchange.d.ts: -------------------------------------------------------------------------------- 1 | import { Balances, Exchange, Market, MinMax } from 'ccxt'; 2 | import Bot from '../Bot'; 3 | import { Amount } from '../common/interfaces'; 4 | import { BidsOrAsks, BuyOrSell, OrderType } from '../common/types'; 5 | import Algorithm from './Algorithm'; 6 | import { AlgorithmCommonParams } from './Algorithm'; 7 | import { BigNumber } from "bignumber.js"; 8 | export interface ArbitrageTriangleWithinExchangeParams extends AlgorithmCommonParams { 9 | exchange: Exchange; 10 | validateMarkets: boolean; 11 | minimumsCorrectionTries?: number; 12 | } 13 | interface DIRECTIONS_SEQUENCE { 14 | orders: BuyOrSell[]; 15 | ordersbookSide: BidsOrAsks[]; 16 | } 17 | export default class ArbitrageTriangleWithinExchange extends Algorithm { 18 | static ALGORITHM_TYPE: string; 19 | DIRECTIONS_SEQUENCES: DIRECTIONS_SEQUENCE[]; 20 | availableDirections: DIRECTIONS_SEQUENCE[]; 21 | bot: Bot; 22 | exchange: Exchange; 23 | marketsTriplet: Market[]; 24 | availableBalances: Balances; 25 | validateMarkets: boolean; 26 | orderBooks: { 27 | [key: string]: { 28 | bids: number[][]; 29 | asks: number[][]; 30 | }; 31 | }; 32 | minimum: { 33 | amount: Amount[]; 34 | cost: Amount[]; 35 | market: string; 36 | }[]; 37 | minimumsCorrectionTries: any; 38 | constructor(params: ArbitrageTriangleWithinExchangeParams); 39 | static validateMarkets(markets: Market[]): boolean; 40 | isBalanceSufficientForOrder(order: BuyOrSell, balance: number, marketLimits: { 41 | amount: MinMax; 42 | price: MinMax; 43 | cost?: MinMax; 44 | }): boolean; 45 | validateBalances(markets: Market[], balances: Balances): boolean; 46 | static getValidatedTripletsOnExchange(exchange: Exchange, toWatch?: string[], showLoadingStatus?: boolean, showLoadingErrors?: boolean, checkBalances?: boolean): string[][]; 47 | static throwValidationException(message: any): void; 48 | validate(markets: Market[], balances: Balances): void; 49 | getOrderBooks(): Promise; 50 | getPrecision(market: Market, of: string): any; 51 | getQuantity(market: Market, total: BigNumber, ordersbookSide: BidsOrAsks): { 52 | quantity: BigNumber; 53 | price: number; 54 | }; 55 | onArbitrageTriangleWithinExchangeRun(): Promise; 56 | makeOrder(market: any, side: any, amount: any, price: any, additionalParams?: {}): any; 57 | getCost(market: Market, amount: BigNumber, ordersbookSide: BidsOrAsks): { 58 | total: BigNumber; 59 | price: number; 60 | }; 61 | getMinCost(market: Market, ordersbookSide: BidsOrAsks): { 62 | total: BigNumber; 63 | price: number; 64 | }; 65 | fees(market: Market, amount: number, price: number, buyOrSell: BuyOrSell, orderType?: OrderType): any; 66 | calculateFeeFallback(fees: any, market: any, side: any, amount: any, price: any, takerOrMaker?: string): any; 67 | } 68 | export {}; 69 | -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangleWithinExchange.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const constants_1 = require("../common/constants"); 16 | const helpers_1 = require("../common/helpers"); 17 | const Algorithm_1 = __importDefault(require("./Algorithm")); 18 | const bignumber_js_1 = require("bignumber.js"); 19 | class ArbitrageTriangleWithinExchange extends Algorithm_1.default { 20 | constructor(params) { 21 | super(); 22 | this.DIRECTIONS_SEQUENCES = [ 23 | { 24 | orders: [constants_1.BUY, constants_1.BUY, constants_1.SELL], 25 | ordersbookSide: [constants_1.ASKS, constants_1.ASKS, constants_1.BIDS] 26 | }, 27 | { 28 | orders: [constants_1.SELL, constants_1.SELL, constants_1.BUY], 29 | ordersbookSide: [constants_1.BIDS, constants_1.BIDS, constants_1.ASKS] 30 | } 31 | ]; 32 | this.availableDirections = []; 33 | this.validateMarkets = true; 34 | this.orderBooks = {}; 35 | params.bot.printProfileTime(); 36 | const { bot, markets, balances, validateMarkets, exchange, minimumsCorrectionTries } = Object.assign({}, params); 37 | this.bot = bot; 38 | this.exchange = exchange; 39 | this.validateMarkets = validateMarkets; 40 | this.minimumsCorrectionTries = minimumsCorrectionTries || 1000; 41 | this.validate(markets, balances); 42 | this.marketsTriplet = markets; 43 | this.availableBalances = balances; 44 | this.onRun = this.onArbitrageTriangleWithinExchangeRun; 45 | params.bot.printProfileTime(); 46 | } 47 | static validateMarkets(markets) { 48 | if (markets.length !== 3) { 49 | this.throwValidationException('InvalidMarketsLength (should be equal to 3)'); 50 | } 51 | if (markets[0].quote !== markets[1].base) { 52 | this.throwValidationException('InvalidMarkets (first market quote should be secound market base)'); 53 | } 54 | if (markets[1].quote !== markets[2].quote) { 55 | this.throwValidationException('InvalidMarkets (second market quote should be third market base)'); 56 | } 57 | if (markets[2].base !== markets[0].base) { 58 | this.throwValidationException('InvalidMarkets (third market base should be first market base)'); 59 | } 60 | return true; 61 | } 62 | isBalanceSufficientForOrder(order, balance, marketLimits) { 63 | return order === constants_1.BUY 64 | ? balance >= (marketLimits.cost && marketLimits.cost.min ? marketLimits.cost.min : 0) 65 | : balance >= marketLimits.amount.min; 66 | } 67 | validateBalances(markets, balances) { 68 | // @TODO: check again after getting orders and price 69 | // log('validateBalances'); 70 | let isDirAvailable = { 71 | 0: true, 72 | 1: true 73 | }; 74 | [0, 1].forEach(dirIndex => { 75 | this.DIRECTIONS_SEQUENCES[dirIndex].orders.forEach((order, index) => { 76 | const isBuy = order === constants_1.BUY; 77 | const quoteOrBase = isBuy ? 'quote' : 'base'; 78 | const isSufficient = this.isBalanceSufficientForOrder(order, balances.free[markets[index][quoteOrBase]], markets[index].limits); 79 | if (this.bot.config.logWarnings && !isSufficient) { 80 | helpers_1.log(`\x1b[33mBalance ${balances.free[markets[index][quoteOrBase]]} ${markets[index].quote} under market ${markets[index].symbol} MIN ${markets[index].limits[isBuy ? 'cost' : 'amount'].min} ${isBuy ? 'cost' : 'amount'} limit\x1b[0m`); 81 | } 82 | isDirAvailable[dirIndex] = isDirAvailable[dirIndex] && isSufficient; 83 | }); 84 | if (isDirAvailable[dirIndex]) { 85 | this.availableDirections.push(this.DIRECTIONS_SEQUENCES[dirIndex]); 86 | } 87 | }); 88 | return this.availableDirections.length > 0; 89 | } 90 | static getValidatedTripletsOnExchange(exchange, toWatch = [], showLoadingStatus = true, showLoadingErrors = false, checkBalances = false) { 91 | const validatedTriplets = []; 92 | let marketsNumber = Object.entries(exchange.markets).length; 93 | let i = 0; 94 | for (let marketA in exchange.markets) { 95 | if (showLoadingStatus) { 96 | console.clear(); 97 | helpers_1.log(`Loading ${exchange.id} ArbitrageTriangleWithinExchange ${((i / marketsNumber) * 100).toFixed()}%`); 98 | } 99 | i++; 100 | if (toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketA].base) < 0 || toWatch.indexOf(exchange.markets[marketA].quote) < 0)) 101 | continue; 102 | for (let marketB in exchange.markets) { 103 | if (toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketB].base) < 0 || toWatch.indexOf(exchange.markets[marketB].quote) < 0)) 104 | continue; 105 | for (let marketC in exchange.markets) { 106 | if (toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketC].base) < 0 || toWatch.indexOf(exchange.markets[marketC].quote) < 0)) 107 | continue; 108 | if (marketA === marketB || marketA === marketC || marketB === marketC) { 109 | continue; 110 | } 111 | const exchangeMarketA = exchange.markets[marketA]; 112 | const exchangeMarketB = exchange.markets[marketB]; 113 | const exchangeMarketC = exchange.markets[marketC]; 114 | if (!exchangeMarketA.active || !exchangeMarketB.active || !exchangeMarketC.active) { 115 | continue; 116 | } 117 | try { 118 | if (ArbitrageTriangleWithinExchange.validateMarkets([ 119 | exchange.markets[marketA], 120 | exchange.markets[marketB], 121 | exchange.markets[marketC] 122 | ])) { 123 | validatedTriplets.push([marketA, marketB, marketC]); 124 | } 125 | } 126 | catch (err) { 127 | if (showLoadingErrors) { 128 | helpers_1.log(`\x1b[33mLoading ${exchange.id} ArbitrageTriangleWithinExchange ${err.name} ${err.message}\x1b[0m`); 129 | } 130 | } 131 | } 132 | } 133 | } 134 | return validatedTriplets; 135 | } 136 | static throwValidationException(message) { 137 | throw helpers_1.validationException(this.ALGORITHM_TYPE, message); 138 | } 139 | validate(markets, balances) { 140 | if (this.validateMarkets) { 141 | ArbitrageTriangleWithinExchange.validateMarkets(markets); 142 | } 143 | if (!this.validateBalances(markets, balances)) { 144 | ArbitrageTriangleWithinExchange.throwValidationException(`Balances insufficient for ${markets[0].symbol} ${markets[1].symbol} ${markets[2].symbol}`); 145 | } 146 | } 147 | getOrderBooks() { 148 | return __awaiter(this, void 0, void 0, function* () { 149 | // log('getOrderBooks'); 150 | this.bot.printProfileTime(); 151 | yield Promise.all(this.marketsTriplet.map((market) => __awaiter(this, void 0, void 0, function* () { 152 | // log('getOrderBook ' + this.exchange.name); 153 | let orderbook = yield this.exchange.fetchL2OrderBook(market.symbol, this.bot.config.orderBookLimit); 154 | this.orderBooks[market.symbol] = { [constants_1.ASKS]: orderbook[constants_1.ASKS], [constants_1.BIDS]: orderbook[constants_1.BIDS] }; 155 | this.bot.printProfileTime(); 156 | }))); 157 | }); 158 | } 159 | getPrecision(market, of) { 160 | // exceptations 161 | if (this.exchange.id === 'bleutrade') { 162 | switch (of) { 163 | case 'quote': 164 | return market.info.DivisorDecimal ? market.info.DivisorDecimal : 8; 165 | case 'base': 166 | return market.info.DividendDecimal ? market.info.DividendDecimal : 8; 167 | default: 168 | return market.precision[of]; 169 | } 170 | } 171 | return market.precision[of]; 172 | } 173 | getQuantity(market, total, ordersbookSide) { 174 | let calcAmount = new bignumber_js_1.BigNumber(0); 175 | let orderbookIndex = 0; 176 | let price = 0; 177 | let order; 178 | let stop = false; 179 | do { 180 | order = this.orderBooks[market.symbol][ordersbookSide][orderbookIndex]; 181 | if (!order) { 182 | break; 183 | } 184 | calcAmount = calcAmount.plus(order ? order[1] : 0); 185 | // console.log(orderbookIndex, order, total.toString(), calcAmount.toString(), calcAmount.times(order[0]).isGreaterThanOrEqualTo(total)); 186 | if (calcAmount.times(order[0]).isGreaterThanOrEqualTo(total)) { 187 | calcAmount = new bignumber_js_1.BigNumber(total).dividedBy(order[0]); 188 | price = order[0]; 189 | stop = true; 190 | } 191 | else { 192 | orderbookIndex = orderbookIndex + 1; 193 | } 194 | } while (!stop); 195 | // console.log(order, !order); 196 | if (!order) { 197 | return { 198 | quantity: new bignumber_js_1.BigNumber(Infinity), 199 | price: Infinity 200 | }; 201 | } 202 | return { 203 | // total: new BigNumber(amount).times(price).precision(market.precision.quote), 204 | // @TODO: add rounding depends on buyOrSell 205 | quantity: new bignumber_js_1.BigNumber(calcAmount.toPrecision(this.getPrecision(market, ordersbookSide === 'asks' ? 'quote' : 'base'))), 206 | price: price 207 | }; 208 | } 209 | onArbitrageTriangleWithinExchangeRun() { 210 | return __awaiter(this, void 0, void 0, function* () { 211 | this.bot.printProfileTime(); 212 | return new Promise((resolve) => __awaiter(this, void 0, void 0, function* () { 213 | // log(`onArbitrageTriangleWithinExchangeRun ${this.marketsTriplet.map(market => market.symbol).join(' ')}`) 214 | yield this.getOrderBooks(); 215 | this.availableDirections.forEach((direction, directionIndex) => __awaiter(this, void 0, void 0, function* () { 216 | let success = false; 217 | do { 218 | let minAB, qunatityAB, totalAB, minAC, minBC, qunatityBC, totalBC, qunatityAC, totalAC, feesAB, feesBC, feesAC, result; 219 | let step = 1; 220 | let reachedMinimum = false; 221 | minAB = Math.max(this.marketsTriplet[0].limits.amount.min, this.marketsTriplet[2].limits.amount.min); 222 | const firstSide = direction.orders[0] === 'buy'; 223 | do { 224 | if (this.bot.config.logAdditionalWarnings && step > 1) { 225 | console.log("\x1b[33m%s\x1b[0m", `MINIMUMS CORRECTION (${step}. times)`); 226 | } 227 | if (firstSide) { 228 | qunatityAB = new bignumber_js_1.BigNumber(minAB).multipliedBy(step + this.bot.config.feesRate); // [A], market A/B 229 | totalAB = this.getCost(this.marketsTriplet[0], qunatityAB, constants_1.bidsOrAsksByBuyOrSell[direction.orders[0]]); // [B], market A/B 230 | feesAB = this.fees(this.marketsTriplet[0], qunatityAB.toNumber(), totalAB.price, direction.orders[0]); 231 | qunatityBC = totalAB.total; // [B], market B/C 232 | totalBC = this.getCost(this.marketsTriplet[1], qunatityBC, constants_1.bidsOrAsksByBuyOrSell[direction.orders[1]]); // [C], market B/C 233 | feesBC = this.fees(this.marketsTriplet[1], qunatityBC.toNumber(), totalBC.price, direction.orders[1]); 234 | qunatityAC = new bignumber_js_1.BigNumber(minAB).multipliedBy(step).minus(feesAB.cost); // [A], market A/C 235 | totalAC = this.getCost(this.marketsTriplet[2], qunatityAC, constants_1.bidsOrAsksByBuyOrSell[direction.orders[2]]); // [C], market A/C 236 | feesAC = this.fees(this.marketsTriplet[2], qunatityAC.toNumber(), totalAC.price, direction.orders[2]); 237 | // results in C 238 | result = totalAC.total.minus(totalBC.total).minus(feesAC.cost); 239 | } 240 | else { 241 | qunatityAC = new bignumber_js_1.BigNumber(minAB).multipliedBy(step + this.bot.config.feesRate); // [A], market A/C 242 | totalAC = this.getCost(this.marketsTriplet[2], qunatityAC, constants_1.bidsOrAsksByBuyOrSell[direction.orders[2]]); // [C], market A/C 243 | feesAC = this.fees(this.marketsTriplet[2], qunatityAC.toNumber(), totalAC.price, direction.orders[2]); 244 | qunatityAB = new bignumber_js_1.BigNumber(minAB).multipliedBy(step).minus(feesAC.cost); // [A], market A/B 245 | totalAB = this.getCost(this.marketsTriplet[0], qunatityAB, constants_1.bidsOrAsksByBuyOrSell[direction.orders[0]]); // [B], market A/B 246 | feesAB = this.fees(this.marketsTriplet[0], qunatityAB.toNumber(), totalAB.price, direction.orders[0]); 247 | qunatityBC = totalAB.total.minus(feesAB.cost); // [B], market B/C 248 | totalBC = this.getCost(this.marketsTriplet[1], qunatityBC, constants_1.bidsOrAsksByBuyOrSell[direction.orders[1]]); // [C], market B/C 249 | feesBC = this.fees(this.marketsTriplet[1], qunatityBC.toNumber(), totalBC.price, direction.orders[1]); 250 | // results in C 251 | result = totalBC.total.minus(totalAC.total).minus(feesBC.cost); 252 | } 253 | step = step + 1; 254 | reachedMinimum = !(qunatityAB.isLessThan(this.marketsTriplet[0].limits.amount.min) 255 | || qunatityBC.isLessThan(this.marketsTriplet[1].limits.amount.min) 256 | || qunatityAC.isLessThan(this.marketsTriplet[2].limits.amount.min) 257 | || totalAB.total < this.marketsTriplet[0].limits.cost.min 258 | || totalBC.total < this.marketsTriplet[1].limits.cost.min 259 | || totalAC.total < this.marketsTriplet[2].limits.cost.min); 260 | } while (!reachedMinimum && step < this.minimumsCorrectionTries); 261 | // @TODO: add fees to AB or AC (depends on ordersDirection) 262 | this.bot.config.logAdditionalDetails && console.table({ 263 | ' ': { 264 | 'direction (max depth)': '-', 265 | '1. price': '-', 266 | '1. amount': '-', 267 | 'min cost': '(by amount)', 268 | 'amount min': '(limit)', 269 | 'cost min': '(limit)', 270 | 'price min': '(limit)' 271 | }, 272 | [`${this.marketsTriplet[0].symbol}`]: { 273 | 'direction (max depth)': `${direction.orders[0]} (${this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]].length} ${direction.ordersbookSide[0]})`, 274 | '1. price': this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]][0][0], 275 | '1. amount': this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]][0][1], 276 | 'min cost': this.getMinCost(this.marketsTriplet[0], direction.ordersbookSide[0]).total.toString(), 277 | 'amount min': this.marketsTriplet[0].limits.amount.min, 278 | 'cost min': this.marketsTriplet[0].limits.cost.min, 279 | 'price min': this.marketsTriplet[0].limits.price.min 280 | }, 281 | [`${this.marketsTriplet[1].symbol}`]: { 282 | 'direction (max depth)': `${direction.orders[1]} (${this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]].length} ${direction.ordersbookSide[1]})`, 283 | '1. price': this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]][0][0], 284 | '1. amount': this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]][0][1], 285 | 'min cost': this.getMinCost(this.marketsTriplet[1], direction.ordersbookSide[1]).total.toString(), 286 | 'amount min': this.marketsTriplet[1].limits.amount.min, 287 | 'cost min': this.marketsTriplet[1].limits.cost.min, 288 | 'price min': this.marketsTriplet[1].limits.price.min 289 | }, 290 | [`${this.marketsTriplet[2].symbol}`]: { 291 | 'direction (max depth)': `${direction.orders[2]} (${this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]].length} ${direction.ordersbookSide[2]})`, 292 | '1. price': this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]][0][0], 293 | '1. amount': this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]][0][1], 294 | 'min cost': this.getMinCost(this.marketsTriplet[2], direction.ordersbookSide[2]).total.toString(), 295 | 'amount min': this.marketsTriplet[2].limits.amount.min, 296 | 'cost min': this.marketsTriplet[2].limits.cost.min, 297 | 'price min': this.marketsTriplet[2].limits.price.min 298 | } 299 | }); 300 | this.bot.config.logDetails && console.table({ 301 | [this.marketsTriplet[0].symbol]: { 302 | direction: direction.orders[0], 303 | quantity: `${qunatityAB.toString()} ${this.marketsTriplet[0].base}`, 304 | ' ': 'for', 305 | total: `${totalAB.total.toPrecision(this.getPrecision(this.marketsTriplet[0], 'quote')).toString()} ${this.marketsTriplet[0].quote}`, 306 | fees: `${feesAB.cost} ${feesAB.currency}`, 307 | 'fees rate': feesAB.rate 308 | }, 309 | [this.marketsTriplet[1].symbol]: { 310 | direction: direction.orders[1], 311 | quantity: `${qunatityBC.toString()} ${this.marketsTriplet[1].base}`, 312 | ' ': 'for', 313 | total: `${totalBC.total.toPrecision(this.getPrecision(this.marketsTriplet[1], 'quote')).toString()} ${this.marketsTriplet[1].quote}`, 314 | fees: `${feesBC.cost} ${feesBC.currency}`, 315 | 'fees rate': feesBC.rate 316 | }, 317 | [this.marketsTriplet[2].symbol]: { 318 | direction: direction.orders[2], 319 | quantity: `${qunatityAC.toString()} ${this.marketsTriplet[2].base}`, 320 | ' ': 'for', 321 | total: `${totalAC.total.toPrecision(this.getPrecision(this.marketsTriplet[2], 'quote')).toString()} ${this.marketsTriplet[2].quote}`, 322 | fees: `${feesAC.cost} ${feesAC.currency}`, 323 | 'fees rate': feesAC.rate 324 | } 325 | }); 326 | const resultString = `${result.toString()} ${this.marketsTriplet[1].quote}`.padStart(100, ' '); 327 | if (result.isGreaterThan(0)) { 328 | console.log("\x1b[42m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 329 | // @TODO: check balances, make order (then check again if there iss still arbitrage) 330 | // @TODO: check minimums again 331 | if (!reachedMinimum) { 332 | } 333 | else { 334 | // use await instead promise.all if proxies list is not configured 335 | // await Promise.all([ 336 | // this.makeOrder(this.marketsTriplet[0].symbol, direction.orders[0], qunatityAB, totalAB.price), 337 | // this.makeOrder(this.marketsTriplet[1].symbol, direction.orders[1], qunatityBC, totalBC.price), 338 | // this.makeOrder(this.marketsTriplet[2].symbol, direction.orders[2], qunatityAC, totalAC.price) 339 | // ]).then((values) => { 340 | // console.log(values); 341 | // success = ; 342 | // }); 343 | } 344 | } 345 | else if (!result.isEqualTo(0)) { 346 | console.log("\x1b[41m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 347 | } 348 | else { 349 | console.log("\x1b[44m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 350 | } 351 | success = false; 352 | } while (success); 353 | })); 354 | resolve(false); 355 | })); 356 | }); 357 | } 358 | ; 359 | makeOrder(market, side, amount, price, additionalParams = {}) { 360 | return this.bot.makeOrder(this.exchange, market, side, amount, price, additionalParams); 361 | } 362 | getCost(market, amount, ordersbookSide) { 363 | let calcAmount = new bignumber_js_1.BigNumber(0); 364 | let orderbookIndex = 0; 365 | let price = 0; 366 | let order; 367 | do { 368 | order = this.orderBooks[market.symbol][ordersbookSide][orderbookIndex]; 369 | if (!order) { 370 | break; 371 | } 372 | calcAmount = calcAmount.plus(order ? order[1] : 0); 373 | // console.log(orderbookIndex, order, amount.toString(), calcAmount.toString()); 374 | if (calcAmount.isGreaterThanOrEqualTo(amount)) { 375 | calcAmount = amount; 376 | price = order[0]; 377 | } 378 | else { 379 | orderbookIndex = orderbookIndex + 1; 380 | } 381 | } while (calcAmount !== amount); 382 | // console.log(order, !order); 383 | if (!order) { 384 | return { 385 | total: new bignumber_js_1.BigNumber(Infinity), 386 | price: Infinity 387 | }; 388 | } 389 | // const isBuy = buyOrSellByBidsOrAsks[ordersbookSide] === BUY; 390 | // const quoteOrBase = isBuy ? 'base': 'quote'; 391 | return { 392 | // total: new BigNumber(amount).times(price).precision(market.precision.quote), 393 | total: new bignumber_js_1.BigNumber(new bignumber_js_1.BigNumber(amount).times(price).toPrecision(this.getPrecision(market, 'quote'))), 394 | price: price 395 | }; 396 | } 397 | getMinCost(market, ordersbookSide) { 398 | return this.getCost(market, new bignumber_js_1.BigNumber(market.limits.amount.min), ordersbookSide); 399 | } 400 | fees(market, amount, price, buyOrSell, orderType = 'limit') { 401 | if (!Number.isFinite(amount) || !Number.isFinite(price)) { 402 | return { 403 | 'cost': Infinity, 404 | }; 405 | } 406 | const fees = this.exchange.calculate_fee(market.symbol, orderType, buyOrSell, amount, price, 'taker'); 407 | if ((!fees.cost && this.bot.config.zeroesFeesCorrection) || this.bot.config.correctAllFees) { 408 | return this.calculateFeeFallback(fees, market, buyOrSell, amount, price); 409 | } 410 | return fees; 411 | } 412 | calculateFeeFallback(fees, market, side, amount, price, takerOrMaker = 'taker') { 413 | if (this.bot.config.logAdditionalWarnings) { 414 | console.log("\x1b[33m%s\x1b[0m", `FEES CORRECTION ${market.symbol} (to correct: ${fees.cost} ${fees.currency})`); 415 | } 416 | return Object.assign(Object.assign({}, fees), { cost: helpers_1.floatRound(amount * price * this.exchange.markets[market.symbol].taker, market['precision']['price'], this.bot.config.feesRoundType) }); 417 | } 418 | } 419 | exports.default = ArbitrageTriangleWithinExchange; 420 | ArbitrageTriangleWithinExchange.ALGORITHM_TYPE = 'ArbitrageTriangleWithinExchange'; 421 | //# sourceMappingURL=ArbitrageTriangleWithinExchange.js.map -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangleWithinExchange.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ArbitrageTriangleWithinExchange.js","sourceRoot":"","sources":["../../src/algorithms/ArbitrageTriangleWithinExchange.ts"],"names":[],"mappings":"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"} -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangularBetweenExchanges.d.ts: -------------------------------------------------------------------------------- 1 | import Algorithm from './Algorithm'; 2 | export default class ArbitrageTriangularBetweenExchanges extends Algorithm { 3 | } 4 | -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangularBetweenExchanges.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | const Algorithm_1 = __importDefault(require("./Algorithm")); 7 | class ArbitrageTriangularBetweenExchanges extends Algorithm_1.default { 8 | } 9 | exports.default = ArbitrageTriangularBetweenExchanges; 10 | //# sourceMappingURL=ArbitrageTriangularBetweenExchanges.js.map -------------------------------------------------------------------------------- /dist/algorithms/ArbitrageTriangularBetweenExchanges.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"ArbitrageTriangularBetweenExchanges.js","sourceRoot":"","sources":["../../src/algorithms/ArbitrageTriangularBetweenExchanges.ts"],"names":[],"mappings":";;;;;AAAA,4DAAoC;AAEpC,MAAqB,mCAAoC,SAAQ,mBAAS;CAKzE;AALD,sDAKC"} -------------------------------------------------------------------------------- /dist/common/config.d.ts: -------------------------------------------------------------------------------- 1 | import { BotConfig } from "../Bot"; 2 | declare const _default: BotConfig; 3 | export default _default; 4 | export declare const config: (config: any) => BotConfig; 5 | -------------------------------------------------------------------------------- /dist/common/config.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.config = void 0; 4 | const defaultConfig = require("../config.json"); 5 | exports.default = Object.assign({}, defaultConfig); 6 | const config = (config) => (Object.assign(Object.assign({}, defaultConfig), config)); 7 | exports.config = config; 8 | //# sourceMappingURL=config.js.map -------------------------------------------------------------------------------- /dist/common/config.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"config.js","sourceRoot":"","sources":["../../src/common/config.ts"],"names":[],"mappings":";;;AAAA,gDAAiD;AAIjD,kBAAe,kBACR,aAAa,CAEN,CAAA;AAEP,MAAM,MAAM,GAAG,CAAC,MAAW,EAAE,EAAE,CAAC,iCAChC,aAAa,GACb,MAAM,EAEE,CAAA;AAJF,QAAA,MAAM,UAIJ"} -------------------------------------------------------------------------------- /dist/common/constants.d.ts: -------------------------------------------------------------------------------- 1 | export declare const BUY = "buy"; 2 | export declare const SELL = "sell"; 3 | export declare const ASKS = "asks"; 4 | export declare const BIDS = "bids"; 5 | export declare const buyOrSellByBidsOrAsks: { 6 | asks: string; 7 | bids: string; 8 | }; 9 | export declare const bidsOrAsksByBuyOrSell: { 10 | buy: string; 11 | sell: string; 12 | }; 13 | -------------------------------------------------------------------------------- /dist/common/constants.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.bidsOrAsksByBuyOrSell = exports.buyOrSellByBidsOrAsks = exports.BIDS = exports.ASKS = exports.SELL = exports.BUY = void 0; 4 | exports.BUY = 'buy'; 5 | exports.SELL = 'sell'; 6 | exports.ASKS = 'asks'; 7 | exports.BIDS = 'bids'; 8 | exports.buyOrSellByBidsOrAsks = { 9 | [exports.ASKS]: exports.BUY, 10 | [exports.BIDS]: exports.SELL 11 | }; 12 | exports.bidsOrAsksByBuyOrSell = { 13 | [exports.BUY]: exports.ASKS, 14 | [exports.SELL]: exports.BIDS 15 | }; 16 | //# sourceMappingURL=constants.js.map -------------------------------------------------------------------------------- /dist/common/constants.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"constants.js","sourceRoot":"","sources":["../../src/common/constants.ts"],"names":[],"mappings":";;;AAEa,QAAA,GAAG,GAAG,KAAK,CAAC;AACZ,QAAA,IAAI,GAAG,MAAM,CAAC;AACd,QAAA,IAAI,GAAG,MAAM,CAAC;AACd,QAAA,IAAI,GAAG,MAAM,CAAC;AAEd,QAAA,qBAAqB,GAAG;IACpC,CAAC,YAAI,CAAC,EAAE,WAAG;IACX,CAAC,YAAI,CAAC,EAAE,YAAI;CACZ,CAAA;AAEY,QAAA,qBAAqB,GAAG;IACpC,CAAC,WAAG,CAAC,EAAE,YAAI;IACX,CAAC,YAAI,CAAC,EAAE,YAAI;CACZ,CAAA"} -------------------------------------------------------------------------------- /dist/common/errors.d.ts: -------------------------------------------------------------------------------- 1 | import Bot from "../Bot"; 2 | import { BaseError, Exchange } from 'ccxt'; 3 | export default class BotErrorHandler { 4 | static handleError: (bot: Bot, err: BaseError, exchange: Exchange, market?: string) => void; 5 | } 6 | -------------------------------------------------------------------------------- /dist/common/errors.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __setModuleDefault = (this && this.__setModuleDefault) || (Object.create ? (function(o, v) { 10 | Object.defineProperty(o, "default", { enumerable: true, value: v }); 11 | }) : function(o, v) { 12 | o["default"] = v; 13 | }); 14 | var __importStar = (this && this.__importStar) || function (mod) { 15 | if (mod && mod.__esModule) return mod; 16 | var result = {}; 17 | if (mod != null) for (var k in mod) if (k !== "default" && Object.prototype.hasOwnProperty.call(mod, k)) __createBinding(result, mod, k); 18 | __setModuleDefault(result, mod); 19 | return result; 20 | }; 21 | Object.defineProperty(exports, "__esModule", { value: true }); 22 | const ccxt_1 = __importStar(require("ccxt")); 23 | class BotErrorHandler { 24 | } 25 | exports.default = BotErrorHandler; 26 | BotErrorHandler.handleError = (bot, err, exchange, market = '') => { 27 | const changeProxy = bot.config.enableProxy && bot.config.changeProxyAfterAnyNetworkError && (err instanceof ccxt_1.default.RequestTimeout || 28 | err instanceof ccxt_1.default.ExchangeNotAvailable || 29 | err instanceof ccxt_1.default.NetworkError || 30 | err instanceof ccxt_1.default.DDoSProtection); 31 | if (changeProxy) { 32 | bot.setExchangeProxy(exchange); 33 | } 34 | if (bot.config.logError) { 35 | // @TODO: format 36 | console.log(exchange.symbol, market, typeof err); 37 | if (!bot.config.logErrorDetails) 38 | console.log(err); 39 | return; 40 | } 41 | if (err instanceof ccxt_1.BaseError) { 42 | throw err; 43 | } 44 | }; 45 | //# sourceMappingURL=errors.js.map -------------------------------------------------------------------------------- /dist/common/errors.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"errors.js","sourceRoot":"","sources":["../../src/common/errors.ts"],"names":[],"mappings":";;;;;;;;;;;;;;;;;;;;;AACA,6CAAiD;AAEjD,MAAqB,eAAe;;AAApC,kCAwBC;AAvBU,2BAAW,GAAG,CAAC,GAAQ,EAAE,GAAc,EAAE,QAAkB,EAAE,MAAM,GAAG,EAAE,EAAE,EAAE;IAC/E,MAAM,WAAW,GAAG,GAAG,CAAC,MAAM,CAAC,WAAW,IAAI,GAAG,CAAC,MAAM,CAAC,+BAA+B,IAAI,CACxF,GAAG,YAAY,cAAI,CAAC,cAAc;QAClC,GAAG,YAAY,cAAI,CAAC,oBAAoB;QACxC,GAAG,YAAY,cAAI,CAAC,YAAY;QAChC,GAAG,YAAY,cAAI,CAAC,cAAc,CACrC,CAAC;IAEF,IAAI,WAAW,EAAE;QACb,GAAG,CAAC,gBAAgB,CAAC,QAAQ,CAAC,CAAC;KAClC;IAED,IAAG,GAAG,CAAC,MAAM,CAAC,QAAQ,EAAE;QACpB,gBAAgB;QAChB,OAAO,CAAC,GAAG,CAAC,QAAQ,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAG,CAAC,CAAC;QACjD,IAAG,CAAC,GAAG,CAAC,MAAM,CAAC,eAAe;YAAE,OAAO,CAAC,GAAG,CAAC,GAAG,CAAC,CAAC;QACjD,OAAO;KACV;IAED,IAAI,GAAK,YAAW,gBAAS,EAAE;QAC3B,MAAM,GAAG,CAAC;KACb;AACL,CAAC,CAAA"} -------------------------------------------------------------------------------- /dist/common/helpers.d.ts: -------------------------------------------------------------------------------- 1 | export declare const errorLogTemplate: (e: Error) => string; 2 | export declare function log(message: any, type?: 'log' | 'warn', run?: boolean): void; 3 | export declare const validationException: (algorithmType: string, message: string) => { 4 | message: string; 5 | name: string; 6 | }; 7 | export declare const floatRound: (value: number, precision: number, type: 'ceil' | 'floor' | 'round') => number; 8 | -------------------------------------------------------------------------------- /dist/common/helpers.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | exports.floatRound = exports.validationException = exports.log = exports.errorLogTemplate = void 0; 4 | const errorLogTemplate = (e) => `\x1b[31m${e.name} ${e.message}\x1b[0m`; 5 | exports.errorLogTemplate = errorLogTemplate; 6 | function log(message, type = 'log', run = true) { 7 | if (!run) { 8 | return; 9 | } 10 | console[type](message); 11 | } 12 | exports.log = log; 13 | const validationException = (algorithmType, message) => ({ 14 | message: `\n\x1b[31m${message}\x1b[0m`, 15 | name: `\x1b[31m${algorithmType} Validation Exception${message ? ':' : ''}\x1b[0m` 16 | }); 17 | exports.validationException = validationException; 18 | const floatRound = (value, precision, type) => { 19 | switch (type) { 20 | case 'ceil': return Math.ceil(value * Math.pow(10, precision)) / Math.pow(10, precision); 21 | case 'floor': return Math.floor(value * Math.pow(10, precision)) / Math.pow(10, precision); 22 | case 'round': return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision); 23 | } 24 | }; 25 | exports.floatRound = floatRound; 26 | //# sourceMappingURL=helpers.js.map -------------------------------------------------------------------------------- /dist/common/helpers.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"helpers.js","sourceRoot":"","sources":["../../src/common/helpers.ts"],"names":[],"mappings":";;;AAAO,MAAM,gBAAgB,GAAG,CAAC,CAAQ,EAAE,EAAE,CAAC,WAAW,CAAC,CAAC,IAAI,IAAI,CAAC,CAAC,OAAO,SAAS,CAAC;AAAzE,QAAA,gBAAgB,oBAAyD;AAEtF,SAAgB,GAAG,CAAC,OAAO,EAAE,OAAuB,KAAK,EAAE,GAAG,GAAG,IAAI;IACpE,IAAI,CAAC,GAAG,EAAE;QAAE,OAAO;KAAE;IACrB,OAAO,CAAC,IAAI,CAAC,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC;AAHD,kBAGC;AAEM,MAAM,mBAAmB,GAAG,CAAC,aAAqB,EAAE,OAAe,EAAE,EAAE,CAAC,CAAC;IAC7E,OAAO,EAAE,aAAa,OAAO,SAAS;IACtC,IAAI,EAAE,WAAW,aAAa,wBAAwB,OAAO,CAAC,CAAC,CAAC,GAAG,CAAC,CAAC,CAAC,EAAE,SAAS;CACnF,CAAC,CAAC;AAHU,QAAA,mBAAmB,uBAG7B;AAGI,MAAM,UAAU,GAAG,CAAC,KAAa,EAAE,SAAiB,EAAE,IAA+B,EAAE,EAAE;IAC7F,QAAO,IAAI,EAAC;QACR,KAAK,MAAM,CAAC,CAAC,OAAO,IAAI,CAAC,IAAI,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QACxF,KAAK,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;QAC1F,KAAK,OAAO,CAAC,CAAC,OAAO,IAAI,CAAC,KAAK,CAAC,KAAK,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAC,GAAG,IAAI,CAAC,GAAG,CAAC,EAAE,EAAE,SAAS,CAAC,CAAA;KAC7F;AACJ,CAAC,CAAA;AANY,QAAA,UAAU,cAMtB"} -------------------------------------------------------------------------------- /dist/common/interfaces.d.ts: -------------------------------------------------------------------------------- 1 | import { BidsOrAsks } from "./types"; 2 | export interface Amount { 3 | symbol: string; 4 | amount: number; 5 | } 6 | export interface Order { 7 | price: number; 8 | amount: number; 9 | type: BidsOrAsks; 10 | } 11 | export interface ValidationException { 12 | message: string; 13 | name: string; 14 | } 15 | -------------------------------------------------------------------------------- /dist/common/interfaces.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=interfaces.js.map -------------------------------------------------------------------------------- /dist/common/interfaces.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"interfaces.js","sourceRoot":"","sources":["../../src/common/interfaces.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/common/types.d.ts: -------------------------------------------------------------------------------- 1 | export declare type MarketOrBase = 'market' | 'base'; 2 | export declare type BidsOrAsks = 'bids' | 'asks'; 3 | export declare type OrderType = 'market' | 'limit'; 4 | export declare type BuyOrSell = 'buy' | 'sell'; 5 | -------------------------------------------------------------------------------- /dist/common/types.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | Object.defineProperty(exports, "__esModule", { value: true }); 3 | //# sourceMappingURL=types.js.map -------------------------------------------------------------------------------- /dist/common/types.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"types.js","sourceRoot":"","sources":["../../src/common/types.ts"],"names":[],"mappings":""} -------------------------------------------------------------------------------- /dist/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeOrders": false, 3 | "profile": false, 4 | "logDetails": false, 5 | "logAdditionalDetails": false, 6 | "logWarnings": false, 7 | "logAdditionalWarnings": false, 8 | "logError": false, 9 | "logErrorDetails": false, 10 | "exchangesToWatch": ["example"], 11 | "orderBookLimit": 1000, 12 | "exchangeOptions": { 13 | "example": {} 14 | }, 15 | "defaultExchangeOptions": { 16 | "enableRateLimit": true, 17 | "proxy": false 18 | }, 19 | "keys": { 20 | "example": { 21 | "apiKey": "123456789ABC", 22 | "secret": "123456789DEF" 23 | } 24 | }, 25 | "currenciesToWatch": [], 26 | "feesRate": 0.001, 27 | "zeroesFeesCorrection": true, 28 | "correctAllFees": false, 29 | "feesRoundType": "ceil", 30 | "orderOptionsByExchange": {}, 31 | "defaultOrderOptions": {}, 32 | "parallelOrders": false, 33 | "enableProxy": false, 34 | "changeProxyAfterEveryOrder": false, 35 | "changeProxyAfterAnyNetworkError": false, 36 | "proxies": [], 37 | "telegram": { 38 | "token": "", 39 | "startPhrase": "", 40 | "stopPhrase": "", 41 | "chats": [], 42 | "logErrors": false 43 | } 44 | } 45 | -------------------------------------------------------------------------------- /dist/config.local.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeOrders": false, 3 | "profile": false, 4 | "logDetails": true, 5 | "logAdditionalDetails": true, 6 | "logWarnings": false, 7 | "logAdditionalWarnings": false, 8 | "logError": false, 9 | "logErrorDetails": false, 10 | "exchangesToWatch": ["binance"], 11 | "orderBookLimit": 1000, 12 | "exchangeOptions": { 13 | "bleutrade": {}, 14 | "binance": {} 15 | }, 16 | "keys": { 17 | "bleutrade": { 18 | "apiKey": "", 19 | "secret": "" 20 | }, 21 | "binance": { 22 | "apiKey": "", 23 | "secret": "" 24 | } 25 | }, 26 | "currenciesToWatch": ["ETH", "BTC", "BNB"], 27 | "orderOptionsByExchange": { 28 | "binance": { 29 | "adjustForTimeDifference": true 30 | } 31 | }, 32 | "defaultOrderOptions": {}, 33 | "enableProxy": false, 34 | "changeProxyAfterEveryOrder": false, 35 | "changeProxyAfterAnyNetworkError": false, 36 | "parallelOrders": false, 37 | "proxies": [], 38 | "telegram": { 39 | "token": "", 40 | "startPhrase": "", 41 | "stopPhrase": "", 42 | "chats": [], 43 | "logErrors": false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /dist/example.d.ts: -------------------------------------------------------------------------------- 1 | export {}; 2 | -------------------------------------------------------------------------------- /dist/example.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __awaiter = (this && this.__awaiter) || function (thisArg, _arguments, P, generator) { 3 | function adopt(value) { return value instanceof P ? value : new P(function (resolve) { resolve(value); }); } 4 | return new (P || (P = Promise))(function (resolve, reject) { 5 | function fulfilled(value) { try { step(generator.next(value)); } catch (e) { reject(e); } } 6 | function rejected(value) { try { step(generator["throw"](value)); } catch (e) { reject(e); } } 7 | function step(result) { result.done ? resolve(result.value) : adopt(result.value).then(fulfilled, rejected); } 8 | step((generator = generator.apply(thisArg, _arguments || [])).next()); 9 | }); 10 | }; 11 | var __importDefault = (this && this.__importDefault) || function (mod) { 12 | return (mod && mod.__esModule) ? mod : { "default": mod }; 13 | }; 14 | Object.defineProperty(exports, "__esModule", { value: true }); 15 | const ArbitrageTriangleWithinExchange_1 = __importDefault(require("./algorithms/ArbitrageTriangleWithinExchange")); 16 | const Bot_1 = __importDefault(require("./Bot")); 17 | const helpers_1 = require("./common/helpers"); 18 | const config_1 = require("./common/config"); 19 | const localConfig = require("./config.local.example.json"); 20 | // ccxt.d.ts 21 | process 22 | .on('unhandledRejection', (reason, p) => { 23 | console.error(reason, '\x1b[34mUnhandled Rejection at Promise\x1b[0m', p); 24 | }) 25 | .on('uncaughtException', err => { 26 | console.error(err, '\x1b[34mUncaught Exception thrown\x1b[0m'); 27 | process.exit(1); 28 | }); 29 | // add triggering by socket (additional lib per exchange, compatible with ccxt) 30 | // crawl over markets by default 31 | // @TODO: consider: 32 | // moving to new CPU worker (optionally behind robin rounded proxy) if there is no other already running with given exchanges, otherwise add to queue 33 | const bot = new Bot_1.default(config_1.config(localConfig)); 34 | startBot(); 35 | function startBot() { 36 | return __awaiter(this, void 0, void 0, function* () { 37 | bot.init().then(() => startArbitrageTriangleWithinExchangeAlgorithm(), err => helpers_1.log(helpers_1.errorLogTemplate(err))); 38 | bot.printProfileTime(); 39 | }); 40 | } 41 | function startArbitrageTriangleWithinExchangeAlgorithm() { 42 | Object.entries(bot.exchanges).forEach(([key, exchange]) => __awaiter(this, void 0, void 0, function* () { 43 | try { 44 | yield exchange.loadMarkets(); 45 | const validatedTriplets = ArbitrageTriangleWithinExchange_1.default.getValidatedTripletsOnExchange(exchange, bot.config.currenciesToWatch); 46 | helpers_1.log(`\x1b[32mFound ${validatedTriplets.length} ArbitrageTriangleWithinExchange triplets on ${exchange.id}\x1b[0m`); 47 | console.table(validatedTriplets); 48 | bot.cycle(validatedTriplets, (params) => { 49 | return new ArbitrageTriangleWithinExchange_1.default(params); 50 | }, element => ({ 51 | exchange: exchange, 52 | bot: bot, 53 | markets: [ 54 | exchange.markets[element[0]], 55 | exchange.markets[element[1]], 56 | exchange.markets[element[2]] 57 | ], 58 | balances: bot.balances[key], 59 | validateMarkets: false 60 | }), () => { 61 | helpers_1.log(`\x1b[32mCycle ${exchange.id} ArbitrageTriangleWithinExchange\x1b[0m`); 62 | }); 63 | } 64 | catch (err) { 65 | helpers_1.log(helpers_1.errorLogTemplate(err)); 66 | bot.printProfileTime(); 67 | } 68 | })); 69 | } 70 | // @TODO: add checking 71 | // { 72 | // ...bot.config.ignore, 73 | // ...bot.config.MARKET_EXCHANGE_KEY.ignore, 74 | // ...bot.config.MARKET_KEY.ignore, 75 | // ...bot.config.MARKET_EXCHANGE_KEY.MARKET_KEY.ignore 76 | // } 77 | //# sourceMappingURL=example.js.map -------------------------------------------------------------------------------- /dist/example.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"example.js","sourceRoot":"","sources":["../src/example.ts"],"names":[],"mappings":";;;;;;;;;;;;;;AAAA,mHAAsI;AACtI,gDAAwB;AACxB,8CAAyD;AACzD,4CAAyC;AACzC,2DAA4D;AAC5D,YAAY;AAEZ,OAAO;KACN,EAAE,CAAC,oBAAoB,EAAE,CAAC,MAAM,EAAE,CAAC,EAAE,EAAE;IACpC,OAAO,CAAC,KAAK,CAAC,MAAM,EAAE,+CAA+C,EAAE,CAAC,CAAC,CAAC;AAC9E,CAAC,CAAC;KACD,EAAE,CAAC,mBAAmB,EAAE,GAAG,CAAC,EAAE;IAC3B,OAAO,CAAC,KAAK,CAAC,GAAG,EAAE,0CAA0C,CAAC,CAAC;IAC/D,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;AACpB,CAAC,CAAC,CAAC;AAEH,+EAA+E;AAC/E,gCAAgC;AAEhC,mBAAmB;AACnB,wJAAwJ;AAExJ,MAAM,GAAG,GAAG,IAAI,aAAG,CAAC,eAAM,CAAC,WAAW,CAAC,CAAC,CAAC;AACzC,QAAQ,EAAE,CAAC;AAEX,SAAe,QAAQ;;QACnB,GAAG,CAAC,IAAI,EAAE,CAAC,IAAI,CACX,GAAG,EAAE,CAAC,6CAA6C,EAAE,EACrD,GAAG,CAAC,EAAE,CAAC,aAAG,CAAC,0BAAgB,CAAC,GAAG,CAAC,CAAC,CACpC,CAAC;QACF,GAAG,CAAC,gBAAgB,EAAE,CAAC;IAC3B,CAAC;CAAA;AAED,SAAS,6CAA6C;IAClD,MAAM,CAAC,OAAO,CAAC,GAAG,CAAC,SAAS,CAAC,CAAC,OAAO,CAAC,CAAO,CAAC,GAAG,EAAE,QAAQ,CAAC,EAAE,EAAE;QAC5D,IAAI;YACA,MAAM,QAAQ,CAAC,WAAW,EAAE,CAAC;YAC7B,MAAM,iBAAiB,GAAG,yCAA+B,CAAC,8BAA8B,CAAC,QAAQ,EAAE,GAAG,CAAC,MAAM,CAAC,iBAAiB,CAAC,CAAC;YAEjI,aAAG,CAAC,iBAAiB,iBAAiB,CAAC,MAAM,gDAAgD,QAAQ,CAAC,EAAE,SAAS,CAAC,CAAC;YACnH,OAAO,CAAC,KAAK,CAAC,iBAAiB,CAAC,CAAA;YAEhC,GAAG,CAAC,KAAK,CACL,iBAAiB,EACjB,CAAC,MAA6C,EAAE,EAAE;gBAC9C,OAAO,IAAI,yCAA+B,CAAC,MAAM,CAAC,CAAA;YACtD,CAAC,EAAE,OAAO,CAAC,EAAE,CAAC,CAAC;gBACX,QAAQ,EAAE,QAAQ;gBAClB,GAAG,EAAE,GAAG;gBACR,OAAO,EAAE;oBACL,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBAC5B,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;oBAC5B,QAAQ,CAAC,OAAO,CAAC,OAAO,CAAC,CAAC,CAAC,CAAC;iBAC/B;gBACD,QAAQ,EAAE,GAAG,CAAC,QAAQ,CAAC,GAAG,CAAC;gBAC3B,eAAe,EAAE,KAAK;aACzB,CAAC,EACF,GAAG,EAAE;gBACD,aAAG,CAAE,iBAAiB,QAAQ,CAAC,EAAE,yCAAyC,CAAC,CAAA;YAC/E,CAAC,CACJ,CAAC;SACL;QAAC,OAAO,GAAG,EAAE;YACV,aAAG,CAAC,0BAAgB,CAAC,GAAG,CAAC,CAAC,CAAC;YAC3B,GAAG,CAAC,gBAAgB,EAAE,CAAC;SAC1B;IACL,CAAC,CAAA,CAAC,CAAC;AACP,CAAC;AAED,sBAAsB;AACtB,IAAI;AACJ,yBAAyB;AACzB,6CAA6C;AAC7C,oCAAoC;AACpC,uDAAuD;AACvD,IAAI"} -------------------------------------------------------------------------------- /dist/index.d.ts: -------------------------------------------------------------------------------- 1 | export * from "./Bot"; 2 | export * from "./common/config"; 3 | export * from "./algorithms/Algorithm"; 4 | export * from "./algorithms/ArbitrageBetweenExchanges"; 5 | export * from "./algorithms/ArbitrageTriangleWithinExchange"; 6 | export * from "./algorithms/ArbitrageTriangularBetweenExchanges"; 7 | export * from "./common/constants"; 8 | export * from "./common/helpers"; 9 | export * from "./common/interfaces"; 10 | export * from "./common/types"; 11 | -------------------------------------------------------------------------------- /dist/index.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { 3 | if (k2 === undefined) k2 = k; 4 | Object.defineProperty(o, k2, { enumerable: true, get: function() { return m[k]; } }); 5 | }) : (function(o, m, k, k2) { 6 | if (k2 === undefined) k2 = k; 7 | o[k2] = m[k]; 8 | })); 9 | var __exportStar = (this && this.__exportStar) || function(m, exports) { 10 | for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); 11 | }; 12 | Object.defineProperty(exports, "__esModule", { value: true }); 13 | __exportStar(require("./Bot"), exports); 14 | __exportStar(require("./common/config"), exports); 15 | __exportStar(require("./algorithms/Algorithm"), exports); 16 | __exportStar(require("./algorithms/ArbitrageBetweenExchanges"), exports); 17 | __exportStar(require("./algorithms/ArbitrageTriangleWithinExchange"), exports); 18 | __exportStar(require("./algorithms/ArbitrageTriangularBetweenExchanges"), exports); 19 | __exportStar(require("./common/constants"), exports); 20 | __exportStar(require("./common/helpers"), exports); 21 | __exportStar(require("./common/interfaces"), exports); 22 | __exportStar(require("./common/types"), exports); 23 | //# sourceMappingURL=index.js.map -------------------------------------------------------------------------------- /dist/index.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";;;;;;;;;;;;AACA,wCAAsB;AACtB,kDAAgC;AAChC,yDAAuC;AACvC,yEAAuD;AACvD,+EAA6D;AAC7D,mFAAiE;AACjE,qDAAmC;AACnC,mDAAiC;AACjC,sDAAoC;AACpC,iDAA+B"} -------------------------------------------------------------------------------- /dist/misc/Telegram.d.ts: -------------------------------------------------------------------------------- 1 | export interface TelegramParams { 2 | token: string; 3 | startPhrase: string; 4 | stopPhrase: string; 5 | chats: string[]; 6 | logErrors: boolean; 7 | } 8 | export default class Telegram { 9 | token: string; 10 | startPhrase: string; 11 | stopPhrase: string; 12 | chats: any[]; 13 | telegramBot: any; 14 | logErrors: boolean; 15 | constructor(params: TelegramParams); 16 | init(): void; 17 | addChat: (id: any) => void; 18 | removeChat: (id: any) => void; 19 | sendMessage: (message: any) => void; 20 | } 21 | -------------------------------------------------------------------------------- /dist/misc/Telegram.js: -------------------------------------------------------------------------------- 1 | "use strict"; 2 | var __importDefault = (this && this.__importDefault) || function (mod) { 3 | return (mod && mod.__esModule) ? mod : { "default": mod }; 4 | }; 5 | Object.defineProperty(exports, "__esModule", { value: true }); 6 | process.env.NTBA_FIX_319 = '1'; 7 | const node_telegram_bot_api_1 = __importDefault(require("node-telegram-bot-api")); 8 | class Telegram { 9 | constructor(params) { 10 | this.token = ''; 11 | this.startPhrase = 'start'; 12 | this.stopPhrase = 'stop'; 13 | this.chats = []; 14 | this.logErrors = false; 15 | this.addChat = (id) => { 16 | if (!this.token) 17 | return; 18 | if (this.chats.indexOf(id) >= 0) 19 | return; 20 | this.chats.push(id); 21 | }; 22 | this.removeChat = (id) => { 23 | if (!this.token) 24 | return; 25 | const index = this.chats.indexOf(id); 26 | if (index < 0) 27 | return; 28 | this.chats.splice(index, 1); 29 | }; 30 | this.sendMessage = (message) => { 31 | if (!this.token) 32 | return; 33 | if (this.chats.length === 0) 34 | return; 35 | this.chats.forEach(chat => { 36 | this.telegramBot 37 | .sendMessage(chat, message, { parse_mode: 'HTML' }) 38 | .catch(error => { 39 | // @TODO: format 40 | if (this.logErrors) 41 | console.log(error); 42 | }); 43 | }); 44 | }; 45 | const { token, startPhrase, stopPhrase, chats, logErrors } = Object.assign({}, params); 46 | this.token = token; 47 | this.startPhrase = startPhrase; 48 | this.stopPhrase = stopPhrase; 49 | this.chats = chats; 50 | this.logErrors = logErrors; 51 | if (!this.token) { 52 | // @TODO: format 53 | if (this.logErrors) 54 | console.log('Cannot start Telegram notifications without token'); 55 | return; 56 | } 57 | this.init(); 58 | } 59 | init() { 60 | this.telegramBot = new node_telegram_bot_api_1.default(this.token, { polling: true }); 61 | this.telegramBot.onText(this.startPhrase, (msg) => { 62 | this.addChat(msg.chat.id); 63 | let chatId = msg.chat.id; 64 | this.telegramBot.sendMessage(chatId, 'Notifications started'); 65 | }); 66 | this.telegramBot.onText(this.stopPhrase, (msg) => { 67 | this.removeChat(msg.chat.id); 68 | let chatId = msg.chat.id; 69 | this.telegramBot.sendMessage(chatId, 'Notifications stopped'); 70 | }); 71 | this.telegramBot.on('message', (msg) => { 72 | let chatId = msg.chat.id; 73 | this.telegramBot.sendMessage(chatId, 'Enter start phrase to start. Enter stop phrase to stop notifications.'); 74 | }); 75 | } 76 | } 77 | exports.default = Telegram; 78 | //# sourceMappingURL=Telegram.js.map -------------------------------------------------------------------------------- /dist/misc/Telegram.js.map: -------------------------------------------------------------------------------- 1 | {"version":3,"file":"Telegram.js","sourceRoot":"","sources":["../../src/misc/Telegram.ts"],"names":[],"mappings":";;;;;AAAA,OAAO,CAAC,GAAG,CAAC,YAAY,GAAG,GAAG,CAAC;AAC/B,kFAAgD;AAUhD,MAAqB,QAAQ;IAQzB,YAAY,MAAsB;QAPlC,UAAK,GAAG,EAAE,CAAC;QACX,gBAAW,GAAG,OAAO,CAAC;QACtB,eAAU,GAAG,MAAM,CAAC;QACpB,UAAK,GAAG,EAAE,CAAC;QAEX,cAAS,GAAG,KAAK,CAAC;QAyClB,YAAO,GAAG,CAAC,EAAE,EAAE,EAAE;YACb,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAE,OAAO;YACxB,IAAI,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,IAAI,CAAC;gBAAE,OAAO;YACxC,IAAI,CAAC,KAAK,CAAC,IAAI,CAAC,EAAE,CAAC,CAAC;QACxB,CAAC,CAAA;QAED,eAAU,GAAG,CAAC,EAAE,EAAE,EAAE;YAChB,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAE,OAAO;YACxB,MAAM,KAAK,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,EAAE,CAAC,CAAA;YACpC,IAAI,KAAK,GAAG,CAAC;gBAAE,OAAO;YACtB,IAAI,CAAC,KAAK,CAAC,MAAM,CAAC,KAAK,EAAE,CAAC,CAAC,CAAC;QAChC,CAAC,CAAA;QAED,gBAAW,GAAG,CAAC,OAAO,EAAE,EAAE;YACtB,IAAI,CAAC,IAAI,CAAC,KAAK;gBAAE,OAAO;YACxB,IAAI,IAAI,CAAC,KAAK,CAAC,MAAM,KAAK,CAAC;gBAAE,OAAO;YACpC,IAAI,CAAC,KAAK,CAAC,OAAO,CAAC,IAAI,CAAC,EAAE;gBACtB,IAAI,CAAC,WAAW;qBACf,WAAW,CAAC,IAAI,EAAE,OAAO,EAAE,EAAC,UAAU,EAAE,MAAM,EAAC,CAAC;qBAChD,KAAK,CAAC,KAAK,CAAC,EAAE;oBACX,gBAAgB;oBAChB,IAAG,IAAI,CAAC,SAAS;wBAAE,OAAO,CAAC,GAAG,CAAC,KAAK,CAAC,CAAA;gBACzC,CAAC,CAAC,CAAA;YACN,CAAC,CAAC,CAAA;QACN,CAAC,CAAA;QA9DH,MAAM,EAAC,KAAK,EAAE,WAAW,EAAE,UAAU,EAAE,KAAK,EAAE,SAAS,EAAC,qBAAO,MAAM,CAAC,CAAC;QAEjE,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,WAAW,GAAG,WAAW,CAAC;QAC/B,IAAI,CAAC,UAAU,GAAG,UAAU,CAAC;QAC7B,IAAI,CAAC,KAAK,GAAG,KAAK,CAAC;QACnB,IAAI,CAAC,SAAS,GAAG,SAAS,CAAC;QAE3B,IAAG,CAAC,IAAI,CAAC,KAAK,EAAE;YACZ,gBAAgB;YAChB,IAAG,IAAI,CAAC,SAAS;gBAAE,OAAO,CAAC,GAAG,CAAC,mDAAmD,CAAC,CAAC;YACpF,OAAO;SACV;QAED,IAAI,CAAC,IAAI,EAAE,CAAC;IAChB,CAAC;IAED,IAAI;QACA,IAAI,CAAC,WAAW,GAAG,IAAI,+BAAW,CAAC,IAAI,CAAC,KAAK,EAAE,EAAC,OAAO,EAAE,IAAI,EAAC,CAAC,CAAC;QAEhE,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,WAAW,EAAE,CAAC,GAAG,EAAE,EAAE;YAC9C,IAAI,CAAC,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YACzB,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAA;YACxB,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;QACjE,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,CAAC,UAAU,EAAE,CAAC,GAAG,EAAE,EAAE;YAC7C,IAAI,CAAC,UAAU,CAAC,GAAG,CAAC,IAAI,CAAC,EAAE,CAAC,CAAA;YAC5B,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAA;YACxB,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,uBAAuB,CAAC,CAAA;QACjE,CAAC,CAAC,CAAA;QAEF,IAAI,CAAC,WAAW,CAAC,EAAE,CAAC,SAAS,EAAE,CAAC,GAAG,EAAE,EAAE;YACnC,IAAI,MAAM,GAAG,GAAG,CAAC,IAAI,CAAC,EAAE,CAAA;YACxB,IAAI,CAAC,WAAW,CAAC,WAAW,CAAC,MAAM,EAAE,uEAAuE,CAAC,CAAA;QACjH,CAAC,CAAC,CAAC;IACP,CAAC;CA2BJ;AAxED,2BAwEC"} -------------------------------------------------------------------------------- /docs/example.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/g-zwolinski/arbitrage-algorithms-framework/4cd146037db8ad64473f6f3727f154b78336910a/docs/example.png -------------------------------------------------------------------------------- /jest.config.js: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | preset: 'ts-jest', 3 | testEnvironment: 'node', 4 | }; -------------------------------------------------------------------------------- /misc/proxy.js: -------------------------------------------------------------------------------- 1 | let port = (process.argv.length > 2) ? parseInt (process.argv[2]) : 8080; 2 | let host = (process.argv.length > 3) ? parseInt (process.argv[3]) : process.env.HOST || '0.0.0.0'; 3 | require('cors-anywhere').createServer().listen(port, host).listen(port, host, function() { 4 | console.log('Running CORS Anywhere on ' + host + ':' + port); 5 | }); -------------------------------------------------------------------------------- /nodemon.json: -------------------------------------------------------------------------------- 1 | { 2 | "watch": ["src"], 3 | "ext": ".ts,.js", 4 | "ignore": [], 5 | "exec": "ts-node ./src/app.ts" 6 | } -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "arbitrage-algorithms-framework", 3 | "version": "0.0.3", 4 | "description": "working prototype typescript framework for making arbitrage bots on cryptocurrencies exchanges, on top of ccxt", 5 | "main": "dist/src/index.js", 6 | "types": "./dist/src/index.d.ts", 7 | "scripts": { 8 | "build": "rimraf ./dist && tsc", 9 | "start": "tsc && node dist/example.js", 10 | "start:dev": "nodemon", 11 | "run": "ts-node ./src/example.ts", 12 | "lint": "tslint -c tslint.json 'src/**/*.ts'", 13 | "lint:fix": "npm run lint -- --fix", 14 | "test": "npx jest" 15 | }, 16 | "keywords": ["multiple exchanges", "bot", "algorithm", "arbitrage", "framework", "ccxt", "typescript", "ts", "cryptocurrency"], 17 | "homepage": "https://github.com/g-zwolinski/arbitrage-algorithms-framework#readme", 18 | "bugs": "https://github.com/g-zwolinski/arbitrage-algorithms-framework/issues", 19 | "repository": { 20 | "type": "git", 21 | "url": "https://github.com/g-zwolinski/arbitrage-algorithms-framework" 22 | }, 23 | "author": "zvvolinski.g@gmail.com", 24 | "license": "ISC", 25 | "devDependencies": { 26 | "@babel/preset-typescript": "^7.12.7", 27 | "@types/jest": "^26.0.20", 28 | "@types/node": "^14.14.22", 29 | "@types/node-telegram-bot-api": "^0.51.1", 30 | "jest": "^26.6.3", 31 | "nodemon": "^2.0.7", 32 | "rimraf": "^3.0.2", 33 | "ts-jest": "^26.5.0", 34 | "ts-node": "^9.1.1", 35 | "tslint": "^6.1.3", 36 | "typescript": "^4.1.3" 37 | }, 38 | "dependencies": { 39 | "bignumber.js": "^9.0.1", 40 | "ccxt": "^1.41.20", 41 | "node-telegram-bot-api": "^0.52.0" 42 | } 43 | } 44 | -------------------------------------------------------------------------------- /src/Bot.ts: -------------------------------------------------------------------------------- 1 | import { errorLogTemplate, log, validationException } from './common/helpers'; 2 | import ccxt, { Balances, Exchange } from 'ccxt'; 3 | import Algorithm from './algorithms/Algorithm'; 4 | import Telegram, { TelegramParams } from './misc/Telegram'; 5 | import { BUY, SELL } from './common/constants'; 6 | 7 | export interface BotConfig { 8 | keys: { 9 | [key: string /* exchange */]: { 10 | apiKey: string; 11 | secret: string; 12 | } 13 | }; 14 | exchangesToWatch: string[]; 15 | orderBookLimit: number; 16 | exchangeOptions: { 17 | [key: string /* exchange */]: any 18 | }; 19 | defaultExchangeOptions: any; 20 | currenciesToWatch: string[]; 21 | 22 | makeOrders: boolean; 23 | parallelOrders: boolean; 24 | 25 | profile: boolean; 26 | logDetails: boolean; 27 | logAdditionalDetails: boolean; 28 | logWarnings: boolean; 29 | logAdditionalWarnings: boolean; 30 | logError: boolean; 31 | logErrorDetails: boolean; 32 | 33 | // ccxt calculate_fees correction 34 | feesRate: number; 35 | zeroesFeesCorrection: boolean; 36 | correctAllFees: boolean; 37 | feesRoundType: 'ceil' | 'floor' |'round'; 38 | 39 | orderOptionsByExchange: { 40 | [key: string /* exchange */]: any 41 | }; 42 | defaultOrderOptions: any; 43 | 44 | enableProxy: boolean; 45 | changeProxyAfterEveryOrder: boolean; 46 | changeProxyAfterAnyNetworkError: boolean; 47 | proxies: string[]; 48 | 49 | telegram?: TelegramParams 50 | } 51 | 52 | export default class Bot { 53 | config: BotConfig; 54 | exchanges: { 55 | [key: string]: Exchange 56 | } = {}; 57 | balances: { 58 | [key: string]: Balances 59 | } = {}; 60 | cycleIndex = 0; 61 | proxyIndex = 0; 62 | 63 | telegram: Telegram; 64 | 65 | constructor(config: BotConfig) { 66 | this.config = config; 67 | this.telegram = new Telegram(this.config.telegram); 68 | this.telegram.sendMessage('Bot started'); 69 | } 70 | 71 | printProfileTime() { 72 | if (!this.config.profile) { return; } 73 | console.log("\x1b[47m\x1b[30m%s\x1b[0m", new Date().toLocaleString().padEnd(100, ' ')); 74 | } 75 | 76 | async init() { 77 | const self = this; 78 | return new Promise(async (resolve, reject) => { 79 | await Promise.all (ccxt.exchanges.map ((exchange) => (async function () { 80 | if (self.config.exchangesToWatch.includes(exchange)) { 81 | self.validateExchange(exchange); 82 | self.exchanges[exchange] = new (ccxt)[exchange]({ 83 | ...self.config.defaultExchangeOptions, 84 | ...self.config.exchangeOptions[exchange] 85 | }); 86 | self.exchanges[exchange].apiKey = self.config.keys[exchange].apiKey; 87 | self.exchanges[exchange].secret = self.config.keys[exchange].secret; 88 | 89 | try { 90 | self.balances[exchange] = await self.fetchBalance(self.exchanges[exchange]); 91 | } catch (err) { 92 | log(errorLogTemplate(err)); 93 | } 94 | } 95 | })())); 96 | resolve(); 97 | }) 98 | } 99 | 100 | async runAlgorithm( 101 | algorithm: Algorithm 102 | ) { 103 | return new Promise(resolve => { 104 | algorithm.run().then(res => resolve(res)); 105 | }) 106 | } 107 | 108 | async runAlgorithmOnIteratedElement(elementsArray, algorithm, paramsFromElement) { 109 | const element = elementsArray[this.cycleIndex]; 110 | console.log('\x1b[45m%s\x1b[0m', `RUNNING: ${element}`.padEnd(100, ' ')); 111 | 112 | let runningAlgorithm; 113 | try { 114 | runningAlgorithm = algorithm(paramsFromElement(element)); 115 | } catch (err) { 116 | log(errorLogTemplate(err)); 117 | } 118 | 119 | let result = runningAlgorithm ? await this.runAlgorithm(runningAlgorithm) : false; 120 | if (!result) { 121 | this.cycleIndex = this.cycleIndex + 1; 122 | } 123 | } 124 | 125 | cycle: ( 126 | toIterate: any[], 127 | algorithm: (params) => Algorithm, 128 | paramsFromElement: (element: any) => any, 129 | onCycleRun: () => any 130 | ) => void = async (toIterate, algorithm, paramsFromElement, onCycleRun = () => null) => { 131 | this.cycleIndex = 0; 132 | const cycleMaxIndex = toIterate.length; 133 | onCycleRun(); 134 | 135 | do { 136 | await this.runAlgorithmOnIteratedElement(toIterate, algorithm, paramsFromElement); 137 | } while (this.cycleIndex < cycleMaxIndex) 138 | 139 | process.nextTick(() => { 140 | this.cycle(toIterate, algorithm, paramsFromElement, onCycleRun) 141 | }); 142 | } 143 | 144 | validateExchange(exchange: string) { 145 | // @TODO: add validaiton (check if exchange has every required method) 146 | if (!this.config.exchangesToWatch.includes(exchange)) { 147 | validationException('EXCHANGE', `${exchange} IS IGNORED`); 148 | } 149 | } 150 | 151 | async fetchBalanceAsync(exchange: Exchange) { 152 | let balance: Balances; 153 | 154 | try { 155 | balance = await exchange.fetchBalance(); 156 | } catch (err) { 157 | log(errorLogTemplate(err)); 158 | await exports.fetchBalance(exchange); 159 | } 160 | return balance; 161 | } 162 | 163 | fetchBalance(exchange: Exchange) { 164 | return exchange.fetchBalance(); 165 | } 166 | 167 | setExchangeProxy(exchange: Exchange, index: number | null = null) { 168 | this.proxyIndex = index === null ? this.proxyIndex + 1 : index; 169 | this.proxyIndex = this.proxyIndex === this.config.proxies.length ? 0 : this.proxyIndex; 170 | exchange.proxy = this.config.proxies[this.proxyIndex]; 171 | } 172 | 173 | makeOrder(exchange: Exchange, market, side, amount, price, additionalParams = {}) { 174 | // @wip 175 | if (!this.config.makeOrders) false; 176 | // @TODO: send Telegram notification after order 177 | // @TODO: setExchangeProxy if config.changeProxyAfterEveryOrder 178 | if (side === BUY) { 179 | return exchange.createLimitBuyOrder(market, amount, price, { 180 | ...additionalParams, 181 | ...this.config.defaultOrderOptions, 182 | ...this.config.orderOptionsByExchange[exchange.id] 183 | }); 184 | } 185 | if (side === SELL) { 186 | return exchange.createLimitSellOrder(market, amount, price, { 187 | ...additionalParams, 188 | ...this.config.defaultOrderOptions, 189 | ...this.config.orderOptionsByExchange[exchange.id] 190 | }); 191 | } 192 | return false; 193 | } 194 | } -------------------------------------------------------------------------------- /src/algorithms/Algorithm.ts: -------------------------------------------------------------------------------- 1 | import { Balances, Market } from "ccxt"; 2 | import Bot from "../Bot"; 3 | 4 | export interface AlgorithmCommonParams { 5 | bot: Bot; 6 | markets: Market[]; 7 | balances: Balances; 8 | logWarnings: boolean; 9 | } 10 | 11 | export default class Algorithm { 12 | onRun: (params: any) => Promise = () => new Promise(() => false); 13 | 14 | run = async (params?: any): Promise => { 15 | return await this.onRun(params); 16 | } 17 | } -------------------------------------------------------------------------------- /src/algorithms/ArbitrageBetweenExchanges.ts: -------------------------------------------------------------------------------- 1 | import Algorithm from './Algorithm'; 2 | 3 | export default class ArbitrageBetweenExchanges extends Algorithm { 4 | // BUY C0 FOR C1 -> transfer C0 -> SELL C0 FOR C1 -> transfer C1 5 | // SELL C0 FOR C1 -> transfer C1 -> BUY C0 FOR C1 -> transfer C0 6 | 7 | // check markets on exchanges pair (if transfer costs are less than difference) 8 | } -------------------------------------------------------------------------------- /src/algorithms/ArbitrageTriangleWithinExchange.ts: -------------------------------------------------------------------------------- 1 | import { Balances, Exchange, Market, MinMax } from 'ccxt'; 2 | import Bot from '../Bot'; 3 | import { BUY, SELL, ASKS, BIDS, bidsOrAsksByBuyOrSell } from '../common/constants'; 4 | import { floatRound, log, validationException } from '../common/helpers'; 5 | import { Amount } from '../common/interfaces'; 6 | import { BidsOrAsks, BuyOrSell, OrderType } from '../common/types'; 7 | import Algorithm from './Algorithm'; 8 | import {AlgorithmCommonParams} from './Algorithm'; 9 | import { BigNumber } from "bignumber.js"; 10 | 11 | export interface ArbitrageTriangleWithinExchangeParams extends AlgorithmCommonParams { 12 | exchange: Exchange; 13 | validateMarkets: boolean; 14 | minimumsCorrectionTries?: number; 15 | } 16 | 17 | interface DIRECTIONS_SEQUENCE { 18 | orders: BuyOrSell[], 19 | ordersbookSide: BidsOrAsks[] 20 | } 21 | 22 | export default class ArbitrageTriangleWithinExchange extends Algorithm { 23 | static ALGORITHM_TYPE = 'ArbitrageTriangleWithinExchange'; 24 | DIRECTIONS_SEQUENCES: DIRECTIONS_SEQUENCE[] = [ 25 | { 26 | orders: [BUY, BUY, SELL], 27 | ordersbookSide: [ASKS, ASKS, BIDS] 28 | }, 29 | { 30 | orders: [SELL, SELL, BUY], 31 | ordersbookSide: [BIDS, BIDS, ASKS] 32 | } 33 | ]; 34 | availableDirections: DIRECTIONS_SEQUENCE[] = []; 35 | bot: Bot; 36 | exchange: Exchange; 37 | marketsTriplet: Market[]; 38 | availableBalances: Balances; 39 | validateMarkets = true; 40 | orderBooks: { 41 | [key: string]: { 42 | bids: number[][], 43 | asks: number[][], 44 | } 45 | } = {}; 46 | // every minimum amount in every symbol 47 | minimum: {amount: Amount[], cost: Amount[], market: string}[]; 48 | minimumsCorrectionTries; 49 | 50 | constructor(params: ArbitrageTriangleWithinExchangeParams) { 51 | super(); 52 | 53 | params.bot.printProfileTime(); 54 | 55 | const {bot, markets, balances, validateMarkets, exchange, minimumsCorrectionTries} = {...params}; 56 | 57 | this.bot = bot; 58 | this.exchange = exchange; 59 | this.validateMarkets = validateMarkets; 60 | this.minimumsCorrectionTries = minimumsCorrectionTries || 1000; 61 | this.validate(markets, balances); 62 | this.marketsTriplet = markets; 63 | this.availableBalances = balances; 64 | this.onRun = this.onArbitrageTriangleWithinExchangeRun; 65 | 66 | params.bot.printProfileTime(); 67 | } 68 | 69 | static validateMarkets(markets: Market[]) { 70 | if (markets.length !== 3) { 71 | this.throwValidationException('InvalidMarketsLength (should be equal to 3)'); 72 | } 73 | 74 | if (markets[0].quote !== markets[1].base) { 75 | this.throwValidationException('InvalidMarkets (first market quote should be secound market base)'); 76 | } 77 | 78 | if (markets[1].quote !== markets[2].quote) { 79 | this.throwValidationException('InvalidMarkets (second market quote should be third market base)'); 80 | } 81 | 82 | if (markets[2].base !== markets[0].base) { 83 | this.throwValidationException('InvalidMarkets (third market base should be first market base)'); 84 | } 85 | 86 | return true; 87 | } 88 | 89 | isBalanceSufficientForOrder(order: BuyOrSell, balance: number, marketLimits: { amount: MinMax, price: MinMax, cost?: MinMax },) { 90 | return order === BUY 91 | ? balance >= (marketLimits.cost && marketLimits.cost.min ? marketLimits.cost.min : 0) 92 | : balance >= marketLimits.amount.min; 93 | } 94 | 95 | validateBalances(markets: Market[], balances: Balances) { 96 | // @TODO: check again after getting orders and price 97 | // log('validateBalances'); 98 | 99 | let isDirAvailable = { 100 | 0: true, 101 | 1: true 102 | }; 103 | 104 | [0, 1].forEach(dirIndex => { 105 | this.DIRECTIONS_SEQUENCES[dirIndex].orders.forEach((order: BuyOrSell, index) => { 106 | const isBuy = order === BUY; 107 | const quoteOrBase = isBuy ? 'quote': 'base' 108 | const isSufficient = this.isBalanceSufficientForOrder( 109 | order, 110 | balances.free[markets[index][quoteOrBase]], 111 | markets[index].limits 112 | ); 113 | 114 | if(this.bot.config.logWarnings && !isSufficient) { 115 | log( 116 | `\x1b[33mBalance ${balances.free[markets[index][quoteOrBase]]} ${markets[index].quote} under market ${markets[index].symbol} MIN ${markets[index].limits[isBuy ? 'cost' : 'amount'].min} ${isBuy ? 'cost' : 'amount'} limit\x1b[0m` 117 | ); 118 | } 119 | 120 | isDirAvailable[dirIndex] = isDirAvailable[dirIndex] && isSufficient; 121 | }) 122 | 123 | if (isDirAvailable[dirIndex]) { 124 | this.availableDirections.push(this.DIRECTIONS_SEQUENCES[dirIndex]); 125 | } 126 | }) 127 | return this.availableDirections.length > 0; 128 | } 129 | 130 | static getValidatedTripletsOnExchange(exchange: Exchange, toWatch: string[] = [], showLoadingStatus: boolean = true, showLoadingErrors: boolean = false, checkBalances = false) { 131 | const validatedTriplets: string[][] = []; 132 | let marketsNumber = Object.entries(exchange.markets).length; 133 | let i = 0; 134 | 135 | for (let marketA in exchange.markets) { 136 | if (showLoadingStatus) { 137 | console.clear(); 138 | log(`Loading ${exchange.id} ArbitrageTriangleWithinExchange ${((i / marketsNumber) * 100).toFixed()}%`); 139 | } 140 | i++; 141 | if(toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketA].base) < 0 || toWatch.indexOf(exchange.markets[marketA].quote) < 0)) continue; 142 | for (let marketB in exchange.markets) { 143 | if(toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketB].base) < 0 || toWatch.indexOf(exchange.markets[marketB].quote) < 0)) continue; 144 | for (let marketC in exchange.markets) { 145 | if(toWatch.length > 0 && (toWatch.indexOf(exchange.markets[marketC].base) < 0 || toWatch.indexOf(exchange.markets[marketC].quote) < 0)) continue; 146 | if(marketA === marketB || marketA === marketC || marketB === marketC) { continue; } 147 | 148 | const exchangeMarketA = exchange.markets[marketA]; 149 | const exchangeMarketB = exchange.markets[marketB]; 150 | const exchangeMarketC = exchange.markets[marketC]; 151 | 152 | if(!exchangeMarketA.active || !exchangeMarketB.active || !exchangeMarketC.active) { continue; } 153 | 154 | try { 155 | if (ArbitrageTriangleWithinExchange.validateMarkets([ 156 | exchange.markets[marketA], 157 | exchange.markets[marketB], 158 | exchange.markets[marketC] 159 | ])) { 160 | validatedTriplets.push([marketA, marketB, marketC]); 161 | } 162 | } catch (err) { 163 | if (showLoadingErrors) { 164 | log(`\x1b[33mLoading ${exchange.id} ArbitrageTriangleWithinExchange ${err.name} ${err.message}\x1b[0m`); 165 | } 166 | } 167 | } 168 | } 169 | } 170 | 171 | return validatedTriplets; 172 | } 173 | 174 | static throwValidationException(message) { 175 | throw validationException(this.ALGORITHM_TYPE, message); 176 | } 177 | 178 | validate(markets: Market[], balances: Balances) { 179 | if (this.validateMarkets) { 180 | ArbitrageTriangleWithinExchange.validateMarkets(markets); 181 | } 182 | if (!this.validateBalances(markets, balances)) { 183 | ArbitrageTriangleWithinExchange.throwValidationException( 184 | `Balances insufficient for ${markets[0].symbol} ${markets[1].symbol} ${markets[2].symbol}` 185 | ); 186 | } 187 | } 188 | 189 | async getOrderBooks(){ 190 | // log('getOrderBooks'); 191 | this.bot.printProfileTime(); 192 | await Promise.all(this.marketsTriplet.map(async market => { 193 | // log('getOrderBook ' + this.exchange.name); 194 | let orderbook = await this.exchange.fetchL2OrderBook(market.symbol, this.bot.config.orderBookLimit) 195 | this.orderBooks[market.symbol] = {[ASKS]: orderbook[ASKS], [BIDS]: orderbook[BIDS]}; 196 | this.bot.printProfileTime(); 197 | })) 198 | } 199 | 200 | getPrecision(market: Market, of: string) { 201 | // exceptations 202 | if (this.exchange.id === 'bleutrade') { 203 | switch (of) { 204 | case 'quote': 205 | return market.info.DivisorDecimal ? market.info.DivisorDecimal : 8 206 | case 'base': 207 | return market.info.DividendDecimal ? market.info.DividendDecimal : 8 208 | default: 209 | return market.precision[of]; 210 | } 211 | } 212 | return market.precision[of]; 213 | } 214 | 215 | getQuantity(market: Market, total: BigNumber, ordersbookSide: BidsOrAsks): {quantity: BigNumber; price: number} { 216 | let calcAmount = new BigNumber(0); 217 | let orderbookIndex = 0; 218 | let price = 0; 219 | let order; 220 | let stop = false; 221 | 222 | do { 223 | order = this.orderBooks[market.symbol][ordersbookSide][orderbookIndex]; 224 | if (!order) {break;} 225 | calcAmount = calcAmount.plus(order ? order[1] : 0); 226 | // console.log(orderbookIndex, order, total.toString(), calcAmount.toString(), calcAmount.times(order[0]).isGreaterThanOrEqualTo(total)); 227 | if (calcAmount.times(order[0]).isGreaterThanOrEqualTo(total)) { 228 | calcAmount = new BigNumber(total).dividedBy(order[0]); 229 | price = order[0]; 230 | stop = true; 231 | } else { 232 | orderbookIndex = orderbookIndex + 1; 233 | } 234 | } while (!stop) 235 | 236 | // console.log(order, !order); 237 | 238 | if (!order) { 239 | return { 240 | quantity: new BigNumber(Infinity), 241 | price: Infinity 242 | } 243 | } 244 | 245 | return { 246 | // total: new BigNumber(amount).times(price).precision(market.precision.quote), 247 | // @TODO: add rounding depends on buyOrSell 248 | quantity: new BigNumber(calcAmount.toPrecision(this.getPrecision(market, ordersbookSide === 'asks' ? 'quote': 'base'))), 249 | price: price 250 | }; 251 | } 252 | 253 | async onArbitrageTriangleWithinExchangeRun() { 254 | this.bot.printProfileTime(); 255 | 256 | return new Promise(async resolve => { 257 | // log(`onArbitrageTriangleWithinExchangeRun ${this.marketsTriplet.map(market => market.symbol).join(' ')}`) 258 | await this.getOrderBooks(); 259 | 260 | this.availableDirections.forEach(async (direction, directionIndex) => { 261 | let success = false; 262 | do { 263 | let minAB, qunatityAB, totalAB, minAC, minBC, qunatityBC, totalBC, qunatityAC, totalAC, feesAB, feesBC, feesAC, result; 264 | let step = 1; 265 | let reachedMinimum = false; 266 | minAB = Math.max(this.marketsTriplet[0].limits.amount.min, this.marketsTriplet[2].limits.amount.min); 267 | 268 | const firstSide = direction.orders[0] === 'buy'; 269 | do { 270 | if(this.bot.config.logAdditionalWarnings && step > 1) { 271 | console.log("\x1b[33m%s\x1b[0m", `MINIMUMS CORRECTION (${step}. times)`); 272 | } 273 | 274 | if(firstSide) { 275 | qunatityAB = new BigNumber(minAB).multipliedBy(step + this.bot.config.feesRate); // [A], market A/B 276 | totalAB = this.getCost(this.marketsTriplet[0], qunatityAB, bidsOrAsksByBuyOrSell[direction.orders[0]] as BidsOrAsks); // [B], market A/B 277 | feesAB = this.fees(this.marketsTriplet[0], qunatityAB.toNumber(), totalAB.price, direction.orders[0]); 278 | 279 | qunatityBC = totalAB.total; // [B], market B/C 280 | totalBC = this.getCost(this.marketsTriplet[1], qunatityBC, bidsOrAsksByBuyOrSell[direction.orders[1]] as BidsOrAsks); // [C], market B/C 281 | feesBC = this.fees(this.marketsTriplet[1], qunatityBC.toNumber(), totalBC.price, direction.orders[1]); 282 | 283 | qunatityAC = new BigNumber(minAB).multipliedBy(step).minus(feesAB.cost); // [A], market A/C 284 | totalAC = this.getCost(this.marketsTriplet[2], qunatityAC, bidsOrAsksByBuyOrSell[direction.orders[2]] as BidsOrAsks); // [C], market A/C 285 | feesAC = this.fees(this.marketsTriplet[2], qunatityAC.toNumber(), totalAC.price, direction.orders[2]); 286 | 287 | // results in C 288 | result = totalAC.total.minus(totalBC.total).minus(feesAC.cost); 289 | } else { 290 | 291 | qunatityAC = new BigNumber(minAB).multipliedBy(step + this.bot.config.feesRate); // [A], market A/C 292 | totalAC = this.getCost(this.marketsTriplet[2], qunatityAC, bidsOrAsksByBuyOrSell[direction.orders[2]] as BidsOrAsks); // [C], market A/C 293 | feesAC = this.fees(this.marketsTriplet[2], qunatityAC.toNumber(), totalAC.price, direction.orders[2]); 294 | 295 | qunatityAB = new BigNumber(minAB).multipliedBy(step).minus(feesAC.cost); // [A], market A/B 296 | totalAB = this.getCost(this.marketsTriplet[0], qunatityAB, bidsOrAsksByBuyOrSell[direction.orders[0]] as BidsOrAsks); // [B], market A/B 297 | feesAB = this.fees(this.marketsTriplet[0], qunatityAB.toNumber(), totalAB.price, direction.orders[0]); 298 | 299 | qunatityBC = totalAB.total.minus(feesAB.cost); // [B], market B/C 300 | totalBC = this.getCost(this.marketsTriplet[1], qunatityBC, bidsOrAsksByBuyOrSell[direction.orders[1]] as BidsOrAsks); // [C], market B/C 301 | feesBC = this.fees(this.marketsTriplet[1], qunatityBC.toNumber(), totalBC.price, direction.orders[1]); 302 | 303 | // results in C 304 | result = totalBC.total.minus(totalAC.total).minus(feesBC.cost); 305 | } 306 | step = step + 1; 307 | 308 | reachedMinimum = !(qunatityAB.isLessThan(this.marketsTriplet[0].limits.amount.min) 309 | || qunatityBC.isLessThan(this.marketsTriplet[1].limits.amount.min) 310 | || qunatityAC.isLessThan(this.marketsTriplet[2].limits.amount.min) 311 | || totalAB.total < this.marketsTriplet[0].limits.cost.min 312 | || totalBC.total < this.marketsTriplet[1].limits.cost.min 313 | || totalAC.total < this.marketsTriplet[2].limits.cost.min); 314 | } while(!reachedMinimum && step < this.minimumsCorrectionTries) 315 | 316 | // @TODO: add fees to AB or AC (depends on ordersDirection) 317 | 318 | this.bot.config.logAdditionalDetails && console.table({ 319 | ' ': { 320 | 'direction (max depth)': '-', 321 | '1. price': '-', 322 | '1. amount': '-', 323 | 'min cost': '(by amount)', 324 | 'amount min': '(limit)', 325 | 'cost min': '(limit)', 326 | 'price min': '(limit)' 327 | }, 328 | [`${this.marketsTriplet[0].symbol}`]: { 329 | 'direction (max depth)': `${direction.orders[0]} (${this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]].length} ${direction.ordersbookSide[0]})`, 330 | '1. price': this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]][0][0], 331 | '1. amount': this.orderBooks[this.marketsTriplet[0].symbol][direction.ordersbookSide[0]][0][1], 332 | 'min cost': this.getMinCost(this.marketsTriplet[0], direction.ordersbookSide[0]).total.toString(), 333 | 'amount min': this.marketsTriplet[0].limits.amount.min, 334 | 'cost min': this.marketsTriplet[0].limits.cost.min, 335 | 'price min': this.marketsTriplet[0].limits.price.min 336 | }, 337 | [`${this.marketsTriplet[1].symbol}`]: { 338 | 'direction (max depth)': `${direction.orders[1]} (${this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]].length} ${direction.ordersbookSide[1]})`, 339 | '1. price': this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]][0][0], 340 | '1. amount': this.orderBooks[this.marketsTriplet[1].symbol][direction.ordersbookSide[1]][0][1], 341 | 'min cost': this.getMinCost(this.marketsTriplet[1], direction.ordersbookSide[1]).total.toString(), 342 | 'amount min': this.marketsTriplet[1].limits.amount.min, 343 | 'cost min': this.marketsTriplet[1].limits.cost.min, 344 | 'price min': this.marketsTriplet[1].limits.price.min 345 | }, 346 | [`${this.marketsTriplet[2].symbol}`]: { 347 | 'direction (max depth)': `${direction.orders[2]} (${this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]].length} ${direction.ordersbookSide[2]})`, 348 | '1. price': this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]][0][0], 349 | '1. amount': this.orderBooks[this.marketsTriplet[2].symbol][direction.ordersbookSide[2]][0][1], 350 | 'min cost': this.getMinCost(this.marketsTriplet[2], direction.ordersbookSide[2]).total.toString(), 351 | 'amount min': this.marketsTriplet[2].limits.amount.min, 352 | 'cost min': this.marketsTriplet[2].limits.cost.min, 353 | 'price min': this.marketsTriplet[2].limits.price.min 354 | } 355 | }); 356 | 357 | this.bot.config.logDetails && console.table({ 358 | [this.marketsTriplet[0].symbol]: { 359 | direction: direction.orders[0], 360 | quantity: `${qunatityAB.toString()} ${this.marketsTriplet[0].base}`, 361 | ' ': 'for', 362 | total: `${totalAB.total.toPrecision(this.getPrecision(this.marketsTriplet[0], 'quote')).toString()} ${this.marketsTriplet[0].quote}`, 363 | fees: `${feesAB.cost} ${feesAB.currency}`, 364 | 'fees rate': feesAB.rate 365 | }, 366 | [this.marketsTriplet[1].symbol]: { 367 | direction: direction.orders[1], 368 | quantity: `${qunatityBC.toString()} ${this.marketsTriplet[1].base}`, 369 | ' ': 'for', 370 | total: `${totalBC.total.toPrecision(this.getPrecision(this.marketsTriplet[1], 'quote')).toString()} ${this.marketsTriplet[1].quote}`, 371 | fees: `${feesBC.cost} ${feesBC.currency}`, 372 | 'fees rate': feesBC.rate 373 | }, 374 | [this.marketsTriplet[2].symbol]: { 375 | direction: direction.orders[2], 376 | quantity: `${qunatityAC.toString()} ${this.marketsTriplet[2].base}`, 377 | ' ': 'for', 378 | total: `${totalAC.total.toPrecision(this.getPrecision(this.marketsTriplet[2], 'quote')).toString()} ${this.marketsTriplet[2].quote}`, 379 | fees: `${feesAC.cost} ${feesAC.currency}`, 380 | 'fees rate': feesAC.rate 381 | } 382 | }) 383 | 384 | const resultString = `${result.toString()} ${this.marketsTriplet[1].quote}`.padStart(100, ' '); 385 | if(result.isGreaterThan(0)) { 386 | console.log("\x1b[42m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 387 | // @TODO: check balances, make order (then check again if there iss still arbitrage) 388 | // @TODO: check minimums again 389 | 390 | if (!reachedMinimum) { 391 | 392 | } else { 393 | // use await instead promise.all if proxies list is not configured 394 | // await Promise.all([ 395 | // this.makeOrder(this.marketsTriplet[0].symbol, direction.orders[0], qunatityAB, totalAB.price), 396 | // this.makeOrder(this.marketsTriplet[1].symbol, direction.orders[1], qunatityBC, totalBC.price), 397 | // this.makeOrder(this.marketsTriplet[2].symbol, direction.orders[2], qunatityAC, totalAC.price) 398 | // ]).then((values) => { 399 | // console.log(values); 400 | // success = ; 401 | // }); 402 | } 403 | 404 | } else if(!result.isEqualTo(0)) { 405 | console.log("\x1b[41m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 406 | } else { 407 | console.log("\x1b[44m\x1b[37m%s\x1b[0m\x1b[0m", resultString); 408 | } 409 | 410 | success = false; 411 | } while (success) 412 | }) 413 | resolve(false); 414 | }) 415 | }; 416 | 417 | makeOrder(market, side, amount, price, additionalParams = {}) { 418 | return this.bot.makeOrder(this.exchange, market, side, amount, price, additionalParams); 419 | } 420 | 421 | getCost(market: Market, amount: BigNumber, ordersbookSide: BidsOrAsks): {total: BigNumber; price: number} { 422 | let calcAmount = new BigNumber(0); 423 | let orderbookIndex = 0; 424 | let price = 0; 425 | let order; 426 | 427 | do { 428 | order = this.orderBooks[market.symbol][ordersbookSide][orderbookIndex]; 429 | if (!order) {break;} 430 | calcAmount = calcAmount.plus(order ? order[1] : 0); 431 | // console.log(orderbookIndex, order, amount.toString(), calcAmount.toString()); 432 | if (calcAmount.isGreaterThanOrEqualTo(amount)) { 433 | calcAmount = amount; 434 | price = order[0]; 435 | } else { 436 | orderbookIndex = orderbookIndex + 1; 437 | } 438 | } while (calcAmount !== amount) 439 | 440 | // console.log(order, !order); 441 | 442 | if (!order) { 443 | return { 444 | total: new BigNumber(Infinity), 445 | price: Infinity 446 | } 447 | } 448 | 449 | // const isBuy = buyOrSellByBidsOrAsks[ordersbookSide] === BUY; 450 | // const quoteOrBase = isBuy ? 'base': 'quote'; 451 | 452 | return { 453 | // total: new BigNumber(amount).times(price).precision(market.precision.quote), 454 | total: new BigNumber(new BigNumber(amount).times(price).toPrecision(this.getPrecision(market, 'quote'))), 455 | price: price 456 | }; 457 | } 458 | 459 | getMinCost(market: Market, ordersbookSide: BidsOrAsks) { 460 | return this.getCost(market, new BigNumber(market.limits.amount.min), ordersbookSide); 461 | } 462 | 463 | fees(market: Market, amount: number, price: number, buyOrSell: BuyOrSell, orderType: OrderType = 'limit') { 464 | if(!Number.isFinite(amount) || !Number.isFinite(price)) { 465 | return { 466 | 'cost': Infinity, 467 | }; 468 | } 469 | 470 | const fees = this.exchange.calculate_fee(market.symbol, orderType, buyOrSell, amount, price, 'taker') 471 | 472 | if((!fees.cost && this.bot.config.zeroesFeesCorrection) || this.bot.config.correctAllFees) { 473 | return this.calculateFeeFallback(fees, market, buyOrSell, amount, price); 474 | } 475 | return fees; 476 | } 477 | 478 | calculateFeeFallback (fees, market, side, amount, price, takerOrMaker = 'taker') { 479 | if(this.bot.config.logAdditionalWarnings) { 480 | console.log("\x1b[33m%s\x1b[0m", `FEES CORRECTION ${market.symbol} (to correct: ${fees.cost} ${fees.currency})`); 481 | } 482 | return { 483 | ...fees, 484 | cost: floatRound(amount * price * this.exchange.markets[market.symbol].taker, market['precision']['price'], this.bot.config.feesRoundType) 485 | } 486 | } 487 | 488 | // check if balances ar bigger than mins 489 | 490 | // dry run check (with min amounts) 491 | // dry run check (with max amounts - balances or in ordersbook amount) 492 | // if there is profit in at least one coin, try optimize (bisection 'oscilate') results 493 | 494 | // operates[3] = [...mins] 495 | // increments[3] = mins.map(min => min / 2) 496 | 497 | // optimize amounts 498 | 499 | // do while overall proift is bigger 500 | // for markets 501 | // make order amount bigger 502 | // operate = operate + increment 503 | // check if new overall proift is bigger 504 | // if yes - true (till max) 505 | // if no 506 | // increment = increment/2 (till operate < min or increment < 10 ^ MARKET_PRECISION) 507 | } -------------------------------------------------------------------------------- /src/algorithms/ArbitrageTriangularBetweenExchanges.ts: -------------------------------------------------------------------------------- 1 | import Algorithm from './Algorithm'; 2 | 3 | export default class ArbitrageTriangularBetweenExchanges extends Algorithm { 4 | // BUY C0 FOR C1 -> SELL C0 FOR C2 -> transfer C2 -> BUY C0 FOR C2 -> transfer C0 5 | // SELL C0 FOR C1 -> BUY C2 FOR C1 -> transfer C2 -> BUY C0 FOR C2 -> transfer C0 6 | 7 | // check currency on exchanges pair (if transfer costs are less than difference) 8 | } -------------------------------------------------------------------------------- /src/common/config.ts: -------------------------------------------------------------------------------- 1 | import defaultConfig = require("../config.json"); 2 | // import localConfig = require("./config.local.json"); 3 | import { BotConfig } from "../Bot"; 4 | 5 | export default { 6 | ...defaultConfig, 7 | // ...(localConfig ?? {}) 8 | } as BotConfig 9 | 10 | export const config = (config: any) => ({ 11 | ...defaultConfig, 12 | ...config, 13 | // ...(localConfig ?? {}) 14 | }) as BotConfig -------------------------------------------------------------------------------- /src/common/constants.ts: -------------------------------------------------------------------------------- 1 | import { BidsOrAsks } from "./types"; 2 | 3 | export const BUY = 'buy'; 4 | export const SELL = 'sell'; 5 | export const ASKS = 'asks'; 6 | export const BIDS = 'bids'; 7 | 8 | export const buyOrSellByBidsOrAsks = { 9 | [ASKS]: BUY, 10 | [BIDS]: SELL 11 | } 12 | 13 | export const bidsOrAsksByBuyOrSell = { 14 | [BUY]: ASKS, 15 | [SELL]: BIDS 16 | } -------------------------------------------------------------------------------- /src/common/errors.ts: -------------------------------------------------------------------------------- 1 | import Bot from "../Bot"; 2 | import ccxt, { BaseError, Exchange } from 'ccxt'; 3 | 4 | export default class BotErrorHandler { 5 | static handleError = (bot: Bot, err: BaseError, exchange: Exchange, market = '') => { 6 | const changeProxy = bot.config.enableProxy && bot.config.changeProxyAfterAnyNetworkError && ( 7 | err instanceof ccxt.RequestTimeout || 8 | err instanceof ccxt.ExchangeNotAvailable || 9 | err instanceof ccxt.NetworkError || 10 | err instanceof ccxt.DDoSProtection 11 | ); 12 | 13 | if (changeProxy) { 14 | bot.setExchangeProxy(exchange); 15 | } 16 | 17 | if(bot.config.logError) { 18 | // @TODO: format 19 | console.log(exchange.symbol, market, typeof err); 20 | if(!bot.config.logErrorDetails) console.log(err); 21 | return; 22 | } 23 | 24 | if (err !instanceof BaseError) { 25 | throw err; 26 | } 27 | } 28 | } -------------------------------------------------------------------------------- /src/common/helpers.ts: -------------------------------------------------------------------------------- 1 | export const errorLogTemplate = (e: Error) => `\x1b[31m${e.name} ${e.message}\x1b[0m`; 2 | 3 | export function log(message, type: 'log' | 'warn' = 'log', run = true) { 4 | if (!run) { return; } 5 | console[type](message); 6 | } 7 | 8 | export const validationException = (algorithmType: string, message: string) => ({ 9 | message: `\n\x1b[31m${message}\x1b[0m`, 10 | name: `\x1b[31m${algorithmType} Validation Exception${message ? ':' : ''}\x1b[0m` 11 | }); 12 | 13 | 14 | export const floatRound = (value: number, precision: number, type: 'ceil' | 'floor' |'round') => { 15 | switch(type){ 16 | case 'ceil': return Math.ceil(value * Math.pow(10, precision)) / Math.pow(10, precision) 17 | case 'floor': return Math.floor(value * Math.pow(10, precision)) / Math.pow(10, precision) 18 | case 'round': return Math.round(value * Math.pow(10, precision)) / Math.pow(10, precision) 19 | } 20 | } -------------------------------------------------------------------------------- /src/common/interfaces.ts: -------------------------------------------------------------------------------- 1 | import { BidsOrAsks } from "./types"; 2 | 3 | export interface Amount { 4 | symbol: string; 5 | amount: number; 6 | } 7 | 8 | export interface Order { 9 | price: number; 10 | amount: number; 11 | type: BidsOrAsks; 12 | } 13 | 14 | export interface ValidationException { 15 | message: string; 16 | name: string; 17 | } -------------------------------------------------------------------------------- /src/common/types.ts: -------------------------------------------------------------------------------- 1 | export type MarketOrBase = 'market' | 'base'; 2 | export type BidsOrAsks = 'bids' | 'asks'; 3 | export type OrderType = 'market' | 'limit'; 4 | export type BuyOrSell = 'buy' | 'sell'; -------------------------------------------------------------------------------- /src/config.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeOrders": false, 3 | "profile": false, 4 | "logDetails": false, 5 | "logAdditionalDetails": false, 6 | "logWarnings": false, 7 | "logAdditionalWarnings": false, 8 | "logError": false, 9 | "logErrorDetails": false, 10 | "exchangesToWatch": ["example"], 11 | "orderBookLimit": 1000, 12 | "exchangeOptions": { 13 | "example": { 14 | } 15 | }, 16 | "defaultExchangeOptions": { 17 | "enableRateLimit": true, 18 | "proxy": false 19 | }, 20 | "keys": { 21 | "example": { 22 | "apiKey": "123456789ABC", 23 | "secret": "123456789DEF" 24 | } 25 | }, 26 | "currenciesToWatch": [], 27 | "feesRate": 0.001, 28 | "zeroesFeesCorrection": true, 29 | "correctAllFees": false, 30 | "feesRoundType": "ceil", 31 | "orderOptionsByExchange": {}, 32 | "defaultOrderOptions": {}, 33 | "parallelOrders": false, 34 | "enableProxy": false, 35 | "changeProxyAfterEveryOrder": false, 36 | "changeProxyAfterAnyNetworkError": false, 37 | "proxies": [], 38 | "telegram": { 39 | "token": "", 40 | "startPhrase": "", 41 | "stopPhrase": "", 42 | "chats": [], 43 | "logErrors": false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/config.local.example.json: -------------------------------------------------------------------------------- 1 | { 2 | "makeOrders": false, 3 | "profile": false, 4 | "logDetails": true, 5 | "logAdditionalDetails": true, 6 | "logWarnings": false, 7 | "logAdditionalWarnings": false, 8 | "logError": false, 9 | "logErrorDetails": false, 10 | "exchangesToWatch": ["binance"], 11 | "orderBookLimit": 1000, 12 | "exchangeOptions": { 13 | "bleutrade": {}, 14 | "binance": {} 15 | }, 16 | "keys": { 17 | "bleutrade": { 18 | "apiKey": "", 19 | "secret": "" 20 | }, 21 | "binance": { 22 | "apiKey": "", 23 | "secret": "" 24 | } 25 | }, 26 | "currenciesToWatch": ["ETH", "BTC", "BNB"], 27 | "orderOptionsByExchange": { 28 | "binance": { 29 | "adjustForTimeDifference": true 30 | } 31 | }, 32 | "defaultOrderOptions": {}, 33 | "enableProxy": false, 34 | "changeProxyAfterEveryOrder": false, 35 | "changeProxyAfterAnyNetworkError": false, 36 | "parallelOrders": false, 37 | "proxies": [], 38 | "telegram": { 39 | "token": "", 40 | "startPhrase": "", 41 | "stopPhrase": "", 42 | "chats": [], 43 | "logErrors": false 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /src/example.ts: -------------------------------------------------------------------------------- 1 | import ArbitrageTriangleWithinExchange, { ArbitrageTriangleWithinExchangeParams } from "./algorithms/ArbitrageTriangleWithinExchange"; 2 | import Bot from "./Bot"; 3 | import { errorLogTemplate, log } from "./common/helpers"; 4 | import { config } from "./common/config"; 5 | import localConfig = require("./config.local.example.json"); 6 | // ccxt.d.ts 7 | 8 | process 9 | .on('unhandledRejection', (reason, p) => { 10 | console.error(reason, '\x1b[34mUnhandled Rejection at Promise\x1b[0m', p); 11 | }) 12 | .on('uncaughtException', err => { 13 | console.error(err, '\x1b[34mUncaught Exception thrown\x1b[0m'); 14 | process.exit(1); 15 | }); 16 | 17 | // add triggering by socket (additional lib per exchange, compatible with ccxt) 18 | // crawl over markets by default 19 | 20 | // @TODO: consider: 21 | // moving to new CPU worker (optionally behind robin rounded proxy) if there is no other already running with given exchanges, otherwise add to queue 22 | 23 | const bot = new Bot(config(localConfig)); 24 | startBot(); 25 | 26 | async function startBot() { 27 | bot.init().then( 28 | () => startArbitrageTriangleWithinExchangeAlgorithm(), 29 | err => log(errorLogTemplate(err)) 30 | ); 31 | bot.printProfileTime(); 32 | } 33 | 34 | function startArbitrageTriangleWithinExchangeAlgorithm() { 35 | Object.entries(bot.exchanges).forEach(async ([key, exchange]) => { 36 | try { 37 | await exchange.loadMarkets(); 38 | const validatedTriplets = ArbitrageTriangleWithinExchange.getValidatedTripletsOnExchange(exchange, bot.config.currenciesToWatch); 39 | 40 | log(`\x1b[32mFound ${validatedTriplets.length} ArbitrageTriangleWithinExchange triplets on ${exchange.id}\x1b[0m`); 41 | console.table(validatedTriplets) 42 | 43 | bot.cycle( 44 | validatedTriplets, 45 | (params: ArbitrageTriangleWithinExchangeParams) => { 46 | return new ArbitrageTriangleWithinExchange(params) 47 | }, element => ({ 48 | exchange: exchange, 49 | bot: bot, 50 | markets: [ 51 | exchange.markets[element[0]], 52 | exchange.markets[element[1]], 53 | exchange.markets[element[2]] 54 | ], 55 | balances: bot.balances[key], 56 | validateMarkets: false 57 | }), 58 | () => { 59 | log( `\x1b[32mCycle ${exchange.id} ArbitrageTriangleWithinExchange\x1b[0m`) 60 | } 61 | ); 62 | } catch (err) { 63 | log(errorLogTemplate(err)); 64 | bot.printProfileTime(); 65 | } 66 | }); 67 | } 68 | 69 | // @TODO: add checking 70 | // { 71 | // ...bot.config.ignore, 72 | // ...bot.config.MARKET_EXCHANGE_KEY.ignore, 73 | // ...bot.config.MARKET_KEY.ignore, 74 | // ...bot.config.MARKET_EXCHANGE_KEY.MARKET_KEY.ignore 75 | // } -------------------------------------------------------------------------------- /src/index.ts: -------------------------------------------------------------------------------- 1 | import Algorithm from "./algorithms/Algorithm"; 2 | export * from "./Bot"; 3 | export * from "./common/config"; 4 | export * from "./algorithms/Algorithm"; 5 | export * from "./algorithms/ArbitrageBetweenExchanges"; 6 | export * from "./algorithms/ArbitrageTriangleWithinExchange"; 7 | export * from "./algorithms/ArbitrageTriangularBetweenExchanges"; 8 | export * from "./common/constants"; 9 | export * from "./common/helpers"; 10 | export * from "./common/interfaces"; 11 | export * from "./common/types"; -------------------------------------------------------------------------------- /src/misc/Telegram.ts: -------------------------------------------------------------------------------- 1 | process.env.NTBA_FIX_319 = '1'; 2 | import TelegramBot from 'node-telegram-bot-api'; 3 | 4 | export interface TelegramParams { 5 | token: string; 6 | startPhrase: string; 7 | stopPhrase: string; 8 | chats: string[]; 9 | logErrors: boolean; 10 | } 11 | 12 | export default class Telegram { 13 | token = ''; 14 | startPhrase = 'start'; 15 | stopPhrase = 'stop'; 16 | chats = []; 17 | telegramBot: any; 18 | logErrors = false; 19 | 20 | constructor(params: TelegramParams) { 21 | const {token, startPhrase, stopPhrase, chats, logErrors} = {...params}; 22 | 23 | this.token = token; 24 | this.startPhrase = startPhrase; 25 | this.stopPhrase = stopPhrase; 26 | this.chats = chats; 27 | this.logErrors = logErrors; 28 | 29 | if(!this.token) { 30 | // @TODO: format 31 | if(this.logErrors) console.log('Cannot start Telegram notifications without token'); 32 | return; 33 | } 34 | 35 | this.init(); 36 | } 37 | 38 | init() { 39 | this.telegramBot = new TelegramBot(this.token, {polling: true}); 40 | 41 | this.telegramBot.onText(this.startPhrase, (msg) => { 42 | this.addChat(msg.chat.id) 43 | let chatId = msg.chat.id 44 | this.telegramBot.sendMessage(chatId, 'Notifications started') 45 | }) 46 | 47 | this.telegramBot.onText(this.stopPhrase, (msg) => { 48 | this.removeChat(msg.chat.id) 49 | let chatId = msg.chat.id 50 | this.telegramBot.sendMessage(chatId, 'Notifications stopped') 51 | }) 52 | 53 | this.telegramBot.on('message', (msg) => { 54 | let chatId = msg.chat.id 55 | this.telegramBot.sendMessage(chatId, 'Enter start phrase to start. Enter stop phrase to stop notifications.') 56 | }); 57 | } 58 | 59 | addChat = (id) => { 60 | if (!this.token) return; 61 | if (this.chats.indexOf(id) >= 0) return; 62 | this.chats.push(id); 63 | } 64 | 65 | removeChat = (id) => { 66 | if (!this.token) return; 67 | const index = this.chats.indexOf(id) 68 | if (index < 0) return; 69 | this.chats.splice(index, 1); 70 | } 71 | 72 | sendMessage = (message) => { 73 | if (!this.token) return; 74 | if (this.chats.length === 0) return; 75 | this.chats.forEach(chat => { 76 | this.telegramBot 77 | .sendMessage(chat, message, {parse_mode: 'HTML'}) 78 | .catch(error => { 79 | // @TODO: format 80 | if(this.logErrors) console.log(error) 81 | }) 82 | }) 83 | } 84 | } -------------------------------------------------------------------------------- /test/ArbitrageTriangleWithinExchange.spec.ts: -------------------------------------------------------------------------------- 1 | import { log } from "../src/common/helpers"; 2 | import ArbitrageTriangleWithinExchange from "../src/algorithms/ArbitrageTriangleWithinExchange"; 3 | import { defaultMarket } from "./common/helpers"; 4 | 5 | describe("ArbitrageTriangleWithinExchange validateMarkets", () => { 6 | test("should fails with A/B A/B A/B markets given", () => { 7 | let failResults = false; 8 | 9 | try { 10 | failResults = ArbitrageTriangleWithinExchange.validateMarkets([ 11 | { 12 | ...defaultMarket, 13 | base: 'a', 14 | quote: 'b' 15 | },{ 16 | ...defaultMarket, 17 | base: 'a', 18 | quote: 'b' 19 | },{ 20 | ...defaultMarket, 21 | base: 'a', 22 | quote: 'b' 23 | } 24 | ]); 25 | } catch (err) { 26 | log(`${err.name} ${err.message}`); 27 | } 28 | expect(failResults).toBe(false); 29 | }) 30 | 31 | test("should passes with A/B B/C A/C markets given", () => { 32 | let passResults = false; 33 | 34 | try { 35 | passResults = ArbitrageTriangleWithinExchange.validateMarkets([ 36 | { 37 | ...defaultMarket, 38 | base: 'a', 39 | quote: 'b' 40 | },{ 41 | ...defaultMarket, 42 | base: 'b', 43 | quote: 'c' 44 | },{ 45 | ...defaultMarket, 46 | base: 'a', 47 | quote: 'c' 48 | } 49 | ]); 50 | } catch (err) { 51 | log(`${err.name} ${err.message}`); 52 | } 53 | 54 | expect(passResults).toBe(true); 55 | }); 56 | }); -------------------------------------------------------------------------------- /test/common/helpers.ts: -------------------------------------------------------------------------------- 1 | export const defaultMarket = { 2 | id: '', 3 | symbol: '', 4 | base: '', 5 | quote: '', 6 | baseId: '', 7 | quoteId: '', 8 | active: true, 9 | precision: { base: 8, quote: 8, amount: 8, price: 8 }, 10 | limits: { amount: {min: 0, max: 0}, price: {min: 0, max: 0}, cost: {min: 0, max: 0} }, 11 | tierBased: false, 12 | percentage: true, 13 | taker: 0.02, 14 | maker: 0.02, 15 | info: {} 16 | }; -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | "module": "commonjs", 4 | "esModuleInterop": true, 5 | "target": "es6", 6 | "moduleResolution": "node", 7 | "sourceMap": true, 8 | "outDir": "dist", 9 | "resolveJsonModule": true, 10 | "declaration": true 11 | }, 12 | "lib": ["es2015"], 13 | "include": [ 14 | "src/**/*.ts" 15 | ], 16 | "exclude": [ 17 | // "src/example.ts" 18 | ] 19 | } -------------------------------------------------------------------------------- /tslint.json: -------------------------------------------------------------------------------- 1 | { 2 | "defaultSeverity": "error", 3 | "extends": [ 4 | "tslint:recommended" 5 | ], 6 | "jsRules": {}, 7 | "rules": { 8 | "no-console": false 9 | }, 10 | "rulesDirectory": [] 11 | } --------------------------------------------------------------------------------