├── binance.jpg ├── futures.png ├── api_key_web.png ├── pnl-example.jpeg ├── .github ├── FUNDING.yml ├── ISSUE_TEMPLATE │ ├── config.yml │ └── bug_report.yml └── workflows │ ├── add-stars.yml │ ├── antispamm.yml │ ├── stale.yml │ ├── build.yml │ └── welcome-new-users.yml ├── .gitignore ├── binance.nimble ├── examples ├── giftcardbot.nim ├── binanceVslocalTime.nim ├── bot │ ├── loging.nim │ ├── dotenv.nim │ ├── constants.nim │ └── tradingbot_futures.nim ├── tradingbot_stablecoins.nim ├── example.nim ├── tradingbot_btc.nim ├── newcoin.nim └── futures_maker.nim ├── LICENSE ├── README.md ├── src ├── binance │ └── binance_sha256.nim └── binance.nim └── docs └── nimdoc.out.css /binance.jpg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/binance/HEAD/binance.jpg -------------------------------------------------------------------------------- /futures.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/binance/HEAD/futures.png -------------------------------------------------------------------------------- /api_key_web.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/binance/HEAD/api_key_web.png -------------------------------------------------------------------------------- /pnl-example.jpeg: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/juancarlospaco/binance/HEAD/pnl-example.jpeg -------------------------------------------------------------------------------- /.github/FUNDING.yml: -------------------------------------------------------------------------------- 1 | custom: ["https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e"] 2 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | nimcache/ 2 | nimblecache/ 3 | htmldocs/ 4 | *.ini 5 | *.exe 6 | *.log 7 | examples/bot/tradingbot_futures 8 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/config.yml: -------------------------------------------------------------------------------- 1 | contact_links: 2 | - name: Sponsor this Project 3 | url: https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e 4 | about: Toss a coin to your witcher... 5 | -------------------------------------------------------------------------------- /binance.nimble: -------------------------------------------------------------------------------- 1 | version = "0.0.2" 2 | author = "Juan Carlos" 3 | description = "Binance Client" 4 | license = "MIT" 5 | srcDir = "src" 6 | skipDirs = @["examples", "docs", "tests"] 7 | 8 | requires "nim >= 1.6.0" 9 | -------------------------------------------------------------------------------- /.github/workflows/add-stars.yml: -------------------------------------------------------------------------------- 1 | name: Add Stars 2 | 3 | on: [watch] 4 | 5 | jobs: 6 | addstars: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/checkout@v1 10 | - name: Add Stars to Readme 11 | run: | 12 | echo -e ":star: [@${{github.actor}}](https://github.com/${{github.actor}} '`date --iso-8601`')\t" >> README.md 13 | 14 | - name: Commit changes 15 | uses: elstudio/actions-js-build/commit@v2 16 | env: 17 | GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} 18 | PUSH_BRANCH: 'nim' 19 | -------------------------------------------------------------------------------- /.github/workflows/antispamm.yml: -------------------------------------------------------------------------------- 1 | name: Pull Request AntiSpamm 2 | on: [pull_request] 3 | jobs: 4 | automation: 5 | name: Repo Automation 6 | runs-on: ubuntu-latest 7 | if: ${{ !github.event.pull_request.draft && !contains(github.event.head_commit.message, '[skip ci]') }} 8 | steps: 9 | 10 | # AntiSpamm for Pull Requests: Stops single-commit PR automatically, like Hacktoberfests, Bots, Trolls, etc. 11 | - name: Must be >= 2 commits 12 | if: ${{ github.event.pull_request.commits < 2 }} 13 | uses: actions/github-script@v5 14 | with: 15 | script: core.setFailed('AntiSpamm\tMust be >= 2 commits, make more commits to unlock.') 16 | -------------------------------------------------------------------------------- /examples/giftcardbot.nim: -------------------------------------------------------------------------------- 1 | import std/[json, os, httpcore, rdstdin, strutils], binance 2 | 3 | 4 | proc main() = 5 | let 6 | client: Binance = newBinance(getEnv"BINANCE_API_KEY", getEnv"BINANCE_API_SECRET") # API key and secret from env vars. 7 | ticker: string = readLineFromStdin"Cryptocurrency coin ticker?: ".toUpperAscii # "BTC" 8 | quantity: float = readLineFromStdin"Cryptocurrency coin quantity per Gift-Card code?: ".parseFloat # 0.0000001 9 | count: int = readLineFromStdin"How many Gift-Card codes to generate in total?: ".parseInt.Positive # 1 10 | 11 | for _ in 0 .. count: echo client.request(client.createCode(token = ticker, quantity = quantity), HttpPost).parseJson["data"] 12 | client.close() 13 | 14 | 15 | when isMainModule: 16 | main() 17 | -------------------------------------------------------------------------------- /examples/binanceVslocalTime.nim: -------------------------------------------------------------------------------- 1 | ## Show time diff between Binance and your PC, Binance is strict about Timestamps this helps debugging. 2 | import std/[times, os, json, httpcore], binance 3 | 4 | 5 | var 6 | message = newStringOfCap(150) 7 | localTime, serverTime, diff: int64 8 | let client = newBinance(getEnv"BINANCE_API_KEY", getEnv"BINANCE_API_SECRET") 9 | 10 | 11 | for i in 0 .. 9: 12 | # localTime = now().utc.toTime.toUnix * 1_000 13 | localTime = fromUnixFloat(epochTime() * 1_000).toUnix 14 | serverTime = client.request(client.time(), HttpGet)["serverTime"].getBiggestInt 15 | diff = serverTime - localTime 16 | message.add "{\"binance\": " 17 | message.addInt serverTime 18 | message.add ",\t\"local1\": " 19 | message.addInt localTime 20 | localTime = fromUnixFloat(epochTime() * 1_000).toUnix 21 | message.add ",\t\"local2\": " 22 | message.addInt localTime 23 | message.add ",\t\"diff1\": " 24 | message.addInt diff 25 | message.add ",\t\"diff2\": " 26 | message.addInt localTime - serverTime 27 | message.add '}' 28 | echo message 29 | message.setLen 0 30 | sleep 3_000 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2022 Juan Carlos 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /.github/workflows/stale.yml: -------------------------------------------------------------------------------- 1 | name: Stale Issues & PRs 2 | 3 | on: 4 | schedule: 5 | - cron: '0 0 * * *' 6 | 7 | jobs: 8 | mark_stale: 9 | name: Mark issues and PRs as Stale 10 | runs-on: ubuntu-latest 11 | steps: 12 | - uses: actions/stale@v3 13 | with: 14 | repo-token: ${{ secrets.GITHUB_TOKEN }} 15 | days-before-pr-stale: 90 16 | days-before-pr-close: 10 17 | days-before-issue-stale: 60 18 | days-before-issue-close: 3 19 | stale-issue-message: > 20 | This issue is Stale because it has been open for 60 days with no activity. 21 | Contribute a fix or comment on the issue, or it will be closed in 3 days. 22 | stale-pr-message: > 23 | This pull request is Stale because it has been open for 90 days with no activity. 24 | Contribute more commits on the pull request, or it will be closed in 10 days. 25 | close-issue-message: > 26 | This issue has been marked as Stale and closed due to inactivity. 27 | close-pr-message: > 28 | This pull request has been marked as Stale and Closed due to inactivity for 100 days. 29 | -------------------------------------------------------------------------------- /.github/workflows/build.yml: -------------------------------------------------------------------------------- 1 | name: Build 2 | on: [push, pull_request] 3 | 4 | jobs: 5 | build: 6 | if: "!contains(github.event.head_commit.message, '[skip ci]')" 7 | strategy: 8 | fail-fast: false 9 | matrix: 10 | platform: [ubuntu-latest, windows-latest] 11 | nim-channel: [stable, devel] 12 | name: ${{ matrix.platform }}-${{ matrix.nim-channel }} 13 | runs-on: ${{ matrix.platform }} 14 | steps: 15 | - uses: actions/checkout@v2 16 | 17 | - name: Set Environment Variables 18 | uses: allenevans/set-env@v2.0.0 19 | with: 20 | ACTIONS_ALLOW_UNSECURE_COMMANDS: true 21 | CMD: "nim doc -d:ssl -d:nimDisableCertificateValidation -d:nimStrictDelete -d:nimPreviewFloatRoundtrip -d:nimPreviewDotLikeOps --gc:orc --index:on --project --experimental:strictEffects --experimental:strictFuncs --styleCheck:usages --styleCheck:hint --outdir:../../docs" 22 | 23 | - uses: jiro4989/setup-nim-action@v1.0.2 24 | with: 25 | nim-version: ${{ matrix.nim-channel }} 26 | 27 | - name: Build docs 28 | shell: bash 29 | run: $CMD src/binance.nim 30 | 31 | 32 | - name: Clean out 33 | shell: bash 34 | run: | 35 | rm --verbose --force --recursive docs/*.idx 36 | rm --verbose --force --recursive docs/nimcache/*.* 37 | rm --verbose --force --recursive docs/nimcache/runnableExamples/*.* 38 | -------------------------------------------------------------------------------- /.github/workflows/welcome-new-users.yml: -------------------------------------------------------------------------------- 1 | name: Welcome New Users 2 | 3 | on: [pull_request, issues] 4 | 5 | jobs: 6 | greeting: 7 | runs-on: ubuntu-latest 8 | steps: 9 | - uses: actions/first-interaction@v1 10 | with: 11 | repo-token: ${{ secrets.GITHUB_TOKEN }} 12 | issue-message: | 13 | # Welcome ${{github.actor}} 14 | - **Remember to :star: Star the project on GitHub!.** 15 | - **Congrats for your first issue!, please provide a minimal code example that reproduces the :bug: Bug!.** 16 | Reports with full repro code and descriptive detailed information will be fixed faster. 17 | - [Please, keep in mind there is ZERO FUNDING for the project!, we have no sponsors, no company behind, no dev team, 18 | :heart: Send crypto today to speed up development!](https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e) 19 | 20 | pr-message: | 21 | # Welcome ${{github.actor}} 22 | - **Remember to :star: Star the project on GitHub!.** 23 | - **Congrats for your first Pull Request!, we will review your contributions very soon, and likely merge it!.** 24 | Pull Requests with detailed description of the changes and documentation on the code will be merged faster. 25 | - [Please, keep in mind there is ZERO FUNDING for the project!, we have no sponsors, no company behind, no dev team, 26 | :heart: Send crypto today to speed up development!](https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e) 27 | -------------------------------------------------------------------------------- /examples/bot/loging.nim: -------------------------------------------------------------------------------- 1 | import std/macros 2 | 3 | 4 | macro logs*(file: var File; args: openArray[(string, auto)]; level: static[string] = "") = 5 | result = newStmtList() 6 | 7 | template writes(it) = 8 | if level.len > 0: result.add nnkCall.newTree(nnkDotExpr.newTree(file, newIdentNode"write"), it) 9 | result.add nnkCall.newTree(nnkDotExpr.newTree(newIdentNode"stdout", newIdentNode"write"), it) 10 | 11 | writes newLit('{') 12 | writes newLit('"') 13 | writes newLit('t') 14 | writes newLit('i') 15 | writes newLit('m') 16 | writes newLit('e') 17 | writes newLit('"') 18 | writes newLit(':') 19 | writes newLit('"') 20 | writes nnkPrefix.newTree(newIdentNode"$", nnkCall.newTree(newIdentNode"now")) 21 | writes newLit('"') 22 | writes newLit(',') 23 | if level.len > 0: 24 | writes newLit('"') 25 | writes newLit('l') 26 | writes newLit('v') 27 | writes newLit('l') 28 | writes newLit('"') 29 | writes newLit(':') 30 | writes newLit('"') 31 | writes newLit(level) 32 | writes newLit('"') 33 | writes newLit(',') 34 | for i, item in args: 35 | let key: string = item[1][0].strVal 36 | doAssert key.len > 0, "Key must not be empty string." 37 | writes newLit('"') 38 | for c in key: writes c.newLit 39 | writes newLit('"') 40 | writes newLit(':') 41 | case item[1][1].kind 42 | of nnkNilLit: 43 | writes newLit('n') 44 | writes newLit('u') 45 | writes newLit('l') 46 | writes newLit('l') 47 | of nnkIntLit .. nnkUInt64Lit, nnkFloatLit .. nnkFloat64Lit: 48 | writes item[1][1] 49 | else: 50 | writes newLit('"') 51 | writes item[1][1] 52 | writes newLit('"') 53 | if i < args.len - 1: writes newLit(',') 54 | writes newLit('}') 55 | writes newLit('\n') 56 | -------------------------------------------------------------------------------- /examples/tradingbot_stablecoins.nim: -------------------------------------------------------------------------------- 1 | ## Crypto Trading Bot for Stablecoin pairs, see https://youtu.be/Ve687Pvzplk 2 | import std/[os, json, strutils, httpcore, rdstdin] 3 | import binance 4 | 5 | 6 | proc main() = 7 | ## Binance does NOT charge commissions for stablecoins trading pairs so this is free money. 8 | const ticker = "BUSDUSDT" # Other stablecoin pairs: "TUSDUSDT", "USDPUSDT", "USDCUSDT", "BUSDUSDT", "TUSDBUSD", "USDPBUSD", "USDCBUSD" 9 | let 10 | client = newBinance(readLineFromStdin"Binance API Key?: ", readLineFromStdin"Binance API Secret?: ") 11 | usdQuantity = readLineFromStdin"USD quantity for position size? (integer >10): ".parseInt.float 12 | var lastOp: binance.Side 13 | 14 | while true: 15 | let 16 | price = client.getPrice(ticker) 17 | (hi24h, lo24h) = client.get24hHiLo(ticker) 18 | var 19 | order: string 20 | trade: JsonNode 21 | if (hi24h > 1.0 and lo24h < 1.0) and (price >= hi24h or price <= lo24h): 22 | let side = if price >= hi24h: SIDE_SELL else: SIDE_BUY 23 | if lastOp == side: continue # Swing trade. 24 | order = client.postOrder( 25 | symbol = ticker, 26 | quantity = usdQuantity, 27 | side = side, 28 | tipe = ORDER_TYPE_MARKET, 29 | ) 30 | trade = client.request(order, HttpPost) 31 | echo '#', order, '\n', trade 32 | 33 | if trade.hasKey"fills": 34 | lastOp = side # Remember last trade operation side. 35 | doAssert parseFloat(trade["fills"][0]["commission"].getStr) == 0.0, "Commission is not zero." 36 | echo( 37 | "ticker=", ticker, 38 | ",side=" , side, 39 | ",price=", price, 40 | ",size=" , int(usdQuantity), 41 | ) 42 | else: 43 | echo ticker, '\t', price 44 | sleep 60_000 # Sleep for 1 minute. 45 | client.close() 46 | 47 | 48 | when isMainModule: 49 | main() 50 | -------------------------------------------------------------------------------- /examples/example.nim: -------------------------------------------------------------------------------- 1 | from std/os import getEnv 2 | import std/[httpcore, json] 3 | import binance 4 | 5 | let client = newBinance(getEnv"BINANCE_API_KEY", getEnv"BINANCE_API_SECRET") 6 | 7 | 8 | #orderTest 9 | var preparedEndpoint = client.orderTest(SIDE_BUY, ORDER_TYPE_LIMIT, ORDER_RESP_TYPE_FULL, $TIME_IN_FORCE_GTC, "1", "BTCUSDT", 0.1, 10_000.00) 10 | 11 | echo "\nPOST -> /api/v3/order/test" 12 | echo client.request(preparedEndpoint, HttpPost) 13 | 14 | 15 | #postOrder -> api/v3/order POST 16 | # echo "\nPOST -> api/v3/order" 17 | # preparedEndpoint = client.postOrder(SIDE_BUY, ORDER_TYPE_LIMIT, $TIME_IN_FORCE_GTC, "BNBUSDT", 0.01, 100.00) 18 | # echo client.request(preparedEndpoint, HttpPost) 19 | 20 | 21 | #getOrder -> api/v3/order GET 22 | echo "\nGET -> api/v3/order" 23 | preparedEndpoint = client.getOrder("BTCUSDT") 24 | echo client.request(preparedEndpoint, HttpGet) 25 | 26 | 27 | #GET -> api/v3/account 28 | echo "\nGET -> api/v3/account" 29 | preparedEndpoint = client.accountData() 30 | echo client.request(preparedEndpoint, HttpGet) 31 | 32 | 33 | #GET -> api/v3/myTrades 34 | echo "\nGET -> api/v3/myTrades" 35 | preparedEndpoint = client.myTrades("BTCUSDT") 36 | echo client.request(preparedEndpoint, HttpGet) 37 | 38 | 39 | #GET -> api/v3/rateLimit/order 40 | echo "\nGET -> api/v3/rateLimit/order" 41 | preparedEndpoint = client.rateLimitOrder() 42 | echo client.request(preparedEndpoint, HttpGet) 43 | 44 | 45 | #GET -> api/v3/allOrderList 46 | echo "\nGET -> api/v3/allOrderList" 47 | preparedEndpoint = client.allOrderList() 48 | echo client.request(preparedEndpoint, HttpGet) 49 | 50 | 51 | #GET -> api/v3/allOrderList 52 | echo "\nGET -> api/v3/openOrderList" 53 | preparedEndpoint = client.openOrderList() 54 | echo client.request(preparedEndpoint, HttpGet) 55 | 56 | 57 | #GET -> api/v3/orderList 58 | echo "\nGET -> api/v3/orderList" 59 | preparedEndpoint = client.orderList(1) 60 | echo client.request(preparedEndpoint, HttpGet) 61 | -------------------------------------------------------------------------------- /examples/tradingbot_btc.nim: -------------------------------------------------------------------------------- 1 | ## Crypto Trading Bot for BTC pairs, see https://www.binance.com/en/support/announcement/10435147c55d4a40b64fcbf43cb46329 2 | import std/[os, json, strutils, httpcore, rdstdin] 3 | import binance 4 | 5 | 6 | proc main() = 7 | ## Binance does NOT charge commissions for BTC trading pairs so this is free money. 8 | # Other BTC pairs: "BTCAUD", "BTCBIDR", "BTCBRL", "BTCBUSD", "BTCEUR", "BTCGBP", "BTCRUB", "BTCTRY", "BTCTUSD", "BTCUAH", "BTCUSDC", "BTCUSDP". 9 | const ticker = "BTCUSDT" 10 | let 11 | client = newBinance(readLineFromStdin"Binance API Key?: ", readLineFromStdin"Binance API Secret?: ") 12 | usdQuantity = readLineFromStdin"USD quantity for position size? (integer >10): ".parseInt.float 13 | quantity = truncate(usdQuantity / client.getPrice(ticker)) 14 | var lastOp: binance.Side 15 | 16 | while true: 17 | let 18 | price = client.getPrice(ticker) 19 | (hi24h, lo24h) = client.get24hHiLo(ticker) 20 | var 21 | order: string 22 | trade: JsonNode 23 | if hi24h.int > lo24h.int and (price.int > hi24h.int or price.int < lo24h.int): 24 | let side = if price >= hi24h: SIDE_SELL else: SIDE_BUY 25 | if lastOp == side: continue # Swing trade. 26 | order = client.postOrder( 27 | symbol = ticker, 28 | quantity = quantity, 29 | side = side, 30 | tipe = ORDER_TYPE_MARKET, 31 | ) 32 | trade = client.request(order, HttpPost) 33 | echo '#', order, '\n', trade 34 | 35 | if trade.hasKey"fills": 36 | lastOp = side # Remember last trade operation side. 37 | doAssert parseFloat(trade["fills"][0]["commission"].getStr) == 0.0, "Commission is not zero." 38 | echo( 39 | "ticker=", ticker, 40 | ",side=" , side, 41 | ",price=", price, 42 | ",size=" , int(usdQuantity), 43 | ) 44 | else: 45 | echo ticker, '\t', price 46 | sleep 60_000 # Sleep for 1 minute. 47 | client.close() 48 | 49 | 50 | when isMainModule: 51 | main() 52 | -------------------------------------------------------------------------------- /examples/bot/dotenv.nim: -------------------------------------------------------------------------------- 1 | # https://juancarlospaco.github.io/nodejs/nodejs/jsdotenv 2 | from std/strutils import split, strip 3 | from std/parseutils import parseSaturatedNatural, parseFloat 4 | from std/json import JsonNode, newJInt, newJFloat, newJBool, newJString, newJObject, newJArray, add, parseJson 5 | 6 | proc parseBool(s: string): bool {.inline.} = 7 | case s 8 | of "true": result = true 9 | of "false": result = false 10 | else: doAssert false, "Can not interpret as a bool." 11 | 12 | func validateKey(s: string): bool {.inline.} = 13 | result = true 14 | for c in s: 15 | if c notin {'a'..'z', 'A'..'Z', '0'..'9', '_'}: return false 16 | 17 | proc parseDotEnv*(s: string): JsonNode = 18 | assert s.len > 0, "DotEnv must not be empty string" 19 | result = newJObject() 20 | if likely(s.len > 1): 21 | for zz in s.split('\n'): # Split by lines 22 | var z = zz # k= is the shortest possible 23 | z = strip(z) 24 | if z.len > 1 and z[0] != '#': # No comment lines, no empty lines 25 | let kV = z.split('=') 26 | if kV.len >= 2: # k sep v 27 | var k = kV[0] # Key name 28 | k = strip(k) 29 | doAssert validateKey(k), "DotEnv key must be a non-empty ASCII string ([a-zA-Z0-9_])" 30 | var v = kV[1].split('#')[0] # remove inline comments 31 | v = strip(v) 32 | var tipe = kV[^1].split('#')[1] # Get type annotation 33 | tipe = strip(tipe) 34 | if k.len > 0: # k must not be empty string 35 | case tipe 36 | of "bool": result.add k, newJBool(parseBool(v)) 37 | of "string": result.add k, newJString(v) 38 | of "json": result.add k, parseJson(v) 39 | of "int": 40 | var i = 0 41 | discard parseSaturatedNatural(v, i) 42 | result.add k, newJInt(i) 43 | of "float": 44 | var f = 0.0 45 | discard parseFloat(v, f) 46 | result.add k, newJFloat(f) 47 | else: doAssert false, "Type must be 1 of int, float, bool, string, json" 48 | -------------------------------------------------------------------------------- /examples/newcoin.nim: -------------------------------------------------------------------------------- 1 | ## TradingBot that buys new coins listed on Binance, as soon as they are available, at a cheap price, and sell them when they are pumped up in price. 2 | import std/[os, json, strutils, httpcore, math], binance 3 | 4 | 5 | proc main() = 6 | 7 | let 8 | client = newBinance(getEnv"BINANCE_API_KEY", getEnv"BINANCE_API_SECRET") 9 | exchangeData = parseJson(client.exchangeInfo())["symbols"] 10 | 11 | var 12 | i = 0 13 | currentCryptos: JsonNode 14 | currentLen, newCoin: int 15 | amount, priceToBuy: float 16 | symbolToBuy, symbol: string 17 | message = newStringOfCap(6) 18 | prevCryptos = parseJson(client.request(client.tickerPrice())) 19 | prevLen = prevCryptos.len 20 | 21 | while on: 22 | currentCryptos = parseJson(client.request(client.tickerPrice())) 23 | currentLen = currentCryptos.len 24 | message.addInt prevLen 25 | echo message 26 | message.setLen 0 27 | 28 | if prevLen < currentLen: 29 | newCoin = prevLen 30 | echo currentCryptos[newCoin] 31 | break 32 | else: 33 | sleep 10_000 34 | 35 | for indx in newCoin ..< currentLen: 36 | symbol = currentCryptos[indx]["symbol"].getStr 37 | 38 | for asset in exchangeData: 39 | if asset["symbol"].getStr == symbol: 40 | symbolToBuy = asset["quoteAsset"].getStr 41 | break 42 | 43 | priceToBuy = currentCryptos[i]["price"].getStr.parseFloat 44 | checkFloat priceToBuy 45 | 46 | case symbolToBuy 47 | of "BTC" : amount = 0.0013 48 | of "ETH" : amount = 0.03 49 | of "USDT": amount = 15.0 50 | of "BUSD": amount = 15.0 51 | of "BNB" : amount = 0.7 52 | else: echo "symbolToBuy not in 'BTC', 'ETH', 'USDT', 'BUSD', 'BNB'." 53 | 54 | if amount == 0.0: continue 55 | elif amount > 0.0: 56 | inc i 57 | echo client.request(client.postOrder( 58 | symbol = symbol, 59 | side = SIDE_BUY, 60 | quantity = client.truncate(amount / priceToBuy, 3), 61 | price = client.truncate(priceToBuy, 3), 62 | tipe = ORDER_TYPE_LIMIT, 63 | ), HttpPost) 64 | 65 | echo client.request(client.newOrderOco( 66 | symbol = symbol, 67 | side = SIDE_SELL, 68 | quantity = client.truncate(amount / priceToBuy, 3), 69 | price = client.truncate(priceToBuy * 1.020, 3), 70 | stopPrice = client.truncate(priceToBuy * 0.995, 3), 71 | stopLimitPrice = client.truncate(priceToBuy * 0.994, 3), 72 | stopLimitTimeInForce = $TIME_IN_FORCE_GTC 73 | ), HttpPost) 74 | sleep 10_000 75 | client.close() 76 | 77 | 78 | when isMainModule: 79 | main() 80 | -------------------------------------------------------------------------------- /examples/futures_maker.nim: -------------------------------------------------------------------------------- 1 | import std/[json, strutils, httpcore, math, rdstdin] 2 | import binance 3 | 4 | 5 | template cancelAllFutures() = 6 | order = client.cancelAllOrdersFutures(symbol = ticker) 7 | trade = client.request(order, HttpDelete) 8 | echo '#', order, '\n', trade 9 | 10 | 11 | proc main() = 12 | let client = newBinance(readLineFromStdin"Binance API Key?: ", readLineFromStdin"Binance API Secret?: ") 13 | var 14 | order: string 15 | trade: JsonNode 16 | side: binance.Side 17 | while true: 18 | case readLineFromStdin"Long or Short? (l for Long, s for Short, c for Cancel, other for Quit): " 19 | of "l", "L": side = SIDE_BUY 20 | of "s", "S": side = SIDE_SELL 21 | of "c", "C": 22 | let ticker = readLineFromStdin"Ticker? (BTCUSDT): ".toUpperAscii 23 | cancelAllFutures() 24 | else: break # quit"bye" 25 | let 26 | ticker = readLineFromStdin"Ticker? (BTCUSDT): ".toUpperAscii 27 | usdQuantity = readLineFromStdin"USD quantity for position size? (integer >10): ".parseInt.float 28 | leverage: 1 .. 125 = readLineFromStdin"Leverage? (integer 1-125): ".parseInt 29 | stopLossPrice = readLineFromStdin"Stop-Loss market-exit close position price? (integer): ".parseInt.float 30 | trailingStopLossOffset = readLineFromStdin"Trailing Stop-Loss offset percentage? (integer 1-5): ".parseInt.float 31 | baseAssetPrice = client.getPrice(ticker) 32 | baseAssetQuantity = truncate(round(usdQuantity / baseAssetPrice, 3)) 33 | 34 | order = client.postLeverageFutures(symbol = ticker, leverage = leverage) 35 | trade = client.request(order, HttpPost) 36 | echo '#', order, '\n', trade 37 | 38 | if trade.hasKey"leverage": 39 | # Trailing Stop-Loss market-exit order, closes position. {#000} 40 | order = client.postOrderFutures( 41 | symbol = ticker, 42 | quantity = baseAssetQuantity, 43 | callbackRate = trailingStopLossOffset, 44 | tipe = ORDER_TYPE_TRAILING_STOP_MARKET, 45 | side = side, 46 | ) 47 | trade = client.request(order, HttpPost) 48 | echo '#', order, '\n', trade 49 | 50 | if trade.hasKey"orderId": 51 | # Fixed Stop-Loss market-exit order, closes position. {#000} 52 | order = client.postOrderFutures( 53 | closePosition = true, 54 | symbol = ticker, 55 | stopPrice = stopLossPrice, 56 | tipe = ORDER_TYPE_STOP_MARKET, 57 | side = side, 58 | ) 59 | trade = client.request(order, HttpPost) 60 | echo '#', order, '\n', trade 61 | 62 | if trade.hasKey"orderId": 63 | # Open position market-entry order at specific price. {#000} 64 | order = client.postOrderFutures( 65 | symbol = ticker, 66 | quantity = baseAssetQuantity, 67 | tipe = ORDER_TYPE_MARKET, 68 | side = side, 69 | ) 70 | trade = client.request(order, HttpPost) 71 | echo '#', order, '\n', trade 72 | 73 | if trade.hasKey"orderId": 74 | order = client.positionRiskFutures(symbol = ticker) 75 | trade = client.request(order, HttpGet) 76 | echo '#', order, '\n', trade 77 | 78 | if trade.len == 1 and trade[0]["liquidationPrice"].getStr != "0": 79 | let 80 | liquidationPrice = round(trade[0]["liquidationPrice"].getStr.parseFloat, 1) 81 | sl_liq1 = round(liquidationPrice * (1.0 + 0.01), 1) 82 | sl_liq2 = round(liquidationPrice * (1.0 - 0.01), 1) 83 | 84 | # Fixed Stop-Loss market-exit order at Liquidation price and above and below Liquidation price. {#000} 85 | if int(sl_liq1) > 0 and int(liquidationPrice) > 0 and int(sl_liq2) > 0: 86 | for precio in [sl_liq1, liquidationPrice, sl_liq2]: 87 | order = client.postOrderFutures( 88 | closePosition = true, 89 | symbol = ticker, 90 | stopPrice = precio, 91 | tipe = ORDER_TYPE_STOP_MARKET, 92 | side = if side == SIDE_SELL: SIDE_BUY else: SIDE_SELL, 93 | ) 94 | trade = client.request(order, HttpPost) 95 | echo '#', order, '\n', trade 96 | 97 | if trade.hasKey"orderId": 98 | echo( 99 | "ticker=" , ticker, 100 | ",side=" , if side == SIDE_BUY: "long" else: "short", 101 | ",entry=" , int(baseAssetPrice), 102 | ",SL=" , int(stopLossPrice), 103 | ",TSL=" , $int(trailingStopLossOffset) & '%', 104 | ",lever=" , $leverage & 'x', 105 | ",size=" , $int(usdQuantity) & '$', 106 | ",liquid=", int(liquidationPrice), 107 | ",amount=", baseAssetQuantity, 108 | ) 109 | else: cancelAllFutures() 110 | else: cancelAllFutures() 111 | else: cancelAllFutures() 112 | else: cancelAllFutures() 113 | else: cancelAllFutures() 114 | else: cancelAllFutures() 115 | else: cancelAllFutures() 116 | client.close() 117 | 118 | 119 | when isMainModule: 120 | main() 121 | -------------------------------------------------------------------------------- /examples/bot/constants.nim: -------------------------------------------------------------------------------- 1 | const configiniTemplateFutures* = """ 2 | # IMPORTANT RECOMMENDATIONS: 3 | # If the market is too parallel, just dont do Futures at all, not by bot nor manually. 4 | # Monitor the bot status and profits at least once a month or once a week approx. 5 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 6 | # RECOMENDACIONES IMPORTANTES: 7 | # Si el mercado esta demasiado paralelo, no hagas Futuros, no via bot ni manualmente. 8 | # Monitorea el estado y las ganancias del bot por lo menos una vez al mes o una vez a la semana aproximadamente. 9 | # 10 | # Strategy: 11 | # Bot finds lowest possible price to place a 25x Long with SL and TSL, 12 | # if price goes down whole grid goes down, if price goes up SL and TSL goes up. 13 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 14 | # Bot encontrara el precio mas bajo posible para colocar un 25x Long con SL y TSL, 15 | # si el precio baja todo el grid baja, si el precio sube SL y TSL suben. 16 | # 17 | # Trading pairs are: 18 | # "BTCUSDT" , "ETHUSDT", "BNBUSDT" , "YFIUSDT" , "MKRUSDT" , 19 | # "BCHUSDT" , "XMRUSDT", "ZECUSDT" , "LTCUSDT" , "EGLDUSDT". 20 | # 21 | # You must have in Binance USD-M Futures Wallet: 22 | # NO Leverage, >100 USDT minimum. 23 | # Leveraged, >1000 USDT minimum, >10000 USDT recommended. 24 | # >1 USD in BNB for Commisions. 25 | # these minimum limits are fixed by Binance, not by the bot itself, 26 | # bot may fail to trade if the stablecoins balance is not bigger than the lower limits. 27 | # The bot will trade in all pairs in a loop to try to profit as much as possible. 28 | # The bot may open leveraged positions in 10 trading pairs, up to 50 orders, having >100000 USDT free is recommended. 29 | # On start the bot will force MultiAsset-Mode to OFF, Margin-Mode to Isolated, Hedge-Mode to OFF. 30 | # Try to keep the bot working as much as possible, so it can "learn" about the market. 31 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 32 | # Los pares de Trading son: 33 | # "BTCUSDT" , "ETHUSDT", "BNBUSDT" , "YFIUSDT" , "MKRUSDT" , 34 | # "BCHUSDT" , "XMRUSDT", "ZECUSDT" , "LTCUSDT" , "EGLDUSDT". 35 | # 36 | # Debes tener en Binance USD-M Futuros Wallet: 37 | # SIN Apalancamiento, >100 USDT minimo. 38 | # Apalancado, >1000 USDT minimo, >10000 USDT recomendado. 39 | # >1 USD en BNB para Comisiones. 40 | # Estos limites minimos los fija Binance, no el propio bot, 41 | # bot puede fallar el Trade si el saldo de monedas estables no es mayor que los limites inferiores. 42 | # El bot intercambiara todos los pares de monedas en bucle para intentar obtener el mayor beneficio posible. 43 | # El bot puede abrir posiciones apalancadas en 10 pares de trading, hasta 50 ordenes, tener >100000 USDT libres es recomendado. 44 | # Al inicio el bot forzara MultiAsset-Mode a OFF, Margin-Mode a Isolated, Hedge-Mode a ON. 45 | # Trate de mantener el bot funcionando el mayor tiempo posible, para que pueda "aprender" sobre el mercado. 46 | # https://www.binance.com/en/my/settings/api-management 47 | binance_api_key = 1CESnDgOsANx6hDvT9KmX85C0DwtSaendYbcWh6pWYZYB3df5aUnubWsSaa0pJAC # string 48 | binance_api_secret = COPYPASTE_YOUR_BINANCE_SECRET_API_KEY_HERE # string 49 | 50 | 51 | # Position size in USDT, more quantity more profits. 52 | # Minimum must be >50 USD, >100 USD is Ok, recommended >1000 USD. 53 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 54 | # Cantidad de la Posicion en USDT, cuanta mas cantidad mas ganancias. 55 | # El minimo debe ser >50 USD, >100 USD esta bien, recomendado >1000 USD. 56 | usd_quantity = 75 # int 57 | 58 | 59 | # Minimum leverage for trading, maximum leverage is fixed to 25x, 25 is the maximum allowed by the strategy. 60 | # You must keep a big amount of USDT in Binance USD-M Futures Wallet to avoid getting Liquidated. 61 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 62 | # Apalancamiento minimo para trading, apalancamiento maximo esta fijado en 25x, 25 es el maximo para la estrategia. 63 | # Debes mantener un monton de USDT en Binance USD-M Futures Wallet para evitar ser Liquidado. 64 | min_leverage = 15 # int 65 | 66 | 67 | # Offset from the current market price for the market-entry limit order, 68 | # bigger float means far from the current market price, 69 | # smaller float means close from the current market price, 70 | # if float is too big, then will be placed too far, then positions may never open, 71 | # if float is too small, then will be placed too close, then positions may close by Stop-Loss. 72 | # must be a positive float greater than 0.001 and smaller than 0.999. 73 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 74 | # Offset desde el precio de mercado actual para la order de entrada de mercado limit, 75 | # float grandes significa lejos del precio actual de mercado, 76 | # float chico significa cerca del precio actual de mercado, 77 | # si el flotante es muy grande, entonces sera colocado muy lejos, entonces las posiciones nunca se abriran, 78 | # si el flotante es muy chico, entonces sera colocado muy cerca, entonces las posiciones se cerraran por Stop-Loss. 79 | # debe ser un flotante positivo mayor que 0.001 y menor que 0.999. 80 | price_offset_for_market_entry_order = 0.015 # float 81 | 82 | 83 | # Do NOT open positions for coins below this current price in USD. 84 | # This prevents "glitchy" Liquidation prices near zero. 85 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 86 | # NO abrir posiciones para coins por debajo de este precio en USD. 87 | # Esto previene precios de Liquidacion "raros" cerca del cero. 88 | do_not_trade_coins_below_usd = 100 # int 89 | 90 | 91 | # Trailing Stop-Loss offset in percentage of the price, 92 | # this controls how far the Stop-Loss chases the current price. 93 | # Minimum value is 1, maximum value is 5, most used values are 3 and 5. 94 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 95 | # Offset en porcentage del precio del Trailing Stop-Loss, 96 | # esto controla cuan lejos el Stop-Loss persigue al precio actual. 97 | # Valor minimo es 1, valor maximo es 5, valores mas usados son 3 y 5. 98 | trailing_stop_loss_offset_percent = 3 # int 99 | 100 | 101 | # Auto-Cancel all open positions and cancel all orders after timeout hours. 102 | # 0 is Disabled. 0 is Default. Use 0 if you dont known whats it means. 103 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 104 | # Auto-Cancela posiciones abiertas y cancela todas las ordenes abiertas luego de horas de timeout. 105 | # 0 es Desabilitado. 0 es valor por defecto. Usa 0 si no sabes que significa. 106 | auto_cancel_order_timeout_hours = 0 # int 107 | 108 | 109 | # Maximum balance in USDT to keep in Futures wallet on Binance, 110 | # if the balance exceeds this value, then 10 USDT will be withdraw to wallet_address_bsc_to_send_profits, 111 | # withdraws profits out of the exchange to a cold wallet address of BSC network. 112 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 113 | # Maximo balance en USDT para mantener en Futures wallet en Binance, 114 | # si el balance excede este valor, 10 USDT seran transferidos a wallet_address_bsc_to_send_profits, 115 | # transfiere ganancias fuera del exchange hacia una cold wallet address de la red BSC. 116 | max_balance_usdt_to_keep = 100 # int 117 | wallet_address_bsc_to_send_profits = 0xb78c4cf63274bb22f83481986157d234105ac17e # string 118 | 119 | 120 | # Maximum trade count to perform in total in a single run, then quit. 121 | # - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - 122 | # Numero maximo de trades a realizar en total en una sola ejecucion, luego de eso, salir. 123 | trade_count_limit = 9999999 # int 124 | 125 | 126 | # Please send crypto today to speed up development and improve the bot to make more profits!: 127 | # 128 | # Bitcoin BTC BEP20 BSC 0xb78c4cf63274bb22f83481986157d234105ac17e 129 | # Bitcoin BTC BTC Network 1Pnf45MgGgY32X4KDNJbutnpx96E4FxqVi 130 | # 131 | # Tether USDT BEP20 BSC 0xb78c4cf63274bb22f83481986157d234105ac17e 132 | # Tether USDT TRC20 Tron TWGft53WgWvH2mnqR8ZUXq1GD8M4gZ4Yfu 133 | # 134 | # ETH/DAI/USDC BEP20 BSC 0xb78c4cf63274bb22f83481986157d234105ac17e 135 | # ETH/DAI/USDC ERC20 0xb78c4cf63274bb22f83481986157d234105ac17e 136 | """ 137 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Binance 2 | 3 | ![](https://raw.githubusercontent.com/juancarlospaco/binance/nim/binance.jpg) 4 | 5 | ![](https://raw.githubusercontent.com/juancarlospaco/binance/nim/futures.png "Leveraged Perpetual Futures") 6 | 7 | 8 | ![](https://github.com/juancarlospaco/binance/actions/workflows/build.yml/badge.svg) 9 | ![](https://img.shields.io/github/languages/top/juancarlospaco/binance?style=for-the-badge) 10 | ![](https://img.shields.io/github/stars/juancarlospaco/binance?style=for-the-badge) 11 | ![](https://img.shields.io/github/languages/code-size/juancarlospaco/binance?style=for-the-badge) 12 | ![](https://img.shields.io/github/issues-raw/juancarlospaco/binance?style=for-the-badge) 13 | ![](https://img.shields.io/github/issues-pr-raw/juancarlospaco/binance?style=for-the-badge) 14 | ![](https://img.shields.io/github/last-commit/juancarlospaco/binance?style=for-the-badge) 15 | 16 | 17 | # Requisites 18 | 19 | - Valid API Key and API Secret, with all permissions, get it for free at https://www.binance.com/en/my/settings/api-management 20 | 21 | ![](https://raw.githubusercontent.com/juancarlospaco/binance/nim/api_key_web.png) 22 | 23 | - Device Date and Time must be configured, up to the seconds precision, Binance is strict about timestamps, 24 | use https://github.com/juancarlospaco/binance/blob/nim/examples/binanceVslocalTime.nim to debug time diff. 25 | In Windows, if you get `400 Bad Request`, check the time, Windows like to change the time without warning after updates, 26 | set the correct time, up to the seconds precision, and disable Windows auto-update of time from the Windows settings. 27 | 28 | - Some USDT in Binance Spot wallet to trade with, >10 USDT minimal, >100 USDT recommended. 29 | - Some BNB coins in Binance Spot wallet for commisions, >1 USD in BNB minimal, >10 USD in BNB recommended. 30 | - Some USDT in Binance Futures USD-M wallet to trade with, >100 USDT minimal, >1000 USDT recommended. 31 | - Some BNB coins in Binance Spot wallet for commisions, >1 USD in BNB minimal, >10 USD in BNB recommended. 32 | - If you are reading this after year 2023, the web of Binance for the API Keys has changed, but it still works. 33 | 34 | 35 | # Examples 36 | 37 | ```nim 38 | import std/httpcore, binance 39 | let client = newBinance("YOUR_BINANCE_API_KEY", "YOUR_BINANCE_API_SECRET") 40 | let preparedEndpoint = client.orderTest(SIDE_BUY, ORDER_TYPE_LIMIT, ORDER_RESP_TYPE_FULL, $TIME_IN_FORCE_GTC, "1", "BTCUSDT", 0.1, 10_000.00) 41 | echo client.request(preparedEndpoint, HttpPost) 42 | ``` 43 | 44 | 45 | # Documentation 46 | 47 | - https://juancarlospaco.github.io/binance 48 | - By default is using the real production Binance API. 49 | - Spot API (including OCO Orders) and Futures API (including 125x Leveraged Perpetual Futures) are supported. 50 | - Automatic Trailing Stop-Loss is supported. 51 | - Automatic Cancelation of Futures is supported. 52 | 53 | 54 | # TradingBot 55 | 56 | - How to create a TradingBot ?. 57 | 58 | TradingBot example: https://github.com/juancarlospaco/binance/blob/nim/examples/newcoin.nim 59 | 60 | Gift-Card Bot example: https://github.com/juancarlospaco/binance/blob/nim/examples/giftcardbot.nim 61 | 62 | Leveraged Perpetual Futures with Stop-Loss and Trailing Stop-Loss example: 63 | https://github.com/juancarlospaco/binance/blob/nim/examples/futures_maker.nim 64 | 65 | 66 | # More 67 | 68 | - See also https://github.com/juancarlospaco/tradingview#tradingview 69 | - See also https://github.com/juancarlospaco/cloudbet#cloudbet 70 | 71 | 72 | # 💰➡️🍕 73 | 74 |
75 | Bitcoin BTC 76 | 77 | **BEP20 Binance Smart Chain Network BSC** 78 | ``` 79 | 0xb78c4cf63274bb22f83481986157d234105ac17e 80 | ``` 81 | **BTC Bitcoin Network** 82 | ``` 83 | 1Pnf45MgGgY32X4KDNJbutnpx96E4FxqVi 84 | ``` 85 | **Lightning Network** 86 | ``` 87 | juancarlospaco@bitrefill.me 88 | ``` 89 |
90 | 91 |
92 | Ethereum ETH Dai DAI Uniswap UNI Axie Infinity AXS Smooth Love Potion SLP Uniswap UNI USDC 93 | 94 | **BEP20 Binance Smart Chain Network BSC** 95 | ``` 96 | 0xb78c4cf63274bb22f83481986157d234105ac17e 97 | ``` 98 | **ERC20 Ethereum Network** 99 | ``` 100 | 0xb78c4cf63274bb22f83481986157d234105ac17e 101 | ``` 102 |
103 |
104 | Tether USDT 105 | 106 | **BEP20 Binance Smart Chain Network BSC** 107 | ``` 108 | 0xb78c4cf63274bb22f83481986157d234105ac17e 109 | ``` 110 | **ERC20 Ethereum Network** 111 | ``` 112 | 0xb78c4cf63274bb22f83481986157d234105ac17e 113 | ``` 114 | **TRC20 Tron Network** 115 | ``` 116 | TWGft53WgWvH2mnqR8ZUXq1GD8M4gZ4Yfu 117 | ``` 118 |
119 |
120 | Solana SOL 121 | 122 | **BEP20 Binance Smart Chain Network BSC** 123 | ``` 124 | 0xb78c4cf63274bb22f83481986157d234105ac17e 125 | ``` 126 | **SOL Solana Network** 127 | ``` 128 | FKaPSd8kTUpH7Q76d77toy1jjPGpZSxR4xbhQHyCMSGq 129 | ``` 130 |
131 |
132 | Cardano ADA 133 | 134 | **BEP20 Binance Smart Chain Network BSC** 135 | ``` 136 | 0xb78c4cf63274bb22f83481986157d234105ac17e 137 | ``` 138 | **ADA Cardano Network** 139 | ``` 140 | DdzFFzCqrht9Y1r4Yx7ouqG9yJNWeXFt69xavLdaeXdu4cQi2yXgNWagzh52o9k9YRh3ussHnBnDrg7v7W2hSXWXfBhbo2ooUKRFMieM 141 | ``` 142 |
143 |
144 | Sandbox SAND Decentraland MANA 145 | 146 | **ERC20 Ethereum Network** 147 | ``` 148 | 0xb78c4cf63274bb22f83481986157d234105ac17e 149 | ``` 150 |
151 |
152 | Algorand ALGO 153 | 154 | **ALGO Algorand Network** 155 | ``` 156 | WM54DHVZQIQDVTHMPOH6FEZ4U2AU3OBPGAFTHSCYWMFE7ETKCUUOYAW24Q 157 | ``` 158 |
159 |
160 | Polkadot DOT 161 | 162 | **DOT Network** 163 | ``` 164 | 13GdxHQbQA1K6i7Ctf781nQkhQhoVhGgUnrjn9EvcJnYWCEd 165 | ``` 166 | **BEP20 Binance Smart Chain Network BSC** 167 | ``` 168 | 0xb78c4cf63274bb22f83481986157d234105ac17e 169 | ``` 170 |
171 |
172 | Binance 173 | 174 | [https://pay.binance.com/en/checkout/e92e536210fd4f62b426ea7ee65b49c3](https://pay.binance.com/en/checkout/e92e536210fd4f62b426ea7ee65b49c3 "Send via Binance Pay") 175 |
176 | 177 | 178 | # Stars 179 | 180 | ![](https://starchart.cc/juancarlospaco/binance.svg) 181 | :star: [@juancarlospaco](https://github.com/juancarlospaco '2022-02-15') 182 | :star: [@kennym](https://github.com/kennym '2022-02-16') 183 | :star: [@nickolaz](https://github.com/nickolaz '2022-02-18') 184 | :star: [@Nacho512](https://github.com/Nacho512 '2022-02-20') 185 | :star: [@hannylicious](https://github.com/hannylicious '2022-03-02') 186 | :star: [@Walter-Santillan](https://github.com/Walter-Santillan '2022-03-21') 187 | :star: [@kamilchm](https://github.com/kamilchm '2022-03-23') 188 | :star: [@Parzivalcen](https://github.com/Parzivalcen '2022-04-06') 189 | :star: [@hugosenari](https://github.com/hugosenari '2022-05-28') 190 | :star: [@RodrigoTorresWeb](https://github.com/RodrigoTorresWeb '2022-06-25') 191 | :star: [@villawolfpy](https://github.com/villawolfpy '2022-07-11') 192 | :star: [@frankmoshe](https://github.com/frankmoshe '2022-07-13') 193 | :star: [@lf-araujo](https://github.com/lf-araujo '2022-07-28') 194 | :star: [@badsector666](https://github.com/badsector666 '2022-09-24') 195 | :star: [@Hamzadutsher](https://github.com/Hamzadutsher '2022-10-03') 196 | :star: [@Kensingtonn](https://github.com/Kensingtonn '2022-11-09') 197 | :star: [@ccamateur](https://github.com/ccamateur '2022-12-28') 198 | :star: [@hienpro00123](https://github.com/hienpro00123 '2023-01-09') 199 | :star: [@moigagoo](https://github.com/moigagoo '2023-04-06') 200 | :star: [@vegardsjo](https://github.com/vegardsjo '2023-04-12') 201 | :star: [@serjepatoff](https://github.com/serjepatoff '2023-04-25') 202 | :star: [@teroz](https://github.com/teroz '2023-05-03') 203 | :star: [@AndrewGPU](https://github.com/AndrewGPU '2023-06-13') 204 | :star: [@touhid12310](https://github.com/touhid12310 '2023-08-12') 205 | :star: [@JPANA108](https://github.com/JPANA108 '2023-08-22') 206 | :star: [@tejusb9](https://github.com/tejusb9 '2023-09-22') 207 | :star: [@Osamaalsabahy](https://github.com/Osamaalsabahy '2023-10-03') 208 | :star: [@Osamaalsabahy](https://github.com/Osamaalsabahy '2023-10-04') 209 | :star: [@Osamaalsabahy](https://github.com/Osamaalsabahy '2023-10-07') 210 | :star: [@Osamaalsabahy](https://github.com/Osamaalsabahy '2023-10-10') 211 | :star: [@deliriant](https://github.com/deliriant '2024-02-27') 212 | :star: [@Nomemartin](https://github.com/Nomemartin '2024-04-12') 213 | :star: [@FENIXG99](https://github.com/FENIXG99 '2024-05-19') 214 | :star: [@tuanductran](https://github.com/tuanductran '2024-05-29') 215 | -------------------------------------------------------------------------------- /.github/ISSUE_TEMPLATE/bug_report.yml: -------------------------------------------------------------------------------- 1 | name: "BUG Report" 2 | description: "Create a new Bug report." 3 | title: "[bug] " 4 | labels: ["unconfirmed"] 5 | assignees: ["juancarlospaco"] 6 | body: 7 | 8 | - type: markdown 9 | attributes: 10 | value: | 11 | - **Remember to :star: Star the project on GitHub!.** 12 | - **Please provide a minimal code example that reproduces the :bug: Bug!.** 13 | Reports with full repro code and descriptive detailed information will be fixed faster. 14 | - [Please, keep in mind there is ZERO FUNDING for the project!, we have no sponsors, no company behind, no dev team, 15 | :heart: Send crypto today to speed up development!](https://gist.github.com/juancarlospaco/37da34ed13a609663f55f4466c4dbc3e) 16 | 17 | - type: dropdown 18 | id: architecture 19 | attributes: 20 | label: Architecture 21 | description: What is your Hardware Architecture?. 22 | options: 23 | - x86_64 (Default) 24 | - x86_32 (32Bit) 25 | - ARM_64 (64Bit) 26 | - ARM_32 (32Bit) 27 | - AVR (Arduino, ESP32) 28 | - RISC (RISC-V) 29 | - Others (Unkown) 30 | validations: 31 | required: true 32 | 33 | - type: dropdown 34 | id: os 35 | attributes: 36 | label: Operating System 37 | description: What is your Operating System?. 38 | options: 39 | - Linux 40 | - Windows 41 | - Mac OSX 42 | - Android 43 | - BSD 44 | - FreeDOS 45 | - ReactOS 46 | - Others (Unkown) 47 | validations: 48 | required: true 49 | 50 | - type: dropdown 51 | id: disk 52 | attributes: 53 | label: Disk 54 | description: What is your main Disk Storage?. 55 | options: 56 | - SSD (Solid) 57 | - HDD (SATA, IDE, Mechanical) 58 | - NVME (M2, MSATA) 59 | - Others (USB) 60 | validations: 61 | required: true 62 | 63 | - type: dropdown 64 | id: ram 65 | attributes: 66 | label: Memory 67 | description: What is your total RAM Memory capacity?. 68 | options: 69 | - 1 Gigabytes 70 | - 2 Gigabytes 71 | - 4 Gigabytes 72 | - 8 Gigabytes 73 | - 16 Gigabytes 74 | - 32 Gigabytes 75 | - 64 Gigabytes 76 | - 128 Gigabytes 77 | - 256 Gigabytes 78 | - 512 Gigabytes 79 | - Others (Unkown) 80 | validations: 81 | required: true 82 | 83 | - type: dropdown 84 | id: cores 85 | attributes: 86 | label: CPU Cores 87 | description: What is your total CPU Cores count?. 88 | options: 89 | - 1 CPU Cores 90 | - 2 CPU Cores 91 | - 4 CPU Cores 92 | - 8 CPU Cores 93 | - 16 CPU Cores 94 | - 32 CPU Cores 95 | - 64 CPU Cores 96 | - 128 CPU Cores 97 | - 256 CPU Cores 98 | - 512 CPU Cores 99 | - Others (Unkown) 100 | validations: 101 | required: true 102 | 103 | - type: dropdown 104 | id: internet 105 | attributes: 106 | label: Internet Connection 107 | description: What is your Internet connection?. 108 | options: 109 | - Optical Fiber (very fast) 110 | - DSL (aDSL, DSL, etc) 111 | - Wifi (WLAN, Wireless) 112 | - LAN (RJ45, Local, etc) 113 | - Satellite (StarLink, etc) 114 | - Mobile (4G, 3G, Edge, etc) 115 | - Offline (No Internet) 116 | - Others (Unkown) 117 | validations: 118 | required: true 119 | 120 | - type: dropdown 121 | id: browser 122 | attributes: 123 | label: What is your web browser? 124 | options: 125 | - Chrome/Chromium 126 | - Firefox/Firefox Fork 127 | - Apple Safari 128 | - Microsoft Edge 129 | - KDE (Konqueror, Falkon, etc) 130 | - Others (Unkown) 131 | validations: 132 | required: true 133 | 134 | - type: dropdown 135 | id: device 136 | attributes: 137 | label: Device 138 | description: What kind of computer is it?. 139 | options: 140 | - Desktop PC 141 | - Server PC 142 | - Docker/Qemu (Container) 143 | - VirtualBox/Vagrant (Virtual Machine) 144 | - Embedded/IOT 145 | - Arduino/ESP32 Kit 146 | - SmartTV/SmartDisplay 147 | - Drone/Robot 148 | - ASIC/FPGA/Crypto-mining hardware 149 | - PLC/Industrial/heavy machine 150 | - Point Of Sale/Kiosk/ATM 151 | - Car/Self-Driving/On-Board Computer 152 | - Electric scooter/Electric bike 153 | - Satellite/MicroSatellite 154 | - Military machine 155 | - Others (Unkown) 156 | validations: 157 | required: true 158 | 159 | - type: dropdown 160 | id: country 161 | attributes: 162 | label: Where are you from? 163 | options: 164 | - Afghanistan 165 | - Albania 166 | - Algeria 167 | - Andorra 168 | - Angola 169 | - Antigua 170 | - Argentina 171 | - Armenia 172 | - Australia 173 | - Austria 174 | - Azerbaijan 175 | - Bahamas 176 | - Bahrain 177 | - Bangladesh 178 | - Barbados 179 | - Belarus 180 | - Belgium 181 | - Belize 182 | - Benin 183 | - Bhutan 184 | - Bolivia 185 | - Bosnia Herzegovina 186 | - Botswana 187 | - Brazil 188 | - Brunei 189 | - Bulgaria 190 | - Burkina 191 | - Burundi 192 | - Cambodia 193 | - Cameroon 194 | - Canada 195 | - Cape Verde 196 | - Central African Republic 197 | - Chad 198 | - Chile 199 | - China 200 | - Colombia 201 | - Comoros 202 | - Congo 203 | - Congo Democratic Republic 204 | - Costa Rica 205 | - Croatia 206 | - Cuba 207 | - Curacao 208 | - Cyprus 209 | - Czech Republic 210 | - Denmark 211 | - Djibouti 212 | - Dominica 213 | - Dominican Republic 214 | - East Timor 215 | - Ecuador 216 | - Egypt 217 | - El Salvador 218 | - Equatorial Guinea 219 | - Eritrea 220 | - Estonia 221 | - Ethiopia 222 | - Fiji 223 | - Finland 224 | - France 225 | - Gabon 226 | - Gambia 227 | - Georgia 228 | - Germany 229 | - Ghana 230 | - Greece 231 | - Grenada 232 | - Guatemala 233 | - Guinea 234 | - Guinea-Bissau 235 | - Guyana 236 | - Haiti 237 | - Honduras 238 | - Hungary 239 | - Iceland 240 | - India 241 | - Indonesia 242 | - Iran 243 | - Iraq 244 | - Ireland 245 | - Israel 246 | - Italy 247 | - Ivory Coast 248 | - Jamaica 249 | - Japan 250 | - Jordan 251 | - Kazakhstan 252 | - Kenya 253 | - Kiribati 254 | - Korea North 255 | - Korea South 256 | - Kosovo 257 | - Kuwait 258 | - Kyrgyzstan 259 | - Laos 260 | - Latvia 261 | - Lebanon 262 | - Lesotho 263 | - Liberia 264 | - Libya 265 | - Liechtenstein 266 | - Lithuania 267 | - Luxembourg 268 | - Macedonia 269 | - Madagascar 270 | - Malawi 271 | - Malaysia 272 | - Maldives 273 | - Mali 274 | - Malvinas Argentinas Islands 275 | - Malta 276 | - Marshall Islands 277 | - Mauritania 278 | - Mauritius 279 | - Mexico 280 | - Micronesia 281 | - Moldova 282 | - Monaco 283 | - Mongolia 284 | - Montenegro 285 | - Morocco 286 | - Mozambique 287 | - Myanmar 288 | - Namibia 289 | - Nauru 290 | - Nepal 291 | - Netherlands 292 | - New Zealand 293 | - Nicaragua 294 | - Niger 295 | - Nigeria 296 | - Norway 297 | - Oman 298 | - Pakistan 299 | - Palau 300 | - Palestine 301 | - Panama 302 | - Papua New Guinea 303 | - Paraguay 304 | - Peru 305 | - Philippines 306 | - Poland 307 | - Portugal 308 | - Qatar 309 | - Romania 310 | - Russia 311 | - Rwanda 312 | - St Kitts 313 | - St Lucia 314 | - Saint Vincent 315 | - Samoa 316 | - San Marino 317 | - Sao Tome Principe 318 | - Saudi Arabia 319 | - Senegal 320 | - Scotland 321 | - Serbia 322 | - Seychelles 323 | - Sierra Leone 324 | - Singapore 325 | - Slovakia 326 | - Slovenia 327 | - Solomon Islands 328 | - Somalia 329 | - South Africa 330 | - South Sudan 331 | - Spain 332 | - Sri Lanka 333 | - Sudan 334 | - Suriname 335 | - Swaziland 336 | - Sweden 337 | - Switzerland 338 | - Syria 339 | - Taiwan 340 | - Tajikistan 341 | - Tanzania 342 | - Thailand 343 | - Togo 344 | - Tonga 345 | - Trinidad Tobago 346 | - Tunisia 347 | - Turkey 348 | - Turkmenistan 349 | - Tuvalu 350 | - Uganda 351 | - Ukraine 352 | - United Arab Emirates 353 | - United Kingdom 354 | - United States 355 | - Uruguay 356 | - Uzbekistan 357 | - Vanuatu 358 | - Vatican City 359 | - Venezuela 360 | - Vietnam 361 | - Yemen 362 | - Zambia 363 | - Zimbabwe 364 | validations: 365 | required: true 366 | 367 | - type: textarea 368 | id: what-happened 369 | attributes: 370 | label: What happened? 371 | description: Use DETAILED DESCRIPTIVE information about the problem 372 | placeholder: Bug reports with full repro code and detailed information will be fixed faster. 373 | validations: 374 | required: true 375 | 376 | - type: textarea 377 | id: logs 378 | attributes: 379 | label: Standard Output Logs 380 | description: Please copy and paste any relevant log output. This will be automatically formatted into code, so no need for backticks. 381 | placeholder: Bug reports with full repro code and detailed information will be fixed faster. 382 | render: shell 383 | 384 | - type: markdown 385 | attributes: 386 | value: | 387 | **Before you open a new bug...** 388 | - 32-Bit is NOT supported. 389 | - Windows older than Windows 10 is NOT supported. 390 | - Mac OSX support is Experimental. 391 | - ARM support is Experimental. 392 | - Alpine Linux support is Experimental. 393 | - Termux support is Experimental. 394 | - Check if Git main branch already have a fix for your problem. 395 | -------------------------------------------------------------------------------- /examples/bot/tradingbot_futures.nim: -------------------------------------------------------------------------------- 1 | ## Crypto Trading Bot for Leveraged Perpetual Futures. 2 | # For Dev, use https://marketplace.visualstudio.com/items?itemName=zimonitrome.color-blocks 3 | import std/[json, os, strutils, httpcore, times, math, random] 4 | import dotenv, constants, loging 5 | import binance 6 | 7 | 8 | template cancelAllFutures() = 9 | ## Cancel all open orders (but NOT open positions). {#000} 10 | sl[ti] = (price: 0.0, orderId: 0) 11 | order = client.cancelAllOrdersFutures(symbol = ticker) 12 | for _ in 0 .. 2: # Repeat, because sometimes 1 gets ignored? 13 | trade = client.request(order, HttpDelete) 14 | echo trade 15 | 16 | 17 | proc main(config: JsonNode) = 18 | 19 | # Tickers for perpertual futures. {#c00ffe} 20 | const tickers = ["BTCUSDT", "ETHUSDT", "BNBUSDT", "YFIUSDT", "MKRUSDT", "BCHUSDT", "XMRUSDT", "ZECUSDT", "LTCUSDT", "EGLDUSDT"] 21 | 22 | # Read the configuration from file into variables. {#000} 23 | let 24 | startTime = now() 25 | walletBscProfits = config["wallet_address_bsc_to_send_profits"].getStr 26 | minLeverage: 1 .. 20 = config["min_leverage"].getInt 27 | doNotTradeBelow: 50 .. 500 = config["do_not_trade_coins_below_usd"].getInt 28 | trailingStopLossOffset: 1.0 .. 5.0 = config["trailing_stop_loss_offset_percent"].getInt.float 29 | marketEntryOffset: 0.015 .. 0.9999 = config["price_offset_for_market_entry_order"].getFloat # Do NOT use values lower than 0.015 30 | usdQuantity: 55.0 .. 9999.9 = config["usd_quantity"].getInt.float 31 | maxBalanceUsdt: 99.9 .. 9999.9 = config["max_balance_usdt_to_keep"].getInt.float 32 | autoCancelTimeout: Natural = (config["auto_cancel_order_timeout_hours"].getInt * 1_000) * 60 * 60 33 | tradeCounts: Positive = config["trade_count_limit"].getInt 34 | client = newBinance(config["binance_api_key"].getStr, config["binance_api_secret"].getStr) 35 | var 36 | sl: array[tickers.len, tuple[price: float, orderId: int]] 37 | prevPrice: array[tickers.len, float] 38 | i, leverage, positions: int 39 | order: string 40 | trade: JsonNode 41 | logFile = open(getCurrentDir() / "tradingbot_futures_" & startTime.format("yyyy-MM-dd") & ".log", fmWrite, 4_096) 42 | 43 | ## Basic sanity checks for the config. {#000} 44 | let accountStatus = client.request(client.apiRestrictions(), HttpGet) 45 | doAssert accountStatus["enableSpotAndMarginTrading"].getBool, "Configure your Binance API Key with 'Spot and Margin trading' permissions enabled. https://www.binance.com/en/my/settings/api-management" 46 | doAssert accountStatus["enableFutures"].getBool, "Configure your Binance API Key with 'Futures' permissions enabled. https://www.binance.com/en/my/settings/api-management" 47 | doAssert accountStatus["enableWithdrawals"].getBool, "Configure your Binance API Key with 'Spot and Margin trading' and 'Withdraw' permissions enabled. https://www.binance.com/en/my/settings/api-management" 48 | 49 | # Force MultiAssets-Mode to OFF. {#000} 50 | order = client.postMultiAssetModeFutures(multiAssetsMode = false) 51 | trade = client.request(order, HttpPost) 52 | echo '#', '\t', order, '\n', trade 53 | 54 | # Force Hedge-Mode to OFF. {#000} 55 | order = client.postPositionModeFutures(hedgeMode = false) 56 | trade = client.request(order, HttpPost) 57 | echo '#', '\t', order, '\n', trade 58 | 59 | randomize() 60 | for ti, ticker in tickers: 61 | # Set Margin Type to "Isolated". {#000} 62 | order = client.marginTypeFutures( 63 | symbol = ticker, 64 | isolated = true, 65 | ) 66 | trade = client.request(order, HttpPost) # {"code":200,"msg":"success"} 67 | echo '#', '\t', order, '\n', trade 68 | 69 | # Set position timeout, this auto-closes the position. {#000} 70 | if trade.hasKey"msg" and trade["msg"].getStr in ["success", "No need to change margin type."]: 71 | order = client.autoCancelAllOrdersFutures( 72 | symbol = ticker, 73 | countdownTime = autoCancelTimeout, 74 | ) 75 | trade = client.request(order, HttpPost) # {"symbol":"LPTUSDT","countdownTime":"0"} 76 | echo '#', '\t', order, '\n', trade 77 | 78 | # Set Leverage for the position, can not be changed with open positions. {#000} 79 | if trade.hasKey"countdownTime": 80 | leverage = rand(minLeverage.int .. 20) # >25 gets Liquidated. 81 | order = client.postLeverageFutures(symbol = ticker, leverage = leverage) 82 | trade = client.request(order, HttpPost) # {"symbol":"LPTUSDT","leverage":9,"maxNotionalValue":"100000"} 83 | echo '#', '\t', order, '\n', trade 84 | 85 | # Init array of previous prices to a big float, so it is overwritten later. {#000} 86 | prevPrice[ti] = 999_999_999.9 87 | 88 | # Trading loop. 89 | while tradeCounts > i: 90 | # This iterates all trading pairs of tickers. 91 | for ti, ticker in tickers: 92 | let 93 | hasOpenOrders = client.hasOpenOrdersFutures(ticker) 94 | hasOpenPositions = client.hasOpenPositionsFutures(ticker) 95 | 96 | if not hasOpenOrders and not hasOpenPositions: 97 | let baseAssetPrice = client.getPrice(ticker) 98 | if int(prevPrice[ti]) > int(baseAssetPrice): 99 | prevPrice[ti] = baseAssetPrice 100 | let 101 | baseAssetPriceAbove = (baseAssetPrice * (1.0 + marketEntryOffset)).int.float # Long + TSL 102 | baseAssetPriceBelow = (baseAssetPrice * (1.0 - marketEntryOffset)).int.float # SL 103 | baseAssetQuantity = truncate(round(usdQuantity / baseAssetPrice, 3)) # {"code":-1111,"msg":"Precision is over the maximum defined for this asset."} 104 | echo '#', '\t', ticker, "\tlong=", baseAssetPriceAbove, "\tprice=", baseAssetPrice, "\tSL=", baseAssetPriceBelow 105 | 106 | # Stop-Market because Stop-Limit can not be placed too far from price. 107 | if int(baseAssetPrice) > doNotTradeBelow and baseAssetQuantity > 0.0: 108 | 109 | # Trailing Stop-Loss market-exit order, closes position. {#000} 110 | order = client.postOrderFutures( 111 | symbol = ticker, 112 | quantity = baseAssetQuantity, 113 | activationPrice = baseAssetPriceAbove, 114 | callbackRate = trailingStopLossOffset, # 2.0 ~ 3.0 ? 115 | tipe = ORDER_TYPE_TRAILING_STOP_MARKET, 116 | side = SIDE_SELL, 117 | ) # closePosition=true does NOT work here. 118 | trade = client.request(order, HttpPost) 119 | echo '#', '\t', order, '\n', trade 120 | 121 | if trade.hasKey"orderId": 122 | # Fixed Stop-Loss market-exit order, closes position. {#000} 123 | order = client.postOrderFutures( 124 | closePosition = true, 125 | symbol = ticker, 126 | stopPrice = baseAssetPriceBelow, 127 | tipe = ORDER_TYPE_STOP_MARKET, 128 | side = SIDE_SELL, 129 | ) 130 | trade = client.request(order, HttpPost) 131 | echo '#', '\t', order, '\n', trade 132 | 133 | if trade.hasKey"orderId": 134 | # Open position market-entry order at specific price. {#000} 135 | sl[ti] = (price: baseAssetPriceBelow, orderId: trade["orderId"].getInt) # Remember SL. 136 | order = client.postOrderFutures( 137 | symbol = ticker, 138 | quantity = baseAssetQuantity, 139 | stopPrice = baseAssetPriceAbove, 140 | tipe = ORDER_TYPE_STOP_MARKET, 141 | side = SIDE_BUY, 142 | ) 143 | trade = client.request(order, HttpPost) 144 | echo '#', '\t', order, '\n', trade 145 | 146 | if trade.hasKey"orderId": 147 | ## Show information after a trade. {#000} 148 | inc i 149 | logfile.logs { 150 | "coin" : ticker, 151 | "price" : $int(baseAssetPrice), 152 | "long" : $int(baseAssetPriceAbove), 153 | "sl" : $int(sl[ti].price), 154 | "tsl" : $int(trailingStopLossOffset) & '%', 155 | "lever" : $leverage & 'x', 156 | "size" : $int(usdQuantity) & '$', 157 | "orders" : $i, 158 | "positions": $positions, 159 | }, "future" 160 | 161 | else: cancelAllFutures() 162 | else: cancelAllFutures() 163 | else: cancelAllFutures() 164 | 165 | if hasOpenOrders and hasOpenPositions: 166 | order = client.positionRiskFutures(symbol = ticker) 167 | trade = client.request(order, HttpGet) 168 | echo '#', '\t', order, '\n', trade 169 | 170 | # Get unRealizedProfit, if > 0, get markPrice, move SL up. {#000} 171 | if trade[0]["unRealizedProfit"].getStr != "0.00000000": 172 | let slPrice = float(int(trade[0]["markPrice"].getStr.parseFloat * 0.95)) 173 | 174 | if int(slPrice) > int(sl[ti].price): 175 | 176 | # Fixed Stop-Loss market-exit order only moves up. {#000} 177 | order = client.postOrderFutures( 178 | closePosition = true, 179 | symbol = ticker, 180 | stopPrice = slPrice, 181 | tipe = ORDER_TYPE_STOP_MARKET, 182 | side = SIDE_SELL, 183 | ) 184 | trade = client.request(order, HttpPost) 185 | echo '#', '\t', order, '\n', trade 186 | 187 | # Close old SL {#000}. 188 | if trade.hasKey"orderId": 189 | inc positions 190 | prevPrice[ti] = 999_999_999.9 191 | if sl[ti].orderId > 0: 192 | let newOrderId = trade["orderId"].getInt 193 | order = client.cancelOrderFutures(ticker, sl[ti].orderId) 194 | trade = client.request(order, HttpDelete) 195 | echo '#', '\t', order, '\n', trade 196 | sl[ti] = (price: slPrice, orderId: newOrderId) 197 | 198 | if hasOpenOrders and not hasOpenPositions: 199 | # Previous price is bigger than current price, orders are too high, cancel all orders to move them down. {#000} 200 | if int(prevPrice[ti]) > int(client.getPrice(ticker)): 201 | prevPrice[ti] = 999_999_999.9 202 | cancelAllFutures() 203 | 204 | if i > tickers.len and i mod tickers.len == 0: 205 | # Check for maximum balance in USDT to keep in Binance, 206 | # withdraw profits out of the exchange to user wallet if balance grew too much. 207 | # 10 USDT is the minimum allowed to withdraw via BSC. 208 | if client.getBalanceFutures("USDT") > maxBalanceUsdt + 10.0: 209 | # Move 10 USDT from Futures to Spot, because cant withdraw from Futures. {#000} 210 | order = client.postTransferFutures( 211 | asset = "USDT", 212 | amount = 10.0, 213 | tipe = TransferType.futuresToSpot, 214 | ) 215 | trade = client.request(order, HttpPost) 216 | echo '#', '\t', order, '\n', trade 217 | 218 | if trade.hasKey"tranId": 219 | trade = client.donateToAddress(walletBscProfits, 10.0, "USDT") 220 | echo trade 221 | 222 | client.close() 223 | logFile.close() 224 | 225 | 226 | when isMainModule: 227 | let pat = absolutePath(getCurrentDir() / "config_futures.ini") 228 | if not fileExists(pat): 229 | writeFile pat, configiniTemplateFutures 230 | quit(pat & " not found, wrote an empty blank template config_futures.ini please edit the config_futures.ini and restart.\n\n" & 231 | pat & " no encontrado, creado un nuevo template config_futures.ini vacio por favor editar el config_futures.ini y reiniciar.") 232 | main(pat.readFile.parseDotEnv) 233 | -------------------------------------------------------------------------------- /src/binance/binance_sha256.nim: -------------------------------------------------------------------------------- 1 | when not defined(js): 2 | 3 | from std/strutils import toLowerAscii 4 | 5 | type 6 | HexFlags {.pure.} = enum 7 | LowerCase, ## Produce lowercase hexadecimal characters 8 | PadOdd, ## Pads odd strings 9 | SkipSpaces, ## Skips all the whitespace characters inside of string 10 | SkipPrefix 11 | 12 | bchar = byte | char 13 | 14 | SHA256 = object 15 | bits: int # 256 16 | block_size: int # block-size 64 17 | count: array[2, uint32] 18 | state: array[8, uint32] 19 | buffer: array[64,byte] 20 | 21 | sha256 = SHA256 22 | 23 | HMAC = object 24 | mdctx: sha256 25 | opadctx: sha256 26 | ipad: array[64, byte] 27 | opad: array[64, byte] 28 | 29 | const 30 | K0 = [ 31 | 0x428a2f98'u32, 0x71374491'u32, 0xb5c0fbcf'u32, 0xe9b5dba5'u32, 32 | 0x3956c25b'u32, 0x59f111f1'u32, 0x923f82a4'u32, 0xab1c5ed5'u32, 33 | 0xd807aa98'u32, 0x12835b01'u32, 0x243185be'u32, 0x550c7dc3'u32, 34 | 0x72be5d74'u32, 0x80deb1fe'u32, 0x9bdc06a7'u32, 0xc19bf174'u32, 35 | 0xe49b69c1'u32, 0xefbe4786'u32, 0x0fc19dc6'u32, 0x240ca1cc'u32, 36 | 0x2de92c6f'u32, 0x4a7484aa'u32, 0x5cb0a9dc'u32, 0x76f988da'u32, 37 | 0x983e5152'u32, 0xa831c66d'u32, 0xb00327c8'u32, 0xbf597fc7'u32, 38 | 0xc6e00bf3'u32, 0xd5a79147'u32, 0x06ca6351'u32, 0x14292967'u32, 39 | 0x27b70a85'u32, 0x2e1b2138'u32, 0x4d2c6dfc'u32, 0x53380d13'u32, 40 | 0x650a7354'u32, 0x766a0abb'u32, 0x81c2c92e'u32, 0x92722c85'u32, 41 | 0xa2bfe8a1'u32, 0xa81a664b'u32, 0xc24b8b70'u32, 0xc76c51a3'u32, 42 | 0xd192e819'u32, 0xd6990624'u32, 0xf40e3585'u32, 0x106aa070'u32, 43 | 0x19a4c116'u32, 0x1e376c08'u32, 0x2748774c'u32, 0x34b0bcb5'u32, 44 | 0x391c0cb3'u32, 0x4ed8aa4a'u32, 0x5b9cca4f'u32, 0x682e6ff3'u32, 45 | 0x748f82ee'u32, 0x78a5636f'u32, 0x84c87814'u32, 0x8cc70208'u32, 46 | 0x90befffa'u32, 0xa4506ceb'u32, 0xbef9a3f7'u32, 0xc67178f2'u32 47 | ] 48 | 49 | when defined(gcc) or defined(llvm_gcc) or defined(clang): 50 | func swapBytesBuiltin(x: uint8 ): uint8 = x 51 | func swapBytesBuiltin(x: uint16): uint16 {.importc: "__builtin_bswap16", nodecl.} 52 | func swapBytesBuiltin(x: uint32): uint32 {.importc: "__builtin_bswap32", nodecl.} 53 | func swapBytesBuiltin(x: uint64): uint64 {.importc: "__builtin_bswap64", nodecl.} 54 | else: {.fatal: "Requires GCC or Clang or LLVM Compiler.".} 55 | 56 | template copyMem[A, B](dst: var openArray[A], dsto: int, src: openArray[B], srco: int, length: int) = copyMem(addr dst[dsto], unsafeAddr src[srco], length * sizeof(B)) 57 | template ROR(x: uint32, n: int): uint32 = (x shr uint32(n and 0x1F)) or (x shl uint32(32 - (n and 0x1F))) 58 | template ROR(x: uint64, n: int): uint64 = (x shr uint64(n and 0x3F)) or (x shl uint64(64 - (n and 0x3F))) 59 | template SIG0(x): uint32 = ROR(x, 7) xor ROR(x, 18) xor (x shr 3) 60 | template SIG1(x): uint32 = ROR(x, 17) xor ROR(x, 19) xor (x shr 10) 61 | template TAU0(x: uint32): uint32 = (ROR(x, 2) xor ROR(x, 13) xor ROR(x, 22)) 62 | template TAU1(x: uint32): uint32 = (ROR(x, 6) xor ROR(x, 11) xor ROR(x, 25)) 63 | template CH0(x, y, z): uint32 = ((x) and (y)) xor (not(x) and (z)) 64 | template MAJ0(x, y, z): uint32 = ((x) and (y)) xor ((x) and (z)) xor ((y) and (z)) 65 | template leSwap32(a: uint32): uint32 = (when system.cpuEndian == bigEndian: (a) else: swapBytesBuiltin(a)) 66 | template beStore32*(dst: var openArray[byte], so: int, v: uint32) = cast[ptr uint32](addr dst[so])[] = leSwap32(v) 67 | 68 | template ROUND256(a, b, c, d, e, f, g, h, z) = 69 | t0 = h + TAU1(e) + CH0(e, f, g) + K0[z] + W[z] 70 | t1 = TAU0(a) + MAJ0(a, b, c) 71 | d = d + t0 72 | h = t0 + t1 73 | 74 | template beLoad32[T: byte|char](src: openArray[T], srco: int): uint32 = 75 | var p = cast[ptr uint32](unsafeAddr src[srco])[] 76 | leSwap32(p) 77 | 78 | proc sha256Transform(state: var array[8, uint32], data: openArray[byte]) = 79 | var 80 | t0, t1: uint32 81 | W {.noinit.}: array[64, uint32] 82 | 83 | W[0] = beLoad32(data, 0) 84 | W[1] = beLoad32(data, 4) 85 | W[2] = beLoad32(data, 8) 86 | W[3] = beLoad32(data, 12) 87 | W[4] = beLoad32(data, 16) 88 | W[5] = beLoad32(data, 20) 89 | W[6] = beLoad32(data, 24) 90 | W[7] = beLoad32(data, 28) 91 | W[8] = beLoad32(data, 32) 92 | W[9] = beLoad32(data, 36) 93 | W[10] = beLoad32(data, 40) 94 | W[11] = beLoad32(data, 44) 95 | W[12] = beLoad32(data, 48) 96 | W[13] = beLoad32(data, 52) 97 | W[14] = beLoad32(data, 56) 98 | W[15] = beLoad32(data, 60) 99 | 100 | for i in 16 ..< 64: W[i] = SIG1(W[i - 2]) + W[i - 7] + SIG0(W[i - 15]) + W[i - 16] 101 | 102 | var s0 = state[0] 103 | var s1 = state[1] 104 | var s2 = state[2] 105 | var s3 = state[3] 106 | var s4 = state[4] 107 | var s5 = state[5] 108 | var s6 = state[6] 109 | var s7 = state[7] 110 | 111 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 0) 112 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 1) 113 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 2) 114 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 3) 115 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 4) 116 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 5) 117 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 6) 118 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 7) 119 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 8) 120 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 9) 121 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 10) 122 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 11) 123 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 12) 124 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 13) 125 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 14) 126 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 15) 127 | 128 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 16) 129 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 17) 130 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 18) 131 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 19) 132 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 20) 133 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 21) 134 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 22) 135 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 23) 136 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 24) 137 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 25) 138 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 26) 139 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 27) 140 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 28) 141 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 29) 142 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 30) 143 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 31) 144 | 145 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 32) 146 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 33) 147 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 34) 148 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 35) 149 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 36) 150 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 37) 151 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 38) 152 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 39) 153 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 40) 154 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 41) 155 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 42) 156 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 43) 157 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 44) 158 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 45) 159 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 46) 160 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 47) 161 | 162 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 48) 163 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 49) 164 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 50) 165 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 51) 166 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 52) 167 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 53) 168 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 54) 169 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 55) 170 | ROUND256(s0, s1, s2, s3, s4, s5, s6, s7, 56) 171 | ROUND256(s7, s0, s1, s2, s3, s4, s5, s6, 57) 172 | ROUND256(s6, s7, s0, s1, s2, s3, s4, s5, 58) 173 | ROUND256(s5, s6, s7, s0, s1, s2, s3, s4, 59) 174 | ROUND256(s4, s5, s6, s7, s0, s1, s2, s3, 60) 175 | ROUND256(s3, s4, s5, s6, s7, s0, s1, s2, 61) 176 | ROUND256(s2, s3, s4, s5, s6, s7, s0, s1, 62) 177 | ROUND256(s1, s2, s3, s4, s5, s6, s7, s0, 63) 178 | 179 | state[0] = state[0] + s0 180 | state[1] = state[1] + s1 181 | state[2] = state[2] + s2 182 | state[3] = state[3] + s3 183 | state[4] = state[4] + s4 184 | state[5] = state[5] + s5 185 | state[6] = state[6] + s6 186 | state[7] = state[7] + s7 187 | 188 | proc update[T: bchar](ctx: var SHA256, data: openArray[T])= 189 | var pos = 0 190 | var length = len(data) 191 | 192 | while length > 0: 193 | let offset = int(ctx.count[0] and 0x3f) 194 | let size = min(64 - offset, length) 195 | copyMem(ctx.buffer, offset, data, pos, size) 196 | pos += size 197 | length -= size 198 | ctx.count[0] += uint32(size) 199 | if ctx.count[0] < uint32(size): 200 | ctx.count[1] = ctx.count[1] + 1'u32 201 | if (ctx.count[0] and 0x3F'u32) == 0: 202 | sha256Transform(ctx.state, ctx.buffer) 203 | 204 | proc init(ctx: var SHA256) = 205 | ctx.count[0] = 0 206 | ctx.count[1] = 0 207 | ctx.state[0] = 0x6A09E667'u32 208 | ctx.state[1] = 0xBB67AE85'u32 209 | ctx.state[2] = 0x3C6EF372'u32 210 | ctx.state[3] = 0xA54FF53A'u32 211 | ctx.state[4] = 0x510E527F'u32 212 | ctx.state[5] = 0x9B05688C'u32 213 | ctx.state[6] = 0x1F83D9AB'u32 214 | ctx.state[7] = 0x5BE0CD19'u32 215 | 216 | proc init[M](hmctx: var HMAC, key: openArray[M]) = 217 | var kpad: hmctx.ipad.type 218 | hmctx.mdctx = sha256() 219 | hmctx.opadctx = sha256() 220 | init hmctx.opadctx 221 | 222 | if key.len > 0: copyMem(kpad, 0, key, 0, len(key)) 223 | 224 | for i in 0 ..< 64: 225 | hmctx.opad[i] = 0x5C'u8 xor kpad[i] 226 | hmctx.ipad[i] = 0x36'u8 xor kpad[i] 227 | 228 | init hmctx.mdctx 229 | update hmctx.mdctx, hmctx.ipad 230 | update hmctx.opadctx, hmctx.opad 231 | 232 | proc finalize256(ctx: var SHA256) {.inline.} = 233 | var j = int(ctx.count[0] and 0x3f'u32) 234 | ctx.buffer[j] = 0x80'u8 235 | j += 1 236 | while j != 56: 237 | if j == 64: 238 | sha256Transform(ctx.state, ctx.buffer) 239 | j = 0 240 | ctx.buffer[j] = 0x00'u8 241 | j += 1 242 | ctx.count[1] = (ctx.count[1] shl 3) + (ctx.count[0] shr 29) 243 | ctx.count[0] = ctx.count[0] shl 3 244 | beStore32(ctx.buffer, 56, ctx.count[1]) 245 | beStore32(ctx.buffer, 60, ctx.count[0]) 246 | sha256Transform(ctx.state, ctx.buffer) 247 | 248 | proc finish*(ctx: var SHA256, data: var openArray[byte]): uint {.inline.} = 249 | finalize256(ctx) 250 | beStore32(data, 0, ctx.state[0]) 251 | beStore32(data, 4, ctx.state[1]) 252 | beStore32(data, 8, ctx.state[2]) 253 | beStore32(data, 12, ctx.state[3]) 254 | beStore32(data, 16, ctx.state[4]) 255 | beStore32(data, 20, ctx.state[5]) 256 | beStore32(data, 24, ctx.state[6]) 257 | beStore32(data, 28, ctx.state[7]) 258 | result = 32 259 | 260 | proc finish[T: bchar](hmctx: var HMAC, data: var openArray[T]) {.inline.} = 261 | var buffer: array[32, byte] 262 | discard finish(hmctx.mdctx, buffer) 263 | hmctx.opadctx.update(buffer) 264 | discard hmctx.opadctx.finish(data) 265 | 266 | proc burnMem(p: pointer, size: Natural) = 267 | var sp {.volatile.} = cast[ptr byte](p) 268 | var c = size 269 | if not isNil(sp): 270 | zeroMem(p, size) 271 | while c > 0: 272 | sp[] = 0 273 | sp = cast[ptr byte](cast[uint](sp) + 1) 274 | dec(c) 275 | 276 | proc burnMem[T](a: var T) {.inline.} = burnMem(addr a, sizeof(T)) 277 | proc clear(hmctx: var HMAC) {.inline.} = burnMem(hmctx) 278 | 279 | proc hexDigit(x: int, lowercase: bool = false): char = 280 | var off = uint32(0x41 - 0x3A) 281 | if lowercase: 282 | off += 0x20 283 | char(0x30'u32 + uint32(x) + (off and not((uint32(x) - 10) shr 8))) 284 | 285 | proc bytesToHex(src: openArray[byte], dst: var openArray[char], flags: set[HexFlags]): int = 286 | if len(dst) == 0: 287 | (len(src) shl 1) 288 | else: 289 | var halflast = false 290 | let dstlen = len(dst) 291 | var srclen = len(src) 292 | 293 | if dstlen < (srclen shl 1): 294 | if (dstlen and 1) == 1: 295 | srclen = (dstlen - 1) shr 1 296 | halflast = true 297 | else: 298 | srclen = (dstlen shr 1) 299 | 300 | let lowercase = (HexFlags.LowerCase in flags) 301 | 302 | var k = 0 303 | for i in 0 ..< srclen: 304 | let x = int(src[i]) 305 | dst[k + 0] = hexDigit(x shr 4, lowercase) 306 | dst[k + 1] = hexDigit(x and 15, lowercase) 307 | inc(k, 2) 308 | 309 | if halflast: 310 | let x = int(src[srclen]) 311 | dst[k + 0] = hexDigit(x shr 4, lowercase) 312 | inc(k) 313 | k 314 | 315 | proc sha256hmac*(key, data: openArray[char]): string = 316 | var ctx: HMAC 317 | ctx.init key 318 | ctx.mdctx.update data 319 | var result_data: array[32, byte] 320 | ctx.finish result_data 321 | var res = newString((len(result_data) shl 1)) 322 | discard bytesToHex(result_data, res, {}) 323 | ctx.clear 324 | res.toLowerAscii 325 | 326 | 327 | when defined(js): 328 | {.emit: """ 329 | 330 | const sha256hmacjs = (function() { 331 | 332 | function hex_hmac_sha256(k, d) { return rstr2hex(rstr_hmac_sha256(str2rstr_utf8(k), str2rstr_utf8(d))); } 333 | 334 | function rstr_hmac_sha256(key, data) { 335 | var bkey = rstr2binb(key); 336 | if (bkey.length > 16) { bkey = binb_sha256(bkey, key.length * 8); } 337 | 338 | var ipad = Array(16), opad = Array(16); 339 | for (var i = 0; i < 16; i++) { 340 | ipad[i] = bkey[i] ^ 0x36363636; 341 | opad[i] = bkey[i] ^ 0x5C5C5C5C; 342 | } 343 | 344 | const hash = binb_sha256(ipad.concat(rstr2binb(data)), 512 + data.length * 8); 345 | return binb2rstr(binb_sha256(opad.concat(hash), 512 + 256)); 346 | } 347 | 348 | function rstr2hex(input) { 349 | const hex_tab = "0123456789abcdef"; 350 | var output = ""; 351 | var x; 352 | for (var i = 0; i < input.length; i++) { 353 | x = input.charCodeAt(i); 354 | output += hex_tab.charAt((x >>> 4) & 0x0F) + hex_tab.charAt(x & 0x0F); 355 | } 356 | return output; 357 | } 358 | 359 | function str2rstr_utf8(input) { 360 | var output = ""; 361 | var i = -1; 362 | var x, y; 363 | 364 | while (++i < input.length) { 365 | x = input.charCodeAt(i); 366 | y = i + 1 < input.length ? input.charCodeAt(i + 1) : 0; 367 | if (0xD800 <= x && x <= 0xDBFF && 0xDC00 <= y && y <= 0xDFFF) { 368 | x = 0x10000 + ((x & 0x03FF) << 10) + (y & 0x03FF); 369 | i++; 370 | } 371 | 372 | /* Encode output as utf-8 */ 373 | if (x <= 0x7F) 374 | output += String.fromCharCode(x); 375 | else if (x <= 0x7FF) 376 | output += String.fromCharCode(0xC0 | ((x >>> 6 ) & 0x1F), 377 | 0x80 | ( x & 0x3F)); 378 | else if (x <= 0xFFFF) 379 | output += String.fromCharCode(0xE0 | ((x >>> 12) & 0x0F), 380 | 0x80 | ((x >>> 6 ) & 0x3F), 381 | 0x80 | ( x & 0x3F)); 382 | else if (x <= 0x1FFFFF) 383 | output += String.fromCharCode(0xF0 | ((x >>> 18) & 0x07), 384 | 0x80 | ((x >>> 12) & 0x3F), 385 | 0x80 | ((x >>> 6 ) & 0x3F), 386 | 0x80 | ( x & 0x3F)); 387 | } 388 | return output; 389 | } 390 | 391 | function rstr2binb(input) { 392 | var output = Array(input.length >> 2); 393 | for (var i = 0; i < output.length; i++) 394 | output[i] = 0; 395 | for (var i = 0; i < input.length * 8; i += 8) 396 | output[i>>5] |= (input.charCodeAt(i / 8) & 0xFF) << (24 - i % 32); 397 | return output; 398 | } 399 | 400 | function binb2rstr(input) { 401 | var output = ""; 402 | for (var i = 0; i < input.length * 32; i += 8) 403 | output += String.fromCharCode((input[i>>5] >>> (24 - i % 32)) & 0xFF); 404 | return output; 405 | } 406 | 407 | function sha256_S(X, n) { return ( X >>> n ) | (X << (32 - n)); } 408 | function sha256_R(X, n) { return ( X >>> n ); } 409 | function sha256_Ch(x, y, z) { return ((x & y) ^ ((~x) & z)); } 410 | function sha256_Maj(x, y, z) { return ((x & y) ^ (x & z) ^ (y & z)); } 411 | function sha256_Sigma0256(x) { return (sha256_S(x, 2) ^ sha256_S(x, 13) ^ sha256_S(x, 22)); } 412 | function sha256_Sigma1256(x) { return (sha256_S(x, 6) ^ sha256_S(x, 11) ^ sha256_S(x, 25)); } 413 | function sha256_Gamma0256(x) { return (sha256_S(x, 7) ^ sha256_S(x, 18) ^ sha256_R(x, 3)); } 414 | function sha256_Gamma1256(x) { return (sha256_S(x, 17) ^ sha256_S(x, 19) ^ sha256_R(x, 10));} 415 | 416 | var sha256_K = new Array( 417 | 1116352408, 1899447441, -1245643825, -373957723, 961987163, 1508970993, 418 | -1841331548, -1424204075, -670586216, 310598401, 607225278, 1426881987, 419 | 1925078388, -2132889090, -1680079193, -1046744716, -459576895, -272742522, 420 | 264347078, 604807628, 770255983, 1249150122, 1555081692, 1996064986, 421 | -1740746414, -1473132947, -1341970488, -1084653625, -958395405, -710438585, 422 | 113926993, 338241895, 666307205, 773529912, 1294757372, 1396182291, 423 | 1695183700, 1986661051, -2117940946, -1838011259, -1564481375, -1474664885, 424 | -1035236496, -949202525, -778901479, -694614492, -200395387, 275423344, 425 | 430227734, 506948616, 659060556, 883997877, 958139571, 1322822218, 426 | 1537002063, 1747873779, 1955562222, 2024104815, -2067236844, -1933114872, 427 | -1866530822, -1538233109, -1090935817, -965641998 428 | ); 429 | 430 | function binb_sha256(m, l) { 431 | var HASH = new Array(1779033703, -1150833019, 1013904242, -1521486534, 1359893119, -1694144372, 528734635, 1541459225); 432 | var W = new Array(64); 433 | var a, b, c, d, e, f, g, h; 434 | var i, j, T1, T2; 435 | 436 | m[l >> 5] |= 0x80 << (24 - l % 32); 437 | m[((l + 64 >> 9) << 4) + 15] = l; 438 | 439 | for (i = 0; i < m.length; i += 16) { 440 | a = HASH[0]; 441 | b = HASH[1]; 442 | c = HASH[2]; 443 | d = HASH[3]; 444 | e = HASH[4]; 445 | f = HASH[5]; 446 | g = HASH[6]; 447 | h = HASH[7]; 448 | 449 | for (j = 0; j < 64; j++) { 450 | if (j < 16) { W[j] = m[j + i]; } 451 | else { W[j] = safe_add(safe_add(safe_add(sha256_Gamma1256(W[j - 2]), W[j - 7]), sha256_Gamma0256(W[j - 15])), W[j - 16]); } 452 | 453 | T1 = safe_add(safe_add(safe_add(safe_add(h, sha256_Sigma1256(e)), sha256_Ch(e, f, g)), sha256_K[j]), W[j]); 454 | T2 = safe_add(sha256_Sigma0256(a), sha256_Maj(a, b, c)); 455 | h = g; 456 | g = f; 457 | f = e; 458 | e = safe_add(d, T1); 459 | d = c; 460 | c = b; 461 | b = a; 462 | a = safe_add(T1, T2); 463 | } 464 | 465 | HASH[0] = safe_add(a, HASH[0]); 466 | HASH[1] = safe_add(b, HASH[1]); 467 | HASH[2] = safe_add(c, HASH[2]); 468 | HASH[3] = safe_add(d, HASH[3]); 469 | HASH[4] = safe_add(e, HASH[4]); 470 | HASH[5] = safe_add(f, HASH[5]); 471 | HASH[6] = safe_add(g, HASH[6]); 472 | HASH[7] = safe_add(h, HASH[7]); 473 | } 474 | return HASH; 475 | } 476 | 477 | function safe_add(x, y) { 478 | const lsw = (x & 0xFFFF) + (y & 0xFFFF); 479 | const msw = (x >> 16) + (y >> 16) + (lsw >> 16); 480 | return (msw << 16) | (lsw & 0xFFFF); 481 | } 482 | 483 | return hex_hmac_sha256; 484 | 485 | }()); 486 | 487 | """.} 488 | 489 | func sha256hmacjs(key, data: cstring): cstring {.importjs: "$1(#, #)".} 490 | 491 | proc sha256hmac*(key, data: string): string = 492 | $sha256hmacjs(key.cstring, data.cstring) 493 | 494 | 495 | when isMainModule: 496 | doAssert sha256hmac("key", "value") == "90fbfcf15e74a36b89dbdb2a721d9aecffdfdddc5c83e27f7592594f71932481" 497 | -------------------------------------------------------------------------------- /docs/nimdoc.out.css: -------------------------------------------------------------------------------- 1 | /* 2 | Stylesheet for use with Docutils/rst2html. 3 | 4 | See http://docutils.sf.net/docs/howto/html-stylesheets.html for how to 5 | customize this style sheet. 6 | 7 | Modified from Chad Skeeters' rst2html-style 8 | https://bitbucket.org/cskeeters/rst2html-style/ 9 | 10 | Modified by Boyd Greenfield and narimiran 11 | */ 12 | 13 | :root { 14 | --primary-background: #fff; 15 | --secondary-background: ghostwhite; 16 | --third-background: #e8e8e8; 17 | --info-background: #50c050; 18 | --warning-background: #c0a000; 19 | --error-background: #e04040; 20 | --border: #dde; 21 | --text: #222; 22 | --anchor: #07b; 23 | --anchor-focus: #607c9f; 24 | --input-focus: #1fa0eb; 25 | --strong: #3c3c3c; 26 | --hint: #9A9A9A; 27 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1NGQtOTVjNy1iOTgxMDFlMDFhMmEiIHhtcE1NOkRvY3VtZW50SUQ9InhtcC5kaWQ6MzMzNGYwMWEtMjAxMS1hNTRkLTk1YzctYjk4MTAxZTAxYTJhIiB4bXBNTTpPcmlnaW5hbERvY3VtZW50SUQ9InhtcC5kaWQ6MzMzNGYwMWEtMjAxMS1hNTRkLTk1YzctYjk4MTAxZTAxYTJhIj4gPHhtcE1NOkhpc3Rvcnk+IDxyZGY6U2VxPiA8cmRmOmxpIHN0RXZ0OmFjdGlvbj0iY3JlYXRlZCIgc3RFdnQ6aW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1NGQtOTVjNy1iOTgxMDFlMDFhMmEiIHN0RXZ0OndoZW49IjIwMTktMTItMDNUMDE6MDM6NDgrMDE6MDAiIHN0RXZ0OnNvZnR3YXJlQWdlbnQ9IkFkb2JlIFBob3Rvc2hvcCAyMS4wIChXaW5kb3dzKSIvPiA8L3JkZjpTZXE+IDwveG1wTU06SGlzdG9yeT4gPC9yZGY6RGVzY3JpcHRpb24+IDwvcmRmOlJERj4gPC94OnhtcG1ldGE+IDw/eHBhY2tldCBlbmQ9InIiPz4PsixkAAAJ5klEQVR4nO2dfbBUZR3HP3vvxVD0zo0ACXxBuQMoQjJ1DfMl0NIhNcuSZqQhfGt6UWtK06xJexkrmywVRTQlHCIdtclC0zBJvYIvvEUgZpc3XyC7RVbKlQu1/fHdbc+uu2fPOfs85+y55/nMnBl2z+5zfnc5v/M8z+8119XVRYroAG4HfgvMT1YUR4MMAa4HLkhakCRoSVqAELwLeBY4C7gF+D6QS1QiR1ROAJ4Dzk9akKQwoXhtwL4GxvHjU8AKoNPz3leAu4HBFq+bAyZZHD9rDAK+BywDDklYlkQxoXhfAtYAEw2MVckQYBHwU6or99nA08BBFq49GngUeBIYaWH8rNEJdAOXA60Jy5I4jSreSOBKYDzwBPCJhiUqcSjwe2BWnc9NLnxuvMFrnwqsAqYBBwBfNzh2FpmNfs9jkhakWcg1aFxZiH5UL3cDnwf+Xue7BwFjgFHAOwuv24tyob3cO0LIshP4EbCn8Pq/wKvA9sLxMvCvOmPsA1yDZnHv/nEv2mM+F0IeR4m8z7lM7tMbUbzj0CxX7YfbAXwaWFJ4PRrNIu9FS9KJyEIZN68CG4DnkRJtLBw7gHHAYuDdNb77EDAjBhkHIk7xKoiqeK3IwjilzuceQJvoZjdQ/AMZaeoZiWYgBXSEwyleBW0Rv3cR9ZUO4LSI48fN2wN+bi5wJNBvUZaBSCaVy48oxpVhwDdMC5ISxpJRh6/DLGEUrxXt29YBQ+2IkwquR76ofZIWxJFegireNLSnm48skFmmDfmiVgJHJyuKI620ADOpbWEcDPwYOZKD7OmyxCTkXL+wzueOiEEWR8poQb60V4A7kLm/yFjgKeALuM1xLfYDbkX+zEGe98cAX0Oui6viF8vR7OS6urragW2UZr21wK+Aiwlu7XPoN3sYOAd4H6WH1SnA0qSEcjQnRT/e1bgnsw16kGPez4/lyCBF48oNwL+TFGSAsgCndI4qFBVvJ0owdZhjL3CnxfHzBo8+YBMyol0CHBijrKbHS/LoA7Yio9sPgJNr/QHekLGR6MffL+KP4SjnHmQxtoXNmbQP+CHyV75hYDzTIWNpWkU8iR5mq71vVsZqXgtcFqNQ/wG2IOtfD8oi6AX+Ujj+isKz8sBrnu+1okyGdmD/wnEgcDClTIdRyJRvI1cvCMciq7At4rj5eoCPAusbHCfLigda/VyKgi+AtyreMGAzykGzQQ/wO+BxSlkCuy1dq8hw5OieUjimYT+x9bHCdWwS1823Ez1EXmhgjKwrXpHzkduuanbCtzGX+NkPPAj8GincNkPjNkIO5dadUjiOB95m+BonopQpm8R58/0JJbHWy2eshVM8sRvdbyurKV4Hmoka2WA/iwwLP6d+QmzSdKC92GzK/W9R+Q3woQbHCELcN991wJcjftcpXolngKm18vFmoVonYcgDv0Qz5pqGREuOTuA8lPYUZbndh0LJNpkUqgZx33xvomim7RG+6xSvnOm1gqQXoyiMoKxFs8VZpFfpQHvQK4HDUPnAsBa9bxGP0tUjF+IYCkxFew+/G3owdq20pgjzt3uPRscs/o43IaOhH2f4ZaAPRyZQP6vgbuCbyGext87F0sgIZFI/N8BnlwBnolovcWAjq/uzwM0+55cBJ0UYN84ZL+rfbnLMM4FfUDv7Z1XlCe8FetETbleNL7+CZrnvMjCVDuTOOA84Hf+96ga0PC8qXY50FQsuMg+41+d8p885R4n7gdt8zo+qvDkmUF4fZQXwEbS+99KDMhlWkw0eALqQglXyDDCdcovf+4lv5jPNXJ9zWc/FDMMdPudGVCreRlTWwVtWbynwYVQQCFSp61Q042WJLUjB1nneuw8tvXo97x1Lugvg+j1Mo9boySLVHtJFWqsthx5GlbSGeN5bigrHdqPl52Zj4qWLXvTQWY4KOX2ccgPMBLRcuy9+0YzhguXN4GuYq2Zc2R/NZg+hfYt3/9ZCepdQthmB4vIWIYOTbWyWzGt2Y0izG1fqjlltxnsdpbPMRMmd3lqTTumqMw7FZY5G5mSHw5dalreiRWYGWjbZ7gYUlFa0xOtIWA4vk1E6zWEoI+FvyYrjSAO1FG8DCmQGKd+DJFsGogWVVFiP/GWbga9Svg9NgtPQvnd04fUNCcriSBF+vqZ5nn9PQ+Xs4q401oI6EP0R+BkyXoAeAtcgBfwidnvkVaMVFTO6n1JoWTfqiONw1MVP8e6l3GVwOPJZXW5VItGGiuduAu5CZdOrMQJ1CHqpIFccS+LxaD/3Hcr7vF0Xw7UdAwQ/xduLGkJ6aUMhVAuwU006B3wM+ZLmozJ5QRhWkGs9yjKw1fhwDsq8eE/F+y+i1CeHIxD1wppupXrA5xyUOjQHMzU3cyjTeS2aaaN2Fzoc1bhch3xspuqBTkDulQVUz1q4mYEbNuewQD3FexGFS1VjOLoRHwOOinj9HAooXY2CSidHHKeSI5GFcRWNdSxqR7VH1iHHeTV24R+X53C8hSCBvPPqnD8B+AOygn6OYAm0ORSGthLl8B0d4DtRmIKsoMsJF1U/Hi1dt6DusIN8PrsIlUdwOAITpDFlC6q3MTbgmHm011qGepOvQSXPipyOCujW6rxqk0dRWYsVFe8PRSn5JxWOoEvdfOGzfnF5tnCRK+bGi33MoB1hL0U5d1H5J5oVD6A5mp8sQS6KSWh5e0jEcR4BPmhKqJA4xTM3XuxjBlW8DuRacDU3y0myNbNTPHPjxT5m0GTN15A/zVFiI+HKYzgc/ydMlrRfgmQWuYn0F91xJEQYxVuDnMcOrQAWJi2EI72ErQviwqLEQpQ+5XBEIqzi3YWLwF+BMiMcjshEqYR1Gdk1KmxBsaR9SQviSDdRFK8fxVU+YliWZmcbcq7vSFoQR/qJWvuxD0WgLDYoSzPzAqowtjVhORwDhEaKru4GPoliGgcyy4Hj0DLT4TBCo9WO88jQ8Bns97lLghvRTOfqqDiMYqrM+HyUYdBtaLykeRmlK12C9rQOh1FM1vd/HqUIzaT5e+LVoh/VxByHShs6HFaw0VjjHhTxP5d0LT+fRnu5q3HuAodlbHW02Q5cDByM+sw1642cRylCx6PeZiuTFScUFxK+f19QovaRS+t4tsasxhvABbZbSfUCV6CM7qtQl6Fm4E1U22UqcAYqvZ42fgJMxH6vdYc5nkBlSW6Pq4fbS6hb6jg0u9yGug7FyS5U1+UcVBbwbFSuMM1sQ1bXK4A9CcviqM0e9H80HdUxCpwIa4McygA/GfgAcCJqmGKKXUixupEv7nHsLc2agWNQ0d9OzC+PHNHIo1XeLCoe8kkqXiUtwKFoWXoEKqk3BpWLaC8cXsV8HT1J+tFTZKvn+DMqFZi1knvtyKg1O2lBHADcCVxEedNSAP4HJcsr0NNWHVUAAAAASUVORK5CYII="); 28 | 29 | --keyword: #5e8f60; 30 | --identifier: #222; 31 | --comment: #484a86; 32 | --operator: #155da4; 33 | --punctuation: black; 34 | --other: black; 35 | --escapeSequence: #c4891b; 36 | --number: #252dbe; 37 | --literal: #a4255b; 38 | --program: #6060c0; 39 | --option: #508000; 40 | --raw-data: #a4255b; 41 | } 42 | 43 | [data-theme="dark"] { 44 | --primary-background: #171921; 45 | --secondary-background: #1e202a; 46 | --third-background: #2b2e3b; 47 | --info-background: #008000; 48 | --warning-background: #807000; 49 | --error-background: #c03000; 50 | --border: #0e1014; 51 | --text: #fff; 52 | --anchor: #8be9fd; 53 | --anchor-focus: #8be9fd; 54 | --input-focus: #8be9fd; 55 | --strong: #bd93f9; 56 | --hint: #7A7C85; 57 | --nim-sprite-base64: url("data:image/png;base64,iVBORw0KGgoAAAANSUhEUgAAARMAAABMCAYAAABOBlMuAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjE4OjIyKzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMToyMDoxMCswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDplZGViMzU3MC1iNmZjLWQy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"); 58 | 59 | --keyword: #ff79c6; 60 | --identifier: #f8f8f2; 61 | --comment: #6272a4; 62 | --operator: #ff79c6; 63 | --punctuation: #f8f8f2; 64 | --other: #f8f8f2; 65 | --escapeSequence: #bd93f9; 66 | --number: #bd93f9; 67 | --literal: #f1fa8c; 68 | --program: #9090c0; 69 | --option: #90b010; 70 | --raw-data: #8be9fd; 71 | } 72 | 73 | .theme-switch-wrapper { 74 | display: flex; 75 | align-items: center; 76 | } 77 | 78 | .theme-switch-wrapper em { 79 | margin-left: 10px; 80 | font-size: 1rem; 81 | } 82 | 83 | .theme-switch { 84 | display: inline-block; 85 | height: 22px; 86 | position: relative; 87 | width: 50px; 88 | } 89 | 90 | .theme-switch input { 91 | display: none; 92 | } 93 | 94 | .slider { 95 | background-color: #ccc; 96 | bottom: 0; 97 | cursor: pointer; 98 | left: 0; 99 | position: absolute; 100 | right: 0; 101 | top: 0; 102 | transition: .4s; 103 | } 104 | 105 | .slider:before { 106 | background-color: #fff; 107 | bottom: 4px; 108 | content: ""; 109 | height: 13px; 110 | left: 4px; 111 | position: absolute; 112 | transition: .4s; 113 | width: 13px; 114 | } 115 | 116 | input:checked + .slider { 117 | background-color: #66bb6a; 118 | } 119 | 120 | input:checked + .slider:before { 121 | transform: translateX(26px); 122 | } 123 | 124 | .slider.round { 125 | border-radius: 17px; 126 | } 127 | 128 | .slider.round:before { 129 | border-radius: 50%; 130 | } 131 | 132 | html { 133 | font-size: 100%; 134 | -webkit-text-size-adjust: 100%; 135 | -ms-text-size-adjust: 100%; } 136 | 137 | body { 138 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 139 | font-weight: 400; 140 | font-size: 1.125em; 141 | line-height: 1.5; 142 | color: var(--text); 143 | background-color: var(--primary-background); } 144 | 145 | /* Skeleton grid */ 146 | .container { 147 | position: relative; 148 | width: 100%; 149 | max-width: 1050px; 150 | margin: 0 auto; 151 | padding: 0; 152 | box-sizing: border-box; } 153 | 154 | .column, 155 | .columns { 156 | width: 100%; 157 | float: left; 158 | box-sizing: border-box; 159 | margin-left: 1%; 160 | } 161 | 162 | .column:first-child, 163 | .columns:first-child { 164 | margin-left: 0; } 165 | 166 | .three.columns { 167 | width: 22%; 168 | } 169 | 170 | .nine.columns { 171 | width: 77.0%; } 172 | 173 | .twelve.columns { 174 | width: 100%; 175 | margin-left: 0; } 176 | 177 | @media screen and (max-width: 860px) { 178 | .three.columns { 179 | display: none; 180 | } 181 | .nine.columns { 182 | width: 98.0%; 183 | } 184 | body { 185 | font-size: 1em; 186 | line-height: 1.35; 187 | } 188 | } 189 | 190 | cite { 191 | font-style: italic !important; } 192 | 193 | 194 | /* Nim search input */ 195 | div#searchInputDiv { 196 | margin-bottom: 1em; 197 | } 198 | input#searchInput { 199 | width: 80%; 200 | } 201 | 202 | /* 203 | * Some custom formatting for input forms. 204 | * This also fixes input form colors on Firefox with a dark system theme on Linux. 205 | */ 206 | input { 207 | -moz-appearance: none; 208 | background-color: var(--secondary-background); 209 | color: var(--text); 210 | border: 1px solid var(--border); 211 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 212 | font-size: 0.9em; 213 | padding: 6px; 214 | } 215 | 216 | input:focus { 217 | border: 1px solid var(--input-focus); 218 | box-shadow: 0 0 3px var(--input-focus); 219 | } 220 | 221 | select { 222 | -moz-appearance: none; 223 | background-color: var(--secondary-background); 224 | color: var(--text); 225 | border: 1px solid var(--border); 226 | font-family: "Lato", "Helvetica Neue", "HelveticaNeue", Helvetica, Arial, sans-serif; 227 | font-size: 0.9em; 228 | padding: 6px; 229 | } 230 | 231 | select:focus { 232 | border: 1px solid var(--input-focus); 233 | box-shadow: 0 0 3px var(--input-focus); 234 | } 235 | 236 | /* Docgen styles */ 237 | 238 | :target { 239 | border: 2px solid #B5651D; 240 | border-style: dotted; 241 | } 242 | 243 | /* Links */ 244 | a { 245 | color: var(--anchor); 246 | text-decoration: none; 247 | } 248 | 249 | a span.Identifier { 250 | text-decoration: underline; 251 | text-decoration-color: #aab; 252 | } 253 | 254 | a.reference-toplevel { 255 | font-weight: bold; 256 | } 257 | 258 | a.toc-backref { 259 | text-decoration: none; 260 | color: var(--text); } 261 | 262 | a.link-seesrc { 263 | color: #607c9f; 264 | font-size: 0.9em; 265 | font-style: italic; } 266 | 267 | a:hover, 268 | a:focus { 269 | color: var(--anchor-focus); 270 | text-decoration: underline; } 271 | 272 | a:hover span.Identifier { 273 | color: var(--anchor); 274 | } 275 | 276 | 277 | sub, 278 | sup { 279 | position: relative; 280 | font-size: 75%; 281 | line-height: 0; 282 | vertical-align: baseline; } 283 | 284 | sup { 285 | top: -0.5em; } 286 | 287 | sub { 288 | bottom: -0.25em; } 289 | 290 | img { 291 | width: auto; 292 | height: auto; 293 | max-width: 100%; 294 | vertical-align: middle; 295 | border: 0; 296 | -ms-interpolation-mode: bicubic; } 297 | 298 | @media print { 299 | * { 300 | color: black !important; 301 | text-shadow: none !important; 302 | background: transparent !important; 303 | box-shadow: none !important; } 304 | 305 | a, 306 | a:visited { 307 | text-decoration: underline; } 308 | 309 | a[href]:after { 310 | content: " (" attr(href) ")"; } 311 | 312 | abbr[title]:after { 313 | content: " (" attr(title) ")"; } 314 | 315 | .ir a:after, 316 | a[href^="javascript:"]:after, 317 | a[href^="#"]:after { 318 | content: ""; } 319 | 320 | pre, 321 | blockquote { 322 | border: 1px solid #999; 323 | page-break-inside: avoid; } 324 | 325 | thead { 326 | display: table-header-group; } 327 | 328 | tr, 329 | img { 330 | page-break-inside: avoid; } 331 | 332 | img { 333 | max-width: 100% !important; } 334 | 335 | @page { 336 | margin: 0.5cm; } 337 | 338 | h1 { 339 | page-break-before: always; } 340 | 341 | h1.title { 342 | page-break-before: avoid; } 343 | 344 | p, 345 | h2, 346 | h3 { 347 | orphans: 3; 348 | widows: 3; } 349 | 350 | h2, 351 | h3 { 352 | page-break-after: avoid; } 353 | } 354 | 355 | 356 | p { 357 | margin-top: 0.5em; 358 | margin-bottom: 0.5em; 359 | } 360 | 361 | small { 362 | font-size: 85%; } 363 | 364 | strong { 365 | font-weight: 600; 366 | font-size: 0.95em; 367 | color: var(--strong); 368 | } 369 | 370 | em { 371 | font-style: italic; } 372 | 373 | h1 { 374 | font-size: 1.8em; 375 | font-weight: 400; 376 | padding-bottom: .25em; 377 | border-bottom: 6px solid var(--third-background); 378 | margin-top: 2.5em; 379 | margin-bottom: 1em; 380 | line-height: 1.2em; } 381 | 382 | h1.title { 383 | padding-bottom: 1em; 384 | border-bottom: 0px; 385 | font-size: 2.5em; 386 | text-align: center; 387 | font-weight: 900; 388 | margin-top: 0.75em; 389 | margin-bottom: 0em; 390 | } 391 | 392 | h2 { 393 | font-size: 1.3em; 394 | margin-top: 2em; } 395 | 396 | h2.subtitle { 397 | margin-top: 0em; 398 | text-align: center; } 399 | 400 | h3 { 401 | font-size: 1.125em; 402 | font-style: italic; 403 | margin-top: 1.5em; } 404 | 405 | h4 { 406 | font-size: 1.125em; 407 | margin-top: 1em; } 408 | 409 | h5 { 410 | font-size: 1.125em; 411 | margin-top: 0.75em; } 412 | 413 | h6 { 414 | font-size: 1.1em; } 415 | 416 | 417 | ul, 418 | ol { 419 | padding: 0; 420 | margin-top: 0.5em; 421 | margin-left: 0.75em; } 422 | 423 | ul ul, 424 | ul ol, 425 | ol ol, 426 | ol ul { 427 | margin-bottom: 0; 428 | margin-left: 1.25em; } 429 | 430 | ul.simple > li { 431 | list-style-type: circle; 432 | } 433 | 434 | ul.simple-boot li { 435 | list-style-type: none; 436 | margin-left: 0em; 437 | margin-bottom: 0.5em; 438 | } 439 | 440 | ol.simple > li, ul.simple > li { 441 | margin-bottom: 0.2em; 442 | margin-left: 0.4em } 443 | 444 | ul.simple.simple-toc > li { 445 | margin-top: 1em; 446 | } 447 | 448 | ul.simple-toc { 449 | list-style: none; 450 | font-size: 0.9em; 451 | margin-left: -0.3em; 452 | margin-top: 1em; } 453 | 454 | ul.simple-toc > li { 455 | list-style-type: none; 456 | } 457 | 458 | ul.simple-toc-section { 459 | list-style-type: circle; 460 | margin-left: 0.8em; 461 | color: #6c9aae; } 462 | 463 | ul.nested-toc-section { 464 | list-style-type: circle; 465 | margin-left: -0.75em; 466 | color: var(--text); 467 | } 468 | 469 | ul.nested-toc-section > li { 470 | margin-left: 1.25em; 471 | } 472 | 473 | 474 | ol.arabic { 475 | list-style: decimal; } 476 | 477 | ol.loweralpha { 478 | list-style: lower-alpha; } 479 | 480 | ol.upperalpha { 481 | list-style: upper-alpha; } 482 | 483 | ol.lowerroman { 484 | list-style: lower-roman; } 485 | 486 | ol.upperroman { 487 | list-style: upper-roman; } 488 | 489 | ul.auto-toc { 490 | list-style-type: none; } 491 | 492 | 493 | dl { 494 | margin-bottom: 1.5em; } 495 | 496 | dt { 497 | margin-bottom: -0.5em; 498 | margin-left: 0.0em; } 499 | 500 | dd { 501 | margin-left: 2.0em; 502 | margin-bottom: 3.0em; 503 | margin-top: 0.5em; } 504 | 505 | 506 | hr { 507 | margin: 2em 0; 508 | border: 0; 509 | border-top: 1px solid #aaa; } 510 | 511 | hr.footnote { 512 | width: 25%; 513 | border-top: 0.15em solid #999; 514 | margin-bottom: 0.15em; 515 | margin-top: 0.15em; 516 | } 517 | div.footnote-group { 518 | margin-left: 1em; } 519 | div.footnote-label { 520 | display: inline-block; 521 | min-width: 1.7em; 522 | } 523 | 524 | div.option-list { 525 | border: 0.1em solid var(--border); 526 | } 527 | div.option-list-item { 528 | padding-left: 12em; 529 | padding-right: 0; 530 | padding-bottom: 0.3em; 531 | padding-top: 0.3em; 532 | } 533 | div.odd { 534 | background-color: var(--secondary-background); 535 | } 536 | div.option-list-label { 537 | margin-left: -11.5em; 538 | margin-right: 0em; 539 | min-width: 11.5em; 540 | display: inline-block; 541 | vertical-align: top; 542 | } 543 | div.option-list-description { 544 | width: calc(100% - 1em); 545 | padding-left: 1em; 546 | padding-right: 0; 547 | display: inline-block; 548 | } 549 | 550 | blockquote { 551 | font-size: 0.9em; 552 | font-style: italic; 553 | padding-left: 0.5em; 554 | margin-left: 0; 555 | border-left: 5px solid #bbc; 556 | } 557 | 558 | .pre, span.tok { 559 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 560 | font-weight: 500; 561 | font-size: 0.85em; 562 | color: var(--text); 563 | background-color: var(--third-background); 564 | padding-left: 3px; 565 | padding-right: 3px; 566 | border-radius: 4px; 567 | } 568 | 569 | span.tok { 570 | border: 1px solid #808080; 571 | padding-bottom: 0.1em; 572 | margin-right: 0.2em; 573 | } 574 | 575 | pre { 576 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 577 | color: var(--text); 578 | font-weight: 500; 579 | display: inline-block; 580 | box-sizing: border-box; 581 | min-width: 100%; 582 | padding: 0.5em; 583 | margin-top: 0.5em; 584 | margin-bottom: 0.5em; 585 | font-size: 0.85em; 586 | white-space: pre !important; 587 | overflow-y: hidden; 588 | overflow-x: visible; 589 | background-color: var(--secondary-background); 590 | border: 1px solid var(--border); 591 | -webkit-border-radius: 6px; 592 | -moz-border-radius: 6px; 593 | border-radius: 6px; } 594 | 595 | .pre-scrollable { 596 | max-height: 340px; 597 | overflow-y: scroll; } 598 | 599 | 600 | /* Nim line-numbered tables */ 601 | .line-nums-table { 602 | width: 100%; 603 | table-layout: fixed; } 604 | 605 | table.line-nums-table { 606 | border-radius: 4px; 607 | border: 1px solid #cccccc; 608 | background-color: ghostwhite; 609 | border-collapse: separate; 610 | margin-top: 15px; 611 | margin-bottom: 25px; } 612 | 613 | .line-nums-table tbody { 614 | border: none; } 615 | 616 | .line-nums-table td pre { 617 | border: none; 618 | background-color: transparent; } 619 | 620 | .line-nums-table td.blob-line-nums { 621 | width: 28px; } 622 | 623 | .line-nums-table td.blob-line-nums pre { 624 | color: #b0b0b0; 625 | -webkit-filter: opacity(75%); 626 | filter: opacity(75%); 627 | text-align: right; 628 | border-color: transparent; 629 | background-color: transparent; 630 | padding-left: 0px; 631 | margin-left: 0px; 632 | padding-right: 0px; 633 | margin-right: 0px; } 634 | 635 | 636 | table { 637 | max-width: 100%; 638 | background-color: transparent; 639 | margin-top: 0.5em; 640 | margin-bottom: 1.5em; 641 | border-collapse: collapse; 642 | border-color: var(--third-background); 643 | border-spacing: 0; 644 | font-size: 0.9em; 645 | } 646 | 647 | table th, table td { 648 | padding: 0px 0.5em 0px; 649 | border-color: var(--third-background); 650 | } 651 | 652 | table th { 653 | background-color: var(--third-background); 654 | border-color: var(--third-background); 655 | font-weight: bold; } 656 | 657 | table th.docinfo-name { 658 | background-color: transparent; 659 | text-align: right; 660 | } 661 | 662 | table tr:hover { 663 | background-color: var(--third-background); } 664 | 665 | 666 | /* rst2html default used to remove borders from tables and images */ 667 | .borderless, table.borderless td, table.borderless th { 668 | border: 0; } 669 | 670 | table.borderless td, table.borderless th { 671 | /* Override padding for "table.docutils td" with "! important". 672 | The right padding separates the table cells. */ 673 | padding: 0 0.5em 0 0 !important; } 674 | 675 | .admonition { 676 | padding: 0.3em; 677 | background-color: var(--secondary-background); 678 | border-left: 0.4em solid #7f7f84; 679 | margin-bottom: 0.5em; 680 | -webkit-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 681 | -moz-box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 682 | box-shadow: 0 5px 8px -6px rgba(0,0,0,.2); 683 | } 684 | .admonition-info { 685 | border-color: var(--info-background); 686 | } 687 | .admonition-info-text { 688 | color: var(--info-background); 689 | } 690 | .admonition-warning { 691 | border-color: var(--warning-background); 692 | } 693 | .admonition-warning-text { 694 | color: var(--warning-background); 695 | } 696 | .admonition-error { 697 | border-color: var(--error-background); 698 | } 699 | .admonition-error-text { 700 | color: var(--error-background); 701 | } 702 | 703 | .first { 704 | /* Override more specific margin styles with "! important". */ 705 | margin-top: 0 !important; } 706 | 707 | .last, .with-subtitle { 708 | margin-bottom: 0 !important; } 709 | 710 | .hidden { 711 | display: none; } 712 | 713 | blockquote.epigraph { 714 | margin: 2em 5em; } 715 | 716 | dl.docutils dd { 717 | margin-bottom: 0.5em; } 718 | 719 | object[type="image/svg+xml"], object[type="application/x-shockwave-flash"] { 720 | overflow: hidden; } 721 | 722 | 723 | div.figure { 724 | margin-left: 2em; 725 | margin-right: 2em; } 726 | 727 | div.footer, div.header { 728 | clear: both; 729 | text-align: center; 730 | color: #666; 731 | font-size: smaller; } 732 | 733 | div.footer { 734 | padding-top: 5em; 735 | } 736 | 737 | div.line-block { 738 | display: block; 739 | margin-top: 1em; 740 | margin-bottom: 1em; } 741 | 742 | div.line-block div.line-block { 743 | margin-top: 0; 744 | margin-bottom: 0; 745 | margin-left: 1.5em; } 746 | 747 | div.topic { 748 | margin: 2em; } 749 | 750 | div.search_results { 751 | background-color: var(--third-background); 752 | margin: 3em; 753 | padding: 1em; 754 | border: 1px solid #4d4d4d; 755 | } 756 | 757 | div#global-links ul { 758 | margin-left: 0; 759 | list-style-type: none; 760 | } 761 | 762 | div#global-links > simple-boot { 763 | margin-left: 3em; 764 | } 765 | 766 | hr.docutils { 767 | width: 75%; } 768 | 769 | img.align-left, .figure.align-left, object.align-left { 770 | clear: left; 771 | float: left; 772 | margin-right: 1em; } 773 | 774 | img.align-right, .figure.align-right, object.align-right { 775 | clear: right; 776 | float: right; 777 | margin-left: 1em; } 778 | 779 | img.align-center, .figure.align-center, object.align-center { 780 | display: block; 781 | margin-left: auto; 782 | margin-right: auto; } 783 | 784 | .align-left { 785 | text-align: left; } 786 | 787 | .align-center { 788 | clear: both; 789 | text-align: center; } 790 | 791 | .align-right { 792 | text-align: right; } 793 | 794 | /* reset inner alignment in figures */ 795 | div.align-right { 796 | text-align: inherit; } 797 | 798 | p.attribution { 799 | text-align: right; 800 | margin-left: 50%; } 801 | 802 | p.caption { 803 | font-style: italic; } 804 | 805 | p.credits { 806 | font-style: italic; 807 | font-size: smaller; } 808 | 809 | p.label { 810 | white-space: nowrap; } 811 | 812 | p.rubric { 813 | font-weight: bold; 814 | font-size: larger; 815 | color: maroon; 816 | text-align: center; } 817 | 818 | p.topic-title { 819 | font-weight: bold; } 820 | 821 | pre.address { 822 | margin-bottom: 0; 823 | margin-top: 0; 824 | font: inherit; } 825 | 826 | pre.literal-block, pre.doctest-block, pre.math, pre.code { 827 | margin-left: 2em; 828 | margin-right: 2em; } 829 | 830 | pre.code .ln { 831 | color: grey; } 832 | 833 | /* line numbers */ 834 | pre.code, code { 835 | background-color: #eeeeee; } 836 | 837 | pre.code .comment, code .comment { 838 | color: #5c6576; } 839 | 840 | pre.code .keyword, code .keyword { 841 | color: #3B0D06; 842 | font-weight: bold; } 843 | 844 | pre.code .literal.string, code .literal.string { 845 | color: #0c5404; } 846 | 847 | pre.code .name.builtin, code .name.builtin { 848 | color: #352b84; } 849 | 850 | pre.code .deleted, code .deleted { 851 | background-color: #DEB0A1; } 852 | 853 | pre.code .inserted, code .inserted { 854 | background-color: #A3D289; } 855 | 856 | span.classifier { 857 | font-style: oblique; } 858 | 859 | span.classifier-delimiter { 860 | font-weight: bold; } 861 | 862 | span.problematic { 863 | color: #b30000; } 864 | 865 | span.section-subtitle { 866 | /* font-size relative to parent (h1..h6 element) */ 867 | font-size: 80%; } 868 | 869 | span.DecNumber { 870 | color: var(--number); } 871 | 872 | span.BinNumber { 873 | color: var(--number); } 874 | 875 | span.HexNumber { 876 | color: var(--number); } 877 | 878 | span.OctNumber { 879 | color: var(--number); } 880 | 881 | span.FloatNumber { 882 | color: var(--number); } 883 | 884 | span.Identifier { 885 | color: var(--identifier); } 886 | 887 | span.Keyword { 888 | font-weight: 600; 889 | color: var(--keyword); } 890 | 891 | span.StringLit { 892 | color: var(--literal); } 893 | 894 | span.LongStringLit { 895 | color: var(--literal); } 896 | 897 | span.CharLit { 898 | color: var(--literal); } 899 | 900 | span.EscapeSequence { 901 | color: var(--escapeSequence); } 902 | 903 | span.Operator { 904 | color: var(--operator); } 905 | 906 | span.Punctuation { 907 | color: var(--punctuation); } 908 | 909 | span.Comment, span.LongComment { 910 | font-style: italic; 911 | font-weight: 400; 912 | color: var(--comment); } 913 | 914 | span.RegularExpression { 915 | color: darkviolet; } 916 | 917 | span.TagStart { 918 | color: darkviolet; } 919 | 920 | span.TagEnd { 921 | color: darkviolet; } 922 | 923 | span.Key { 924 | color: #252dbe; } 925 | 926 | span.Value { 927 | color: #252dbe; } 928 | 929 | span.RawData { 930 | color: var(--raw-data); } 931 | 932 | span.Assembler { 933 | color: #252dbe; } 934 | 935 | span.Preprocessor { 936 | color: #252dbe; } 937 | 938 | span.Directive { 939 | color: #252dbe; } 940 | 941 | span.option { 942 | font-weight: bold; 943 | font-family: "Source Code Pro", Monaco, Menlo, Consolas, "Courier New", monospace; 944 | color: var(--option); 945 | } 946 | 947 | span.Prompt { 948 | font-weight: bold; 949 | color: red; } 950 | 951 | span.ProgramOutput { 952 | font-weight: bold; 953 | color: #808080; } 954 | 955 | span.program { 956 | font-weight: bold; 957 | color: var(--program); 958 | text-decoration: underline; 959 | text-decoration-color: var(--hint); 960 | text-decoration-thickness: 0.05em; 961 | text-underline-offset: 0.15em; 962 | } 963 | 964 | span.Command, span.Rule, span.Hyperlink, span.Label, span.Reference, 965 | span.Other { 966 | color: var(--other); } 967 | 968 | /* Pop type, const, proc, and iterator defs in nim def blocks */ 969 | dt pre > span.Identifier, dt pre > span.Operator { 970 | color: var(--identifier); 971 | font-weight: 700; } 972 | 973 | dt pre > span.Keyword ~ span.Identifier, dt pre > span.Identifier ~ span.Identifier, 974 | dt pre > span.Operator ~ span.Identifier, dt pre > span.Other ~ span.Identifier { 975 | color: var(--identifier); 976 | font-weight: inherit; } 977 | 978 | /* Nim sprite for the footer (taken from main page favicon) */ 979 | .nim-sprite { 980 | display: inline-block; 981 | width: 51px; 982 | height: 14px; 983 | background-position: 0 0; 984 | background-size: 51px 14px; 985 | -webkit-filter: opacity(50%); 986 | filter: opacity(50%); 987 | background-repeat: no-repeat; 988 | background-image: var(--nim-sprite-base64); 989 | margin-bottom: 5px; } 990 | 991 | span.pragmadots { 992 | /* Position: relative frees us up to make the dots 993 | look really nice without fucking up the layout and 994 | causing bulging in the parent container */ 995 | position: relative; 996 | /* 1px down looks slightly nicer */ 997 | top: 1px; 998 | padding: 2px; 999 | background-color: var(--third-background); 1000 | border-radius: 4px; 1001 | margin: 0 2px; 1002 | cursor: pointer; 1003 | font-size: 0.8em; 1004 | } 1005 | 1006 | span.pragmadots:hover { 1007 | background-color: var(--hint); 1008 | } 1009 | span.pragmawrap { 1010 | display: none; 1011 | } 1012 | 1013 | span.attachedType { 1014 | display: none; 1015 | visibility: hidden; 1016 | } 1017 | -------------------------------------------------------------------------------- /src/binance.nim: -------------------------------------------------------------------------------- 1 | import std/[times, httpcore, json, strutils, tables, os, algorithm, macros], binance/binance_sha256 2 | 3 | when defined(js): import nodejs/jshttpclient 4 | else: import std/httpclient 5 | 6 | 7 | type 8 | Binance* = object ## Binance API Client. 9 | apiSecret*: string ## Get API Key and API Secret at https://www.binance.com/en/my/settings/api-management 10 | client: (when defined(js): JsHttpClient else: HttpClient) 11 | 12 | Side* = enum 13 | SIDE_BUY = "BUY" 14 | SIDE_SELL = "SELL" 15 | 16 | Interval* = enum 17 | KLINE_INTERVAL_1MINUTE = "1m" 18 | KLINE_INTERVAL_3MINUTE = "3m" 19 | KLINE_INTERVAL_5MINUTE = "5m" 20 | KLINE_INTERVAL_15MINUTE = "15m" 21 | KLINE_INTERVAL_30MINUTE = "30m" 22 | KLINE_INTERVAL_1HOUR = "1h" 23 | KLINE_INTERVAL_2HOUR = "2h" 24 | KLINE_INTERVAL_4HOUR = "4h" 25 | KLINE_INTERVAL_6HOUR = "6h" 26 | KLINE_INTERVAL_8HOUR = "8h" 27 | KLINE_INTERVAL_12HOUR = "12h" 28 | KLINE_INTERVAL_1DAY = "1d" 29 | KLINE_INTERVAL_3DAY = "3d" 30 | KLINE_INTERVAL_1WEEK = "1w" 31 | KLINE_INTERVAL_1MONTH = "1M" 32 | 33 | OrderType* = enum 34 | ORDER_TYPE_LIMIT = "LIMIT" 35 | ORDER_TYPE_MARKET = "MARKET" 36 | ORDER_TYPE_STOP_LOSS = "STOP_LOSS" 37 | ORDER_TYPE_STOP_LOSS_LIMIT = "STOP_LOSS_LIMIT" 38 | ORDER_TYPE_TAKE_PROFIT = "TAKE_PROFIT" 39 | ORDER_TYPE_TAKE_PROFIT_LIMIT = "TAKE_PROFIT_LIMIT" 40 | ORDER_TYPE_LIMIT_MAKER = "LIMIT_MAKER" 41 | ORDER_TYPE_TRAILING_STOP_MARKET = "TRAILING_STOP_MARKET" 42 | ORDER_TYPE_STOP_MARKET = "STOP_MARKET" 43 | ORDER_TYPE_STOP_LIMIT = "STOP_LIMIT" 44 | 45 | IncomeType* {.pure.} = enum 46 | TRANSFER = "TRANSFER" 47 | WELCOME_BONUS = "WELCOME_BONUS" 48 | REALIZED_PNL = "REALIZED_PNL" 49 | FUNDING_FEE = "FUNDING_FEE" 50 | COMMISSION = "COMMISSION" 51 | INSURANCE_CLEAR = "INSURANCE_CLEAR" 52 | REFERRAL_KICKBACK = "REFERRAL_KICKBACK" 53 | COMMISSION_REBATE = "COMMISSION_REBATE" 54 | DELIVERED_SETTELMENT = "DELIVERED_SETTELMENT" 55 | COIN_SWAP_DEPOSIT = "COIN_SWAP_DEPOSIT" 56 | COIN_SWAP_WITHDRAW = "COIN_SWAP_WITHDRAW" 57 | 58 | TransferType* {.pure.} = enum 59 | spotToFutures = "1" ## Spot to Futures. 60 | futuresToSpot = "2" ## Futures to Spot. 61 | spotToFuturesCoin = "3" ## Spot to Futures Coin-M. 62 | futuresCoinToSpot = "4" ## Futures Coin-M to Spot. 63 | 64 | 65 | const stableCoins* = ["USDT", "BUSD", "USDC", "DAI", "USDP"] 66 | 67 | 68 | macro unrollEncodeQuery*(target: var string; args: openArray[(string, auto)]; charL: static[char] = '&'; charR: static[char] = '\0') = 69 | doAssert args.len > 0, "Iterable must not be empty, because theres nothing to unroll" 70 | result = newStmtList() 71 | if charL != '\0': result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), newLit(charL)) 72 | for i, item in args: 73 | let key: string = item[1][0].strVal 74 | doAssert key.len > 0, "Key must not be empty string." 75 | if i > 0: result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), newLit('&')) 76 | for c in key: result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), c.newLit) 77 | result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), newLit('=')) 78 | result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), item[1][1]) 79 | if charR != '\0': result.add nnkCall.newTree(nnkDotExpr.newTree(target, newIdentNode"add"), newLit(charR)) 80 | 81 | 82 | converter interval_to_milliseconds(interval: Interval): int = 83 | ## Get numeric part of Interval. 84 | ($interval)[0..^2].parseInt * ( 85 | case ($interval)[^1]: 86 | of 'm': 60 87 | of 'h': 60 * 60 88 | of 'd': 24 * 60 * 60 89 | of 'w': 7 * 24 * 60 * 60 90 | else : 1 91 | ) * 1_000 92 | 93 | 94 | converter date_to_milliseconds(d: Duration): int64 = 95 | ## Date to milliseconds. 96 | var epoch = initDuration(seconds = now().utc.toTime.toUnix) 97 | epoch -= d 98 | epoch.inMilliseconds 99 | 100 | 101 | template close*(self: Binance) = self.client.close() 102 | 103 | 104 | proc request*(self: Binance; endpoint: string; httpMethod: static[HttpMethod]): JsonNode = 105 | ## Httpclient request but with a Retry. 106 | when defined(js): 107 | let rekuest = JsRequest( 108 | url: endpoint, `method`: httpMethod, integrity: "", referrer: "", mode: fmCors, keepAlive: false, 109 | credentials: fcOmit, cache: fchDefault, redirect: frFollow, referrerPolicy: frpOrigin, body: cstring.default, 110 | ) 111 | result = parseJson($(self.client.request(rekuest).responseText)) 112 | else: 113 | for _ in 0 .. 9: 114 | try: 115 | result = parseJson(self.client.request(url = endpoint, httpMethod = httpMethod).body) 116 | break 117 | except CatchableError: 118 | continue 119 | 120 | 121 | template signQueryString(self: Binance; endpoint: string) = 122 | ## Sign the query string for Binance API, reusing the same string. 123 | unrollEncodeQuery(result, {"recvWindow": "60000", "timestamp": $(now().utc.toTime.toUnix * 1_000)}) 124 | let signature: string = sha256hmac(self.apiSecret, result) 125 | unrollEncodeQuery(result, {"signature": signature}) # This is special cased, starts with '&'. 126 | result = endpoint & '?' & result 127 | 128 | 129 | proc newBinance*(apiKey, apiSecret: string): Binance = 130 | ## Constructor for Binance client. 131 | assert apiKey.len >= 64, "apiKey must be a string of >= 64 chars." 132 | assert apiSecret.len >= 64, "apiSecret must be a string of >= 64 chars." 133 | when defined(js): 134 | let jeader: Headers = newHeaders() 135 | jeader.add "X-MBX-APIKEY".cstring, apiKey.cstring 136 | jeader.add "DNT".cstring, "1".cstring 137 | var client = newJsHttpClient(headers = jeader) 138 | else: 139 | var client = newHttpClient(timeout = 999_999) 140 | client.headers.add "X-MBX-APIKEY", apiKey 141 | client.headers.add "DNT", "1" 142 | result = Binance(apiSecret: apiSecret, client: client) 143 | 144 | 145 | proc accountData*(self: Binance): string = 146 | ## Get the current account information 147 | self.signQueryString("https://api.binance.com/api/v3/account") 148 | 149 | 150 | proc getWallet*(self: Binance; stablecoinsOnly = false): Table[string, float] = 151 | ## Get user wallet assets. To save memory and increase performance, only get the "free" balance, "locked" balance is ignored because is not usable whatsoever. 152 | for it in self.request(self.accountData(), HttpGet)["balances"]: 153 | let coinAmount: string = it["free"].getStr 154 | if coinAmount != "0.00000000": # Ignore "0.00000000" balances. 155 | result[it["asset"].getStr] = coinAmount.parseFloat # Only parseFloat the needed ones. 156 | 157 | 158 | proc getBalance*(self: Binance; coin: string): float = 159 | ## Get user wallet balance of 1 specific coin, its faster than `getWallet`. 160 | for it in self.request(self.accountData(), HttpGet)["balances"]: 161 | if it["asset"].getStr == coin: return it["free"].getStr.parseFloat 162 | 163 | 164 | template getPrice*(self: Binance; ticker: string): float = 165 | self.request(self.tickerPrice(ticker), HttpGet)["price"].getStr.parseFloat 166 | 167 | 168 | # Market Data ################################################################# 169 | 170 | 171 | proc avgPrice*(self: Binance, symbol: string): string = 172 | ## Current average price for a symbol. 173 | result = "https://api.binance.com/api/v3/avgPrice" 174 | unrollEncodeQuery(result, {"symbol": symbol}, charL = '?') 175 | 176 | 177 | proc orderBook*(self: Binance; symbol: string): string = 178 | ## Order book depth. 179 | result = "https://api.binance.com/api/v3/depth" 180 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}, charL = '?') 181 | 182 | 183 | proc recentTrades*(self: Binance; symbol: string): string = 184 | ## Get a list of recent Trades. 185 | result = "https://api.binance.com/api/v3/trades" 186 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}, charL = '?') 187 | 188 | 189 | proc olderTrades*(self: Binance; symbol: string; fromId: Positive): string = 190 | ## Old historical Trades. 191 | result = "https://api.binance.com/api/v3/historicalTrades" 192 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "limit": "500"}, charL = '?') 193 | 194 | 195 | proc olderTrades*(self: Binance; symbol: string): string = 196 | ## Old historical Trades. 197 | result = "https://api.binance.com/api/v3/historicalTrades" 198 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}, charL = '?') 199 | 200 | 201 | proc aggrTrades*(self: Binance; symbol: string; fromId, startTime, endTime: Positive): string = 202 | ## Aggregated Trades list. 203 | assert endTime - startTime < 24 * 36000000, "startTime/endTime must be 2 integers representing a time interval smaller than 24 hours." 204 | result = "https://api.binance.com/api/v3/aggTrades" 205 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "startTime": $startTime, "endTime": $endTime, "limit": "500"}, charL = '?') 206 | 207 | 208 | proc aggrTrades*(self: Binance; symbol: string; fromId: Positive): string = 209 | ## Aggregated Trades list. 210 | result = "https://api.binance.com/api/v3/aggTrades" 211 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "limit": "500"}, charL = '?') 212 | 213 | 214 | proc aggrTrades*(self: Binance; symbol: string): string = 215 | ## Aggregated Trades list. 216 | result = "https://api.binance.com/api/v3/aggTrades" 217 | unrollEncodeQuery(result, {"symbol": symbol}, charL = '?') 218 | 219 | 220 | proc klines*(self: Binance; symbol: string; interval: Interval, startTime, endTime: int64): string = 221 | ## Klines data, AKA Candlestick data. 222 | result = "https://api.binance.com/api/v3/klines" 223 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "interval": $interval, "limit": "500"}, charL = '?') 224 | 225 | 226 | proc klines*(self: Binance; symbol: string; interval: Interval): string = 227 | ## Klines data, AKA Candlestick data. 228 | result = "https://api.binance.com/api/v3/klines" 229 | unrollEncodeQuery(result, {"symbol": symbol, "interval": $interval, "limit": "500"}, charL = '?') 230 | 231 | 232 | proc getHistoricalKlines*(self: Binance, symbol: string, interval: Interval, start_str: Duration, end_str: Duration = initDuration(seconds = 0)): JsonNode = 233 | var 234 | output_data = newJArray() 235 | timeframe: int = interval # invoke interval_to_milliseconds 236 | start_ts: int64 = start_str 237 | idx = 0 238 | url: string 239 | temp_data: JsonNode 240 | 241 | while true: 242 | url = self.klines(symbol = symbol, interval = interval, startTime = start_str, endTime = end_str) 243 | temp_data = self.request(url, HttpGet) 244 | output_data.add temp_data 245 | 246 | # set our start timestamp using the last value in the array 247 | start_ts = temp_data[^1][0].getBiggestInt 248 | inc idx 249 | 250 | if temp_data.len < 500: # limit is 500. 251 | break 252 | 253 | start_ts += timeframe 254 | 255 | if idx %% 3 == 0: 256 | sleep 1_000 257 | 258 | output_data 259 | 260 | 261 | proc ticker24h*(self: Binance; symbol: string): string = 262 | ## Price changes in the last 24 hours. 263 | result = "https://api.binance.com/api/v3/ticker/24hr" 264 | unrollEncodeQuery(result, {"symbol": symbol}, charL = '?') 265 | 266 | 267 | proc ticker24h*(self: Binance): string {.inline.} = 268 | ## Price changes in the last 24 hours. 269 | result = "https://api.binance.com/api/v3/ticker/24hr" 270 | 271 | 272 | proc tickerPrice*(self: Binance; symbol: string): string = 273 | ## Symbol price. 274 | result = "https://api.binance.com/api/v3/ticker/price" 275 | unrollEncodeQuery(result, {"symbol": symbol}, charL = '?') 276 | 277 | 278 | proc tickerPrice*(self: Binance): string {.inline.} = 279 | ## Symbol price. 280 | result = "https://api.binance.com/api/v3/ticker/price" 281 | 282 | 283 | proc orderBookTicker*(self: Binance; symbol: string): string = 284 | ## Symbol order book. 285 | result = "https://api.binance.com/api/v3/ticker/bookTicker" 286 | unrollEncodeQuery(result, {"symbol": symbol}, charL = '?') 287 | 288 | 289 | proc orderBookTicker*(self: Binance): string {.inline.} = 290 | ## Symbol order book. 291 | result = "https://api.binance.com/api/v3/ticker/bookTicker" 292 | 293 | 294 | # Spot Trading ################################################################ 295 | 296 | 297 | proc getOrder*(self: Binance; symbol: string; orderId: Natural, origClientOrderId: Natural): string = 298 | ## Check an orders status. 299 | unrollEncodeQuery(result, {"symbol": symbol, "orderId": $orderId, "origClientOrderId": $origClientOrderId}) 300 | self.signQueryString("https://api.binance.com/api/v3/order") 301 | 302 | 303 | proc getOrder*(self: Binance; symbol: string; orderId: Natural): string = 304 | ## Check an orders status. 305 | unrollEncodeQuery(result, {"symbol": symbol, "orderId": $orderId}) 306 | self.signQueryString("https://api.binance.com/api/v3/order") 307 | 308 | 309 | proc getOrder*(self: Binance; symbol: string): string = 310 | ## Check an orders status. 311 | unrollEncodeQuery(result, {"symbol": symbol, "orderId": "0"}) 312 | self.signQueryString("https://api.binance.com/api/v3/order") 313 | 314 | 315 | proc postOrder*(self: Binance; side: Side; tipe: OrderType; symbol: string; quantity, price, stopPrice: float): string = 316 | ## Create a new order. 317 | result = "" 318 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": quantity.formatFloat(ffDecimal, 6), "timeInForce": "GTC", "price": price.formatFloat(ffDecimal, 6), "stopPrice": stopPrice.formatFloat(ffDecimal, 6)}) 319 | self.signQueryString"https://api.binance.com/api/v3/order" 320 | 321 | 322 | proc postOrder*(self: var Binance; side: Side; tipe: OrderType; symbol: string; quantity, price: float): string = 323 | ## Create a new order. 324 | result = "" 325 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": quantity.formatFloat(ffDecimal, 6)}) 326 | if tipe == ORDER_TYPE_LIMIT: unrollEncodeQuery(result, {"timeInForce": "GTC", "price": price.formatFloat(ffDecimal, 6)}) 327 | self.signQueryString"https://api.binance.com/api/v3/order" 328 | 329 | 330 | proc postOrder*(self: Binance; side: Side; tipe: OrderType; symbol: string; quantity, price: float): string = 331 | ## Create a new order. 332 | result = "" 333 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": quantity.formatFloat(ffDecimal, 6), "price": price.formatFloat(ffDecimal, 6)}) 334 | self.signQueryString"https://api.binance.com/api/v3/order" 335 | 336 | 337 | proc postOrder*(self: Binance; side: Side; tipe: OrderType; symbol: string; quantity: float): string = 338 | ## Create a new order. 339 | result = "" 340 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": quantity.formatFloat(ffDecimal, 6)}) 341 | self.signQueryString("https://api.binance.com/api/v3/order") 342 | 343 | 344 | proc orderTest*(self: Binance; side: Side; tipe: OrderType; newClientOrderId, symbol: string; quantity, price: float): string = 345 | ## Test new order creation and signature/recvWindow. Creates and validates a new order but does not send it into the matching engine. 346 | result = "" 347 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "timeInForce": "GTC", "quantity": $quantity, "price": $price, "newClientOrderId": $newClientOrderId, "newOrderRespType": "ACK"}) 348 | self.signQueryString"https://api.binance.com/api/v3/order/test" 349 | 350 | 351 | proc myTrades*(self: Binance; symbol: string): string = 352 | ## Get trades for a specific account and symbol. 353 | result = "" 354 | unrollEncodeQuery(result, {"symbol": symbol}) 355 | self.signQueryString"https://api.binance.com/api/v3/myTrades" 356 | 357 | 358 | proc rateLimitOrder*(self: Binance): string = 359 | ## Displays the users current order count usage for all intervals. 360 | self.signQueryString"https://api.binance.com/api/v3/rateLimit/order" 361 | 362 | 363 | proc orderList*(self: Binance; orderListId = 1.Positive): string = 364 | ## Retrieves all Orders based on provided optional parameters. 365 | result = "" 366 | unrollEncodeQuery(result, {"orderListId": $orderListId}) 367 | self.signQueryString"https://api.binance.com/api/v3/orderList" 368 | 369 | 370 | proc allOrderList*(self: Binance): string = 371 | ## Retrieves all Orders. 372 | self.signQueryString"https://api.binance.com/api/v3/allOrderList" 373 | 374 | 375 | proc openOrderList*(self: Binance): string = 376 | ## Retrieves all open Orders. 377 | self.signQueryString"https://api.binance.com/api/v3/openOrderList" 378 | 379 | 380 | proc newOrderOco*(self: Binance, symbol: string, side: Side, quantity, price, stopPrice, stopLimitPrice :float): string = 381 | ## Create a new OCO order. 382 | result = "" 383 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "price": price.formatFloat(ffDecimal, 6), "quantity": quantity.formatFloat(ffDecimal, 6), "stopPrice": stopPrice.formatFloat(ffDecimal, 6), "stopLimitPrice": stopLimitPrice.formatFloat(ffDecimal, 6), "stopLimitTimeInForce": "GTC"}) 384 | self.signQueryString"https://api.binance.com/api/v3/order/oco" 385 | 386 | 387 | proc openOrders*(self: Binance, symbol: string): string = 388 | ## Get all open orders on a symbol. 389 | result = "" 390 | unrollEncodeQuery(result, {"symbol": symbol}) 391 | self.signQueryString"https://api.binance.com/api/v3/openOrders" 392 | 393 | 394 | # Futures endpoints ########################################################### 395 | 396 | 397 | proc pingFutures*(self: Binance): string {.inline.} = 398 | ## Test connectivity to Binance, just a ping. 399 | result = "https://fapi.binance.com/fapi/v1/ping" 400 | 401 | 402 | proc timeFutures*(self: Binance): string {.inline.} = 403 | ## Test connectivity to the Rest API and get the current server time. 404 | result = "https://fapi.binance.com/fapi/v1/time" 405 | 406 | 407 | proc exchangeInfoFutures*(self: Binance): string {.inline.} = 408 | ## Current exchange trading rules and symbol information. 409 | result = "https://fapi.binance.com/fapi/v1/exchangeInfo" 410 | 411 | 412 | proc orderBookFutures*(self: Binance; symbol: string): string {.inline.} = 413 | result = "" 414 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 415 | self.signQueryString("https://fapi.binance.com/fapi/v1/exchangeInfo") 416 | 417 | 418 | proc recentTradesFutures*(self: Binance; symbol: string): string {.inline.} = 419 | result = "" 420 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 421 | self.signQueryString("https://fapi.binance.com/fapi/v1/trades") 422 | 423 | 424 | proc historicalTradesFutures*(self: Binance; symbol: string; fromId: Positive): string {.inline.} = 425 | result = "" 426 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "limit": "500"}) 427 | self.signQueryString("https://fapi.binance.com/fapi/v1/historicalTrades") 428 | 429 | 430 | proc aggTradesFutures*(self: Binance; symbol: string; fromId, startTime, endTime: Positive): string {.inline.} = 431 | result = "" 432 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "startTime": $startTime, "endTime": $endTime, "limit": "500"}) 433 | self.signQueryString("https://fapi.binance.com/fapi/v1/aggTrades") 434 | 435 | 436 | proc aggTradesFutures*(self: Binance; symbol: string; fromId: Positive): string {.inline.} = 437 | result = "" 438 | unrollEncodeQuery(result, {"symbol": symbol, "fromId": $fromId, "limit": "500"}) 439 | self.signQueryString("https://fapi.binance.com/fapi/v1/aggTrades") 440 | 441 | 442 | proc klinesFutures*(self: Binance; symbol: string; period: Interval, startTime, endTime: Positive): string = 443 | result = "" 444 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "period": $period, "limit": "500"}) 445 | self.signQueryString("https://fapi.binance.com/fapi/v1/klines") 446 | 447 | 448 | proc klinesFutures*(self: Binance; symbol: string; period: Interval): string = 449 | result = "" 450 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "500"}) 451 | self.signQueryString("https://fapi.binance.com/fapi/v1/klines") 452 | 453 | 454 | proc continuousKlinesFutures*(self: Binance; pair: string; period: Interval, startTime, endTime: Positive): string = 455 | result = "" 456 | unrollEncodeQuery(result, {"pair": pair, "startTime": $startTime, "endTime": $endTime, "period": $period, "contractType": "PERPETUAL", "limit": "500"}) 457 | self.signQueryString("https://fapi.binance.com/fapi/v1/continuousKlines") 458 | 459 | 460 | proc continuousKlinesFutures*(self: Binance; pair: string; period: Interval): string = 461 | result = "" 462 | unrollEncodeQuery(result, {"pair": pair, "period": $period, "contractType": "PERPETUAL", "limit": "500"}) 463 | self.signQueryString("https://fapi.binance.com/fapi/v1/continuousKlines") 464 | 465 | 466 | proc indexPriceKlinesFutures*(self: Binance; pair: string; period: Interval, startTime, endTime: Positive): string = 467 | result = "" 468 | unrollEncodeQuery(result, {"pair": pair, "startTime": $startTime, "endTime": $endTime, "period": $period, "limit": "500"}) 469 | self.signQueryString("https://fapi.binance.com/fapi/v1/indexPriceKlines") 470 | 471 | 472 | proc indexPriceKlinesFutures*(self: Binance; pair: string; period: Interval): string = 473 | result = "" 474 | unrollEncodeQuery(result, {"pair": pair, "period": $period, "limit": "500"}) 475 | self.signQueryString("https://fapi.binance.com/fapi/v1/indexPriceKlines") 476 | 477 | 478 | proc markPriceKlinesFutures*(self: Binance; pair: string; period: Interval, startTime, endTime: Positive): string = 479 | result = "" 480 | unrollEncodeQuery(result, {"pair": pair, "startTime": $startTime, "endTime": $endTime, "period": $period, "limit": "500"}) 481 | self.signQueryString("https://fapi.binance.com/fapi/v1/markPriceKlines") 482 | 483 | 484 | proc markPriceKlinesFutures*(self: Binance; pair: string; period: Interval): string = 485 | result = "" 486 | unrollEncodeQuery(result, {"pair": pair, "period": $period, "limit": "500"}) 487 | self.signQueryString("https://fapi.binance.com/fapi/v1/markPriceKlines") 488 | 489 | 490 | proc markPriceFutures*(self: Binance; symbol: string): string = 491 | ## Mark Price AKA Premium Index. 492 | result = "" 493 | unrollEncodeQuery(result, {"symbol": symbol}) 494 | self.signQueryString("https://fapi.binance.com/fapi/v1/premiumIndex") 495 | 496 | 497 | proc fundingRateFutures*(self: Binance; symbol: string; startTime, endTime: Positive): string = 498 | result = "" 499 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "limit": "500"}) 500 | self.signQueryString("https://fapi.binance.com/fapi/v1/fundingRate") 501 | 502 | 503 | proc fundingRateFutures*(self: Binance; symbol: string): string = 504 | result = "" 505 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 506 | self.signQueryString("https://fapi.binance.com/fapi/v1/fundingRate") 507 | 508 | 509 | proc ticker24hrFutures*(self: Binance; symbol: string): string = 510 | result = "" 511 | unrollEncodeQuery(result, {"symbol": symbol}) 512 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/24hr") 513 | 514 | 515 | proc tickerPriceFutures*(self: Binance; symbol: string): string = 516 | result = "" 517 | unrollEncodeQuery(result, {"symbol": symbol}) 518 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/price") 519 | 520 | 521 | proc tickerPriceFutures*(self: Binance): string = 522 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/price") 523 | 524 | 525 | proc tickerBookTickerFutures*(self: Binance; symbol: string): string = 526 | result = "" 527 | unrollEncodeQuery(result, {"symbol": symbol}) 528 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/bookTicker") 529 | 530 | 531 | proc tickerBookTickerFutures*(self: Binance): string = 532 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/bookTicker") 533 | 534 | 535 | proc openInterestFutures*(self: Binance; symbol: string): string = 536 | result = "" 537 | unrollEncodeQuery(result, {"symbol": symbol}) 538 | self.signQueryString("https://fapi.binance.com/fapi/v1/ticker/openInterest") 539 | 540 | 541 | proc openInterestHistFutures*(self: Binance; symbol: string; period: Interval; startTime, endTime: Positive): string = 542 | result = "" 543 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "period": $period, "limit": "30"}) 544 | self.signQueryString("https://fapi.binance.com/futures/data/openInterestHist") 545 | 546 | 547 | proc openInterestHistFutures*(self: Binance; symbol: string; period: Interval): string = 548 | result = "" 549 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "30"}) 550 | self.signQueryString("https://fapi.binance.com/futures/data/openInterestHist") 551 | 552 | 553 | proc topLongShortAccountRatioFutures*(self: Binance; symbol: string; period: Interval; startTime, endTime: Positive): string = 554 | result = "" 555 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "startTime": $startTime, "endTime": $endTime, "limit": "30"}) 556 | self.signQueryString("https://fapi.binance.com/futures/data/topLongShortAccountRatio") 557 | 558 | 559 | proc topLongShortAccountRatioFutures*(self: Binance; symbol: string; period: Interval): string = 560 | result = "" 561 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "30"}) 562 | self.signQueryString("https://fapi.binance.com/futures/data/topLongShortAccountRatio") 563 | 564 | 565 | proc topLongShortPositionRatioFutures*(self: Binance; symbol: string; period: Interval; startTime, endTime: Positive): string = 566 | result = "" 567 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "startTime": $startTime, "endTime": $endTime, "limit": "30"}) 568 | self.signQueryString("https://fapi.binance.com/futures/data/topLongShortPositionRatio") 569 | 570 | 571 | proc topLongShortPositionRatioFutures*(self: Binance; symbol: string; period: Interval): string = 572 | result = "" 573 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "30"}) 574 | self.signQueryString("https://fapi.binance.com/futures/data/topLongShortPositionRatio") 575 | 576 | 577 | proc globalLongShortAccountRatioFutures*(self: Binance; symbol: string; period: Interval; startTime, endTime: Positive): string = 578 | result = "" 579 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "startTime": $startTime, "endTime": $endTime, "limit": "30"}) 580 | self.signQueryString("https://fapi.binance.com/futures/data/globalLongShortAccountRatio") 581 | 582 | 583 | proc globalLongShortAccountRatioFutures*(self: Binance; symbol: string; period: Interval): string = 584 | result = "" 585 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "30"}) 586 | self.signQueryString("https://fapi.binance.com/futures/data/globalLongShortAccountRatio") 587 | 588 | 589 | proc takerlongshortRatioFutures*(self: Binance; symbol: string; period: Interval; startTime, endTime: Positive): string = 590 | result = "" 591 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "startTime": $startTime, "endTime": $endTime, "limit": "30"}) 592 | self.signQueryString("https://fapi.binance.com/futures/data/takerlongshortRatio") 593 | 594 | 595 | proc takerlongshortRatioFutures*(self: Binance; symbol: string; period: Interval): string = 596 | result = "" 597 | unrollEncodeQuery(result, {"symbol": symbol, "period": $period, "limit": "30"}) 598 | self.signQueryString("https://fapi.binance.com/futures/data/takerlongshortRatio") 599 | 600 | 601 | proc symbolInformationFutures*(self: Binance; symbol: string): string = 602 | result = "" 603 | unrollEncodeQuery(result, {"symbol": symbol}) 604 | self.signQueryString("https://fapi.binance.com/fapi/v1/indexInfo") 605 | 606 | 607 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, price, stopPrice, activationPrice: float; callbackRate: 0.1 .. 5.0; closePosition: bool): string = 608 | result = "" 609 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "timeInForce": "GTC", "closePosition": $closePosition, "quantity": $quantity, "price": $price, "stopPrice": $stopPrice, "activationPrice": $activationPrice, "callbackRate": $callbackRate }) 610 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 611 | 612 | 613 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, price, stopPrice: float; closePosition: bool): string = 614 | result = "" 615 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "closePosition": $closePosition, "quantity": $quantity, "price": $price, "stopPrice": $stopPrice}) 616 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 617 | 618 | 619 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; stopPrice: float; closePosition: bool): string = 620 | result = "" 621 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "closePosition": $closePosition, "stopPrice": $stopPrice}) 622 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 623 | 624 | 625 | proc postOrderFutures*(self: Binance; symbol: string; side, positionSide: Side; tipe: OrderType; price, stopPrice: float; closePosition: bool): string = 626 | result = "" 627 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe, "closePosition": $closePosition, "price": $price, "stopPrice": $stopPrice}) 628 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 629 | 630 | 631 | proc postOrderFutures*(self: Binance; symbol: string; quantity: float; side, positionSide: Side; tipe: OrderType; callbackRate: 0.1 .. 5.0): string = 632 | result = "" 633 | unrollEncodeQuery(result, {"symbol": symbol, "quantity": $quantity, "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe, "callbackRate": $callbackRate}) 634 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 635 | 636 | 637 | proc postOrderFutures*(self: Binance; symbol: string; quantity: float; side, positionSide: Side; tipe: OrderType): string = 638 | result = "" 639 | unrollEncodeQuery(result, {"symbol": symbol, "quantity": $quantity, "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 640 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 641 | 642 | 643 | proc postOrderFutures*(self: Binance; symbol: string; quantity, price: float; side, positionSide: Side; tipe: OrderType): string = 644 | result = "" 645 | unrollEncodeQuery(result, {"symbol": symbol, "quantity": $quantity, "price": $price, "timeInForce": "GTC", "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 646 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 647 | 648 | 649 | proc postOrderFutures*(self: Binance; symbol: string; quantity, price, stopPrice: float; side, positionSide: Side; tipe: OrderType): string = 650 | result = "" 651 | unrollEncodeQuery(result, {"symbol": symbol, "quantity": $quantity, "price": $price, "stopPrice": $stopPrice, "timeInForce": "GTC", "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 652 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 653 | 654 | 655 | proc postOrderFutures*(self: Binance; symbol: string; quantity, stopPrice: float; side, positionSide: Side; tipe: OrderType): string = 656 | result = "" 657 | unrollEncodeQuery(result, {"symbol": symbol, "quantity": $quantity, "stopPrice": $stopPrice, "side": $side, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 658 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 659 | 660 | 661 | proc postOrderFutures*(self: Binance; symbol: string; stopPrice: float; side, positionSide: Side; tipe: OrderType; closePosition: bool): string = 662 | result = "" 663 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "stopPrice": $stopPrice, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe, "closePosition": $closePosition}) 664 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 665 | 666 | 667 | proc postOrderFutures*(self: Binance; symbol: string; stopPrice: float; side, positionSide: Side; tipe: OrderType): string = 668 | result = "" 669 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "stopPrice": $stopPrice, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 670 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 671 | 672 | 673 | proc postOrderFutures*(self: Binance; symbol: string; price, quantity: float; side, positionSide: Side; tipe: OrderType; callbackRate: 0.1 .. 5.0): string = 674 | result = "" 675 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "price": $price, "quantity": $quantity, "callbackRate": $callbackRate, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 676 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 677 | 678 | 679 | proc postOrderFutures*(self: Binance; symbol: string; activationPrice, quantity: float; side, positionSide: Side; tipe: OrderType; callbackRate: 0.1 .. 5.0): string = 680 | result = "" 681 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "activationPrice": $activationPrice, "quantity": $quantity, "callbackRate": $callbackRate, "positionSide": if positionSide == SIDE_SELL: "SHORT" else: "LONG", "type": $tipe}) 682 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 683 | 684 | 685 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity: float; callbackRate: 0.1 .. 5.0): string = 686 | result = "" 687 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity, "callbackRate": $callbackRate}) 688 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 689 | 690 | 691 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; callbackRate: 0.1 .. 5.0; closePosition: bool): string = 692 | result = "" 693 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "callbackRate": $callbackRate, "closePosition": $closePosition}) 694 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 695 | 696 | 697 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, price, stopPrice: float): string = 698 | result = "" 699 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity, "price": $price, "stopPrice": $stopPrice}) 700 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 701 | 702 | 703 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, price: float): string = 704 | result = "" 705 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity, "price": $price, "timeInForce": "GTC"}) 706 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 707 | 708 | 709 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity: float): string = 710 | result = "" 711 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity}) 712 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 713 | 714 | 715 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, stopPrice: float): string = 716 | result = "" 717 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity, "stopPrice": $stopPrice}) 718 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 719 | 720 | 721 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, price, stopPrice, activationPrice: float): string = 722 | result = "" 723 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "quantity": $quantity, "price": $price, "stopPrice": $stopPrice, "activationPrice": $activationPrice}) 724 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 725 | 726 | 727 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; price: float): string = 728 | result = "" 729 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "price": $price}) 730 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 731 | 732 | 733 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType): string = 734 | result = "" 735 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe}) 736 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 737 | 738 | 739 | proc postOrderFutures*(self: Binance; symbol: string; side: Side; tipe: OrderType; quantity, activationPrice: float; callbackRate: 0.1 .. 5.0): string = 740 | result = "" 741 | unrollEncodeQuery(result, {"symbol": symbol, "side": $side, "type": $tipe, "timeInForce": "GTC", "quantity": $quantity, "activationPrice": $activationPrice, "callbackRate": $callbackRate}) 742 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 743 | 744 | 745 | proc getOrderFutures*(self: Binance; symbol: string; orderId: Positive): string = 746 | result = "" 747 | unrollEncodeQuery(result, {"symbol": symbol, "orderId": $orderId}) 748 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 749 | 750 | 751 | proc getOrderFutures*(self: Binance; symbol: string): string = 752 | result = "" 753 | unrollEncodeQuery(result, {"symbol": symbol}) 754 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 755 | 756 | 757 | proc cancelOrderFutures*(self: Binance; symbol: string; orderId: Positive): string = 758 | result = "" 759 | unrollEncodeQuery(result, {"symbol": symbol, "orderId": $orderId}) 760 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 761 | 762 | 763 | proc cancelOrderFutures*(self: Binance; symbol: string): string = 764 | result = "" 765 | unrollEncodeQuery(result, {"symbol": symbol}) 766 | self.signQueryString"https://fapi.binance.com/fapi/v1/order" 767 | 768 | 769 | proc cancelAllOrdersFutures*(self: Binance; symbol: string): string = 770 | result = "" 771 | unrollEncodeQuery(result, {"symbol": symbol}) 772 | self.signQueryString"https://fapi.binance.com/fapi/v1/allOpenOrders" 773 | 774 | 775 | proc autoCancelAllOrdersFutures*(self: Binance; symbol: string; countdownTime: Natural): string = 776 | ## Auto-Cancel All Open Orders with a countdown. 777 | result = "" 778 | unrollEncodeQuery(result, {"symbol": symbol, "countdownTime": $countdownTime}) 779 | self.signQueryString"https://fapi.binance.com/fapi/v1/countdownCancelAll" 780 | 781 | 782 | proc getAllOpenOrdersFutures*(self: Binance; symbol: string): string = 783 | result = "" 784 | unrollEncodeQuery(result, {"symbol": symbol}) 785 | self.signQueryString"https://fapi.binance.com/fapi/v1/openOrders" 786 | 787 | 788 | proc getAllOrdersFutures*(self: Binance; symbol: string; startTime, endTime: Positive): string = 789 | result = "" 790 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "limit": "500"}) 791 | self.signQueryString"https://fapi.binance.com/fapi/v1/allOrders" 792 | 793 | 794 | proc getAllOrdersFutures*(self: Binance; symbol: string): string = 795 | result = "" 796 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 797 | self.signQueryString"https://fapi.binance.com/fapi/v1/allOrders" 798 | 799 | 800 | proc balanceFutures*(self: Binance): string = 801 | self.signQueryString"https://fapi.binance.com/fapi/v2/balance" 802 | 803 | 804 | proc accountFutures*(self: Binance): string = 805 | self.signQueryString"https://fapi.binance.com/fapi/v2/account" 806 | 807 | 808 | proc leverageFutures*(self: Binance; symbol: string; leverage: 1 .. 125): string = 809 | result = "" 810 | unrollEncodeQuery(result, {"symbol": symbol, "leverage": $leverage}) 811 | self.signQueryString"https://fapi.binance.com/fapi/v1/leverage" 812 | 813 | 814 | proc marginTypeFutures*(self: Binance; symbol: string; isolated: bool): string = 815 | result = "" 816 | unrollEncodeQuery(result, {"symbol": symbol, "marginType": if isolated: "ISOLATED" else: "CROSSED"}) 817 | self.signQueryString"https://fapi.binance.com/fapi/v1/marginType" 818 | 819 | 820 | proc positionRiskFutures*(self: Binance; symbol: string): string = 821 | result = "" 822 | unrollEncodeQuery(result, {"symbol": symbol}) 823 | self.signQueryString"https://fapi.binance.com/fapi/v2/positionRisk" 824 | 825 | 826 | proc userTradesFutures*(self: Binance; symbol: string; startTime, endTime: Positive): string = 827 | result = "" 828 | unrollEncodeQuery(result, {"symbol": symbol, "startTime": $startTime, "endTime": $endTime, "limit": "500"}) 829 | self.signQueryString"https://fapi.binance.com/fapi/v1/userTrades" 830 | 831 | 832 | proc userTradesFutures*(self: Binance; symbol: string): string = 833 | result = "" 834 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 835 | self.signQueryString"https://fapi.binance.com/fapi/v1/userTrades" 836 | 837 | 838 | proc incomeFutures*(self: Binance; symbol: string; startTime, endTime: Positive; incomeType: IncomeType): string = 839 | result = "" 840 | unrollEncodeQuery(result, {"symbol": symbol, "incomeType": $incomeType, "startTime": $startTime, "endTime": $endTime, "limit": "500"}) 841 | self.signQueryString"https://fapi.binance.com/fapi/v1/income" 842 | 843 | 844 | proc incomeFutures*(self: Binance; symbol: string): string = 845 | result = "" 846 | unrollEncodeQuery(result, {"symbol": symbol, "limit": "500"}) 847 | self.signQueryString"https://fapi.binance.com/fapi/v1/income" 848 | 849 | 850 | proc commissionRateFutures*(self: Binance; symbol: string): string = 851 | result = "" 852 | unrollEncodeQuery(result, {"symbol": symbol}) 853 | self.signQueryString"https://fapi.binance.com/fapi/v1/commissionRate" 854 | 855 | 856 | proc postLeverageFutures*(self: Binance; symbol: string; leverage: 1 .. 125): string = 857 | result = "" 858 | unrollEncodeQuery(result, {"symbol": symbol, "leverage": $leverage}) 859 | self.signQueryString"https://fapi.binance.com/fapi/v1/leverage" 860 | 861 | 862 | proc postPositionModeFutures*(self: Binance; hedgeMode: bool): string = 863 | result = "" 864 | unrollEncodeQuery(result, {"dualSidePosition": $hedgeMode}) 865 | self.signQueryString"https://fapi.binance.com/fapi/v1/positionSide/dual" 866 | 867 | 868 | proc postMultiAssetModeFutures*(self: Binance; multiAssetsMode: bool): string = 869 | result = "" 870 | unrollEncodeQuery(result, {"multiAssetsMargin": $multiAssetsMode}) 871 | self.signQueryString"https://fapi.binance.com/fapi/v1/multiAssetsMargin" 872 | 873 | 874 | proc postTransferFutures*(self: Binance; asset: string; amount: float; tipe: TransferType): string = 875 | result = "" 876 | unrollEncodeQuery(result, {"asset": asset, "amount": $amount, "type": $tipe}) 877 | self.signQueryString"https://api.binance.com/sapi/v1/futures/transfer" 878 | 879 | 880 | proc getBalanceFutures*(self: Binance; coin: string): float = 881 | ## Get user wallet Futures balance of 1 specific coin. 882 | for it in self.request(self.balanceFutures(), HttpGet): 883 | if it["asset"].getStr == coin: return it["balance"].getStr.parseFloat 884 | 885 | 886 | proc hasOpenPositionsFutures*(self: Binance; ticker: string): bool = 887 | ## Return `true` if `ticker` has open Positions. This is agnostic of open Orders. Works for Hedge OFF. 888 | let position = self.request(self.positionRiskFutures(symbol = ticker), HttpGet) 889 | if position.len == 1: 890 | result = position[0]["entryPrice"].getStr != "0.0" 891 | 892 | 893 | proc hasOpenOrdersFutures*(self: Binance; ticker: string): bool = 894 | ## Return `true` if `ticker` has open Orders. This is agnostic of open Positions. Works for Hedge OFF. 895 | result = self.request(self.getAllOpenOrdersFutures(symbol = ticker), HttpGet).len > 0 896 | 897 | 898 | # User data streams ########################################################### 899 | 900 | 901 | proc userDataStream*(self: Binance): string {.inline.} = 902 | ## Start a new user data stream. 903 | ## * `POST` to Open a new user data stream. 904 | ## * `DELETE` to Delete an existing user data stream. Auto-closes at 60 minutes idle. 905 | ## * `GET` to Keep Alive an existing user data stream. 906 | result = "https://api.binance.com/api/v3/userDataStream" 907 | 908 | 909 | # Generic endpoints ########################################################### 910 | 911 | 912 | proc ping*(self: Binance): string {.inline.} = 913 | ## Test connectivity to Binance, just a ping. 914 | result = "https://api.binance.com/api/v3/ping" 915 | 916 | 917 | proc time*(self: Binance): string {.inline.} = 918 | ## Get current Binance API server time. 919 | result = "https://api.binance.com/api/v3/time" 920 | 921 | 922 | # Wallet endpoints ############################################################ 923 | 924 | 925 | proc getAllCapital*(self: Binance): string = 926 | self.signQueryString("https://api.binance.com/sapi/v1/capital/config/getall") 927 | 928 | 929 | proc withDrawApply*(self: Binance, coin, address: string, amount: float, network: string): string = 930 | result = "" 931 | unrollEncodeQuery(result, {"coin": coin, "address": address, "amount": amount.formatFloat(ffDecimal, 8), "network": network}) 932 | self.signQueryString("https://api.binance.com/sapi/v1/capital/withdraw/apply") 933 | 934 | 935 | proc apiRestrictions*(self: Binance): string = 936 | self.signQueryString("https://api.binance.com/sapi/v1/account/apiRestrictions") 937 | 938 | 939 | proc enableFastWithdraw*(self: Binance): string = 940 | self.signQueryString("https://api.binance.com/sapi/v1/account/enableFastWithdrawSwitch") 941 | 942 | 943 | # Gift Cards endpoints ######################################################## 944 | 945 | 946 | proc createCode*(self: Binance; token: string; quantity: float): string = 947 | ## Create a new Gift Card via API. 948 | result = "" 949 | unrollEncodeQuery(result, {"token": token, "amount": quantity.formatFloat(ffDecimal, 8)}) 950 | self.signQueryString("https://api.binance.com/sapi/v1/giftcard/createCode") 951 | 952 | 953 | proc redeemCode*(self: Binance; code: string): string = 954 | ## If you enter the wrong `code` 5 times within 24 hours, you will no longer be able to redeem any Binance `code` for 1 day. 955 | result = "" 956 | unrollEncodeQuery(result, {"code": code}) 957 | self.signQueryString("https://api.binance.com/sapi/v1/giftcard/redeemCode") 958 | 959 | 960 | proc verify*(self: Binance; referenceNo: string): string = 961 | ## `referenceNo` is the number that `createCode` returns when successful, this is NOT the PIN code. 962 | result = "" 963 | unrollEncodeQuery(result, {"referenceNo": referenceNo}) 964 | self.signQueryString("https://api.binance.com/sapi/v1/giftcard/verify") 965 | 966 | 967 | # Misc utils ################################################################## 968 | 969 | 970 | template getBnb*(self: Binance): float = 971 | ## Get BNB in user wallet, this is useful for Commisions. 972 | try: self.getWallet()["BNB"] except CatchableError: 0.0 973 | 974 | 975 | template getBnbPrice*(self: Binance): string = 976 | ## BNB price in USDT, useful for commision calc. 977 | "https://api.binance.com/api/v3/ticker/price?symbol=BNBUSDT" 978 | 979 | 980 | template donateToAddress*(self: Binance; address: string; amount = 0.0000095; coin = "BTC"): JsonNode = 981 | ## Donate to an specific `address`, using `"BSC"` network, using `"BTC"` as `coin` by default, the minimum `amount` possible of `0.0000095` by default. 982 | assert address.len > 1, "Please provide a valid address, BSC network." 983 | assert coin.len > 1, "Please provide a valid coin ticker." 984 | assert amount > 0.0, "Please provide a valid amount." 985 | self.request(self.withDrawApply(coin, address, amount, "BSC"), HttpPost) 986 | 987 | 988 | proc ma50*(self: Binance; ticker: string): float = 989 | ## Calculate the current Medium Average 50 of the market. 990 | assert ticker.len > 0, "ticker must not be empty string" 991 | var sum: float 992 | let klines = self.getHistoricalKlines(ticker, KLINE_INTERVAL_15MINUTE, initDuration(hours = 15))[0] 993 | if klines.len == 60: 994 | for i in 10 ..< 60: 995 | sum = sum + klines[i][4].getStr.parseFloat 996 | result = sum / 50 997 | 998 | 999 | proc getDynamicSleep*(self: Binance; symbolTicker: string; baseSleep: static[int] = 30_000): int = 1000 | ## Get a "dynamic" sleep time integer for use with `sleep` and loops. 1001 | ## * If more volatility then less sleep time, and viceversa. 1002 | assert symbolTicker.len > 0, "symbolTicker must not be empty string" 1003 | let temp = self.request(self.ticker24h(symbolTicker), HttpGet)["priceChangePercent"].getStr.parseFloat 1004 | result = int(baseSleep / (if temp > 0.0: temp else: 1.0)) 1005 | if result > 120_000: result = 120_000 1006 | 1007 | 1008 | proc getProducts*(self: Binance): string {.inline.} = 1009 | ## Undocumented API endpoint ?, no auth required ?. 1010 | result = "https://www.binance.com/exchange-api/v2/public/asset-service/product/get-products" 1011 | 1012 | 1013 | proc getTopMarketCapPairs*(self: Binance; stablecoin = "USDT"; limit = 100.Positive): seq[tuple[marketCap: int, ticker: string]] = 1014 | ## Get top market cap trading pairs, ordered from big to small, filtered by `stablecoin`, maximum of `limit`. 1015 | ## * This needs to iterate all pairs sadly, because the API sends it unordered, >300 pairs for any `stablecoin`. 1016 | assert stablecoin.len > 0, "stablecoin must not be empty string" 1017 | let data: JsonNode = self.request(self.getProducts(), HttpGet)["data"] 1018 | result = newSeqOfCap[tuple[marketCap: int, ticker: string]](data.len) 1019 | for coin in data: 1020 | let pair: string = coin["s"].getStr 1021 | if coin["q"].getStr == stablecoin and not coin["cs"].isNil and not coin["c"].isNil and coin["cs"].getInt > 0: 1022 | result.add (marketCap: int(coin["cs"].getInt.float * coin["c"].getStr.parseFloat), ticker: pair) 1023 | result.sort Descending 1024 | result.setLen limit 1025 | 1026 | 1027 | proc get24hHiLo*(self: Binance; symbolTicker: string): tuple[hi24h: float, lo24h: float] = 1028 | ## Get 24 hours Highest price and Lowest price for a symbol. 1029 | assert symbolTicker.len > 0, "symbolTicker must not be empty string" 1030 | let temp = self.request(self.ticker24h(symbolTicker), HttpGet) 1031 | result = (hi24h: temp["highPrice"].getStr.parseFloat, lo24h: temp["lowPrice"].getStr.parseFloat) 1032 | 1033 | 1034 | proc getAth*(self: Binance; ticker: string): float = 1035 | ## Get ATH of a ticker. 1036 | assert ticker.len > 0, "ticker must not be empty string" 1037 | for it in self.getHistoricalKlines(ticker, KLINE_INTERVAL_1MONTH, initDuration(days = 365))[0]: 1038 | let thisMonthPrice = it[2].getStr.parseFloat 1039 | if thisMonthPrice > result: result = thisMonthPrice 1040 | 1041 | 1042 | template reversed*(this: Side): Side = 1043 | if this == SIDE_SELL: SIDE_BUY else: SIDE_SELL 1044 | 1045 | 1046 | template truncate*(number: float): float = 1047 | ## Truncate a float, this is a workaround, because `round` and `formatFloat` are fixed precision. 1048 | var dotFound = false 1049 | var s = newStringOfCap(8) 1050 | for c in number.formatFloat(ffDecimal, 4): 1051 | case c 1052 | of '-': s.add '-' 1053 | of '+': discard 1054 | of '.': 1055 | s.add '.' 1056 | dotFound = true 1057 | of '0' .. '9': 1058 | if dotFound: 1059 | if c == '0': 1060 | s.add c 1061 | else: 1062 | s.add c 1063 | break 1064 | else: s.add c 1065 | else: discard 1066 | parseFloat(s) 1067 | 1068 | 1069 | template getMaxLeverage*(ticker: string): int = 1070 | ## Get max leverage for Perpetual Futures USDT based on ticker, 1071 | ## this info can be obtained via API but this saves some requests, 1072 | ## it is unlikely to change too frequently anyways. 1073 | case ticker 1074 | of "BTCUSDT": 125 1075 | of "ETHUSDT": 100 1076 | of "BNBUSDT", "BCHUSDT", "XRPUSDT", "EOSUSDT", "LTCUSDT", "ETCUSDT", "LINKUSDT", "ADAUSDT", "TRXUSDT": 75 1077 | of "ALGOUSDT", "ATOMUSDT", "BSVUSDT", "DOGEUSDT", "DOTUSDT", "EGLDUSDT", "FILUSDT", "HNTUSDT", "ICPUSDT", "KSMUSDT", "MATICUSDT", "NEOUSDT", "QTUMUSDT", "RUNEUSDT", "SOLUSDT", "SRMUSDT", "SUSHIUSDT", "UNIUSDT", "XLMUSDT", "XTZUSDT", "ZECUSDT": 50 1078 | of "BTCDOMUSDT", "QNTUSDT": 20 1079 | else: 25 # "YFIUSDT", "MKRUSDT", "XMRUSDT", "AAVEUSDT", etc 1080 | 1081 | 1082 | template getMinQuantity*(ticker: string): float = 1083 | ## Get minimum quantity for Perpetual Futures USDT based on ticker, 1084 | ## this info can be obtained via API but this saves some requests, 1085 | ## it is unlikely to change too frequently anyways. 1086 | case ticker 1087 | of "DOGEUSDT": 75.0 1088 | of "XMRUSDT": 0.035 1089 | of "BTCDOMUSDT": 0.005 1090 | of "MKRUSDT": 0.006 1091 | of "ALGOUSDT", "MATICUSDT": 1.0 1092 | of "XLMUSDT", "ADAUSDT", "XRPUSDT": 10.0 1093 | of "BTCUSDT", "YFIUSDT", "BCHUSDT": 0.001 1094 | of "ETHUSDT", "LTCUPUSDT", "LTCUSDT": 0.01 1095 | of "BNBUSDT", "DOTUSDT", "SOLUSDT", "LINKUSDT", "AVAXUSDT", "ATOMUSDT", "FILUSDT", "UNIUSDT", "ETCUSDT", "QNTUSDT", "AAVEUSDT": 0.1 1096 | else: 0.001 1097 | 1098 | 1099 | runnableExamples"-d:ssl -d:nimDisableCertificateValidation -r:off": 1100 | import std/[httpcore, json] 1101 | let client: Binance = newBinance("YOUR_BINANCE_API_KEY", "YOUR_BINANCE_API_SECRET") 1102 | let preparedEndpoint: string = client.ping() 1103 | echo client.request(preparedEndpoint, HttpGet) 1104 | --------------------------------------------------------------------------------