├── .gitignore ├── LICENSE ├── README.md ├── examples ├── futuresNewPosition.py ├── rest │ └── futuresNewPosition.py └── websocket │ ├── README.md │ └── listenBidOffer.py ├── lnmarkets ├── __init__.py ├── rest.py └── websockets.py ├── pyproject.toml ├── requirements.txt ├── setup.cfg ├── setup.py └── tests ├── __init__.py └── test_node_state.py /.gitignore: -------------------------------------------------------------------------------- 1 | # Byte-compiled / optimized / DLL files 2 | __pycache__/ 3 | *.py[cod] 4 | *$py.class 5 | 6 | # C extensions 7 | *.so 8 | 9 | # Distribution / packaging 10 | .Python 11 | build/ 12 | develop-eggs/ 13 | dist/ 14 | downloads/ 15 | eggs/ 16 | .eggs/ 17 | lib/ 18 | lib64/ 19 | parts/ 20 | sdist/ 21 | var/ 22 | wheels/ 23 | pip-wheel-metadata/ 24 | share/python-wheels/ 25 | *.egg-info/ 26 | .installed.cfg 27 | *.egg 28 | MANIFEST 29 | 30 | # PyInstaller 31 | # Usually these files are written by a python script from a template 32 | # before PyInstaller builds the exe, so as to inject date/other infos into it. 33 | *.manifest 34 | *.spec 35 | 36 | # Installer logs 37 | pip-log.txt 38 | pip-delete-this-directory.txt 39 | 40 | # Unit test / coverage reports 41 | htmlcov/ 42 | .tox/ 43 | .nox/ 44 | .coverage 45 | .coverage.* 46 | .cache 47 | nosetests.xml 48 | coverage.xml 49 | *.cover 50 | *.py,cover 51 | .hypothesis/ 52 | .pytest_cache/ 53 | 54 | # Translations 55 | *.mo 56 | *.pot 57 | 58 | # Django stuff: 59 | *.log 60 | local_settings.py 61 | db.sqlite3 62 | db.sqlite3-journal 63 | 64 | # Flask stuff: 65 | instance/ 66 | .webassets-cache 67 | 68 | # Scrapy stuff: 69 | .scrapy 70 | 71 | # Sphinx documentation 72 | docs/_build/ 73 | 74 | # PyBuilder 75 | target/ 76 | 77 | # Jupyter Notebook 78 | .ipynb_checkpoints 79 | 80 | # IPython 81 | profile_default/ 82 | ipython_config.py 83 | 84 | # pyenv 85 | .python-version 86 | 87 | # pipenv 88 | # According to pypa/pipenv#598, it is recommended to include Pipfile.lock in version control. 89 | # However, in case of collaboration, if having platform-specific dependencies or dependencies 90 | # having no cross-platform support, pipenv may install dependencies that don't work, or not 91 | # install all needed dependencies. 92 | #Pipfile.lock 93 | 94 | # PEP 582; used by e.g. github.com/David-OConnor/pyflow 95 | __pypackages__/ 96 | 97 | # Celery stuff 98 | celerybeat-schedule 99 | celerybeat.pid 100 | 101 | # SageMath parsed files 102 | *.sage.py 103 | 104 | # Environments 105 | .env 106 | .venv 107 | env/ 108 | venv/ 109 | ENV/ 110 | env.bak/ 111 | venv.bak/ 112 | 113 | # Spyder project settings 114 | .spyderproject 115 | .spyproject 116 | 117 | # Rope project settings 118 | .ropeproject 119 | 120 | # mkdocs documentation 121 | /site 122 | 123 | # mypy 124 | .mypy_cache/ 125 | .dmypy.json 126 | dmypy.json 127 | 128 | # Pyre type checker 129 | .pyre/ 130 | 131 | # Jetbrains 132 | .idea/ 133 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2021 LN Markets 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 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # LN Markets Python API 2 | 3 | A simple way to connect your Python application to [LN Markets](https://lnmarkets.com/)! 4 | 5 | ## Install 6 | 7 | You can install this package with pip: 8 | ```shell 9 | pip3 install ln-markets 10 | ``` 11 | 12 | ## Use 13 | 14 | You can import the rest class from lnmarkets 15 | ```python 16 | from lnmarkets import rest 17 | ``` 18 | And the websocket class as well 19 | ```python 20 | from lnmarkets import websockets 21 | ``` 22 | 23 | ## Authentication 24 | 25 | > For authentication you need your api **key** **secret** and **passphrase** 26 | 27 | Without you will not bet able to authenticate 28 | 29 | > :warning: **Never share your API Key, Secret or Passphrase** 30 | 31 | ## Websocket API 32 | 33 | ### Configuration 34 | 35 | Use the LNMarketsWebsocket and your key / passphrase to instanciate a new api connector: 36 | 37 | ```python 38 | options = {'key': 'your_api_key', 39 | 'secret': 'your_api_secret', 40 | 'passphrase': 'your_api_passphrase', 41 | 'network': 'testnet'} 42 | 43 | lnm = websockets.LNMarketsWebsocket(**options) 44 | lnm.connect() 45 | ``` 46 | 47 | > Check [examples](examples/websocket/README.md) for more details as you'll need to extend this class most of the time. 48 | 49 | ### Subscription 50 | 51 | You can subscribe to LNM Markets public event such as futures last price ('futures:btc_usd:last-price') and index ('futures:btc_usd:index'). 52 | 53 | ## REST API Authentication 54 | 55 | ### Using API key 56 | 57 | Use the LNMarketsRest and your key / passphrase to instanciate a new api connector: 58 | 59 | ```python 60 | from lnmarkets import rest 61 | 62 | options = {'key': 'your_api_key', 63 | 'secret': 'your_api_secret', 64 | 'passphrase': 'your_api_passphrase', 65 | 'network': 'testnet'} 66 | 67 | lnm = rest.LNMarketsRest(**options) 68 | 69 | lnm.futures_get_ticker() 70 | 71 | ``` 72 | 73 | ### Using Mnemonic seed 74 | 75 | Use the LNMarketsRest with [BIP39](https://github.com/bitcoin/bips/blob/master/bip-0039.mediawiki) mnemonic phrase 76 | to instanciate a new api connector: 77 | 78 | ```python 79 | from lnmarkets import rest 80 | 81 | mnemonic = 'struggle goddess action cheap endorse venue force tomato exercise cactus charge such' 82 | 83 | lnm = rest.LNMarketsRest(mnemonic=mnemonic) 84 | 85 | lnm.futures_get_ticker() 86 | 87 | ``` 88 | 89 | Mnemonic seed auth is a "hack" using the cookie used by LNM WebApp, so for avoid risk of stolen cookie, you should 90 | logout, when your program stop running: 91 | 92 | ```python 93 | lnm.logout() 94 | ``` 95 | The cookie have a lifetime (about 1 week), so if your program running for more than that time you might renew the 96 | cookie before expiry date (note that it can also be a safety measure tu renew your cookie often to avoid 97 | stolen cookies risk): 98 | 99 | ```python 100 | lnm.renew_cookie() 101 | ``` 102 | ## REST API 103 | 104 | - [`futures_add_margin`](#futures_add_margin) 105 | - [`futures_cancel_all`](#futures_cancel_all) 106 | - [`futures_close_all`](#futures_close_all) 107 | - [`futures_cancel`](#futures_cancel) 108 | - [`futures_cashin`](#futures_cashin) 109 | - [`futures_close`](#futures_close) 110 | - [`futures_get_trades`](#futures_get_trades) 111 | - [`futures_new_trade`](#futures_new_trade) 112 | - [`futures_update_trade`](#futures_update_trade) 113 | - [`futures_carry_fees`](#futures_carry_fees) 114 | - [`futures_get_price`](#futures_get_price) 115 | - [`futures_fixing`](#futures_fixing) 116 | - [`futures_index`](#futures_index) 117 | - [`futures_get_leaderboard`](#futures_get_leaderboard) 118 | - [`futures_get_ticker`](#futures_get_ticker) 119 | - [`futures_get_market`](#futures_get_market) 120 | - [`options_get_instruments`](#options_get_instruments) 121 | - [`options_get_instrument`](#options_get_instrument) 122 | - [`options_close_all`](#options_close_all) 123 | - [`options_close`](#options_close) 124 | - [`options_get_trades`](#options_get_trades) 125 | - [`options_new_trade`](#options_new_trade) 126 | - [`options_update_trade`](#options_update_trade) 127 | - [`options_get_volatility`](#options_get_volatility) 128 | - [`options_get_market`](#options_get_market) 129 | - [`get_swaps`](#get_swaps) 130 | - [`swap`](#swap) 131 | - get_user 132 | - update_user 133 | - get_new_bitcoin_address 134 | - get_bitcoin_addresses 135 | - get_deposit 136 | - get_deposits 137 | - new_deposit 138 | - new_deposit_susd 139 | - get_withdrawal 140 | - get_withdrawals 141 | - new_withdraw 142 | - new_withdraw_susd 143 | - new_internal_transfer 144 | - get_oracle_index 145 | - get_oracle_last 146 | - fetch_notifications 147 | - mark_notifications_read 148 | - app_configuration 149 | - app_node 150 | 151 | 152 | [`See the API documentation`](https://docs.lnmarkets.com/api/v2) for more details. 153 | 154 | 155 | ### futures_add_margin 156 | 157 | Add more margin to an existing trade. 158 | 159 | ```yml 160 | amount: 161 | type: Integer 162 | required: true 163 | id: 164 | type: String 165 | required: true 166 | ``` 167 | 168 | Example: 169 | 170 | ```python 171 | lnm.futures_add_margin({ 172 | 'amount': 20000, 173 | 'id': '249dc818-f8a5-4713-a3a3-8fe85f2e8969' 174 | }) 175 | ``` 176 | 177 | [`POST /futures/add-margin`](https://docs.lnmarkets.com/api/v2) documentation for more details. 178 | 179 | ### futures_cancel_all 180 | 181 | Cancel all opened (not running) trades for this user. 182 | 183 | ``` 184 | # No parameters 185 | ``` 186 | 187 | Example: 188 | 189 | ```python 190 | lnm.futures_cancel_all() 191 | ``` 192 | 193 | [`DELETE /futures/all/cancel`](https://docs.lnmarkets.com/api/v2) documentation for more details. 194 | 195 | ### futures_close_all 196 | 197 | Close all running trades for this user. 198 | 199 | ``` 200 | # No parameters 201 | ``` 202 | 203 | Example: 204 | 205 | ```python 206 | lnm.futures_close_all() 207 | ``` 208 | 209 | [`DELETE /futures/all/close`](https://docs.lnmarkets.com/api/v2) documentation for more details. 210 | 211 | ### futures_cancel 212 | 213 | Cancel a specific trade for this user. 214 | 215 | ```yml 216 | id: 217 | type: String 218 | required: true 219 | ``` 220 | 221 | Example: 222 | 223 | ```python 224 | lnm.futures_cancel_position({ 225 | 'id': 'b87eef8a-52ab-2fea-1adc-c41fba870b0f' 226 | }) 227 | ``` 228 | 229 | [`POST /futures/cancel`](https://docs.lnmarkets.com/api/v2) documentation for more details. 230 | 231 | ### futures_cashin 232 | 233 | Cash in a part of the PL of a running trade. 234 | 235 | ```yml 236 | amount: 237 | type: Integer 238 | required: true 239 | id: 240 | type: String 241 | required: true 242 | ``` 243 | 244 | Example: 245 | 246 | ```python 247 | lnm.futures_cashin({ 248 | 'amount': 1000, 249 | 'id': "99c470e1-2e03-4486-a37f-1255e08178b1" 250 | }) 251 | ``` 252 | 253 | [`POST /futures/cash-in`](https://docs.lnmarkets.com/api/v2) documentation for more details. 254 | 255 | ### futures_carry_fees 256 | 257 | Get carry-fees history. 258 | 259 | ```yml 260 | from: 261 | type: Integer 262 | required: true 263 | 264 | to: 265 | type: Integer 266 | required: tue 267 | 268 | limit: 269 | type: Integer 270 | required: false 271 | default: 100 272 | ``` 273 | 274 | Example: 275 | 276 | ```python 277 | lnm.futures_carry_fees({ 278 | 'limit': 20 279 | }) 280 | ``` 281 | 282 | [`GET /futures/carry-fees`](https://docs.lnmarkets.com/api/v2) documentation for more details. 283 | 284 | ### futures_close 285 | 286 | Close a specific running trade for this user. 287 | 288 | ```yml 289 | id: 290 | type: String 291 | required: true 292 | ``` 293 | 294 | Example: 295 | 296 | ```python 297 | lnm.futures_close({ 298 | 'id': 'a2ca6172-1078-463d-ae3f-8733f36a9b0e' 299 | }) 300 | ``` 301 | 302 | [`DELETE /futures`](https://docs.lnmarkets.com/api/v2) documentation for more details. 303 | 304 | ### futures_get_trades 305 | 306 | Retrieve all or a part of user's trades. 307 | 308 | ```yml 309 | type: 310 | type: String 311 | required: true 312 | enum: ['open', 'running', 'closed'] 313 | default: 'open' 314 | 315 | from: 316 | type: Integer 317 | required: false 318 | 319 | to: 320 | type: Integer 321 | required: false 322 | 323 | limit: 324 | type: Integer 325 | required: false 326 | default: 100 327 | ``` 328 | 329 | Example: 330 | 331 | ```python 332 | lnm.futures_get_trades({ 333 | 'type': 'running' 334 | }) 335 | ``` 336 | 337 | [`GET /futures`](https://docs.lnmarkets.com/api/v2) documentation for more details. 338 | 339 | ### futures_new_trade 340 | 341 | Open a new trade. If type="l", the property price must be included in the request to know when the trade should be filled. You can choose to use the margin or the quantity as a parameter, the other will be calculated with the one you chose. 342 | 343 | ```yml 344 | type: 345 | type: String 346 | required: true 347 | enum: ['l', 'm'] 348 | 349 | side: 350 | type: String 351 | required: true 352 | enum: ['b', 's'] 353 | 354 | margin: 355 | type: Integer 356 | required: true 357 | 358 | leverage: 359 | type: Float 360 | required: true 361 | 362 | quantity: 363 | type: Integer 364 | required: false 365 | 366 | takeprofit: 367 | type: Integer 368 | required: false 369 | 370 | stoploss: 371 | type: Integer 372 | required: false 373 | 374 | price: 375 | type: Float 376 | required: false 377 | ``` 378 | 379 | Example: 380 | 381 | ```python 382 | lnm.futures_new_trade({ 383 | 'type': 'm', 384 | 'side': 's', 385 | 'margin': 10000, 386 | 'leverage': 25, 387 | }) 388 | ``` 389 | 390 | [`POST /futures`](https://docs.lnmarkets.com/api/v2) documentation for more details. 391 | 392 | ### futures_update_trade 393 | 394 | Modify stoploss or takeprofit parameter of an existing trade. 395 | 396 | ```yml 397 | id: 398 | type: String 399 | required: true 400 | 401 | type: 402 | type: String 403 | required: true 404 | enum: ['takeprofit', 'stoploss'] 405 | 406 | value: 407 | type: Float 408 | required: true 409 | ``` 410 | 411 | Example: 412 | 413 | ```python 414 | lnm.futures_update_trade({ 415 | 'id': 'b87eef8a-52ab-2fea-1adc-c41fba870b0f', 416 | 'type': 'stoploss', 417 | 'value': 13290.5 418 | }) 419 | ``` 420 | 421 | [`PUT /futures`](https://docs.lnmarkets.com/api/v2) documentation for more details. 422 | 423 | ### futures_get_price 424 | 425 | Get Futures price data over time. 426 | 427 | ```yml 428 | from: 429 | type: Integer 430 | required: false 431 | 432 | to: 433 | type: Integer 434 | required: false 435 | 436 | limit: Integer 437 | required: false 438 | default: 100 439 | ``` 440 | 441 | Example: 442 | 443 | ```python 444 | lnm.futures_price({ 445 | 'limit': 20 446 | }) 447 | ``` 448 | 449 | [`GET /futures/history/price`](https://docs.lnmarkets.com/api/v2) documentation for more details. 450 | 451 | ### futures_fixing 452 | 453 | Get fixing data history. 454 | 455 | ```yml 456 | from: 457 | type: Integer 458 | required: false 459 | 460 | to: 461 | type: Integer 462 | required: false 463 | 464 | limit: 465 | type: Integer 466 | required: false 467 | default: 100 468 | ``` 469 | 470 | Example: 471 | 472 | ```python 473 | lnm.futures_fixing({ 474 | 'limit': 20 475 | }) 476 | ``` 477 | 478 | [`GET /futures/history/fixing`](https://docs.lnmarkets.com/api/v2) documentation for more details. 479 | 480 | ### futures_index 481 | 482 | Get index history data. 483 | 484 | ```yml 485 | from: 486 | type: Integer 487 | required: false 488 | 489 | to: 490 | type: Integer 491 | required: false 492 | 493 | limit: 494 | type: Integer 495 | required: false 496 | default: 100 497 | ``` 498 | 499 | Example: 500 | 501 | ```python 502 | lnm.futures_index({ 503 | 'limit': 20 504 | }) 505 | ``` 506 | 507 | [`GET /futures/history/index`](https://docs.lnmarkets.com/api/v2) documentation for more details. 508 | 509 | ### futures_get_ticker 510 | 511 | Get the futures ticker. 512 | 513 | ``` 514 | # No parameters 515 | ``` 516 | 517 | Example: 518 | 519 | ```python 520 | lnm.futures_get_ticker() 521 | ``` 522 | 523 | [`GET /futures/ticker`](https://docs.lnmarkets.com/api/v2) documentation for more details. 524 | 525 | ### futures_get_leaderboard 526 | 527 | Queries the 10 users with the biggest positive PL on a daily, weekly, monthly and all-time basis. 528 | 529 | ``` 530 | # No parameters 531 | ``` 532 | 533 | Example: 534 | 535 | ```python 536 | lnm.futures_get_leaderboard() 537 | ``` 538 | 539 | [`GET /futures/leaderboard`](https://docs.lnmarkets.com/api/v2) documentation for more details. 540 | 541 | ### futures_get_market 542 | 543 | Get the futures market details. 544 | 545 | ``` 546 | # No parameters 547 | ``` 548 | 549 | Example: 550 | 551 | ```python 552 | lnm.futures_get_market() 553 | ``` 554 | 555 | [`GET /futures/market`](https://docs.lnmarkets.com/api/v2) documentation for more details. 556 | 557 | ### options_get_instruments 558 | 559 | Get the options instruments list. 560 | 561 | ``` 562 | # No parameters 563 | ``` 564 | 565 | Example: 566 | 567 | ```python 568 | lnm.options_get_instruments() 569 | ``` 570 | 571 | [`GET /options/instruments`](https://docs.lnmarkets.com/api/v2) documentation for more details. 572 | 573 | ### options_get_instrument 574 | 575 | Get a specific option instrument detail. 576 | 577 | ```yml 578 | instrument_name: 579 | type: String 580 | required: true 581 | ``` 582 | 583 | Example: 584 | 585 | ```python 586 | lnm.options_get_instrument({ 587 | 'instrument_name': 'BTC.2024-01-05.43000.C' 588 | }) 589 | ``` 590 | 591 | 592 | [`GET /options/instrument`](https://docs.lnmarkets.com/api/v2) documentation for more details. 593 | 594 | ### options_close_all 595 | 596 | Close all of the user option trades, the PL will be calculated against the current bid or offer depending on the type of the options. 597 | 598 | ``` 599 | # No parameters 600 | ``` 601 | 602 | Example: 603 | 604 | ```python 605 | lnm.options_close_all() 606 | ``` 607 | 608 | [`DELETE /options/close-all`](https://docs.lnmarkets.com/api/v2) documentation for more details. 609 | 610 | ### options_close 611 | 612 | Close the user option trade, the PL will be calculated against the current bid or offer depending on the type of the option. 613 | 614 | ```yml 615 | id: 616 | type: String 617 | required: true 618 | ``` 619 | 620 | Example: 621 | 622 | ```python 623 | lnm.options_close({ 624 | 'id': 'a61faebc-7cc9-47e4-a22d-9d3e95c98322' 625 | }) 626 | ``` 627 | 628 | [`DELETE /options`](https://docs.lnmarkets.com/api/v2) documentation for more details. 629 | 630 | ### options_get_trades 631 | 632 | Get user's options trades. 633 | 634 | ```yaml 635 | status: 636 | type: String 637 | enum: ['running', 'closed'] 638 | default: running 639 | required: true 640 | 641 | from: 642 | type: Integer 643 | required: false 644 | 645 | to: 646 | type: Integer 647 | required: false 648 | 649 | limit: 650 | type: Integer 651 | required: false 652 | ``` 653 | 654 | Example: 655 | 656 | ```python 657 | lnm.options_get_trades({ 658 | limit: 25, 659 | status: 'closed' 660 | }) 661 | ``` 662 | 663 | [`GET /options`](https://docs.lnmarkets.com/api/v2) documentation for more details. 664 | 665 | ### options_new_trade 666 | 667 | Create a new options trade 668 | 669 | ```yaml 670 | side: 671 | type: String 672 | enum: ['b'] 673 | required: true 674 | 675 | quantity: 676 | type: Integer 677 | required: true 678 | 679 | settlement: 680 | type: String 681 | enum: ['physical', 'cash'] 682 | required: true 683 | 684 | instrument_name: 685 | type: String 686 | required: true 687 | 688 | ``` 689 | 690 | Example: 691 | 692 | ```python 693 | lnm.options_new_trade({ 694 | 'side': 'b', 695 | 'quantity': 10, 696 | 'settlement': 'physical', 697 | 'instrument_name': 'BTC.2024-01-05.43000.C' 698 | }) 699 | ``` 700 | 701 | [`POST /options`](https://docs.lnmarkets.com/api/v2) documentation for more details. 702 | 703 | ### options_update_trade 704 | 705 | Allows user to update settlement parameter in running option trade. 706 | 707 | ```yml 708 | id: 709 | type: String 710 | required: true 711 | 712 | settlement: 713 | type: String 714 | required: true 715 | enum: ['physical', 'cash'] 716 | ``` 717 | 718 | Example: 719 | 720 | ```python 721 | lnm.options_update_trade({ 722 | 'id': 'a2ca6172-1078-463d-ae3f-8733f36a9b0e', 723 | 'settlement': 'physical' 724 | }) 725 | ``` 726 | 727 | [`PUT /options`](https://docs.lnmarkets.com/api/v2) documentation for more details. 728 | 729 | #### options_get_volatility 730 | 731 | Return the volatility 732 | 733 | ```yml 734 | instrument: 735 | type: String 736 | required: false 737 | ``` 738 | 739 | Example: 740 | 741 | ```python 742 | lnm.options_get_volatility({ 743 | 'instrument': 'BTC.2016-01-14.20000.C' 744 | }) 745 | ``` 746 | 747 | [`GET /options/volatility`](https://docs.lnmarkets.com/api/v2) documentation for more details. 748 | 749 | #### options_get_market 750 | 751 | Get the options market details. 752 | 753 | ``` 754 | # No parameters 755 | ``` 756 | 757 | Example: 758 | 759 | ```python 760 | lnm.options_get_market() 761 | ``` 762 | 763 | [`GET /options/market`](https://docs.lnmarkets.com/api/v2) documentation for more details. 764 | 765 | ### get_swaps 766 | 767 | Get swap history 768 | 769 | ```yml 770 | from: 771 | type: Integer 772 | required: false 773 | 774 | to: 775 | type: Integer 776 | required: false 777 | 778 | limit: 779 | type: Integer 780 | required: False 781 | 782 | ``` 783 | 784 | Example: 785 | 786 | ```python 787 | lnm.get_swaps({ 788 | 'from': 1669980001000, 789 | 'to': '1669990201000', 790 | 'limit': 100 791 | }) 792 | ``` 793 | 794 | [`GET /swap`](https://docs.lnmarkets.com/api/v2) documentation for more details. 795 | 796 | ### swap 797 | 798 | Swap betweem sats and synthetic USD 799 | 800 | ```yml 801 | in_asset: 802 | type: String 803 | required: true 804 | enum: ['USD', 'BTC'] 805 | 806 | out_asset: 807 | type: String 808 | required: true 809 | enum: ['USD', 'BTC'] 810 | 811 | in_amount: 812 | type: Integer 813 | required: False 814 | 815 | out_amount: 816 | type: Integer 817 | required: false 818 | 819 | ``` 820 | 821 | Example: 822 | 823 | ```python 824 | lnm.swap({ 825 | 'in_asset': 'BTC', 826 | 'out_asset': 'USD', 827 | 'out_amount': 100 828 | }) 829 | ``` 830 | 831 | [`POST /swap`](https://docs.lnmarkets.com/api/v2) documentation for more details. 832 | -------------------------------------------------------------------------------- /examples/futuresNewPosition.py: -------------------------------------------------------------------------------- 1 | options = { 2 | 'key': 'YOUR_KEY', 3 | 'secret': 'YOUR_SECRET', 4 | 'passphrase': 'YOUR_PASSPHRASE' 5 | } 6 | 7 | 8 | lnm = LNM_REST_API(**options) 9 | lnm.futures_new_position({'type':'m', 'side': 'b', 'quantity': 1, 'leverage': 50}) 10 | -------------------------------------------------------------------------------- /examples/rest/futuresNewPosition.py: -------------------------------------------------------------------------------- 1 | options = { 2 | 'key': 'your_api_key', 3 | 'secret': 'your_api_secret', 4 | 'passphrase': 'your_api_passphrase' 5 | } 6 | 7 | lnm = rest.LNMarketsRest(**options) 8 | lnm.futures_new_position({'type':'m', 'side': 'b', 'quantity': 1, 'leverage': 50}) 9 | -------------------------------------------------------------------------------- /examples/websocket/README.md: -------------------------------------------------------------------------------- 1 | # Extending websocket class 2 | 3 | Since the default behaviour of this class is to only connect to the websocket without anything else, you'll need to extend it to customize its behaviour. 4 | 5 | Here is the list of the function you might want to modify: 6 | 7 | ```python 8 | on_open(self, ws) 9 | ``` 10 | 11 | > This one is triggered once the websocket is connected, `self` is the class itself, `ws` is the instance that issued the event. 12 | 13 | ```python 14 | on_message(self, ws, message) 15 | ``` 16 | 17 | > By default, this event will only print the received message. 18 | 19 | ```python 20 | on_error(self, ws) 21 | ``` 22 | 23 | > By default, this event will only print the received error. 24 | 25 | For example if you want to make your websocket to subscribe to `'futures:btc_usd:last-price' and `'futures:btc_usd:index' you'll do something like... 26 | 27 | ```python 28 | class LNMarketsWebsocketCustom(LNMarketsWebsocket): 29 | def __init__(self, **options): 30 | super().__init__(**options) 31 | 32 | def on_open(self, ws): 33 | print("Opened connection") 34 | self.subscribe([ 35 | 'futures:btc_usd:last-price', 36 | 'futures:btc_usd:index' 37 | ]) 38 | 39 | options = { 40 | 'key': 'your_api_key', 41 | 'secret': 'your_api_secret', 42 | 'passphrase': 'your_api_passphrase' 43 | } 44 | 45 | lnmarkets = LNMarketsWebsocketCustom(**options) 46 | lnmarkets.connect() 47 | 48 | ``` 49 | 50 | You can use `self.subscribe()`, `self.unsubscribe()`, `self.list_methods()`, `self.list_events()` to your liking as well! 51 | -------------------------------------------------------------------------------- /examples/websocket/listenBidOffer.py: -------------------------------------------------------------------------------- 1 | class LNMarketsWebsocketWrapper(LNMarketsWebsocket): 2 | def __init__(self, **options): 3 | super().__init__(**options) 4 | 5 | def on_open(self, ws): 6 | print("Opened connection") 7 | self.subscribe(["futures/market/bid-offer"]) 8 | 9 | options = { 10 | 'key': 'your_api_key', 11 | 'secret': 'your_api_secret', 12 | 'passphrase': 'your_api_passphrase' 13 | } 14 | 15 | lnmarkets = LNMarketsWebsocket() 16 | lnmarkets.connect() 17 | -------------------------------------------------------------------------------- /lnmarkets/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /lnmarkets/rest.py: -------------------------------------------------------------------------------- 1 | import os 2 | 3 | from urllib.parse import urlencode 4 | from datetime import datetime 5 | from base64 import b64encode 6 | from requests import request 7 | 8 | import hashlib 9 | import hmac 10 | import json 11 | 12 | def get_hostname(network): 13 | hostname = os.environ.get('LNMARKETS_API_HOSTNAME') 14 | 15 | if hostname: 16 | return hostname 17 | elif network == 'testnet': 18 | return 'api.testnet4.lnmarkets.com' 19 | else: 20 | return 'api.lnmarkets.com' 21 | 22 | class LNMarketsRest(): 23 | def __init__(self, **options): 24 | self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) 25 | self.secret = options.get('secret', os.getenv('LNMARKETS_API_SECRET')) 26 | self.passphrase = options.get('passphrase', os.getenv('LNMARKETS_API_PASSPHRASE')) 27 | self.network = options.get('network', os.getenv('LNMARKETS_API_NETWORK', 'mainnet')) 28 | self.version = options.get('version', os.getenv('LNMARKETS_API_VERSION', 'v2')) 29 | self.hostname = get_hostname(self.network) 30 | self.custom_headers = options.get('custom_headers') 31 | self.full_response = options.get('full_response', False) 32 | self.debug = options.get('debug', False) 33 | self.skip_api_key = options.get('skip_api_key', False) 34 | 35 | def _request_options(self, **options): 36 | credentials = options.get('credentials') 37 | method = options.get('method') 38 | path = options.get('path') 39 | params = options.get('params') 40 | opts = { 'headers': {} } 41 | 42 | if method != 'DELETE': 43 | opts['headers']['Content-Type'] = 'application/json' 44 | 45 | if self.custom_headers: 46 | opts['headers'].update(**self.custom_headers) 47 | 48 | if method in ['GET', 'DELETE']: 49 | data = urlencode(params) 50 | elif method in ['POST', 'PUT']: 51 | data = json.dumps(params, separators=(',', ':')) 52 | 53 | if credentials and not self.skip_api_key: 54 | if not self.key: 55 | 'You need an API key to use an authenticated route' 56 | elif not self.secret: 57 | 'You need an API secret to use an authenticated route' 58 | elif not self.passphrase: 59 | 'You need an API passphrase to use an authenticated route' 60 | 61 | ts = str(int(datetime.now().timestamp() * 1000)) 62 | 63 | 64 | payload = ts + method + '/' + self.version + path + data 65 | hashed = hmac.new(bytes(self.secret, 'utf-8'), bytes(payload, 'utf-8'), hashlib.sha256).digest() 66 | signature = b64encode(hashed) 67 | 68 | opts['headers']['LNM-ACCESS-KEY'] = self.key 69 | opts['headers']['LNM-ACCESS-PASSPHRASE'] = self.passphrase 70 | opts['headers']['LNM-ACCESS-TIMESTAMP'] = ts 71 | opts['headers']['LNM-ACCESS-SIGNATURE'] = signature 72 | opts['ressource'] = 'https://' + self.hostname + '/' + self.version + path 73 | 74 | if method in ['GET', 'DELETE'] and params: 75 | opts['ressource'] += '?' + data 76 | return opts 77 | 78 | def request_api(self, method, path, params, credentials = False): 79 | options = { 80 | 'method': method, 81 | 'path': path, 82 | 'params': params, 83 | 'credentials': credentials 84 | } 85 | 86 | opts = self._request_options(**options) 87 | ressource = opts.get('ressource') 88 | headers = opts.get('headers') 89 | 90 | if method in ['GET', 'DELETE']: 91 | response = request(method, ressource, headers = headers) 92 | elif method in ['POST', 'PUT']: 93 | response = request(method, ressource, data = json.dumps(params, separators=(',', ':')), headers = headers) 94 | return response.text 95 | 96 | def before_request_api(self, method, path, params, credentials): 97 | return self.request_api(method, path, params, credentials) 98 | 99 | 100 | ## Futures 101 | 102 | def futures_add_margin(self, params): 103 | method = 'POST' 104 | path = '/futures/add-margin' 105 | credentials = True 106 | 107 | return self.before_request_api(method, path, params, credentials) 108 | 109 | def futures_cancel_all(self): 110 | method = 'DELETE' 111 | path = '/futures/all/cancel' 112 | credentials = True 113 | params = {} 114 | 115 | return self.before_request_api(method, path, params, credentials) 116 | 117 | def futures_cancel(self, params): 118 | method = 'POST' 119 | path = '/futures/cancel' 120 | credentials = True 121 | 122 | return self.before_request_api(method, path, params, credentials) 123 | 124 | def futures_cashin(self, params): 125 | method = 'POST' 126 | path = '/futures/cash-in' 127 | credentials = True 128 | 129 | return self.before_request_api(method, path, params, credentials) 130 | 131 | def futures_close_all(self): 132 | method = 'DELETE' 133 | path = '/futures/all/close' 134 | credentials = True 135 | params = {} 136 | 137 | return self.before_request_api(method, path, params, credentials) 138 | 139 | def futures_close(self, params): 140 | method = 'DELETE' 141 | path = '/futures' 142 | credentials = True 143 | 144 | return self.before_request_api(method, path, params, credentials) 145 | 146 | def futures_get_trades(self, params): 147 | method = 'GET' 148 | path = '/futures' 149 | credentials = True 150 | 151 | return self.before_request_api(method, path, params, credentials) 152 | 153 | def futures_new_trade(self, params): 154 | method = 'POST' 155 | path = '/futures' 156 | credentials = True 157 | 158 | return self.before_request_api(method, path, params, credentials) 159 | 160 | def futures_update_trade(self, params): 161 | method = 'PUT' 162 | path = '/futures' 163 | credentials = True 164 | 165 | return self.before_request_api(method, path, params, credentials) 166 | 167 | 168 | def futures_carry_fees(self, params): 169 | method = 'GET' 170 | path = '/futures/carry-fees' 171 | credentials = True 172 | 173 | return self.before_request_api(method, path, params, credentials) 174 | 175 | def futures_fixing(self, params): 176 | method = 'GET' 177 | path = '/futures/history/fixing' 178 | credentials = False 179 | 180 | return self.before_request_api(method, path, params, credentials) 181 | 182 | 183 | def futures_index(self, params): 184 | method = 'GET' 185 | path = '/futures/history/index' 186 | credentials = False 187 | 188 | return self.before_request_api(method, path, params, credentials) 189 | 190 | def futures_get_leaderboard(self): 191 | method = 'GET' 192 | path = '/futures/leaderboard' 193 | credentials = False 194 | params = {} 195 | 196 | return self.before_request_api(method, path, params, credentials) 197 | 198 | def futures_get_market(self): 199 | method = 'GET' 200 | path = '/futures/market' 201 | credentials = False 202 | params = {} 203 | 204 | return self.before_request_api(method, path, params, credentials) 205 | 206 | def futures_get_price(self, params): 207 | method = 'GET' 208 | path = '/futures/history/price' 209 | credentials = True 210 | 211 | return self.before_request_api(method, path, params, credentials) 212 | 213 | def futures_get_ticker(self): 214 | method = 'GET' 215 | path = '/futures/ticker' 216 | credentials = False 217 | params = {} 218 | 219 | return self.before_request_api(method, path, params, credentials) 220 | 221 | 222 | def futures_get_trade(self, params): 223 | method = 'GET' 224 | trade_id = params.get('id') 225 | path = f'/futures/trades/{trade_id}' 226 | credentials = True 227 | 228 | return self.before_request_api(method, path, params, credentials) 229 | 230 | 231 | 232 | 233 | ## Options 234 | 235 | def options_close_all(self): 236 | method = 'DELETE' 237 | path = '/options/close-all' 238 | credentials = True 239 | params = {} 240 | 241 | return self.before_request_api(method, path, params, credentials) 242 | 243 | 244 | def options_close(self, params): 245 | method = 'DELETE' 246 | path = '/options' 247 | credentials = True 248 | 249 | return self.before_request_api(method, path, params, credentials) 250 | 251 | 252 | def options_get_trades(self, params): 253 | method = 'GET' 254 | path = '/options' 255 | credentials = True 256 | 257 | return self.before_request_api(method, path, params, credentials) 258 | 259 | 260 | def options_new_trade(self, params): 261 | method = 'POST' 262 | path = '/options' 263 | credentials = True 264 | 265 | return self.before_request_api(method, path, params, credentials) 266 | 267 | def options_update_trade(self, params): 268 | method = 'PUT' 269 | path = '/options' 270 | credentials = True 271 | 272 | return self.before_request_api(method, path, params, credentials) 273 | 274 | def options_get_trade(self, params): 275 | method = 'GET' 276 | trade_id = params.get('id') 277 | path = f'/options/trades/{trade_id}' 278 | credentials = True 279 | 280 | return self.before_request_api(method, path, params, credentials) 281 | 282 | def options_get_volatility(self): 283 | method = 'GET' 284 | path = '/options/volatility-index' 285 | credentials = False 286 | params = {} 287 | 288 | return self.before_request_api(method, path, params, credentials) 289 | 290 | def options_get_instrument(self, params): 291 | method = 'GET' 292 | path = '/options/instrument' 293 | credentials = False 294 | 295 | return self.before_request_api(method, path, params, credentials) 296 | 297 | def options_get_instruments(self): 298 | method = 'GET' 299 | path = '/options/instruments' 300 | credentials = False 301 | params = {} 302 | 303 | return self.before_request_api(method, path, params, credentials) 304 | 305 | 306 | def options_get_market(self): 307 | method = 'GET' 308 | path = '/options/market' 309 | credentials = False 310 | params = {} 311 | 312 | return self.before_request_api(method, path, params, credentials) 313 | 314 | ## Swap 315 | 316 | def get_swaps(self, params): 317 | method = 'GET' 318 | path = '/swap' 319 | credentials = True 320 | 321 | return self.before_request_api(method, path, params, credentials) 322 | 323 | def swap(self, params): 324 | method = 'POST' 325 | path = '/swap' 326 | credentials = True 327 | 328 | return self.before_request_api(method, path, params, credentials) 329 | 330 | 331 | ## User 332 | 333 | def get_user(self): 334 | method = 'GET' 335 | path = '/user' 336 | credentials = True 337 | params = {} 338 | 339 | return self.before_request_api(method, path, params, credentials) 340 | 341 | def update_user(self, params): 342 | method = 'PUT' 343 | path = '/user' 344 | credentials = True 345 | 346 | return self.before_request_api(method, path, params, credentials) 347 | 348 | def get_new_bitcoin_address(self, params): 349 | method = 'POST' 350 | path = '/user/bitcoin/address' 351 | credentials = True 352 | 353 | return self.before_request_api(method, path, params, credentials) 354 | 355 | def get_bitcoin_addresses(self): 356 | method = 'GET' 357 | path = '/user/bitcoin/address' 358 | credentials = True 359 | params={} 360 | 361 | return self.before_request_api(method, path, params, credentials) 362 | 363 | def get_deposit(self, params): 364 | method = 'GET' 365 | deposit_id = params.get('id') 366 | path = f'/user/deposits/{deposit_id}' 367 | credentials = True 368 | 369 | return self.before_request_api(method, path, params, credentials) 370 | 371 | def get_deposits(self, params): 372 | method = 'GET' 373 | path = '/user/deposit' 374 | credentials = True 375 | 376 | return self.before_request_api(method, path, params, credentials) 377 | 378 | def new_deposit(self, params): 379 | method = 'POST' 380 | path = '/user/deposit' 381 | credentials = True 382 | 383 | return self.before_request_api(method, path, params, credentials) 384 | 385 | def new_deposit_susd(self, params): 386 | method = 'POST' 387 | path = '/user/deposit/susd' 388 | credentials = True 389 | 390 | return self.before_request_api(method, path, params, credentials) 391 | 392 | def get_withdrawal(self, params): 393 | method = 'GET' 394 | withdrawal_id = params.get('id') 395 | path = f'/user/withdrawals/{withdrawal_id}' 396 | credentials = True 397 | 398 | return self.before_request_api(method, path, params, credentials) 399 | 400 | def get_withdrawals(self, params): 401 | method = 'GET' 402 | path = '/user/withdraw' 403 | credentials = True 404 | 405 | return self.before_request_api(method, path, params, credentials) 406 | 407 | def new_withdraw(self, params): 408 | method = 'POST' 409 | path = '/user/withdraw' 410 | credentials = True 411 | 412 | return self.before_request_api(method, path, params, credentials) 413 | 414 | def new_withdraw_susd(self, params): 415 | method = 'POST' 416 | path = '/user/withdraw/susd' 417 | credentials = True 418 | 419 | return self.before_request_api(method, path, params, credentials) 420 | 421 | def new_internal_transfer(self, params): 422 | method = 'POST' 423 | path = '/user/transfer' 424 | credentials = True 425 | 426 | return self.before_request_api(method, path, params, credentials) 427 | 428 | ## Oracle 429 | 430 | def get_oracle_index(self, params): 431 | method = 'GET' 432 | path = '/oracle/index' 433 | credentials = False 434 | 435 | return self.before_request_api(method, path, params, credentials) 436 | 437 | def get_oracle_last(self, params): 438 | method = 'GET' 439 | path = '/oracle/last-price' 440 | credentials = False 441 | 442 | return self.before_request_api(method, path, params, credentials) 443 | 444 | 445 | ## Notifications 446 | 447 | def fetch_notifications(self): 448 | method = 'GET' 449 | path = '/user/notifications' 450 | credentials = True 451 | params = {} 452 | 453 | return self.before_request_api(method, path, params, credentials) 454 | 455 | def mark_notifications_read(self): 456 | method = 'DELETE' 457 | path = '/user/notifications/all' 458 | credentials = True 459 | params = {} 460 | 461 | return self.before_request_api(method, path, params, credentials) 462 | 463 | 464 | ## App 465 | 466 | def app_configuration(self): 467 | method = 'GET' 468 | path = '/app/configuration' 469 | credentials = False 470 | params = {} 471 | 472 | return self.before_request_api(method, path, params, credentials) 473 | 474 | def app_node(self): 475 | method = 'GET' 476 | path = '/app/node' 477 | credentials = False 478 | params = {} 479 | 480 | return self.before_request_api(method, path, params, credentials) 481 | 482 | 483 | 484 | -------------------------------------------------------------------------------- /lnmarkets/websockets.py: -------------------------------------------------------------------------------- 1 | import websocket 2 | import os 3 | import secrets 4 | import json 5 | 6 | def get_hostname(network): 7 | hostname = os.environ.get('LNMARKETS_API_HOSTNAME') 8 | 9 | if hostname: 10 | return hostname 11 | elif network == 'testnet': 12 | return 'api.testnet.lnmarkets.com' 13 | else: 14 | return 'api.lnmarkets.com' 15 | 16 | class LNMarketsWebsocket: 17 | def __init__(self, **options): 18 | self.ws = None 19 | self.key = options.get('key', os.getenv('LNMARKETS_API_KEY')) 20 | self.secret = options.get('secret', os.getenv('LNMARKETS_API_SECRET')) 21 | self.passphrase = options.get('passphrase', os.getenv('LNMARKETS_API_PASSPHRASE')) 22 | self.network = options.get('network', os.getenv('LNMARKETS_API_NETWORK', 'mainnet')) 23 | self.version = options.get('version', os.getenv('LNMARKETS_API_VERSION', 'v2')) 24 | self.hostname = get_hostname(self.network) 25 | self.ws = None 26 | 27 | def on_message(self, ws, message): 28 | print(message) 29 | 30 | def on_error(self, ws, error): 31 | print(error) 32 | 33 | def on_close(self, ws, close_status_code, close_msg): 34 | print("Closed connection") 35 | 36 | def on_open(self, ws): 37 | print("Opened connection") 38 | 39 | def connect(self): 40 | self.ws = websocket.WebSocketApp("wss://" + self.hostname, 41 | on_open = self.on_open, 42 | on_message = self.on_message, 43 | on_error = self.on_error, 44 | ) 45 | self.ws.run_forever() 46 | 47 | def subscribe(self, params): 48 | payload = { 49 | "jsonrpc": "2.0", 50 | "method": "v1/public/subscribe", 51 | "id": secrets.token_bytes(4).hex(), 52 | "params": params 53 | } 54 | 55 | self.ws.send(json.dumps(payload)) 56 | 57 | def unsubscribe(self, params): 58 | payload = { 59 | "jsonrpc": "2.0", 60 | "method": "unsubscribe", 61 | "id": secrets.token_bytes(4).hex(), 62 | "params": params 63 | } 64 | 65 | self.ws.send(json.dumps(payload)) 66 | 67 | def list_methods(self): 68 | payload = { 69 | "jsonrpc": "2.0", 70 | "method": "__listMethods", 71 | "id": secrets.token_bytes(4).hex(), 72 | } 73 | 74 | self.ws.send(json.dumps(payload)) 75 | 76 | def list_events(self): 77 | payload = { 78 | "jsonrpc": "2.0", 79 | "method": "__listEvents", 80 | "id": secrets.token_bytes(4).hex(), 81 | } 82 | 83 | self.ws.send(json.dumps(payload)) 84 | -------------------------------------------------------------------------------- /pyproject.toml: -------------------------------------------------------------------------------- 1 | [build-system] 2 | requires = [ 3 | "setuptools>=42", 4 | "wheel" 5 | ] 6 | build-backend = "setuptools.build_meta" 7 | -------------------------------------------------------------------------------- /requirements.txt: -------------------------------------------------------------------------------- 1 | setuptools~=68.0.0 2 | requests~=2.31.0 3 | bech32~=1.2.0 4 | embit~=0.7.0 5 | websocket-client~=1.6.1 -------------------------------------------------------------------------------- /setup.cfg: -------------------------------------------------------------------------------- 1 | [bdist_wheel] 2 | universal = 1 3 | 4 | [pep8] 5 | ignore = E501 6 | -------------------------------------------------------------------------------- /setup.py: -------------------------------------------------------------------------------- 1 | #!/usr/bin/env python 2 | from setuptools import setup 3 | from pathlib import Path 4 | 5 | this_directory = Path(__file__).parent 6 | long_description = (this_directory / "README.md").read_text() 7 | 8 | setup( 9 | name='ln-markets', 10 | version='2.0.9', 11 | packages=['lnmarkets'], 12 | description='LN Markets API python implementation', 13 | long_description=long_description, 14 | long_description_content_type='text/markdown', 15 | url='https://github.com/ln-markets/api-python', 16 | author='Romain ROUPHAEL', 17 | license='MIT', 18 | keywords='lnmarkets trading rest api bitcoin lightning network futures options', 19 | install_requires=[ 20 | 'requests', 21 | 'websocket-client' 22 | ], 23 | classifiers=[ 24 | 'Intended Audience :: Developers', 25 | 'License :: OSI Approved :: MIT License', 26 | 'Operating System :: OS Independent', 27 | 'Programming Language :: Python :: 3', 28 | 'Programming Language :: Python :: 3.5', 29 | 'Programming Language :: Python :: 3.6', 30 | 'Programming Language :: Python :: 3.7', 31 | 'Programming Language :: Python :: 3.8', 32 | 'Programming Language :: Python', 33 | 'Topic :: Software Development :: Libraries :: Python Modules', 34 | ], 35 | ) 36 | -------------------------------------------------------------------------------- /tests/__init__.py: -------------------------------------------------------------------------------- 1 | 2 | -------------------------------------------------------------------------------- /tests/test_node_state.py: -------------------------------------------------------------------------------- 1 | 2 | --------------------------------------------------------------------------------