├── 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 | 
4 |
5 | 
6 |
7 |
8 | 
9 | 
10 | 
11 | 
12 | 
13 | 
14 | 
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 | 
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 | 
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,iVBORw0KGgoAAAANSUhEUgAAAN4AAAA9CAYAAADCt9ebAAAACXBIWXMAAAsTAAALEwEAmpwYAAAFFmlUWHRYTUw6Y29tLmFkb2JlLnhtcAAAAAAAPD94cGFja2V0IGJlZ2luPSLvu78iIGlkPSJXNU0wTXBDZWhpSHpyZVN6TlRjemtjOWQiPz4gPHg6eG1wbWV0YSB4bWxuczp4PSJhZG9iZTpuczptZXRhLyIgeDp4bXB0az0iQWRvYmUgWE1QIENvcmUgNS42LWMxNDggNzkuMTY0MDM2LCAyMDE5LzA4LzEzLTAxOjA2OjU3ICAgICAgICAiPiA8cmRmOlJERiB4bWxuczpyZGY9Imh0dHA6Ly93d3cudzMub3JnLzE5OTkvMDIvMjItcmRmLXN5bnRheC1ucyMiPiA8cmRmOkRlc2NyaXB0aW9uIHJkZjphYm91dD0iIiB4bWxuczp4bXA9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC8iIHhtbG5zOmRjPSJodHRwOi8vcHVybC5vcmcvZGMvZWxlbWVudHMvMS4xLyIgeG1sbnM6cGhvdG9zaG9wPSJodHRwOi8vbnMuYWRvYmUuY29tL3Bob3Rvc2hvcC8xLjAvIiB4bWxuczp4bXBNTT0iaHR0cDovL25zLmFkb2JlLmNvbS94YXAvMS4wL21tLyIgeG1sbnM6c3RFdnQ9Imh0dHA6Ly9ucy5hZG9iZS5jb20veGFwLzEuMC9zVHlwZS9SZXNvdXJjZUV2ZW50IyIgeG1wOkNyZWF0b3JUb29sPSJBZG9iZSBQaG90b3Nob3AgMjEuMCAoV2luZG93cykiIHhtcDpDcmVhdGVEYXRlPSIyMDE5LTEyLTAzVDAxOjAzOjQ4KzAxOjAwIiB4bXA6TW9kaWZ5RGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgeG1wOk1ldGFkYXRhRGF0ZT0iMjAxOS0xMi0wM1QwMjoyODo0MSswMTowMCIgZGM6Zm9ybWF0PSJpbWFnZS9wbmciIHBob3Rvc2hvcDpDb2xvck1vZGU9IjMiIHBob3Rvc2hvcDpJQ0NQcm9maWxlPSJzUkdCIElFQzYxOTY2LTIuMSIgeG1wTU06SW5zdGFuY2VJRD0ieG1wLmlpZDozMzM0ZjAxYS0yMDExLWE1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");
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 |
--------------------------------------------------------------------------------