Compare commits
1302 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 2152a95451 | |||
| 39eda1b1dc | |||
| ad22716374 | |||
| 927d9b5d5b | |||
| cb06dd8933 | |||
| 33c0fb3a2c | |||
| 9bd5aff879 | |||
| bc5c27ce6e | |||
| 478cc84c8a | |||
| 1805fcac30 | |||
| ced9448d7f | |||
| 9e0d015b1f | |||
| d3c57ca721 | |||
| cccc4b5b85 | |||
| 54ed2f9fd2 | |||
| 5d723e341d | |||
| 81ae8ac830 | |||
| 763dc48606 | |||
| df183c9ccd | |||
| 582032b2e4 | |||
| 8c4fd4911a | |||
| 05068fe463 | |||
| 6934088a59 | |||
| 70396fc346 | |||
| a544fb1eb3 | |||
| 6f1d993721 | |||
| 18e3673a1b | |||
| e452a2389f | |||
| 7846361068 | |||
| d6738da6ec | |||
| e5fadb180d | |||
| 31fb009998 | |||
| 43aee2147a | |||
| 6e443a42cd | |||
| 1649aca411 | |||
| 71ef6f70e5 | |||
| 9d57e3930d | |||
| ab10379833 | |||
| ccd788e2ce | |||
| 8c39740105 | |||
| eeaa9061e5 | |||
| 3e20770446 | |||
| 54d467d2bf | |||
| cabdfabf03 | |||
| d0624570af | |||
| 38b35ec528 | |||
| 21b1f5aab8 | |||
| 37da714610 | |||
| bd608bedf0 | |||
| ad370cbbcd | |||
| 6d46d17e30 | |||
| bc5ded4400 | |||
| 19284e1e91 | |||
| e17258940a | |||
| 52403ceacd | |||
| 3b41c7e7f3 | |||
| 73905b33ad | |||
| dd04c51c42 | |||
| 9bc866e6b2 | |||
| 33454e2f23 | |||
| c37a0706d7 | |||
| 8e5c574224 | |||
| a3e6abef8d | |||
| adbf1b5e6f | |||
| 0a89efd002 | |||
| 35408966f4 | |||
| ad12c98d10 | |||
| 96bb4db68e | |||
| 2d9be6c818 | |||
| 3ca0be7543 | |||
| d6dcd8adca | |||
| 5f1ab7508f | |||
| 2906f2f8e0 | |||
| 652819309d | |||
| 1901da688e | |||
| 365b9c9cfe | |||
| fa6c17c067 | |||
| 86f3b649b9 | |||
| 115cb4bf40 | |||
| 226eaf0d0a | |||
| 6113e47991 | |||
| 7b78912d71 | |||
| 81d80ff5c7 | |||
| 914d630a22 | |||
| 9441eb9b53 | |||
| b7f65a77c7 | |||
| a750e4c107 | |||
| fd5a4d71a0 | |||
| 73c9b04dcd | |||
| aaee5a2abf | |||
| 6f2ec3ced2 | |||
| 7e78a103cf | |||
| aaab37cf52 | |||
| 1167c078cf | |||
| 1223862ede | |||
| 3842b4697f | |||
| a095aa2374 | |||
| d4c37fb59d | |||
| 472d5d8703 | |||
| 6f3bb7f2f8 | |||
| ac5cd778ca | |||
| 938259ffde | |||
| 8f26793b7a | |||
| 8888fd190a | |||
| 4fee0eae6a | |||
| 8932aab20b | |||
| 12ae190fef | |||
| 98807b100c | |||
| 03835350c0 | |||
| 54c2febe1c | |||
| d48cff3b9a | |||
| cc534c5000 | |||
| 4320c7a3cc | |||
| e52caa2a36 | |||
| 6b15ebd4c2 | |||
| c802d39f67 | |||
| 608088bc03 | |||
| b9b044585d | |||
| d4ce774532 | |||
| a375e686de | |||
| 08f2eebd6e | |||
| 927c54094a | |||
| 6dfc34d227 | |||
| 6a1c33e168 | |||
| e0aefb9b54 | |||
| df712c0168 | |||
| 53c548f7b6 | |||
| 1abd8fb8fc | |||
| 7c3feeddc4 | |||
| 0c99ff7f66 | |||
| a078088ea3 | |||
| 31f6030c67 | |||
| 8004829696 | |||
| 448b74840e | |||
| 9b1def604f | |||
| 3d4250ca99 | |||
| 6a1ca75a69 | |||
| 22c61458a4 | |||
| f2cfb127ae | |||
| be1e1453da | |||
| 87d50243e2 | |||
| a451b94387 | |||
| a41b38e94f | |||
| 40aa0800de | |||
| 862d4c8857 | |||
| 37feede6a5 | |||
| 99b4767bf4 | |||
| 0a7ed55ade | |||
| 298f9b225d | |||
| bc85352098 | |||
| 6a570bd82e | |||
| a15793a6ba | |||
| 77682808ea | |||
| 82482ec159 | |||
| 5eeb96fa96 | |||
| c6a50b3c06 | |||
| 3c8366e609 | |||
| 2297b3ed55 | |||
| f8a2569739 | |||
| 393d4b8eb3 | |||
| 3dd7c1e492 | |||
| 0b1f702ef9 | |||
| 54d9dbaea8 | |||
| ba27c41c93 | |||
| d7920c4b64 | |||
| 3338fdece3 | |||
| 523054e8ea | |||
| c8a5904959 | |||
| 7f386874ad | |||
| 18a4d6972d | |||
| a0a22f62e0 | |||
| 7fc1d38ffb | |||
| 3f8078618e | |||
| e7b907a175 | |||
| 7796884119 | |||
| 264669df1d | |||
| eddca5a694 | |||
| ca758c28b7 | |||
| 39ef97a2ac | |||
| 8f78329a23 | |||
| 409a6236d4 | |||
| 42fb9ad281 | |||
| 4946e5978e | |||
| f0606bfe90 | |||
| 4df847fa0f | |||
| c0a684ee7c | |||
| c74faec6d0 | |||
| a266c0832b | |||
| e0d0b17583 | |||
| abfed990d9 | |||
| 183c82903f | |||
| 3225c051a2 | |||
| a4f2ea810d | |||
| caf50c0bd0 | |||
| 76814250e4 | |||
| 7a401415e5 | |||
| 65654a4a44 | |||
| 0c7e89aede | |||
| 094bbb3a50 | |||
| df2432bc18 | |||
| e09abfdc75 | |||
| eaec5065a1 | |||
| 6fa38e4d69 | |||
| 00b80a8b0d | |||
| 9e55690ab3 | |||
| 78535bc7e8 | |||
| 24ba337a56 | |||
| e5f3d04437 | |||
| 081e50ec48 | |||
| 859dd6b7ec | |||
| c924399e96 | |||
| 3073e8810f | |||
| c1726c4401 | |||
| b1f540964c | |||
| 6d9a1a9b98 | |||
| 811c66e8dc | |||
| 4c3df2f73d | |||
| d5bb2e4eb1 | |||
| 16045870a8 | |||
| f741f95c36 | |||
| 564d77f239 | |||
| 095150f6b2 | |||
| c935bd057e | |||
| d8aaaa34d3 | |||
| 08d0e76187 | |||
| 24717b1609 | |||
| 6a8abd5d45 | |||
| b19b4d001c | |||
| 8c12a1fd8b | |||
| a584854d46 | |||
| 1df21d3cdf | |||
| e6da491cac | |||
| d4872eae16 | |||
| 569a87ca28 | |||
| 4affb1eade | |||
| 15cbca51e1 | |||
| 2980503493 | |||
| 8d7a21e9d3 | |||
| 1cbd3f52f1 | |||
| b284abf9e0 | |||
| 55c0cfefec | |||
| 9de4731585 | |||
| 2ef854741c | |||
| 7bb6c5e7bd | |||
| fb5f81e3ca | |||
| 4cbbc97b2b | |||
| eccedaf6c9 | |||
| 500dfec5c0 | |||
| d5b4740023 | |||
| 62a15580ce | |||
| 41db2aec71 | |||
| 04a25e0f39 | |||
| 1d8ebc7ec1 | |||
| 66a05ee672 | |||
| b076ec6c8e | |||
| b277372da4 | |||
| 73363d925f | |||
| 7b6e72ab57 | |||
| fcfd25d50b | |||
| d6aa7f1b8b | |||
| ad56462fa1 | |||
| d112a8c0ed | |||
| a6bfe1a4af | |||
| f960e343cc | |||
| dcb9d5d611 | |||
| b1c83ce189 | |||
| 2072a625bd | |||
| 8792d56251 | |||
| 556db08b0a | |||
| b651e13bd6 | |||
| 13ae0f81ca | |||
| a534340cc1 | |||
| 067a7315f2 | |||
| f405b7d1cd | |||
| 178a0a5db0 | |||
| 055c4396d1 | |||
| 8fae64f373 | |||
| 747266fe42 | |||
| ad06f93501 | |||
| f4074d2960 | |||
| 05f8bb357f | |||
| 7b5e444333 | |||
| 0906f050e5 | |||
| 1551f92832 | |||
| be26e31235 | |||
| 36f1111d92 | |||
| 9726c4ae21 | |||
| 7895eeb3b6 | |||
| 0a186eb8b7 | |||
| 79719bbe82 | |||
| f2335c5db9 | |||
| e100a06785 | |||
| d606d6d08a | |||
| 01f09ca5c8 | |||
| 8ad0375073 | |||
| e8cea35f5e | |||
| 95fe0d5d82 | |||
| 411322357c | |||
| d047ac67a8 | |||
| e072522ed1 | |||
| e971f043f4 | |||
| 66422ce9a9 | |||
| 0a0105c31e | |||
| a6ad36d08b | |||
| 7eb91c74a8 | |||
| 79b8363b7b | |||
| ca9d792d2a | |||
| fba028abd1 | |||
| 3f226c5022 | |||
| afe3d3ebda | |||
| 01bb7706e4 | |||
| 772dbeaf7e | |||
| 0c026f950b | |||
| f0e0957579 | |||
| 7e3e65ecd8 | |||
| bef15b5238 | |||
| 127f61ccb9 | |||
| bd49ad6420 | |||
| 46622f6172 | |||
| 5888da5bc5 | |||
| bfe739da3c | |||
| 1036a890b8 | |||
| 3401e3506b | |||
| e75d46321e | |||
| 26311663e3 | |||
| bda17b59e7 | |||
| 1ec04a8dc8 | |||
| 78442e36e7 | |||
| 82565a9667 | |||
| 38e7b0e8ae | |||
| e49ab2593c | |||
| c433f49089 | |||
| 553094c3c6 | |||
| 06c4431c31 | |||
| 5b4d286637 | |||
| bcc766a8ff | |||
| 666f2fc10a | |||
| 50cd36acbd | |||
| dfd46a19b5 | |||
| 41d508867e | |||
| f60d6c8f65 | |||
| 18e34632d8 | |||
| 526d7fad62 | |||
| e90a68cc1a | |||
| b81735e718 | |||
| f1f272b88f | |||
| 90ae723a74 | |||
| 392598086c | |||
| 691b7d2628 | |||
| cdb2a3aa90 | |||
| 0f24b8d132 | |||
| e8a18c0524 | |||
| 16083b2f63 | |||
| bbeba6ab3a | |||
| 710a953c43 | |||
| 9d5cb103dd | |||
| 674ec6acb2 | |||
| 6e6a93f0ce | |||
| 74641c5659 | |||
| ea634e5cef | |||
| f6a8cb4698 | |||
| 2854186b14 | |||
| 093fd48cf9 | |||
| 7f5c38fd5a | |||
| 82a9bdca5e | |||
| b2a6722687 | |||
| e07f3d266e | |||
| 6d3e3b5bfa | |||
| 72225daa6a | |||
| 720232a047 | |||
| 6e88cbfcab | |||
| 7d6d3d38f7 | |||
| 163d03ac62 | |||
| b4c9541fb8 | |||
| d0bda7c2ce | |||
| d2a6781379 | |||
| cc7d341afc | |||
| 3ea1cccda9 | |||
| 94c0f41564 | |||
| b4ad6122cf | |||
| 0d9775918f | |||
| 150b7f85de | |||
| 40898f522a | |||
| d6ff8ebbc0 | |||
| 65105f7768 | |||
| 82ff4d5879 | |||
| b1fe5b6d8a | |||
| 6dc4fa9dbf | |||
| 81d1a662a2 | |||
| 6b7935f1ae | |||
| 38ae3ac03c | |||
| 3059d66ece | |||
| 7543b2681d | |||
| 8e5097d9a9 | |||
| 21dcb4a6a1 | |||
| 35253f9c8d | |||
| 6219a25901 | |||
| 669076a29f | |||
| abd096dcd8 | |||
| 4a097bd644 | |||
| 64019e0e6c | |||
| 3621ba034b | |||
| 17d052df3f | |||
| ebd516cadb | |||
| b5548dbee0 | |||
| 3a64749678 | |||
| ca94bbe994 | |||
| 85c150b68e | |||
| 02570e285d | |||
| 996fcb6f56 | |||
| 6941953a8b | |||
| 08c1866cdc | |||
| d1e1b8410b | |||
| cd986ced45 | |||
| 18a3489a6f | |||
| a0a4230339 | |||
| 592e86a6e3 | |||
| 1dda952172 | |||
| 6df04d7e3e | |||
| e54ee0b4b3 | |||
| dafc968602 | |||
| d4ddcbc784 | |||
| 8395192e95 | |||
| 76ff0689f0 | |||
| 28575a9ef3 | |||
| 80be86533e | |||
| 2725ee5772 | |||
| 405e0f02d4 | |||
| 9bb9f7453c | |||
| 409729f9d2 | |||
| 838d9cd4d0 | |||
| 3ed53218a0 | |||
| c06f892109 | |||
| ca2d322eb8 | |||
| d07ec50549 | |||
| 600519c36a | |||
| 01c0fd0420 | |||
| 9a72003c74 | |||
| 45bb9ee9a0 | |||
| 60a62d933c | |||
| 4281642f54 | |||
| 5e0aa9108c | |||
| dbcb07275a | |||
| 4aff24f6df | |||
| 3037cef4d2 | |||
| fcc6d0fea8 | |||
| c284da404e | |||
| 93a5f2906a | |||
| 91e1b068e8 | |||
| ec23fec3e6 | |||
| e958552bcc | |||
| 85c145c777 | |||
| 28bdbbffca | |||
| e560f9963c | |||
| 4e02d31cdc | |||
| 0cf46d8605 | |||
| 08cff9890f | |||
| 2b5b518ccf | |||
| ce6dd466fa | |||
| f578453656 | |||
| 72015686b6 | |||
| a4541fda2f | |||
| 14c27f2cbe | |||
| c5201a6476 | |||
| 8000f94295 | |||
| a68fbb7f0c | |||
| 3c34126e96 | |||
| c3e7569820 | |||
| 7888f9265f | |||
| e336d870a6 | |||
| 225ef6b8ca | |||
| 76459f08aa | |||
| 7dae93f6b5 | |||
| 048cad04a8 | |||
| bae4973da5 | |||
| 1d0c0d6ab7 | |||
| 65a09b0d89 | |||
| 6c81807b76 | |||
| b6fb1dc9e0 | |||
| 2a8c6a6d0e | |||
| ca4fb98775 | |||
| 8d22fbf39c | |||
| 1cc0207b9b | |||
| 4f96d6c7f7 | |||
| 9c84d3549c | |||
| f3e8dd896d | |||
| 6a5869fa43 | |||
| 018d10b346 | |||
| 5b188b5e1d | |||
| 33556f3c2c | |||
| e37d29b607 | |||
| 7171dadc52 | |||
| 1c91675c58 | |||
| 31b400406e | |||
| c78480c494 | |||
| 23d226d372 | |||
| 9b44d1d8cb | |||
| ab6a5d75bc | |||
| e1fdb8dec9 | |||
| 6d2f454d8c | |||
| 02dc895c41 | |||
| 80560a389c | |||
| 60b9d9448a | |||
| 518b6eb565 | |||
| 1010ce96fa | |||
| 204849aa85 | |||
| 4e94178169 | |||
| 0bd50a6e24 | |||
| cb1f49e81c | |||
| edc74ae2e4 | |||
| c5f2a69d9c | |||
| 971a81e15d | |||
| cc3b2000eb | |||
| 86db888386 | |||
| 7cceddb3df | |||
| 29f90cbd04 | |||
| 71b4e79874 | |||
| 1b608a162e | |||
| f8cbf138ee | |||
| acbb485302 | |||
| 98c2f81bb9 | |||
| 2cfe993951 | |||
| 6f0f4f06ef | |||
| b690325f22 | |||
| 865ebc3143 | |||
| 3e6e534e76 | |||
| cab38fb8c3 | |||
| 510863f939 | |||
| bcd631d3a8 | |||
| 99c8be4c30 | |||
| 1b92f2522c | |||
| 00e77c7f3f | |||
| 1680728acb | |||
| 9ad60643f5 | |||
| 2ac7500cbf | |||
| a2f7d93d13 | |||
| 6e052d9e0c | |||
| a4aa20c1f2 | |||
| ac6f7dca1f | |||
| 91ff6df512 | |||
| 154064602e | |||
| 426bc4c97b | |||
| 8c0ba2a69a | |||
| d6ae63ac48 | |||
| ec17b5523c | |||
| b8c16fb889 | |||
| c3f9b16c84 | |||
| 062376f573 | |||
| 21709204eb | |||
| 7ca3032d51 | |||
| 07bf19a990 | |||
| a948796ef7 | |||
| 255ea88638 | |||
| 6bdb651573 | |||
| ed8469f23a | |||
| 265a7123da | |||
| ceb461a252 | |||
| c1ae110080 | |||
| 30b4f27152 | |||
| 1176c16b93 | |||
| 093a093bd5 | |||
| b8a1089592 | |||
| fcb16098d8 | |||
| 7ed7ed4081 | |||
| 99da6f70c2 | |||
| 09d763b604 | |||
| 5e7868a28d | |||
| 66e43f2fe8 | |||
| 43103f51e5 | |||
| b6040e270f | |||
| f8cc2a6e74 | |||
| 75c84bfe65 | |||
| bdd63aa1d6 | |||
| 5dee60921f | |||
| 46e616f997 | |||
| 82f191f7b0 | |||
| cdfeae9f90 | |||
| e988995d71 | |||
| 1fc69ef114 | |||
| 2384ba74a3 | |||
| d1028b8ca2 | |||
| b55105ec82 | |||
| 4a8c8f296a | |||
| 883f27d99e | |||
| 0a8719c617 | |||
| e1fdc0c79c | |||
| c0e9726f49 | |||
| 0021e2c205 | |||
| ce2f4f89c4 | |||
| 5912d87b65 | |||
| b1015172c7 | |||
| bd7edfba97 | |||
| d6b01a6ffe | |||
| 01266ed7eb | |||
| 5402d276d6 | |||
| 9d2bfa6594 | |||
| d5c01f7480 | |||
| e61a4ffc98 | |||
| 1b8255a464 | |||
| d9f4c62f15 | |||
| 540ba4c769 | |||
| 080d9b55f8 | |||
| 74bdb94d07 | |||
| 5854c63d05 | |||
| 3957506db0 | |||
| 1ac8210313 | |||
| 4ff888488b | |||
| e2a486f85e | |||
| 313de7b8a0 | |||
| c2998f3d75 | |||
| 9805cd768b | |||
| c06e4ee89c | |||
| d80ed7e33c | |||
| e2d3774b07 | |||
| aad327b1fe | |||
| 3e0755b8ad | |||
| f164b89351 | |||
| e80ad309f1 | |||
| 817ad64402 | |||
| 67152ad48a | |||
| e82d9e2f55 | |||
| b972ee78ec | |||
| 2704f6e758 | |||
| 9dd5967275 | |||
| 6a313aa9e3 | |||
| 81de29a1e3 | |||
| 3677953d90 | |||
| 7af46628f8 | |||
| 88a2995b4c | |||
| 866ff55d84 | |||
| 91ba4f6424 | |||
| 31e2543134 | |||
| eaf70428c1 | |||
| 38ca58c728 | |||
| 0bf73cc64b | |||
| 7ddaa09a23 | |||
| c663016b47 | |||
| 6307e16304 | |||
| 9ac7149c47 | |||
| 0acde28902 | |||
| f4ad1e8020 | |||
| c7fff45bef | |||
| e30fa3a445 | |||
| db83b0cdb0 | |||
| 4cfd5d004e | |||
| 3680e199ce | |||
| 4e87169a0c | |||
| 621e497e7b | |||
| c013c76e64 | |||
| 4bbb3174b2 | |||
| c511d65d2e | |||
| e8ca9ce39b | |||
| f1af00dd39 | |||
| bfb0b535b7 | |||
| 0199e7d3d8 | |||
| b3ba2cee17 | |||
| 411f606476 | |||
| f53c019d2a | |||
| 3497f7946e | |||
| 65af7750e6 | |||
| 103c993591 | |||
| 969128a74f | |||
| f6e2030bf2 | |||
| 00bde70f73 | |||
| 61e2170fc1 | |||
| 434b8a423c | |||
| 0b149cbd6a | |||
| 2323215aa7 | |||
| 82876570a3 | |||
| c329e71fa8 | |||
| a200b5524b | |||
| baf6725a9a | |||
| d655a0a638 | |||
| 39941a7ac0 | |||
| 66f4839101 | |||
| 6aa1bbf574 | |||
| 381576b8f1 | |||
| 82814cb5fa | |||
| 093b44928e | |||
| fa632311f3 | |||
| 5e86ee9de6 | |||
| 549b9f62fd | |||
| edb5431a77 | |||
| 4241db2fe2 | |||
| c966f83147 | |||
| f361824b15 | |||
| 8675f86d14 | |||
| 0979d0b6e4 | |||
| 4c3879cb57 | |||
| 69a0f4c465 | |||
| 61e09ac719 | |||
| 583b2fc690 | |||
| e06b70eb05 | |||
| 51a6a7b632 | |||
| 3250f42257 | |||
| a029d1f08e | |||
| a5d1ae3191 | |||
| ebd439cdd1 | |||
| 78d8a4df2e | |||
| 4bcf2c423a | |||
| 7b36a0fc42 | |||
| bcfe7ef547 | |||
| 8033faa2f2 | |||
| 190ba272de | |||
| 3f3760c0ae | |||
| 4761bf2427 | |||
| c0da1b6922 | |||
| a0b7df70d6 | |||
| 60b12c1d9e | |||
| fd48991fb0 | |||
| 1696aa3915 | |||
| fb54c9ffe4 | |||
| 0f85ef0997 | |||
| c6d1c1a980 | |||
| c1d7184849 | |||
| a22181d721 | |||
| 7f7e9ec875 | |||
| 6c9b9e91e8 | |||
| d01e9cf299 | |||
| 86da9cb659 | |||
| d2b0dc2171 | |||
| 57fd0e379a | |||
| 280737447c | |||
| 3d6079ae19 | |||
| ab062d7bb1 | |||
| d5b21f2a32 | |||
| 9be7759e42 | |||
| 304f52ab79 | |||
| 6a6e3aacf3 | |||
| 1c81a21bb6 | |||
| 67b910835e | |||
| 8364a704d6 | |||
| 790c7e386a | |||
| 8dda28351e | |||
| b7904b8e80 | |||
| 7fd70b82fa | |||
| 9699011cd9 | |||
| 83b22dedd5 | |||
| c67e451fe1 | |||
| 8593094619 | |||
| d49da76382 | |||
| 2c652a2ce6 | |||
| c99009d47e | |||
| 05ab13592b | |||
| a02b1d0306 | |||
| ffe43be3fa | |||
| 94a52a776c | |||
| f2a7312010 | |||
| 72290365db | |||
| fd737af69d | |||
| 0740a13393 | |||
| 1f3047dce4 | |||
| ea0f4f6b86 | |||
| 0ba27ddee6 | |||
| f19aed5349 | |||
| ca72b494bb | |||
| 2c1ea2d256 | |||
| 8ca905b45f | |||
| 903ae33679 | |||
| 7d74a4c193 | |||
| 7e8e36c6be | |||
| d5aec91a41 | |||
| f10c8f9c3b | |||
| 3aa2d0c30a | |||
| a83b5abb51 | |||
| f89147fd44 | |||
| adf63a45cf | |||
| 5bb4fe16b1 | |||
| 444d939f88 | |||
| f6f069511e | |||
| 02742f36cd | |||
| ce6b62f865 | |||
| 1d07ae4f3a | |||
| ba9e8d2bc5 | |||
| 01a00ad048 | |||
| 9f449dd347 | |||
| 68fd7d7ffd | |||
| a0c246fa9a | |||
| 7223a6c504 | |||
| bf1f08cd21 | |||
| 3e5a572fc6 | |||
| 386925f06b | |||
| 626c904103 | |||
| 2393a9fecf | |||
| 8f9f4b40cd | |||
| 4700782f60 | |||
| 43bab85b85 | |||
| 9e89567eeb | |||
| d2e9d36dea | |||
| e50fac34a5 | |||
| 3fd8b2f1c3 | |||
| 8d02504072 | |||
| 4aef5676d7 | |||
| 73e2e034ae | |||
| b0da978999 | |||
| 1eb27c8b87 | |||
| b4f030fd55 | |||
| e03ea80f79 | |||
| d505f98d12 | |||
| b9245aef2d | |||
| 646aca7a36 | |||
| 76fbe6ed1f | |||
| e526311c2c | |||
| a675d2b026 | |||
| fcfde31985 | |||
| 5c672ce487 | |||
| 5ef261d7a7 | |||
| 36cd3c70e2 | |||
| 6c08f0a86b | |||
| 667a8cc59f | |||
| e773276dde | |||
| 87e813a3dd | |||
| 5114be42cd | |||
| 440382ae69 | |||
| ab7364c62d | |||
| 6961182680 | |||
| cb2aaa7bbb | |||
| 35e1421d59 | |||
| 0f9e846587 | |||
| 2989b42760 | |||
| 3711fa5098 | |||
| c5948693a3 | |||
| 8469484998 | |||
| 57df7d9eca | |||
| 80f6fbbae9 | |||
| 0d14b7a800 | |||
| ad121c19b0 | |||
| 67c3bad977 | |||
| 9bfd34a4f3 | |||
| f7472ca74c | |||
| 44bc1b9aa1 | |||
| 008c2feff9 | |||
| d3b6256f8a | |||
| 0bc5c876c2 | |||
| 13ee90725e | |||
| 7082a8073c | |||
| cd484bd483 | |||
| f37c4e5935 | |||
| d1a96af5e8 | |||
| 6ba8966090 | |||
| 78a1c7247a | |||
| 0fa0f49b75 | |||
| 6806fab1b5 | |||
| 79b8496f38 | |||
| d664e76834 | |||
| 398b93beef | |||
| 2d704a77b5 | |||
| 830a004dfd | |||
| 535ff387ff | |||
| 95e51bf816 | |||
| e8288a34c9 | |||
| 39ffee381b | |||
| ccd4c715ca | |||
| d82bfc9fad | |||
| c59ae30e31 | |||
| cfc6d3b32f | |||
| e58b7ea8e5 | |||
| d219983f5b | |||
| 6c27b27d4f | |||
| fcedc7de0d | |||
| 4342aa3bfd | |||
| 43188ca447 | |||
| 36d4f10a60 | |||
| 470a239e82 | |||
| 01baea8aab | |||
| 4a580fc72f | |||
| d691ffe9db | |||
| eb7fbb0096 | |||
| 63aac1a2c9 | |||
| c35b9f8a3a | |||
| 17af69435f | |||
| 7af524ff90 | |||
| 02a7f6398d | |||
| 18723d2b6d | |||
| 608a408c75 | |||
| e5d75c4454 | |||
| effe2b5367 | |||
| 2f37385eb4 | |||
| 0fa7f9e47f | |||
| 81c8e6b943 | |||
| b3843f0d53 | |||
| 84913f2ea8 | |||
| 1450d9e9cd | |||
| b02e15b162 | |||
| 4394fd1346 | |||
| 3a6393e2d1 | |||
| 990f9e03f8 | |||
| 0b549fc7f8 | |||
| 23ac9e145a | |||
| 63d8239011 | |||
| 5890665931 | |||
| b72078e76e | |||
| 076ca75200 | |||
| 132e143b9a | |||
| e03e0e838b | |||
| f8dc161ab4 | |||
| e0f7b62b54 | |||
| c854cef313 | |||
| 006639820b | |||
| bf17236e9b | |||
| 99b11c088b | |||
| f42fd25800 | |||
| a20fe8cd09 | |||
| c398504f23 | |||
| dd3fbfcfda | |||
| f0562c391c | |||
| 027ce4337d | |||
| fd21658523 | |||
| 523864601a | |||
| 8f0dbc6be1 | |||
| 8d72ee358c | |||
| 0077f3c9a5 | |||
| 6aa4de4d29 | |||
| 817aaa164c | |||
| ea9c51570f | |||
| 2fe6fe14aa | |||
| 85dd371ee3 | |||
| 0fc3c675b6 | |||
| 55fe379e13 | |||
| 96a8263c57 | |||
| 79ecca3e40 | |||
| 267f2e352e | |||
| 1ae3b1e622 | |||
| 34ac2dc9ae | |||
| c15f811602 | |||
| efe332a395 | |||
| 8fd2dcd257 | |||
| 48ea43f954 | |||
| 6b78dac6f0 | |||
| 0a40a345fe | |||
| 656b32814b | |||
| c9c44a4710 | |||
| 5167f6936d | |||
| fdf88a8019 | |||
| 087c59cfbf | |||
| 83480d90f1 | |||
| 67abfcf4d4 | |||
| 00e4c51741 | |||
| a1b93dc915 | |||
| 40011eb9a3 | |||
| f1d002a735 | |||
| 991a279b23 | |||
| 5b879df2b0 | |||
| d191138009 | |||
| a8ee0cc4c8 | |||
| 85658bb637 | |||
| b4fb133702 | |||
| b7537b6ade | |||
| 633992da52 | |||
| 0be5ca7503 | |||
| e310e4abbb | |||
| 1b132ee21d | |||
| 7f0b6250d0 | |||
| 3b5c2ea0d7 | |||
| c6f4c82f44 | |||
| 5bc0dcfb22 | |||
| 06630a5991 | |||
| 5f97374752 | |||
| 3dd29437d5 | |||
| 4e18c35727 | |||
| 0f1e7ed652 | |||
| fbb91ca331 | |||
| 713d9fdc8f | |||
| 3681e3e754 | |||
| 32d77f808e | |||
| d58b941bf1 | |||
| 34df710265 | |||
| f324f8775f | |||
| 00f8f05268 | |||
| 3d5620a422 | |||
| e704c2def4 | |||
| d534f2014a | |||
| 612ec38e57 | |||
| ac5b12cfd2 | |||
| f9b6830b78 | |||
| 8a64f0b884 | |||
| 15da4aa9bd | |||
| c167575098 | |||
| b56c663bea | |||
| 501a9a8c98 | |||
| c333c9c5a1 | |||
| 11dd349c2b | |||
| d355f011df | |||
| ef80772fc3 | |||
| ff95adb2eb | |||
| 5b1cda9236 | |||
| 60812983e3 | |||
| bbec51685d | |||
| 32a4aa0ca8 | |||
| ad0f88796b | |||
| e76888882d | |||
| 3ab226a096 | |||
| 58058f0197 | |||
| 6eaf42fe33 | |||
| 88ba82d4fd | |||
| acbea4e26f | |||
| 50864c731b | |||
| 5bfda534b2 | |||
| b9a43b8e24 | |||
| 59b3486574 | |||
| 9f3c6f2dcc | |||
| 22a08c7c8d | |||
| 73b30cf3fa | |||
| 6ce1123136 | |||
| 8cbdcbd50d | |||
| 42ea4821f4 | |||
| b7ab7dd25e | |||
| c2d6ab2fa5 | |||
| 6069670ce2 | |||
| c248bb27e9 | |||
| f3fd1f010c | |||
| a4d078efd1 | |||
| cfdf21942d | |||
| e18fbbd972 | |||
| 78ca939ae6 | |||
| eccc1498af | |||
| 0a6a0a8b88 | |||
| 6cfc2b2bdf | |||
| 2d5f3c150b | |||
| 3ccb612cd9 | |||
| dc5c1e1300 | |||
| 91ceb5c19a | |||
| d073692cae | |||
| e1b8742b1f | |||
| d866bb6f01 | |||
| 2737de1fdd | |||
| a41b72ca19 | |||
| ea1b3c38f8 | |||
| 31fb06fa80 | |||
| 291478a974 | |||
| 29d2fc2e1b | |||
| 59cc607761 | |||
| c79502cb4b | |||
| f124f2daaf | |||
| 026f989625 | |||
| 9a2ed35030 | |||
| d412a8fc1a | |||
| fbed3fad78 | |||
| 393df83a91 | |||
| e967dfa145 | |||
| 4aab57ce62 | |||
| 5841c65430 | |||
| 338ff66268 | |||
| 629a6be269 | |||
| 4896e04c41 | |||
| a53adb95e1 | |||
| ab82f5cc69 | |||
| 6fc1884e8e | |||
| cd392fbbdf | |||
| 893e2cbbc1 | |||
| bcebb4d804 | |||
| cd81897c84 | |||
| cb9fa734d1 | |||
| 3f27c2792a | |||
| 883f508544 | |||
| 3caf964c46 | |||
| 7fcbe9788d | |||
| de91261f73 | |||
| 225c94c0f5 | |||
| e8fcac491a | |||
| 6a5847a157 | |||
| 95af462e80 | |||
| 3b54e1e746 | |||
| d3506c249d | |||
| a9287359a3 | |||
| 3741cbf3a9 | |||
| 962417bdea | |||
| 34c1838566 | |||
| 3b7288edaa | |||
| e28b2f44d3 | |||
| 72c41ec2c5 | |||
| 5be32cd801 | |||
| 6640c9e3d9 | |||
| bc8c5e743f | |||
| ef8712f4d9 | |||
| fdf9cc1b76 | |||
| 442ca86d15 | |||
| d578c910bc | |||
| 37dacdff0d | |||
| 671ce25a28 | |||
| dbc74f0d5e | |||
| f33e89e164 | |||
| 4b320b0f2f | |||
| 7a141abeca | |||
| a8228f0eac | |||
| ad079f858d | |||
| 0b3126a0fe | |||
| 4686d22020 | |||
| 2304f852ff | |||
| 68d2d24f2a | |||
| 67f608fb5f | |||
| 5bb4824aae | |||
| f29fcb31e0 | |||
| 8055140e66 | |||
| 96a6c00acc | |||
| 09cb6cb02d | |||
| 1f0a2ab242 | |||
| 35467619c4 | |||
| bb684dd613 | |||
| adf07dd3e1 | |||
| ef528fa69c | |||
| e10ab8c543 | |||
| a5862dcc4a | |||
| 1cd08c96a1 | |||
| 2eb3b73a93 | |||
| 1b70e9b074 | |||
| 70cc2942d7 | |||
| 7c7ddb3b19 | |||
| 69e9ab90df | |||
| 28b5552231 | |||
| b27f99dbc3 | |||
| 1e49200596 | |||
| 6121c7ed86 | |||
| 394c260eb2 | |||
| 582f51ccdd | |||
| 74bb1a29b6 | |||
| ddb42879ef | |||
| 53b24ea69b | |||
| 80071dd2d8 | |||
| 545096cc47 | |||
| 9f682b5829 | |||
| 209e7033cd | |||
| 7f69a06819 | |||
| 20b2f25067 | |||
| 079330ac64 | |||
| 88524db290 | |||
| e1ad87a565 | |||
| 65009373ee | |||
| 7f84996c1e | |||
| 90df6bcd54 | |||
| 3237cad8c8 | |||
| 2a11597ad3 | |||
| 59ec3fd860 | |||
| 685fef1ee8 | |||
| 690a3599d0 | |||
| e4977d4cbd | |||
| 7b78e66e7d | |||
| 8726d63923 | |||
| 5e9a929e9e | |||
| c3ce11c222 | |||
| 0ab54b88cf | |||
| 934e72656a | |||
| b950128c4d | |||
| c1982ab836 | |||
| 86a9968bdd | |||
| 2bceb35b79 | |||
| e7eb1c132d | |||
| fdc573f2f7 | |||
| 02d124b85b | |||
| 3eade3e8db | |||
| 6cce455835 | |||
| f028bdf342 | |||
| 983764ad0a | |||
| a12f368796 | |||
| c9dd99a4b5 | |||
| 67d3dca49c | |||
| d5d3188b99 | |||
| 50287ce556 | |||
| 3c1bbd9610 | |||
| ec1b2e1da5 | |||
| 73970d27bf | |||
| 0224c50c9e | |||
| b0fdf6ac52 | |||
| 682b462d82 | |||
| 22307913d8 | |||
| b9786b979a | |||
| abda629a72 | |||
| a4fca5b1fe | |||
| 903062d479 | |||
| b6a71f2d2a | |||
| dc9c4da95e | |||
| 14660f54f8 | |||
| e199083287 | |||
| 206809d2e7 | |||
| 128807ff3b | |||
| 7a6888dfd3 | |||
| f3da2c6fd8 | |||
| ce63eb30a2 | |||
| 501e256c58 | |||
| b50a532284 | |||
| a9380f0d53 | |||
| 316812f427 | |||
| 7ba9aa9acd | |||
| fa368a9a4a | |||
| f0073078e9 | |||
| 9030e1041f | |||
| 2c3cef5dee | |||
| e722d81456 | |||
| 10dbf47836 | |||
| a2160e4503 | |||
| 842727202f | |||
| 3526ba1a9b | |||
| c07f2caa4a | |||
| 21e1f3d910 | |||
| c2d265e4b7 | |||
| f0e82592c3 | |||
| 501f629c05 | |||
| 687ad05047 | |||
| 3852757250 | |||
| 256471dacd | |||
| ead7bd8c2e | |||
| 80dbb0dd09 | |||
| 0552df2fe1 | |||
| e4b843405d | |||
| d2802ad499 | |||
| a9906f0788 | |||
| b17c0cd4a2 | |||
| 0575e222b1 | |||
| 1c5a699aef | |||
| 292ef85d96 | |||
| b3c9be1cd0 | |||
| 13e329f176 | |||
| b9f4a23548 | |||
| 9746d38060 | |||
| ae68d02038 | |||
| 9896ddf71e | |||
| 0d4fcad285 | |||
| 3e7d5bbae8 | |||
| 4d0ee014de | |||
| ea8edbd23b | |||
| f6de7d952a | |||
| 3a87522070 | |||
| 7ccddb5d58 | |||
| e9c04debfb | |||
| 79e15591ef | |||
| b62661d8cd | |||
| e5e3002d45 | |||
| e664527da6 | |||
| 063b55d41a | |||
| ce7061c934 | |||
| d78a5798e3 | |||
| 16958eaef2 | |||
| 2c5cca4be6 | |||
| a39b329e3b | |||
| 68f9402384 | |||
| 600e311b3e | |||
| 33bd433c22 | |||
| 1db4732648 | |||
| ae3f62cf9b | |||
| cbfebd397c | |||
| c6ffe82a7a | |||
| 6427144934 | |||
| c35b308ada | |||
| 28e2bfaf1c | |||
| 15058d3ce6 | |||
| 931704abcc | |||
| 8234879b58 | |||
| b33a9059ab | |||
| 13780d5963 | |||
| 279082b202 | |||
| 39b2a096ab | |||
| f7c7990aff | |||
| 3f44f56f6f | |||
| cd28244cf1 | |||
| 94363061ae | |||
| 4f2d7b858f | |||
| 663b1e11f8 | |||
| 2d6a49013f | |||
| 9e2e60e7ad | |||
| bb2024f789 | |||
| e96f4f0e53 | |||
| 6218d732b1 | |||
| 9f1599b7f0 | |||
| e541c95c46 | |||
| bf8b3e831d | |||
| 90332128b1 | |||
| 41e40e6214 | |||
| c2b32769a1 | |||
| 42d75b0754 | |||
| 0bc84c6a0d | |||
| e8be011e2b | |||
| aaa82e1fa9 | |||
| 2bb68ca53d | |||
| df8f1b9328 | |||
| 9d82de15d4 | |||
| ea828ccb4a | |||
| 2565d509a6 | |||
| d5a0759051 | |||
| 9214af6901 | |||
| cac777cb21 | |||
| f755df2568 | |||
| c420304b33 | |||
| 8494bea64f | |||
| c8ba8106e6 | |||
| ce9d9d7e60 | |||
| 365527508b | |||
| 9fdb8b07ac | |||
| 0009b987e4 | |||
| 24b6ce450b | |||
| 3ad8111d11 | |||
| 4c6074062c | |||
| 4f799cc9db | |||
| c719860a16 | |||
| f3dee5ec4f | |||
| be169a23f4 | |||
| 1551510c6f | |||
| 9f1a7209d6 | |||
| abda02572b | |||
| de01aaf290 | |||
| 096e98a68c | |||
| 854bd9af2b | |||
| 10917a280a |
@@ -1,22 +0,0 @@
|
|||||||
FROM freqtradeorg/freqtrade:develop_freqairl
|
|
||||||
|
|
||||||
USER root
|
|
||||||
# Install dependencies
|
|
||||||
COPY requirements-dev.txt /freqtrade/
|
|
||||||
|
|
||||||
RUN apt-get update \
|
|
||||||
&& apt-get -y install --no-install-recommends apt-utils dialog \
|
|
||||||
&& apt-get -y install --no-install-recommends git sudo vim build-essential \
|
|
||||||
&& apt-get clean \
|
|
||||||
&& mkdir -p /home/ftuser/.vscode-server /home/ftuser/.vscode-server-insiders /home/ftuser/commandhistory \
|
|
||||||
&& echo "export PROMPT_COMMAND='history -a'" >> /home/ftuser/.bashrc \
|
|
||||||
&& echo "export HISTFILE=~/commandhistory/.bash_history" >> /home/ftuser/.bashrc \
|
|
||||||
&& chown ftuser:ftuser -R /home/ftuser/.local/ \
|
|
||||||
&& chown ftuser: -R /home/ftuser/
|
|
||||||
|
|
||||||
USER ftuser
|
|
||||||
|
|
||||||
RUN pip install --user autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir
|
|
||||||
|
|
||||||
# Empty the ENTRYPOINT to allow all commands
|
|
||||||
ENTRYPOINT []
|
|
||||||
@@ -1,42 +1,37 @@
|
|||||||
{
|
{
|
||||||
"name": "freqtrade Develop",
|
"name": "freqtrade Develop",
|
||||||
"build": {
|
"image": "ghcr.io/freqtrade/freqtrade-devcontainer:latest",
|
||||||
"dockerfile": "Dockerfile",
|
|
||||||
"context": ".."
|
|
||||||
},
|
|
||||||
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
// Use 'forwardPorts' to make a list of ports inside the container available locally.
|
||||||
"forwardPorts": [
|
"forwardPorts": [
|
||||||
8080
|
8080
|
||||||
],
|
],
|
||||||
"mounts": [
|
|
||||||
"source=freqtrade-bashhistory,target=/home/ftuser/commandhistory,type=volume"
|
|
||||||
],
|
|
||||||
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/freqtrade,type=bind,consistency=cached",
|
"workspaceMount": "source=${localWorkspaceFolder},target=/workspaces/freqtrade,type=bind,consistency=cached",
|
||||||
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
// Uncomment to connect as a non-root user if you've added one. See https://aka.ms/vscode-remote/containers/non-root.
|
||||||
"remoteUser": "ftuser",
|
"remoteUser": "ftuser",
|
||||||
|
|
||||||
"onCreateCommand": "pip install --user -e .",
|
"onCreateCommand": "pip install --user -e .",
|
||||||
"postCreateCommand": "freqtrade create-userdir --userdir user_data/",
|
"postCreateCommand": "freqtrade create-userdir --userdir user_data/",
|
||||||
|
|
||||||
"workspaceFolder": "/workspaces/freqtrade",
|
"workspaceFolder": "/workspaces/freqtrade",
|
||||||
"customizations": {
|
"customizations": {
|
||||||
"settings": {
|
"vscode": {
|
||||||
"terminal.integrated.shell.linux": "/bin/bash",
|
"settings": {
|
||||||
"editor.insertSpaces": true,
|
"terminal.integrated.shell.linux": "/bin/bash",
|
||||||
"files.trimTrailingWhitespace": true,
|
"editor.insertSpaces": true,
|
||||||
"[markdown]": {
|
"files.trimTrailingWhitespace": true,
|
||||||
"files.trimTrailingWhitespace": false,
|
"[markdown]": {
|
||||||
|
"files.trimTrailingWhitespace": false,
|
||||||
|
},
|
||||||
|
"python.pythonPath": "/usr/local/bin/python",
|
||||||
},
|
},
|
||||||
"python.pythonPath": "/usr/local/bin/python",
|
// Add the IDs of extensions you want installed when the container is created.
|
||||||
},
|
"extensions": [
|
||||||
|
"ms-python.python",
|
||||||
// Add the IDs of extensions you want installed when the container is created.
|
"ms-python.vscode-pylance",
|
||||||
"extensions": [
|
"ms-python.isort",
|
||||||
"ms-python.python",
|
"davidanson.vscode-markdownlint",
|
||||||
"ms-python.vscode-pylance",
|
"ms-azuretools.vscode-docker",
|
||||||
"davidanson.vscode-markdownlint",
|
"vscode-icons-team.vscode-icons",
|
||||||
"ms-azuretools.vscode-docker",
|
"github.vscode-github-actions",
|
||||||
"vscode-icons-team.vscode-icons",
|
],
|
||||||
],
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,21 @@
|
|||||||
|
FROM freqtradeorg/freqtrade:develop_freqairl
|
||||||
|
|
||||||
|
USER root
|
||||||
|
# Install dependencies
|
||||||
|
COPY requirements-dev.txt /freqtrade/
|
||||||
|
|
||||||
|
ARG USERNAME=ftuser
|
||||||
|
|
||||||
|
RUN apt-get update \
|
||||||
|
&& apt-get -y install --no-install-recommends apt-utils dialog git ssh vim build-essential zsh \
|
||||||
|
&& apt-get clean \
|
||||||
|
&& mkdir -p /home/${USERNAME}/.vscode-server /home/${USERNAME}/.vscode-server-insiders /home/${USERNAME}/commandhistory \
|
||||||
|
&& chown ${USERNAME}:${USERNAME} -R /home/${USERNAME}/.local/ \
|
||||||
|
&& chown ${USERNAME}: -R /home/${USERNAME}/
|
||||||
|
|
||||||
|
USER ftuser
|
||||||
|
|
||||||
|
RUN pip install --user autopep8 -r docs/requirements-docs.txt -r requirements-dev.txt --no-cache-dir
|
||||||
|
|
||||||
|
# Empty the ENTRYPOINT to allow all commands
|
||||||
|
ENTRYPOINT []
|
||||||
@@ -0,0 +1,12 @@
|
|||||||
|
{
|
||||||
|
"name": "freqtrade Dev container image builder",
|
||||||
|
"build": {
|
||||||
|
"dockerfile": "Dockerfile",
|
||||||
|
"context": "../../"
|
||||||
|
},
|
||||||
|
"features": {
|
||||||
|
"ghcr.io/devcontainers/features/common-utils:2": {
|
||||||
|
},
|
||||||
|
"ghcr.io/stuartleeks/dev-container-features/shell-history:0.0.3": {}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -10,8 +10,20 @@ updates:
|
|||||||
directory: "/"
|
directory: "/"
|
||||||
schedule:
|
schedule:
|
||||||
interval: weekly
|
interval: weekly
|
||||||
|
time: "03:00"
|
||||||
|
timezone: "Etc/UTC"
|
||||||
open-pull-requests-limit: 15
|
open-pull-requests-limit: 15
|
||||||
target-branch: develop
|
target-branch: develop
|
||||||
|
groups:
|
||||||
|
types:
|
||||||
|
patterns:
|
||||||
|
- "types-*"
|
||||||
|
pytest:
|
||||||
|
patterns:
|
||||||
|
- "pytest*"
|
||||||
|
mkdocs:
|
||||||
|
patterns:
|
||||||
|
- "mkdocs*"
|
||||||
|
|
||||||
- package-ecosystem: "github-actions"
|
- package-ecosystem: "github-actions"
|
||||||
directory: "/"
|
directory: "/"
|
||||||
|
|||||||
@@ -0,0 +1,47 @@
|
|||||||
|
name: Binance Leverage tiers update
|
||||||
|
|
||||||
|
on:
|
||||||
|
schedule:
|
||||||
|
- cron: "0 3 * * 4"
|
||||||
|
# on demand
|
||||||
|
workflow_dispatch:
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
contents: read
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
auto-update:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
environment:
|
||||||
|
name: develop
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- uses: actions/setup-python@v5
|
||||||
|
with:
|
||||||
|
python-version: "3.11"
|
||||||
|
|
||||||
|
- name: Install ccxt
|
||||||
|
run: pip install ccxt
|
||||||
|
|
||||||
|
- name: Run leverage tier update
|
||||||
|
env:
|
||||||
|
CI_WEB_PROXY: ${{ secrets.CI_WEB_PROXY }}
|
||||||
|
FREQTRADE__EXCHANGE__KEY: ${{ secrets.BINANCE_EXCHANGE_KEY }}
|
||||||
|
FREQTRADE__EXCHANGE__SECRET: ${{ secrets.BINANCE_EXCHANGE_SECRET }}
|
||||||
|
run: python build_helpers/binance_update_lev_tiers.py
|
||||||
|
|
||||||
|
|
||||||
|
- uses: peter-evans/create-pull-request@v6
|
||||||
|
with:
|
||||||
|
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||||
|
add-paths: freqtrade/exchange/binance_leverage_tiers.json
|
||||||
|
labels: |
|
||||||
|
Tech maintenance
|
||||||
|
Dependencies
|
||||||
|
branch: update/binance-leverage-tiers
|
||||||
|
title: Update Binance Leverage Tiers
|
||||||
|
commit-message: "chore: update pre-commit hooks"
|
||||||
|
committer: Freqtrade Bot <noreply@github.com>
|
||||||
|
body: Update binance leverage tiers.
|
||||||
|
delete-branch: true
|
||||||
+65
-35
@@ -11,7 +11,7 @@ on:
|
|||||||
types: [published]
|
types: [published]
|
||||||
pull_request:
|
pull_request:
|
||||||
schedule:
|
schedule:
|
||||||
- cron: '0 5 * * 4'
|
- cron: '0 3 * * 4'
|
||||||
|
|
||||||
concurrency:
|
concurrency:
|
||||||
group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}"
|
group: "${{ github.workflow }}-${{ github.ref }}-${{ github.event_name }}"
|
||||||
@@ -19,13 +19,13 @@ concurrency:
|
|||||||
permissions:
|
permissions:
|
||||||
repository-projects: read
|
repository-projects: read
|
||||||
jobs:
|
jobs:
|
||||||
build_linux:
|
build-linux:
|
||||||
|
|
||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ ubuntu-20.04, ubuntu-22.04 ]
|
os: [ ubuntu-20.04, ubuntu-22.04 ]
|
||||||
python-version: ["3.9", "3.10", "3.11"]
|
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -36,14 +36,14 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Cache_dependencies
|
- name: Cache_dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: ~/dependencies/
|
path: ~/dependencies/
|
||||||
key: ${{ runner.os }}-dependencies
|
key: ${{ runner.os }}-dependencies
|
||||||
|
|
||||||
- name: pip cache (linux)
|
- name: pip cache (linux)
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
||||||
@@ -60,11 +60,16 @@ jobs:
|
|||||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
pip install -e ft_client/
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
|
- name: Check for version alignment
|
||||||
|
run: |
|
||||||
|
python build_helpers/freqtrade_client_version_align.py
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
run: |
|
run: |
|
||||||
pytest --random-order --cov=freqtrade --cov-config=.coveragerc
|
pytest --random-order --cov=freqtrade --cov=freqtrade_client --cov-config=.coveragerc
|
||||||
|
|
||||||
- name: Coveralls
|
- name: Coveralls
|
||||||
if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04')
|
if: (runner.os == 'Linux' && matrix.python-version == '3.10' && matrix.os == 'ubuntu-22.04')
|
||||||
@@ -124,8 +129,11 @@ jobs:
|
|||||||
runs-on: ${{ matrix.os }}
|
runs-on: ${{ matrix.os }}
|
||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ "macos-latest", "macos-13" ]
|
os: [ "macos-12", "macos-13", "macos-14" ]
|
||||||
python-version: ["3.9", "3.10", "3.11"]
|
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||||
|
exclude:
|
||||||
|
- os: "macos-14"
|
||||||
|
python-version: "3.9"
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -137,14 +145,14 @@ jobs:
|
|||||||
check-latest: true
|
check-latest: true
|
||||||
|
|
||||||
- name: Cache_dependencies
|
- name: Cache_dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: ~/dependencies/
|
path: ~/dependencies/
|
||||||
key: ${{ matrix.os }}-dependencies
|
key: ${{ matrix.os }}-dependencies
|
||||||
|
|
||||||
- name: pip cache (macOS)
|
- name: pip cache (macOS)
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/Library/Caches/pip
|
path: ~/Library/Caches/pip
|
||||||
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
||||||
@@ -154,7 +162,7 @@ jobs:
|
|||||||
run: |
|
run: |
|
||||||
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
|
cd build_helpers && ./install_ta-lib.sh ${HOME}/dependencies/; cd ..
|
||||||
|
|
||||||
- name: Installation - macOS
|
- name: Installation - macOS (Brew)
|
||||||
run: |
|
run: |
|
||||||
# brew update
|
# brew update
|
||||||
# TODO: Should be the brew upgrade
|
# TODO: Should be the brew upgrade
|
||||||
@@ -177,11 +185,15 @@ jobs:
|
|||||||
rm /usr/local/bin/python3.12-config || true
|
rm /usr/local/bin/python3.12-config || true
|
||||||
|
|
||||||
brew install hdf5 c-blosc libomp
|
brew install hdf5 c-blosc libomp
|
||||||
|
|
||||||
|
- name: Installation (python)
|
||||||
|
run: |
|
||||||
python -m pip install --upgrade pip wheel
|
python -m pip install --upgrade pip wheel
|
||||||
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
|
export LD_LIBRARY_PATH=${HOME}/dependencies/lib:$LD_LIBRARY_PATH
|
||||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
pip install -e ft_client/
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
- name: Tests
|
- name: Tests
|
||||||
@@ -238,7 +250,7 @@ jobs:
|
|||||||
strategy:
|
strategy:
|
||||||
matrix:
|
matrix:
|
||||||
os: [ windows-latest ]
|
os: [ windows-latest ]
|
||||||
python-version: ["3.9", "3.10", "3.11"]
|
python-version: ["3.9", "3.10", "3.11", "3.12"]
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
@@ -249,7 +261,7 @@ jobs:
|
|||||||
python-version: ${{ matrix.python-version }}
|
python-version: ${{ matrix.python-version }}
|
||||||
|
|
||||||
- name: Pip cache (Windows)
|
- name: Pip cache (Windows)
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~\AppData\Local\pip\Cache
|
path: ~\AppData\Local\pip\Cache
|
||||||
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: ${{ matrix.os }}-${{ matrix.python-version }}-pip
|
||||||
@@ -325,7 +337,7 @@ jobs:
|
|||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.10"
|
python-version: "3.10"
|
||||||
- uses: pre-commit/action@v3.0.0
|
- uses: pre-commit/action@v3.0.1
|
||||||
|
|
||||||
docs-check:
|
docs-check:
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
@@ -356,7 +368,7 @@ jobs:
|
|||||||
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
|
webhookUrl: ${{ secrets.DISCORD_WEBHOOK }}
|
||||||
|
|
||||||
|
|
||||||
build_linux_online:
|
build-linux-online:
|
||||||
# Run pytest with "live" checks
|
# Run pytest with "live" checks
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
steps:
|
steps:
|
||||||
@@ -368,14 +380,14 @@ jobs:
|
|||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
- name: Cache_dependencies
|
- name: Cache_dependencies
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
id: cache
|
id: cache
|
||||||
with:
|
with:
|
||||||
path: ~/dependencies/
|
path: ~/dependencies/
|
||||||
key: ${{ runner.os }}-dependencies
|
key: ${{ runner.os }}-dependencies
|
||||||
|
|
||||||
- name: pip cache (linux)
|
- name: pip cache (linux)
|
||||||
uses: actions/cache@v3
|
uses: actions/cache@v4
|
||||||
with:
|
with:
|
||||||
path: ~/.cache/pip
|
path: ~/.cache/pip
|
||||||
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
key: test-${{ matrix.os }}-${{ matrix.python-version }}-pip
|
||||||
@@ -392,29 +404,30 @@ jobs:
|
|||||||
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
export TA_LIBRARY_PATH=${HOME}/dependencies/lib
|
||||||
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
export TA_INCLUDE_PATH=${HOME}/dependencies/include
|
||||||
pip install -r requirements-dev.txt
|
pip install -r requirements-dev.txt
|
||||||
|
pip install -e ft_client/
|
||||||
pip install -e .
|
pip install -e .
|
||||||
|
|
||||||
- name: Tests incl. ccxt compatibility tests
|
- name: Tests incl. ccxt compatibility tests
|
||||||
env:
|
env:
|
||||||
CI_WEB_PROXY: http://152.67.78.211:13128
|
CI_WEB_PROXY: http://152.67.78.211:13128
|
||||||
run: |
|
run: |
|
||||||
pytest --random-order --longrun --durations 20 -n auto --dist loadscope
|
pytest --random-order --longrun --durations 20 -n auto
|
||||||
|
|
||||||
|
|
||||||
# Notify only once - when CI completes (and after deploy) in case it's successfull
|
# Notify only once - when CI completes (and after deploy) in case it's successful
|
||||||
notify-complete:
|
notify-complete:
|
||||||
needs: [
|
needs: [
|
||||||
build_linux,
|
build-linux,
|
||||||
build-macos,
|
build-macos,
|
||||||
build-windows,
|
build-windows,
|
||||||
docs-check,
|
docs-check,
|
||||||
mypy-version-check,
|
mypy-version-check,
|
||||||
pre-commit,
|
pre-commit,
|
||||||
build_linux_online
|
build-linux-online
|
||||||
]
|
]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
# Discord notification can't handle schedule events
|
# Discord notification can't handle schedule events
|
||||||
if: (github.event_name != 'schedule')
|
if: github.event_name != 'schedule' && github.repository == 'freqtrade/freqtrade'
|
||||||
permissions:
|
permissions:
|
||||||
repository-projects: read
|
repository-projects: read
|
||||||
steps:
|
steps:
|
||||||
@@ -437,7 +450,7 @@ jobs:
|
|||||||
|
|
||||||
build:
|
build:
|
||||||
name: "Build"
|
name: "Build"
|
||||||
needs: [ build_linux, build-macos, build-windows, docs-check, mypy-version-check, pre-commit ]
|
needs: [ build-linux, build-macos, build-windows, docs-check, mypy-version-check, pre-commit ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
@@ -461,6 +474,19 @@ jobs:
|
|||||||
dist
|
dist
|
||||||
retention-days: 10
|
retention-days: 10
|
||||||
|
|
||||||
|
- name: Build Client distribution
|
||||||
|
run: |
|
||||||
|
pip install -U build
|
||||||
|
python -m build --sdist --wheel ft_client
|
||||||
|
|
||||||
|
- name: Upload artifacts 📦
|
||||||
|
uses: actions/upload-artifact@v4
|
||||||
|
with:
|
||||||
|
name: freqtrade-client-build
|
||||||
|
path: |
|
||||||
|
ft_client/dist
|
||||||
|
retention-days: 10
|
||||||
|
|
||||||
deploy-pypi:
|
deploy-pypi:
|
||||||
name: "Deploy to PyPI"
|
name: "Deploy to PyPI"
|
||||||
needs: [ build ]
|
needs: [ build ]
|
||||||
@@ -478,20 +504,22 @@ jobs:
|
|||||||
- name: Download artifact 📦
|
- name: Download artifact 📦
|
||||||
uses: actions/download-artifact@v4
|
uses: actions/download-artifact@v4
|
||||||
with:
|
with:
|
||||||
name: freqtrade-build
|
pattern: freqtrade*-build
|
||||||
path: dist
|
path: dist
|
||||||
|
merge-multiple: true
|
||||||
|
|
||||||
|
|
||||||
- name: Publish to PyPI (Test)
|
- name: Publish to PyPI (Test)
|
||||||
uses: pypa/gh-action-pypi-publish@v1.8.11
|
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||||
with:
|
with:
|
||||||
repository-url: https://test.pypi.org/legacy/
|
repository-url: https://test.pypi.org/legacy/
|
||||||
|
|
||||||
- name: Publish to PyPI
|
- name: Publish to PyPI
|
||||||
uses: pypa/gh-action-pypi-publish@v1.8.11
|
uses: pypa/gh-action-pypi-publish@v1.8.14
|
||||||
|
|
||||||
|
|
||||||
deploy-docker:
|
deploy-docker:
|
||||||
needs: [ build_linux, build-macos, build-windows, docs-check, mypy-version-check, pre-commit ]
|
needs: [ build-linux, build-macos, build-windows, docs-check, mypy-version-check, pre-commit ]
|
||||||
runs-on: ubuntu-22.04
|
runs-on: ubuntu-22.04
|
||||||
|
|
||||||
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
|
if: (github.event_name == 'push' || github.event_name == 'schedule' || github.event_name == 'release') && github.repository == 'freqtrade/freqtrade'
|
||||||
@@ -505,9 +533,10 @@ jobs:
|
|||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
- name: Extract branch name
|
- name: Extract branch name
|
||||||
shell: bash
|
id: extract-branch
|
||||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
|
run: |
|
||||||
id: extract_branch
|
echo "GITHUB_REF='${GITHUB_REF}'"
|
||||||
|
echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Dockerhub login
|
- name: Dockerhub login
|
||||||
env:
|
env:
|
||||||
@@ -536,7 +565,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and test and push docker images
|
- name: Build and test and push docker images
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
|
||||||
run: |
|
run: |
|
||||||
build_helpers/publish_docker_multi.sh
|
build_helpers/publish_docker_multi.sh
|
||||||
|
|
||||||
@@ -553,9 +582,10 @@ jobs:
|
|||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- name: Extract branch name
|
- name: Extract branch name
|
||||||
shell: bash
|
id: extract-branch
|
||||||
run: echo "##[set-output name=branch;]$(echo ${GITHUB_REF##*/})"
|
run: |
|
||||||
id: extract_branch
|
echo "GITHUB_REF='${GITHUB_REF}'"
|
||||||
|
echo "branch=${GITHUB_REF##*/}" >> "$GITHUB_OUTPUT"
|
||||||
|
|
||||||
- name: Dockerhub login
|
- name: Dockerhub login
|
||||||
env:
|
env:
|
||||||
@@ -566,7 +596,7 @@ jobs:
|
|||||||
|
|
||||||
- name: Build and test and push docker images
|
- name: Build and test and push docker images
|
||||||
env:
|
env:
|
||||||
BRANCH_NAME: ${{ steps.extract_branch.outputs.branch }}
|
BRANCH_NAME: ${{ steps.extract-branch.outputs.branch }}
|
||||||
GHCR_USERNAME: ${{ github.actor }}
|
GHCR_USERNAME: ${{ github.actor }}
|
||||||
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
GHCR_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
run: |
|
run: |
|
||||||
|
|||||||
@@ -0,0 +1,43 @@
|
|||||||
|
name: Devcontainer Pre-Build
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
# push:
|
||||||
|
# branches:
|
||||||
|
# - "master"
|
||||||
|
# tags:
|
||||||
|
# - "v*.*.*"
|
||||||
|
# pull_requests:
|
||||||
|
# branches:
|
||||||
|
# - "master"
|
||||||
|
|
||||||
|
concurrency:
|
||||||
|
group: "${{ github.workflow }}"
|
||||||
|
cancel-in-progress: true
|
||||||
|
|
||||||
|
permissions:
|
||||||
|
packages: write
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
build-and-push:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
-
|
||||||
|
name: Checkout
|
||||||
|
id: checkout
|
||||||
|
uses: actions/checkout@v4
|
||||||
|
-
|
||||||
|
name: Login to GitHub Container Registry
|
||||||
|
uses: docker/login-action@v3
|
||||||
|
with:
|
||||||
|
registry: ghcr.io
|
||||||
|
username: ${{ github.actor }}
|
||||||
|
password: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
-
|
||||||
|
name: Pre-build dev container image
|
||||||
|
uses: devcontainers/ci@v0.3
|
||||||
|
with:
|
||||||
|
subFolder: .github
|
||||||
|
imageName: ghcr.io/${{ github.repository }}-devcontainer
|
||||||
|
cacheFrom: ghcr.io/${{ github.repository }}-devcontainer
|
||||||
|
push: always
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
name: Update Docker Hub Description
|
||||||
|
on:
|
||||||
|
push:
|
||||||
|
branches:
|
||||||
|
- stable
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
dockerHubDescription:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
|
- name: Docker Hub Description
|
||||||
|
uses: peter-evans/dockerhub-description@v4
|
||||||
|
with:
|
||||||
|
username: ${{ secrets.DOCKER_USERNAME }}
|
||||||
|
password: ${{ secrets.DOCKER_PASSWORD }}
|
||||||
|
repository: freqtradeorg/freqtrade
|
||||||
@@ -1,17 +0,0 @@
|
|||||||
name: Update Docker Hub Description
|
|
||||||
on:
|
|
||||||
push:
|
|
||||||
branches:
|
|
||||||
- stable
|
|
||||||
|
|
||||||
jobs:
|
|
||||||
dockerHubDescription:
|
|
||||||
runs-on: ubuntu-latest
|
|
||||||
steps:
|
|
||||||
- uses: actions/checkout@v4
|
|
||||||
- name: Docker Hub Description
|
|
||||||
uses: peter-evans/dockerhub-description@v3
|
|
||||||
env:
|
|
||||||
DOCKERHUB_USERNAME: ${{ secrets.DOCKER_USERNAME }}
|
|
||||||
DOCKERHUB_PASSWORD: ${{ secrets.DOCKER_PASSWORD }}
|
|
||||||
DOCKERHUB_REPOSITORY: freqtradeorg/freqtrade
|
|
||||||
@@ -1,7 +1,6 @@
|
|||||||
name: Pre-commit auto-update
|
name: Pre-commit auto-update
|
||||||
|
|
||||||
on:
|
on:
|
||||||
# every day at midnight
|
|
||||||
schedule:
|
schedule:
|
||||||
- cron: "0 3 * * 2"
|
- cron: "0 3 * * 2"
|
||||||
# on demand
|
# on demand
|
||||||
@@ -14,31 +13,32 @@ jobs:
|
|||||||
auto-update:
|
auto-update:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v4
|
- uses: actions/checkout@v4
|
||||||
|
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v5
|
||||||
with:
|
with:
|
||||||
python-version: "3.11"
|
python-version: "3.11"
|
||||||
|
|
||||||
|
|
||||||
- name: Install pre-commit
|
- name: Install pre-commit
|
||||||
run: pip install pre-commit
|
run: pip install pre-commit
|
||||||
|
|
||||||
- name: Run auto-update
|
- name: Run auto-update
|
||||||
run: pre-commit autoupdate
|
run: pre-commit autoupdate
|
||||||
|
|
||||||
- name: Run pre-commit
|
- name: Run pre-commit
|
||||||
run: pre-commit run --all-files
|
run: pre-commit run --all-files
|
||||||
|
|
||||||
- uses: peter-evans/create-pull-request@v5
|
- uses: peter-evans/create-pull-request@v6
|
||||||
with:
|
with:
|
||||||
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
token: ${{ secrets.REPO_SCOPED_TOKEN }}
|
||||||
add-paths: .pre-commit-config.yaml
|
add-paths: .pre-commit-config.yaml
|
||||||
labels: |
|
labels: |
|
||||||
Tech maintenance
|
Tech maintenance
|
||||||
branch: update/pre-commit-hooks
|
Dependencies
|
||||||
title: Update pre-commit hooks
|
branch: update/pre-commit-hooks
|
||||||
commit-message: "chore: update pre-commit hooks"
|
title: Update pre-commit hooks
|
||||||
committer: Freqtrade Bot <noreply@github.com>
|
commit-message: "chore: update pre-commit hooks"
|
||||||
body: Update versions of pre-commit hooks to latest version.
|
committer: Freqtrade Bot <noreply@github.com>
|
||||||
delete-branch: true
|
body: Update versions of pre-commit hooks to latest version.
|
||||||
|
delete-branch: true
|
||||||
|
|||||||
+15
-8
@@ -2,24 +2,24 @@
|
|||||||
# See https://pre-commit.com/hooks.html for more hooks
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/pycqa/flake8
|
- repo: https://github.com/pycqa/flake8
|
||||||
rev: "6.1.0"
|
rev: "7.0.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: flake8
|
- id: flake8
|
||||||
additional_dependencies: [Flake8-pyproject]
|
additional_dependencies: [Flake8-pyproject]
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/mirrors-mypy
|
- repo: https://github.com/pre-commit/mirrors-mypy
|
||||||
rev: "v1.8.0"
|
rev: "v1.10.0"
|
||||||
hooks:
|
hooks:
|
||||||
- id: mypy
|
- id: mypy
|
||||||
exclude: build_helpers
|
exclude: build_helpers
|
||||||
additional_dependencies:
|
additional_dependencies:
|
||||||
- types-cachetools==5.3.0.7
|
- types-cachetools==5.3.0.7
|
||||||
- types-filelock==3.2.7
|
- types-filelock==3.2.7
|
||||||
- types-requests==2.31.0.10
|
- types-requests==2.31.0.20240406
|
||||||
- types-tabulate==0.9.0.3
|
- types-tabulate==0.9.0.20240106
|
||||||
- types-python-dateutil==2.8.19.14
|
- types-python-dateutil==2.9.0.20240316
|
||||||
- SQLAlchemy==2.0.23
|
- SQLAlchemy==2.0.29
|
||||||
# stages: [push]
|
# stages: [push]
|
||||||
|
|
||||||
- repo: https://github.com/pycqa/isort
|
- repo: https://github.com/pycqa/isort
|
||||||
@@ -31,12 +31,12 @@ repos:
|
|||||||
|
|
||||||
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
- repo: https://github.com/charliermarsh/ruff-pre-commit
|
||||||
# Ruff version.
|
# Ruff version.
|
||||||
rev: 'v0.1.9'
|
rev: 'v0.4.2'
|
||||||
hooks:
|
hooks:
|
||||||
- id: ruff
|
- id: ruff
|
||||||
|
|
||||||
- repo: https://github.com/pre-commit/pre-commit-hooks
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
rev: v4.5.0
|
rev: v4.6.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: end-of-file-fixer
|
- id: end-of-file-fixer
|
||||||
exclude: |
|
exclude: |
|
||||||
@@ -54,3 +54,10 @@ repos:
|
|||||||
(?x)^(
|
(?x)^(
|
||||||
.*\.md
|
.*\.md
|
||||||
)$
|
)$
|
||||||
|
|
||||||
|
- repo: https://github.com/codespell-project/codespell
|
||||||
|
rev: v2.2.6
|
||||||
|
hooks:
|
||||||
|
- id: codespell
|
||||||
|
additional_dependencies:
|
||||||
|
- tomli
|
||||||
|
|||||||
+1
-1
@@ -48,7 +48,7 @@ pytest tests/test_<file_name>.py::test_<method_name>
|
|||||||
#### Run Ruff
|
#### Run Ruff
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
ruff .
|
ruff check .
|
||||||
```
|
```
|
||||||
|
|
||||||
We receive a lot of code that fails the `ruff` checks.
|
We receive a lot of code that fails the `ruff` checks.
|
||||||
|
|||||||
+1
-1
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.6-slim-bookworm as base
|
FROM python:3.12.3-slim-bookworm as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
|||||||
@@ -30,7 +30,7 @@ Please read the [exchange specific notes](docs/exchanges.md) to learn about even
|
|||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [Huobi](http://huobi.com/)
|
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/) (Former OKEX)
|
||||||
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [potentially many others](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
|
|||||||
@@ -0,0 +1,26 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
import json
|
||||||
|
import os
|
||||||
|
from pathlib import Path
|
||||||
|
|
||||||
|
import ccxt
|
||||||
|
|
||||||
|
|
||||||
|
key = os.environ.get('FREQTRADE__EXCHANGE__KEY')
|
||||||
|
secret = os.environ.get('FREQTRADE__EXCHANGE__SECRET')
|
||||||
|
|
||||||
|
proxy = os.environ.get('CI_WEB_PROXY')
|
||||||
|
|
||||||
|
exchange = ccxt.binance({
|
||||||
|
'apiKey': key,
|
||||||
|
'secret': secret,
|
||||||
|
'httpsProxy': proxy,
|
||||||
|
'options': {'defaultType': 'swap'}
|
||||||
|
})
|
||||||
|
_ = exchange.load_markets()
|
||||||
|
|
||||||
|
lev_tiers = exchange.fetch_leverage_tiers()
|
||||||
|
|
||||||
|
# Assumes this is running in the root of the repository.
|
||||||
|
file = Path('freqtrade/exchange/binance_leverage_tiers.json')
|
||||||
|
json.dump(dict(sorted(lev_tiers.items())), file.open('w'), indent=2)
|
||||||
+18
@@ -0,0 +1,18 @@
|
|||||||
|
#!/usr/bin/env python3
|
||||||
|
from freqtrade_client import __version__ as client_version
|
||||||
|
|
||||||
|
from freqtrade import __version__ as ft_version
|
||||||
|
|
||||||
|
|
||||||
|
def main():
|
||||||
|
if ft_version != client_version:
|
||||||
|
print(f"Versions do not match: \n"
|
||||||
|
f"ft: {ft_version} \n"
|
||||||
|
f"client: {client_version}")
|
||||||
|
exit(1)
|
||||||
|
print(f"Versions match: ft: {ft_version}, client: {client_version}")
|
||||||
|
exit(0)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
||||||
@@ -1,4 +1,4 @@
|
|||||||
# File used in CI to ensure pre-commit dependencies are kept uptodate.
|
# File used in CI to ensure pre-commit dependencies are kept up-to-date.
|
||||||
|
|
||||||
import sys
|
import sys
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
@@ -21,7 +21,7 @@ type_reqs = [r.strip('\n') for r in requirements if r.startswith(
|
|||||||
'types-') or r.startswith('SQLAlchemy')]
|
'types-') or r.startswith('SQLAlchemy')]
|
||||||
|
|
||||||
with pre_commit_file.open('r') as file:
|
with pre_commit_file.open('r') as file:
|
||||||
f = yaml.load(file, Loader=yaml.FullLoader)
|
f = yaml.load(file, Loader=yaml.SafeLoader)
|
||||||
|
|
||||||
|
|
||||||
mypy_repo = [repo for repo in f['repos'] if repo['repo']
|
mypy_repo = [repo for repo in f['repos'] if repo['repo']
|
||||||
|
|||||||
BIN
Binary file not shown.
BIN
Binary file not shown.
@@ -52,7 +52,7 @@
|
|||||||
"train_period_days": 15,
|
"train_period_days": 15,
|
||||||
"backtest_period_days": 7,
|
"backtest_period_days": 7,
|
||||||
"live_retrain_hours": 0,
|
"live_retrain_hours": 0,
|
||||||
"identifier": "uniqe-id",
|
"identifier": "unique-id",
|
||||||
"feature_parameters": {
|
"feature_parameters": {
|
||||||
"include_timeframes": [
|
"include_timeframes": [
|
||||||
"3m",
|
"3m",
|
||||||
|
|||||||
@@ -1,4 +1,4 @@
|
|||||||
FROM python:3.11.6-slim-bookworm as base
|
FROM python:3.11.8-slim-bookworm as base
|
||||||
|
|
||||||
# Setup env
|
# Setup env
|
||||||
ENV LANG C.UTF-8
|
ENV LANG C.UTF-8
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
FROM freqtradeorg/freqtrade:develop_plot
|
FROM freqtradeorg/freqtrade:develop_plot
|
||||||
|
|
||||||
|
|
||||||
# Pin jupyter-client to avoid tornado version conflict
|
# Pin prompt-toolkit to avoid questionary version conflict
|
||||||
RUN pip install jupyterlab jupyter-client==7.3.4 --user --no-cache-dir
|
RUN pip install jupyterlab "prompt-toolkit<=3.0.36" jupyter-client --user --no-cache-dir
|
||||||
|
|
||||||
# Empty the ENTRYPOINT to allow all commands
|
# Empty the ENTRYPOINT to allow all commands
|
||||||
ENTRYPOINT []
|
ENTRYPOINT []
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ services:
|
|||||||
context: ..
|
context: ..
|
||||||
dockerfile: docker/Dockerfile.jupyter
|
dockerfile: docker/Dockerfile.jupyter
|
||||||
restart: unless-stopped
|
restart: unless-stopped
|
||||||
container_name: freqtrade
|
# container_name: freqtrade
|
||||||
ports:
|
ports:
|
||||||
- "127.0.0.1:8888:8888"
|
- "127.0.0.1:8888:8888"
|
||||||
volumes:
|
volumes:
|
||||||
|
|||||||
@@ -36,7 +36,7 @@ freqtrade backtesting-analysis -c <config.json> --analysis-groups 0 1 2 3 4 5
|
|||||||
```
|
```
|
||||||
|
|
||||||
This command will read from the last backtesting results. The `--analysis-groups` option is
|
This command will read from the last backtesting results. The `--analysis-groups` option is
|
||||||
used to specify the various tabular outputs showing the profit fo each group or trade,
|
used to specify the various tabular outputs showing the profit of each group or trade,
|
||||||
ranging from the simplest (0) to the most detailed per pair, per buy and per sell tag (4):
|
ranging from the simplest (0) to the most detailed per pair, per buy and per sell tag (4):
|
||||||
|
|
||||||
* 0: overall winrate and profit summary by enter_tag
|
* 0: overall winrate and profit summary by enter_tag
|
||||||
@@ -109,12 +109,12 @@ automatically accessible by including them on the indicator-list, and these incl
|
|||||||
- **open_date :** trade open datetime
|
- **open_date :** trade open datetime
|
||||||
- **close_date :** trade close datetime
|
- **close_date :** trade close datetime
|
||||||
- **min_rate :** minimum price seen throughout the position
|
- **min_rate :** minimum price seen throughout the position
|
||||||
- **max_rate :** maxiumum price seen throughout the position
|
- **max_rate :** maximum price seen throughout the position
|
||||||
- **open :** signal candle open price
|
- **open :** signal candle open price
|
||||||
- **close :** signal candle close price
|
- **close :** signal candle close price
|
||||||
- **high :** signal candle high price
|
- **high :** signal candle high price
|
||||||
- **low :** signal candle low price
|
- **low :** signal candle low price
|
||||||
- **volume :** signal candle volumne
|
- **volume :** signal candle volume
|
||||||
- **profit_ratio :** trade profit ratio
|
- **profit_ratio :** trade profit ratio
|
||||||
- **profit_abs :** absolute profit return of the trade
|
- **profit_abs :** absolute profit return of the trade
|
||||||
|
|
||||||
|
|||||||
Binary file not shown.
|
After Width: | Height: | Size: 29 KiB |
+42
-41
@@ -252,34 +252,34 @@ The most important in the backtesting is to understand the result.
|
|||||||
A backtesting result will look like that:
|
A backtesting result will look like that:
|
||||||
|
|
||||||
```
|
```
|
||||||
========================================================= BACKTESTING REPORT =========================================================
|
================================================ BACKTESTING REPORT =================================================
|
||||||
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|
| Pair | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins Draws Loss Win% |
|
||||||
|:---------|--------:|---------------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
|
|:---------|--------:|---------------:|-----------------:|---------------:|:-------------|-------------------------:|
|
||||||
| ADA/BTC | 35 | -0.11 | -3.88 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
|
| ADA/BTC | 35 | -0.11 | -0.00019428 | -1.94 | 4:35:00 | 14 0 21 40.0 |
|
||||||
| ARK/BTC | 11 | -0.41 | -4.52 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
|
| ARK/BTC | 11 | -0.41 | -0.00022647 | -2.26 | 2:03:00 | 3 0 8 27.3 |
|
||||||
| BTS/BTC | 32 | 0.31 | 9.78 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
|
| BTS/BTC | 32 | 0.31 | 0.00048938 | 4.89 | 5:05:00 | 18 0 14 56.2 |
|
||||||
| DASH/BTC | 13 | -0.08 | -1.07 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
|
| DASH/BTC | 13 | -0.08 | -0.00005343 | -0.53 | 4:39:00 | 6 0 7 46.2 |
|
||||||
| ENG/BTC | 18 | 1.36 | 24.54 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
|
| ENG/BTC | 18 | 1.36 | 0.00122807 | 12.27 | 2:50:00 | 8 0 10 44.4 |
|
||||||
| EOS/BTC | 36 | 0.08 | 3.06 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
|
| EOS/BTC | 36 | 0.08 | 0.00015304 | 1.53 | 3:34:00 | 16 0 20 44.4 |
|
||||||
| ETC/BTC | 26 | 0.37 | 9.51 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
|
| ETC/BTC | 26 | 0.37 | 0.00047576 | 4.75 | 6:14:00 | 11 0 15 42.3 |
|
||||||
| ETH/BTC | 33 | 0.30 | 9.96 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
|
| ETH/BTC | 33 | 0.30 | 0.00049856 | 4.98 | 7:31:00 | 16 0 17 48.5 |
|
||||||
| IOTA/BTC | 32 | 0.03 | 1.09 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
|
| IOTA/BTC | 32 | 0.03 | 0.00005444 | 0.54 | 3:12:00 | 14 0 18 43.8 |
|
||||||
| LSK/BTC | 15 | 1.75 | 26.26 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
|
| LSK/BTC | 15 | 1.75 | 0.00131413 | 13.13 | 2:58:00 | 6 0 9 40.0 |
|
||||||
| LTC/BTC | 32 | -0.04 | -1.38 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
|
| LTC/BTC | 32 | -0.04 | -0.00006886 | -0.69 | 4:49:00 | 11 0 21 34.4 |
|
||||||
| NANO/BTC | 17 | 1.26 | 21.39 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
|
| NANO/BTC | 17 | 1.26 | 0.00107058 | 10.70 | 1:55:00 | 10 0 7 58.5 |
|
||||||
| NEO/BTC | 23 | 0.82 | 18.97 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
|
| NEO/BTC | 23 | 0.82 | 0.00094936 | 9.48 | 2:59:00 | 10 0 13 43.5 |
|
||||||
| REQ/BTC | 9 | 1.17 | 10.54 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
|
| REQ/BTC | 9 | 1.17 | 0.00052734 | 5.27 | 3:47:00 | 4 0 5 44.4 |
|
||||||
| XLM/BTC | 16 | 1.22 | 19.54 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
|
| XLM/BTC | 16 | 1.22 | 0.00097800 | 9.77 | 3:15:00 | 7 0 9 43.8 |
|
||||||
| XMR/BTC | 23 | -0.18 | -4.13 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
|
| XMR/BTC | 23 | -0.18 | -0.00020696 | -2.07 | 5:30:00 | 12 0 11 52.2 |
|
||||||
| XRP/BTC | 35 | 0.66 | 22.96 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
| XRP/BTC | 35 | 0.66 | 0.00114897 | 11.48 | 3:49:00 | 12 0 23 34.3 |
|
||||||
| ZEC/BTC | 22 | -0.46 | -10.18 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
| ZEC/BTC | 22 | -0.46 | -0.00050971 | -5.09 | 2:22:00 | 7 0 15 31.8 |
|
||||||
| TOTAL | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
| TOTAL | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 0 243 43.4 |
|
||||||
====================================================== LEFT OPEN TRADES REPORT ======================================================
|
============================================= LEFT OPEN TRADES REPORT =============================================
|
||||||
| Pair | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
| Pair | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Win Draw Loss Win% |
|
||||||
|:---------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
|:---------|---------:|---------------:|-----------------:|---------------:|:---------------|--------------------:|
|
||||||
| ADA/BTC | 1 | 0.89 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
| ADA/BTC | 1 | 0.89 | 0.00004434 | 0.44 | 6:00:00 | 1 0 0 100 |
|
||||||
| LTC/BTC | 1 | 0.68 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
| LTC/BTC | 1 | 0.68 | 0.00003421 | 0.34 | 2:00:00 | 1 0 0 100 |
|
||||||
| TOTAL | 2 | 0.78 | 1.57 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
| TOTAL | 2 | 0.78 | 0.00007855 | 0.78 | 4:00:00 | 2 0 0 100 |
|
||||||
==================== EXIT REASON STATS ====================
|
==================== EXIT REASON STATS ====================
|
||||||
| Exit Reason | Exits | Wins | Draws | Losses |
|
| Exit Reason | Exits | Wins | Draws | Losses |
|
||||||
|:-------------------|--------:|------:|-------:|--------:|
|
|:-------------------|--------:|------:|-------:|--------:|
|
||||||
@@ -358,7 +358,7 @@ here:
|
|||||||
The bot has made `429` trades for an average duration of `4:12:00`, with a performance of `76.20%` (profit), that means it has
|
The bot has made `429` trades for an average duration of `4:12:00`, with a performance of `76.20%` (profit), that means it has
|
||||||
earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
|
earned a total of `0.00762792 BTC` starting with a capital of 0.01 BTC.
|
||||||
|
|
||||||
The column `Avg Profit %` shows the average profit for all trades made while the column `Cum Profit %` sums up all the profits/losses.
|
The column `Avg Profit %` shows the average profit for all trades made.
|
||||||
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
The column `Tot Profit %` shows instead the total profit % in relation to the starting balance.
|
||||||
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
In the above results, we have a starting balance of 0.01 BTC and an absolute profit of 0.00762792 BTC - so the `Tot Profit %` will be `(0.00762792 / 0.01) * 100 ~= 76.2%`.
|
||||||
|
|
||||||
@@ -464,7 +464,7 @@ It contains some useful key metrics about performance of your strategy on backte
|
|||||||
- `Profit factor`: profit / loss.
|
- `Profit factor`: profit / loss.
|
||||||
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
|
- `Avg. stake amount`: Average stake amount, either `stake_amount` or the average when using dynamic stake amount.
|
||||||
- `Total trade volume`: Volume generated on the exchange to reach the above profit.
|
- `Total trade volume`: Volume generated on the exchange to reach the above profit.
|
||||||
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Cum Profit %`.
|
- `Best Pair` / `Worst Pair`: Best and worst performing pair, and it's corresponding `Tot Profit %`.
|
||||||
- `Best Trade` / `Worst Trade`: Biggest single winning trade and biggest single losing trade.
|
- `Best Trade` / `Worst Trade`: Biggest single winning trade and biggest single losing trade.
|
||||||
- `Best day` / `Worst day`: Best and worst day based on daily profit.
|
- `Best day` / `Worst day`: Best and worst day based on daily profit.
|
||||||
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
|
- `Days win/draw/lose`: Winning / Losing days (draws are usually days without closed trade).
|
||||||
@@ -522,8 +522,8 @@ To save time, by default backtest will reuse a cached result from within the las
|
|||||||
|
|
||||||
### Further backtest-result analysis
|
### Further backtest-result analysis
|
||||||
|
|
||||||
To further analyze your backtest results, you can [export the trades](#exporting-trades-to-file).
|
To further analyze your backtest results, freqtrade will export the trades to file by default.
|
||||||
You can then load the trades to perform further analysis as shown in the [data analysis](data-analysis.md#backtesting) backtesting section.
|
You can then load the trades to perform further analysis as shown in the [data analysis](strategy_analysis_example.md#load-backtest-results-to-pandas-dataframe) backtesting section.
|
||||||
|
|
||||||
## Assumptions made by backtesting
|
## Assumptions made by backtesting
|
||||||
|
|
||||||
@@ -531,12 +531,13 @@ Since backtesting lacks some detailed information about what happens within a ca
|
|||||||
|
|
||||||
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
|
- Exchange [trading limits](#trading-limits-in-backtesting) are respected
|
||||||
- Entries happen at open-price
|
- Entries happen at open-price
|
||||||
- All orders are filled at the requested price (no slippage, no unfilled orders)
|
- All orders are filled at the requested price (no slippage) as long as the price is within the candle's high/low range
|
||||||
- Exit-signal exits happen at open-price of the consecutive candle
|
- Exit-signal exits happen at open-price of the consecutive candle
|
||||||
|
- Exits don't free their trade slot for a new trade until the next candle
|
||||||
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
|
- Exit-signal is favored over Stoploss, because exit-signals are assumed to trigger on candle's open
|
||||||
- ROI
|
- ROI
|
||||||
- exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
|
- Exits are compared to high - but the ROI value is used (e.g. ROI = 2%, high=5% - so the exit will be at 2%)
|
||||||
- exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
|
- Exits are never "below the candle", so a ROI of 2% may result in a exit at 2.4% if low was at 2.4% profit
|
||||||
- ROI entries which came into effect on the triggering candle (e.g. `120: 0.02` for 1h candles, from `60: 0.05`) will use the candle's open as exit rate
|
- ROI entries which came into effect on the triggering candle (e.g. `120: 0.02` for 1h candles, from `60: 0.05`) will use the candle's open as exit rate
|
||||||
- Force-exits caused by `<N>=-1` ROI entries use low as exit value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
|
- Force-exits caused by `<N>=-1` ROI entries use low as exit value, unless N falls on the candle open (e.g. `120: -1` for 1h candles)
|
||||||
- Stoploss exits happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
|
- Stoploss exits happen exactly at stoploss price, even if low was lower, but the loss will be `2 * fees` higher than the stoploss price
|
||||||
@@ -587,7 +588,7 @@ These precision values are based on current exchange limits (as described in the
|
|||||||
|
|
||||||
## Improved backtest accuracy
|
## Improved backtest accuracy
|
||||||
|
|
||||||
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or viceversa?).
|
One big limitation of backtesting is it's inability to know how prices moved intra-candle (was high before close, or vice-versa?).
|
||||||
So assuming you run backtesting with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low, Close).
|
So assuming you run backtesting with a 1h timeframe, there will be 4 prices for that candle (Open, High, Low, Close).
|
||||||
|
|
||||||
While backtesting does take some assumptions (read above) about this - this can never be perfect, and will always be biased in one way or the other.
|
While backtesting does take some assumptions (read above) about this - this can never be perfect, and will always be biased in one way or the other.
|
||||||
@@ -629,11 +630,11 @@ There will be an additional table comparing win/losses of the different strategi
|
|||||||
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
|
Detailed output for all strategies one after the other will be available, so make sure to scroll up to see the details per strategy.
|
||||||
|
|
||||||
```
|
```
|
||||||
=========================================================== STRATEGY SUMMARY ===========================================================================
|
================================================== STRATEGY SUMMARY ===================================================================
|
||||||
| Strategy | Entries | Avg Profit % | Cum Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|
| Strategy | Entries | Avg Profit % | Tot Profit BTC | Tot Profit % | Avg Duration | Wins | Draws | Losses | Drawdown % |
|
||||||
|:------------|---------:|---------------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
|
|:------------|---------:|---------------:|-----------------:|---------------:|:---------------|------:|-------:|-------:|-----------:|
|
||||||
| Strategy1 | 429 | 0.36 | 152.41 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
|
| Strategy1 | 429 | 0.36 | 0.00762792 | 76.20 | 4:12:00 | 186 | 0 | 243 | 45.2 |
|
||||||
| Strategy2 | 1487 | -0.13 | -197.58 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
| Strategy2 | 1487 | -0.13 | -0.00988917 | -98.79 | 4:43:00 | 662 | 0 | 825 | 241.68 |
|
||||||
```
|
```
|
||||||
|
|
||||||
## Next step
|
## Next step
|
||||||
|
|||||||
+8
-5
@@ -33,7 +33,6 @@ For spot pairs, naming will be `base/quote` (e.g. `ETH/USDT`).
|
|||||||
|
|
||||||
For futures pairs, naming will be `base/quote:settle` (e.g. `ETH/USDT:USDT`).
|
For futures pairs, naming will be `base/quote:settle` (e.g. `ETH/USDT:USDT`).
|
||||||
|
|
||||||
|
|
||||||
## Bot execution logic
|
## Bot execution logic
|
||||||
|
|
||||||
Starting freqtrade in dry-run or live mode (using `freqtrade trade`) will start the bot and start the bot iteration loop.
|
Starting freqtrade in dry-run or live mode (using `freqtrade trade`) will start the bot and start the bot iteration loop.
|
||||||
@@ -50,10 +49,12 @@ By default, the bot loop runs every few seconds (`internals.process_throttle_sec
|
|||||||
* Call `populate_indicators()`
|
* Call `populate_indicators()`
|
||||||
* Call `populate_entry_trend()`
|
* Call `populate_entry_trend()`
|
||||||
* Call `populate_exit_trend()`
|
* Call `populate_exit_trend()`
|
||||||
* Check timeouts for open orders.
|
* Update trades open order state from exchange.
|
||||||
* Calls `check_entry_timeout()` strategy callback for open entry orders.
|
* Call `order_filled()` strategy callback for filled orders.
|
||||||
* Calls `check_exit_timeout()` strategy callback for open exit orders.
|
* Check timeouts for open orders.
|
||||||
* Calls `adjust_entry_price()` strategy callback for open entry orders.
|
* Calls `check_entry_timeout()` strategy callback for open entry orders.
|
||||||
|
* Calls `check_exit_timeout()` strategy callback for open exit orders.
|
||||||
|
* Calls `adjust_entry_price()` strategy callback for open entry orders.
|
||||||
* Verifies existing positions and eventually places exit orders.
|
* Verifies existing positions and eventually places exit orders.
|
||||||
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
* Considers stoploss, ROI and exit-signal, `custom_exit()` and `custom_stoploss()`.
|
||||||
* Determine exit-price based on `exit_pricing` configuration setting or by using the `custom_exit_price()` callback.
|
* Determine exit-price based on `exit_pricing` configuration setting or by using the `custom_exit_price()` callback.
|
||||||
@@ -86,8 +87,10 @@ This loop will be repeated again and again until the bot is stopped.
|
|||||||
* In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage.
|
* In Margin and Futures mode, `leverage()` strategy callback is called to determine the desired leverage.
|
||||||
* Determine stake size by calling the `custom_stake_amount()` callback.
|
* Determine stake size by calling the `custom_stake_amount()` callback.
|
||||||
* Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
|
* Check position adjustments for open trades if enabled and call `adjust_trade_position()` to determine if an additional order is requested.
|
||||||
|
* Call `order_filled()` strategy callback for filled entry orders.
|
||||||
* Call `custom_stoploss()` and `custom_exit()` to find custom exit points.
|
* Call `custom_stoploss()` and `custom_exit()` to find custom exit points.
|
||||||
* For exits based on exit-signal, custom-exit and partial exits: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
|
* For exits based on exit-signal, custom-exit and partial exits: Call `custom_exit_price()` to determine exit price (Prices are moved to be within the closing candle).
|
||||||
|
* Call `order_filled()` strategy callback for filled exit orders.
|
||||||
* Generate backtest report output
|
* Generate backtest report output
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
|
|||||||
+23
-9
@@ -14,7 +14,7 @@ You can specify a different configuration file used by the bot with the `-c/--co
|
|||||||
If you used the [Quick start](docker_quickstart.md#docker-quick-start) method for installing
|
If you used the [Quick start](docker_quickstart.md#docker-quick-start) method for installing
|
||||||
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
the bot, the installation script should have already created the default configuration file (`config.json`) for you.
|
||||||
|
|
||||||
If the default configuration file is not created we recommend to use `freqtrade new-config --config config.json` to generate a basic configuration file.
|
If the default configuration file is not created we recommend to use `freqtrade new-config --config user_data/config.json` to generate a basic configuration file.
|
||||||
|
|
||||||
The Freqtrade configuration file is to be written in JSON format.
|
The Freqtrade configuration file is to be written in JSON format.
|
||||||
|
|
||||||
@@ -49,6 +49,13 @@ FREQTRADE__EXCHANGE__SECRET=<yourExchangeSecret>
|
|||||||
!!! Note
|
!!! Note
|
||||||
Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable.
|
Environment variables detected are logged at startup - so if you can't find why a value is not what you think it should be based on the configuration, make sure it's not loaded from an environment variable.
|
||||||
|
|
||||||
|
!!! Tip "Validate combined result"
|
||||||
|
You can use the [show-config subcommand](utils.md#show-config) to see the final, combined configuration.
|
||||||
|
|
||||||
|
??? Warning "Loading sequence"
|
||||||
|
Environment variables are loaded after the initial configuration. As such, you cannot provide the path to the configuration through environment variables. Please use `--config path/to/config.json` for that.
|
||||||
|
This also applies to user_dir to some degree. while the user directory can be set through environment variables - the configuration will **not** be loaded from that location.
|
||||||
|
|
||||||
### Multiple configuration files
|
### Multiple configuration files
|
||||||
|
|
||||||
Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
|
Multiple configuration files can be specified and used by the bot or the bot can read its configuration parameters from the process standard input stream.
|
||||||
@@ -56,6 +63,9 @@ Multiple configuration files can be specified and used by the bot or the bot can
|
|||||||
You can specify additional configuration files in `add_config_files`. Files specified in this parameter will be loaded and merged with the initial config file. The files are resolved relative to the initial configuration file.
|
You can specify additional configuration files in `add_config_files`. Files specified in this parameter will be loaded and merged with the initial config file. The files are resolved relative to the initial configuration file.
|
||||||
This is similar to using multiple `--config` parameters, but simpler in usage as you don't have to specify all files for all commands.
|
This is similar to using multiple `--config` parameters, but simpler in usage as you don't have to specify all files for all commands.
|
||||||
|
|
||||||
|
!!! Tip "Validate combined result"
|
||||||
|
You can use the [show-config subcommand](utils.md#show-config) to see the final, combined configuration.
|
||||||
|
|
||||||
!!! Tip "Use multiple configuration files to keep secrets secret"
|
!!! Tip "Use multiple configuration files to keep secrets secret"
|
||||||
You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself.
|
You can use a 2nd configuration file containing your secrets. That way you can share your "primary" configuration file, while still keeping your API keys for yourself.
|
||||||
The 2nd file should only specify what you intend to override.
|
The 2nd file should only specify what you intend to override.
|
||||||
@@ -187,7 +197,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
|
| `position_adjustment_enable` | Enables the strategy to use position adjustments (additional buys or sells). [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `false`.*<br> **Datatype:** Boolean
|
||||||
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
|
| `max_entry_position_adjustment` | Maximum additional order(s) for each open trade on top of the first entry Order. Set it to `-1` for unlimited additional orders. [More information here](strategy-callbacks.md#adjust-trade-position). <br> [Strategy Override](#parameters-in-the-strategy). <br>*Defaults to `-1`.*<br> **Datatype:** Positive Integer or -1
|
||||||
| | **Exchange**
|
| | **Exchange**
|
||||||
| `exchange.name` | **Required.** Name of the exchange class to use. [List below](#user-content-what-values-for-exchangename). <br> **Datatype:** String
|
| `exchange.name` | **Required.** Name of the exchange class to use. <br> **Datatype:** String
|
||||||
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.key` | API key to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.secret` | API secret to use for the exchange. Only required when you are in production mode.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
| `exchange.password` | API password to use for the exchange. Only required when you are in production mode and for exchanges that use password for API requests.<br>**Keep it in secret, do not disclose publicly.** <br> **Datatype:** String
|
||||||
@@ -242,7 +252,7 @@ Mandatory parameters are marked as **Required**, which means that they are requi
|
|||||||
| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean
|
| `disable_dataframe_checks` | Disable checking the OHLCV dataframe returned from the strategy methods for correctness. Only use when intentionally changing the dataframe and understand what you are doing. [Strategy Override](#parameters-in-the-strategy).<br> *Defaults to `False`*. <br> **Datatype:** Boolean
|
||||||
| `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer
|
| `internals.process_throttle_secs` | Set the process throttle, or minimum loop duration for one bot iteration loop. Value in second. <br>*Defaults to `5` seconds.* <br> **Datatype:** Positive Integer
|
||||||
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
|
| `internals.heartbeat_interval` | Print heartbeat message every N seconds. Set to 0 to disable heartbeat messages. <br>*Defaults to `60` seconds.* <br> **Datatype:** Positive Integer or 0
|
||||||
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](installation.md#7-optional-configure-freqtrade-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
|
| `internals.sd_notify` | Enables use of the sd_notify protocol to tell systemd service manager about changes in the bot state and issue keep-alive pings. See [here](advanced-setup.md#configure-the-bot-running-as-a-systemd-service) for more details. <br> **Datatype:** Boolean
|
||||||
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
|
| `strategy` | **Required** Defines Strategy class to use. Recommended to be set via `--strategy NAME`. <br> **Datatype:** ClassName
|
||||||
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
|
| `strategy_path` | Adds an additional strategy lookup path (must be a directory). <br> **Datatype:** String
|
||||||
| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy. <br> **Datatype:** Boolean
|
| `recursive_strategy_search` | Set to `true` to recursively search sub-directories inside `user_data/strategies` for a strategy. <br> **Datatype:** Boolean
|
||||||
@@ -326,6 +336,8 @@ You'd set `available_capital=5000` - granting each bot an initial capital of 500
|
|||||||
The bot will then split this starting balance equally into `max_open_trades` buckets.
|
The bot will then split this starting balance equally into `max_open_trades` buckets.
|
||||||
Profitable trades will result in increased stake-sizes for this bot - without affecting the stake-sizes of the other bot.
|
Profitable trades will result in increased stake-sizes for this bot - without affecting the stake-sizes of the other bot.
|
||||||
|
|
||||||
|
Adjusting `available_capital` requires reloading the configuration to take effect. Adjusting the `available_capital` adds the difference between the previous `available_capital` and the new `available_capital`. Decreasing the available capital when trades are open doesn't exit the trades. The difference is returned to the wallet when the trades conclude. The outcome of this differs depending on the price movement between the adjustment and exiting the trades.
|
||||||
|
|
||||||
!!! Warning "Incompatible with `tradable_balance_ratio`"
|
!!! Warning "Incompatible with `tradable_balance_ratio`"
|
||||||
Setting this option will replace any configuration of `tradable_balance_ratio`.
|
Setting this option will replace any configuration of `tradable_balance_ratio`.
|
||||||
|
|
||||||
@@ -358,7 +370,7 @@ This setting works in combination with `max_open_trades`. The maximum capital en
|
|||||||
For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`.
|
For example, the bot will at most use (0.05 BTC x 3) = 0.15 BTC, assuming a configuration of `max_open_trades=3` and `stake_amount=0.05`.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
This setting respects the [available balance configuration](#available-balance).
|
This setting respects the [available balance configuration](#tradable-balance).
|
||||||
|
|
||||||
#### Dynamic stake amount
|
#### Dynamic stake amount
|
||||||
|
|
||||||
@@ -503,13 +515,13 @@ Configuration:
|
|||||||
Please carefully read the section [Market order pricing](#market-order-pricing) section when using market orders.
|
Please carefully read the section [Market order pricing](#market-order-pricing) section when using market orders.
|
||||||
|
|
||||||
!!! Note "Stoploss on exchange"
|
!!! Note "Stoploss on exchange"
|
||||||
`stoploss_on_exchange_interval` is not mandatory. Do not change its value if you are
|
`order_types.stoploss_on_exchange_interval` is not mandatory. Do not change its value if you are
|
||||||
unsure of what you are doing. For more information about how stoploss works please
|
unsure of what you are doing. For more information about how stoploss works please
|
||||||
refer to [the stoploss documentation](stoploss.md).
|
refer to [the stoploss documentation](stoploss.md).
|
||||||
|
|
||||||
If `stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
|
If `order_types.stoploss_on_exchange` is enabled and the stoploss is cancelled manually on the exchange, then the bot will create a new stoploss order.
|
||||||
|
|
||||||
!!! Warning "Warning: stoploss_on_exchange failures"
|
!!! Warning "Warning: order_types.stoploss_on_exchange failures"
|
||||||
If stoploss on exchange creation fails for some reason, then an "emergency exit" is initiated. By default, this will exit the trade using a market order. The order-type for the emergency-exit can be changed by setting the `emergency_exit` value in the `order_types` dictionary - however, this is not advised.
|
If stoploss on exchange creation fails for some reason, then an "emergency exit" is initiated. By default, this will exit the trade using a market order. The order-type for the emergency-exit can be changed by setting the `emergency_exit` value in the `order_types` dictionary - however, this is not advised.
|
||||||
|
|
||||||
### Understand order_time_in_force
|
### Understand order_time_in_force
|
||||||
@@ -535,7 +547,7 @@ is automatically cancelled by the exchange.
|
|||||||
**PO (Post only):**
|
**PO (Post only):**
|
||||||
|
|
||||||
Post only order. The order is either placed as a maker order, or it is canceled.
|
Post only order. The order is either placed as a maker order, or it is canceled.
|
||||||
This means the order must be placed on orderbook for at at least time in an unfilled state.
|
This means the order must be placed on orderbook for at least time in an unfilled state.
|
||||||
|
|
||||||
#### time_in_force config
|
#### time_in_force config
|
||||||
|
|
||||||
@@ -572,9 +584,11 @@ In addition to fiat currencies, a range of crypto currencies is supported.
|
|||||||
The valid values are:
|
The valid values are:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"BTC", "ETH", "XRP", "LTC", "BCH", "USDT"
|
"BTC", "ETH", "XRP", "LTC", "BCH", "BNB"
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Removing `fiat_display_currency` completely from the configuration will skip initializing coingecko, and will not show any FIAT currency conversion. This has no importance for the correct functioning of the bot.
|
||||||
|
|
||||||
## Using Dry-run mode
|
## Using Dry-run mode
|
||||||
|
|
||||||
We recommend starting the bot in the Dry-run mode to see how your bot will
|
We recommend starting the bot in the Dry-run mode to see how your bot will
|
||||||
|
|||||||
+6
-4
@@ -83,7 +83,7 @@ Details will obviously vary between setups - but this should work to get you sta
|
|||||||
``` json
|
``` json
|
||||||
{
|
{
|
||||||
"name": "freqtrade trade",
|
"name": "freqtrade trade",
|
||||||
"type": "python",
|
"type": "debugpy",
|
||||||
"request": "launch",
|
"request": "launch",
|
||||||
"module": "freqtrade",
|
"module": "freqtrade",
|
||||||
"console": "integratedTerminal",
|
"console": "integratedTerminal",
|
||||||
@@ -129,6 +129,8 @@ Below is an outline of exception inheritance hierarchy:
|
|||||||
+ FreqtradeException
|
+ FreqtradeException
|
||||||
|
|
|
|
||||||
+---+ OperationalException
|
+---+ OperationalException
|
||||||
|
| |
|
||||||
|
| +---+ ConfigurationError
|
||||||
|
|
|
|
||||||
+---+ DependencyException
|
+---+ DependencyException
|
||||||
| |
|
| |
|
||||||
@@ -259,7 +261,7 @@ For that reason, they must implement the following methods:
|
|||||||
|
|
||||||
The `until` portion should be calculated using the provided `calculate_lock_end()` method.
|
The `until` portion should be calculated using the provided `calculate_lock_end()` method.
|
||||||
|
|
||||||
All Protections should use `"stop_duration"` / `"stop_duration_candles"` to define how long a a pair (or all pairs) should be locked.
|
All Protections should use `"stop_duration"` / `"stop_duration_candles"` to define how long a pair (or all pairs) should be locked.
|
||||||
The content of this is made available as `self._stop_duration` to the each Protection.
|
The content of this is made available as `self._stop_duration` to the each Protection.
|
||||||
|
|
||||||
If your protection requires a look-back period, please use `"lookback_period"` / `"lockback_period_candles"` to keep all protections aligned.
|
If your protection requires a look-back period, please use `"lookback_period"` / `"lockback_period_candles"` to keep all protections aligned.
|
||||||
@@ -303,7 +305,7 @@ The `IProtection` parent class provides a helper method for this in `calculate_l
|
|||||||
|
|
||||||
Most exchanges supported by CCXT should work out of the box.
|
Most exchanges supported by CCXT should work out of the box.
|
||||||
|
|
||||||
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `test_ccxt_compat.py` and run these tests with `pytest --longrun tests/exchange/test_ccxt_compat.py`.
|
To quickly test the public endpoints of an exchange, add a configuration for your exchange to `tests/exchange_online/conftest.py` and run these tests with `pytest --longrun tests/exchange_online/test_ccxt_compat.py`.
|
||||||
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
|
Completing these tests successfully a good basis point (it's a requirement, actually), however these won't guarantee correct exchange functioning, as this only tests public endpoints, but no private endpoint (like generate order or similar).
|
||||||
|
|
||||||
Also try to use `freqtrade download-data` for an extended timerange (multiple months) and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded).
|
Also try to use `freqtrade download-data` for an extended timerange (multiple months) and verify that the data downloaded correctly (no holes, the specified timerange was actually downloaded).
|
||||||
@@ -376,7 +378,7 @@ from pathlib import Path
|
|||||||
|
|
||||||
exchange = ccxt.binance({
|
exchange = ccxt.binance({
|
||||||
'apiKey': '<apikey>',
|
'apiKey': '<apikey>',
|
||||||
'secret': '<secret>'
|
'secret': '<secret>',
|
||||||
'options': {'defaultType': 'swap'}
|
'options': {'defaultType': 'swap'}
|
||||||
})
|
})
|
||||||
_ = exchange.load_markets()
|
_ = exchange.load_markets()
|
||||||
|
|||||||
+1
-1
@@ -137,7 +137,7 @@ $$ R = \frac{\text{average_profit}}{\text{average_loss}} = \frac{\mu_{win}}{\mu_
|
|||||||
|
|
||||||
### Expectancy
|
### Expectancy
|
||||||
|
|
||||||
By combining the Win Rate $W$ and and the Risk Reward ratio $R$ to create an expectancy ratio $E$. A expectance ratio is the expected return of the investment made in a trade. We can compute the value of $E$ as follows:
|
By combining the Win Rate $W$ and the Risk Reward ratio $R$ to create an expectancy ratio $E$. A expectance ratio is the expected return of the investment made in a trade. We can compute the value of $E$ as follows:
|
||||||
|
|
||||||
$$E = R * W - L$$
|
$$E = R * W - L$$
|
||||||
|
|
||||||
|
|||||||
+7
-3
@@ -68,6 +68,8 @@ Binance supports [time_in_force](configuration.md#understand-order_time_in_force
|
|||||||
For Binance, it is suggested to add `"BNB/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `BNB` on the account or unless you're willing to disable using `BNB` for fees.
|
For Binance, it is suggested to add `"BNB/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `BNB` on the account or unless you're willing to disable using `BNB` for fees.
|
||||||
Binance accounts may use `BNB` for fees, and if a trade happens to be on `BNB`, further trades may consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore.
|
Binance accounts may use `BNB` for fees, and if a trade happens to be on `BNB`, further trades may consume this position and make the initial BNB trade unsellable as the expected amount is not there anymore.
|
||||||
|
|
||||||
|
If not enough `BNB` is available to cover transaction fees, then fees will not be covered by `BNB` and no fee reduction will occur. Freqtrade will never buy BNB to cover for fees. BNB needs to be bought and monitored manually to this end.
|
||||||
|
|
||||||
### Binance sites
|
### Binance sites
|
||||||
|
|
||||||
Binance has been split into 2, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized.
|
Binance has been split into 2, and users must use the correct ccxt exchange ID for their exchange, otherwise API keys are not recognized.
|
||||||
@@ -127,6 +129,8 @@ Freqtrade will not attempt to change these settings.
|
|||||||
|
|
||||||
## Kraken
|
## Kraken
|
||||||
|
|
||||||
|
Kraken supports [time_in_force](configuration.md#understand-order_time_in_force) with settings "GTC" (good till cancelled), "IOC" (immediate-or-cancel) and "PO" (Post only) settings.
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "Stoploss on Exchange"
|
||||||
Kraken supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
Kraken supports `stoploss_on_exchange` and can use both stop-loss-market and stop-loss-limit orders. It provides great advantages, so we recommend to benefit from it.
|
||||||
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
You can use either `"limit"` or `"market"` in the `order_types.stoploss` configuration setting to decide which type to use.
|
||||||
@@ -206,10 +210,10 @@ Kucoin supports [time_in_force](configuration.md#understand-order_time_in_force)
|
|||||||
For Kucoin, it is suggested to add `"KCS/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `KCS` on the account or unless you're willing to disable using `KCS` for fees.
|
For Kucoin, it is suggested to add `"KCS/<STAKE>"` to your blacklist to avoid issues, unless you are willing to maintain enough extra `KCS` on the account or unless you're willing to disable using `KCS` for fees.
|
||||||
Kucoin accounts may use `KCS` for fees, and if a trade happens to be on `KCS`, further trades may consume this position and make the initial `KCS` trade unsellable as the expected amount is not there anymore.
|
Kucoin accounts may use `KCS` for fees, and if a trade happens to be on `KCS`, further trades may consume this position and make the initial `KCS` trade unsellable as the expected amount is not there anymore.
|
||||||
|
|
||||||
## Huobi
|
## HTX (formerly Huobi)
|
||||||
|
|
||||||
!!! Tip "Stoploss on Exchange"
|
!!! Tip "Stoploss on Exchange"
|
||||||
Huobi supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
HTX supports `stoploss_on_exchange` and uses `stop-limit` orders. It provides great advantages, so we recommend to benefit from it by enabling stoploss on exchange.
|
||||||
|
|
||||||
## OKX (former OKEX)
|
## OKX (former OKEX)
|
||||||
|
|
||||||
@@ -295,7 +299,7 @@ $ pip3 install web3
|
|||||||
Most exchanges return current incomplete candle via their OHLCV/klines API interface.
|
Most exchanges return current incomplete candle via their OHLCV/klines API interface.
|
||||||
By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle.
|
By default, Freqtrade assumes that incomplete candle is fetched from the exchange and removes the last candle assuming it's the incomplete candle.
|
||||||
|
|
||||||
Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#Incomplete-candles) from the Contributor documentation.
|
Whether your exchange returns incomplete candles or not can be checked using [the helper script](developer.md#incomplete-candles) from the Contributor documentation.
|
||||||
|
|
||||||
Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle.
|
Due to the danger of repainting, Freqtrade does not allow you to use this incomplete candle.
|
||||||
|
|
||||||
|
|||||||
+2
-2
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
## Supported Markets
|
## Supported Markets
|
||||||
|
|
||||||
Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
|
Freqtrade supports spot trading, as well as (isolated) futures trading for some selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
|
||||||
|
|
||||||
### Can my bot open short positions?
|
### Can my bot open short positions?
|
||||||
|
|
||||||
@@ -14,7 +14,7 @@ In spot markets, you can in some cases use leveraged spot tokens, which reflect
|
|||||||
|
|
||||||
### Can my bot trade options or futures?
|
### Can my bot trade options or futures?
|
||||||
|
|
||||||
Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an uptodate list of supported exchanges.
|
Futures trading is supported for selected exchanges. Please refer to the [documentation start page](index.md#supported-futures-exchanges-experimental) for an up-to-date list of supported exchanges.
|
||||||
|
|
||||||
## Beginner Tips & Tricks
|
## Beginner Tips & Tricks
|
||||||
|
|
||||||
|
|||||||
@@ -32,6 +32,9 @@ FreqAI is configured through the typical [Freqtrade config file](configuration.m
|
|||||||
|
|
||||||
A full example config is available in `config_examples/config_freqai.example.json`.
|
A full example config is available in `config_examples/config_freqai.example.json`.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
The `identifier` is commonly overlooked by newcomers, however, this value plays an important role in your configuration. This value is a unique ID that you choose to describe one of your runs. Keeping it the same allows you to maintain crash resilience as well as faster backtesting. As soon as you want to try a new run (new features, new model, etc.), you should change this value (or delete the `user_data/models/unique-id` folder. More details available in the [parameter table](freqai-parameter-table.md#feature-parameters).
|
||||||
|
|
||||||
## Building a FreqAI strategy
|
## Building a FreqAI strategy
|
||||||
|
|
||||||
The FreqAI strategy requires including the following lines of code in the standard [Freqtrade strategy](strategy-customization.md):
|
The FreqAI strategy requires including the following lines of code in the standard [Freqtrade strategy](strategy-customization.md):
|
||||||
@@ -162,7 +165,8 @@ Below are the values you can expect to include/use inside a typical strategy dat
|
|||||||
| `df['&*_std/mean']` | Standard deviation and mean values of the defined labels during training (or live tracking with `fit_live_predictions_candles`). Commonly used to understand the rarity of a prediction (use the z-score as shown in `templates/FreqaiExampleStrategy.py` and explained [here](#creating-a-dynamic-target-threshold) to evaluate how often a particular prediction was observed during training or historically with `fit_live_predictions_candles`). <br> **Datatype:** Float.
|
| `df['&*_std/mean']` | Standard deviation and mean values of the defined labels during training (or live tracking with `fit_live_predictions_candles`). Commonly used to understand the rarity of a prediction (use the z-score as shown in `templates/FreqaiExampleStrategy.py` and explained [here](#creating-a-dynamic-target-threshold) to evaluate how often a particular prediction was observed during training or historically with `fit_live_predictions_candles`). <br> **Datatype:** Float.
|
||||||
| `df['do_predict']` | Indication of an outlier data point. The return value is integer between -2 and 2, which lets you know if the prediction is trustworthy or not. `do_predict==1` means that the prediction is trustworthy. If the Dissimilarity Index (DI, see details [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di)) of the input data point is above the threshold defined in the config, FreqAI will subtract 1 from `do_predict`, resulting in `do_predict==0`. If `use_SVM_to_remove_outliers` is active, the Support Vector Machine (SVM, see details [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm)) may also detect outliers in training and prediction data. In this case, the SVM will also subtract 1 from `do_predict`. If the input data point was considered an outlier by the SVM but not by the DI, or vice versa, the result will be `do_predict==0`. If both the DI and the SVM considers the input data point to be an outlier, the result will be `do_predict==-1`. As with the SVM, if `use_DBSCAN_to_remove_outliers` is active, DBSCAN (see details [here](freqai-feature-engineering.md#identifying-outliers-with-dbscan)) may also detect outliers and subtract 1 from `do_predict`. Hence, if both the SVM and DBSCAN are active and identify a datapoint that was above the DI threshold as an outlier, the result will be `do_predict==-2`. A particular case is when `do_predict == 2`, which means that the model has expired due to exceeding `expired_hours`. <br> **Datatype:** Integer between -2 and 2.
|
| `df['do_predict']` | Indication of an outlier data point. The return value is integer between -2 and 2, which lets you know if the prediction is trustworthy or not. `do_predict==1` means that the prediction is trustworthy. If the Dissimilarity Index (DI, see details [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di)) of the input data point is above the threshold defined in the config, FreqAI will subtract 1 from `do_predict`, resulting in `do_predict==0`. If `use_SVM_to_remove_outliers` is active, the Support Vector Machine (SVM, see details [here](freqai-feature-engineering.md#identifying-outliers-using-a-support-vector-machine-svm)) may also detect outliers in training and prediction data. In this case, the SVM will also subtract 1 from `do_predict`. If the input data point was considered an outlier by the SVM but not by the DI, or vice versa, the result will be `do_predict==0`. If both the DI and the SVM considers the input data point to be an outlier, the result will be `do_predict==-1`. As with the SVM, if `use_DBSCAN_to_remove_outliers` is active, DBSCAN (see details [here](freqai-feature-engineering.md#identifying-outliers-with-dbscan)) may also detect outliers and subtract 1 from `do_predict`. Hence, if both the SVM and DBSCAN are active and identify a datapoint that was above the DI threshold as an outlier, the result will be `do_predict==-2`. A particular case is when `do_predict == 2`, which means that the model has expired due to exceeding `expired_hours`. <br> **Datatype:** Integer between -2 and 2.
|
||||||
| `df['DI_values']` | Dissimilarity Index (DI) values are proxies for the level of confidence FreqAI has in the prediction. A lower DI means the prediction is close to the training data, i.e., higher prediction confidence. See details about the DI [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di). <br> **Datatype:** Float.
|
| `df['DI_values']` | Dissimilarity Index (DI) values are proxies for the level of confidence FreqAI has in the prediction. A lower DI means the prediction is close to the training data, i.e., higher prediction confidence. See details about the DI [here](freqai-feature-engineering.md#identifying-outliers-with-the-dissimilarity-index-di). <br> **Datatype:** Float.
|
||||||
| `df['%*']` | Any dataframe column prepended with `%` in `feature_engineering_*()` is treated as a training feature. For example, you can include the RSI in the training feature set (similar to in `templates/FreqaiExampleStrategy.py`) by setting `df['%-rsi']`. See more details on how this is done [here](freqai-feature-engineering.md). <br> **Note:** Since the number of features prepended with `%` can multiply very quickly (10s of thousands of features are easily engineered using the multiplictative functionality of, e.g., `include_shifted_candles` and `include_timeframes` as described in the [parameter table](freqai-parameter-table.md)), these features are removed from the dataframe that is returned from FreqAI to the strategy. To keep a particular type of feature for plotting purposes, you would prepend it with `%%`. <br> **Datatype:** Depends on the output of the model.
|
| `df['%*']` | Any dataframe column prepended with `%` in `feature_engineering_*()` is treated as a training feature. For example, you can include the RSI in the training feature set (similar to in `templates/FreqaiExampleStrategy.py`) by setting `df['%-rsi']`. See more details on how this is done [here](freqai-feature-engineering.md). <br> **Note:** Since the number of features prepended with `%` can multiply very quickly (10s of thousands of features are easily engineered using the multiplictative functionality of, e.g., `include_shifted_candles` and `include_timeframes` as described in the [parameter table](freqai-parameter-table.md)), these features are removed from the dataframe that is returned from FreqAI to the strategy. To keep a particular type of feature for plotting purposes, you would prepend it with `%%` (see details below). <br> **Datatype:** Depends on the feature created by the user.
|
||||||
|
| `df['%%*']` | Any dataframe column prepended with `%%` in `feature_engineering_*()` is treated as a training feature, just the same as the above `%` prepend. However, in this case, the features are returned back to the strategy for FreqUI/plot-dataframe plotting and monitoring in Dry/Live/Backtesting <br> **Datatype:** Depends on the feature created by the user. Please note that features created in `feature_engineering_expand()` will have automatic FreqAI naming schemas depending on the expansions that you configured (i.e. `include_timeframes`, `include_corr_pairlist`, `indicators_periods_candles`, `include_shifted_candles`). So if you want to plot `%%-rsi` from `feature_engineering_expand_all()`, the final naming scheme for your plotting config would be: `%%-rsi-period_10_ETH/USDT:USDT_1h` for the `rsi` feature with `period=10`, `timeframe=1h`, and `pair=ETH/USDT:USDT` (the `:USDT` is added if you are using futures pairs). It is useful to simply add `print(dataframe.columns)` in your `populate_indicators()` after `self.freqai.start()` to see the full list of available features that are returned to the strategy for plotting purposes.
|
||||||
|
|
||||||
## Setting the `startup_candle_count`
|
## Setting the `startup_candle_count`
|
||||||
|
|
||||||
|
|||||||
@@ -235,7 +235,7 @@ By default, FreqAI builds a dynamic pipeline based on user congfiguration settin
|
|||||||
Users are encouraged to customize the data pipeline to their needs by building their own data pipeline. This can be done by simply setting `dk.feature_pipeline` to their desired `Pipeline` object inside their `IFreqaiModel` `train()` function, or if they prefer not to touch the `train()` function, they can override `define_data_pipeline`/`define_label_pipeline` functions in their `IFreqaiModel`:
|
Users are encouraged to customize the data pipeline to their needs by building their own data pipeline. This can be done by simply setting `dk.feature_pipeline` to their desired `Pipeline` object inside their `IFreqaiModel` `train()` function, or if they prefer not to touch the `train()` function, they can override `define_data_pipeline`/`define_label_pipeline` functions in their `IFreqaiModel`:
|
||||||
|
|
||||||
!!! note "More information available"
|
!!! note "More information available"
|
||||||
FreqAI uses the the [`DataSieve`](https://github.com/emergentmethods/datasieve) pipeline, which follows the SKlearn pipeline API, but adds, among other features, coherence between the X, y, and sample_weight vector point removals, feature removal, feature name following.
|
FreqAI uses the [`DataSieve`](https://github.com/emergentmethods/datasieve) pipeline, which follows the SKlearn pipeline API, but adds, among other features, coherence between the X, y, and sample_weight vector point removals, feature removal, feature name following.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from datasieve.transforms import SKLearnWrapper, DissimilarityIndex
|
from datasieve.transforms import SKLearnWrapper, DissimilarityIndex
|
||||||
|
|||||||
@@ -31,7 +31,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
|
|||||||
| `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md). <br> **Datatype:** Dictionary.
|
| `feature_parameters` | A dictionary containing the parameters used to engineer the feature set. Details and examples are shown [here](freqai-feature-engineering.md). <br> **Datatype:** Dictionary.
|
||||||
| `include_timeframes` | A list of timeframes that all indicators in `feature_engineering_expand_*()` will be created for. The list is added as features to the base indicators dataset. <br> **Datatype:** List of timeframes (strings).
|
| `include_timeframes` | A list of timeframes that all indicators in `feature_engineering_expand_*()` will be created for. The list is added as features to the base indicators dataset. <br> **Datatype:** List of timeframes (strings).
|
||||||
| `include_corr_pairlist` | A list of correlated coins that FreqAI will add as additional features to all `pair_whitelist` coins. All indicators set in `feature_engineering_expand_*()` during feature engineering (see details [here](freqai-feature-engineering.md)) will be created for each correlated coin. The correlated coins features are added to the base indicators dataset. <br> **Datatype:** List of assets (strings).
|
| `include_corr_pairlist` | A list of correlated coins that FreqAI will add as additional features to all `pair_whitelist` coins. All indicators set in `feature_engineering_expand_*()` during feature engineering (see details [here](freqai-feature-engineering.md)) will be created for each correlated coin. The correlated coins features are added to the base indicators dataset. <br> **Datatype:** List of assets (strings).
|
||||||
| `label_period_candles` | Number of candles into the future that the labels are created for. This is used in `feature_engineering_expand_all()` (see `templates/FreqaiExampleStrategy.py` for detailed usage). You can create custom labels and choose whether to make use of this parameter or not. <br> **Datatype:** Positive integer.
|
| `label_period_candles` | Number of candles into the future that the labels are created for. This can be used in `set_freqai_targets()` (see `templates/FreqaiExampleStrategy.py` for detailed usage). This parameter is not necessarily required, you can create custom labels and choose whether to make use of this parameter or not. Please see `templates/FreqaiExampleStrategy.py` to see the example usage. <br> **Datatype:** Positive integer.
|
||||||
| `include_shifted_candles` | Add features from previous candles to subsequent candles with the intent of adding historical information. If used, FreqAI will duplicate and shift all features from the `include_shifted_candles` previous candles so that the information is available for the subsequent candle. <br> **Datatype:** Positive integer.
|
| `include_shifted_candles` | Add features from previous candles to subsequent candles with the intent of adding historical information. If used, FreqAI will duplicate and shift all features from the `include_shifted_candles` previous candles so that the information is available for the subsequent candle. <br> **Datatype:** Positive integer.
|
||||||
| `weight_factor` | Weight training data points according to their recency (see details [here](freqai-feature-engineering.md#weighting-features-for-temporal-importance)). <br> **Datatype:** Positive float (typically < 1).
|
| `weight_factor` | Weight training data points according to their recency (see details [here](freqai-feature-engineering.md#weighting-features-for-temporal-importance)). <br> **Datatype:** Positive float (typically < 1).
|
||||||
| `indicator_max_period_candles` | **No longer used (#7325)**. Replaced by `startup_candle_count` which is set in the [strategy](freqai-configuration.md#building-a-freqai-strategy). `startup_candle_count` is timeframe independent and defines the maximum *period* used in `feature_engineering_*()` for indicator creation. FreqAI uses this parameter together with the maximum timeframe in `include_time_frames` to calculate how many data points to download such that the first data point does not include a NaN. <br> **Datatype:** Positive integer.
|
| `indicator_max_period_candles` | **No longer used (#7325)**. Replaced by `startup_candle_count` which is set in the [strategy](freqai-configuration.md#building-a-freqai-strategy). `startup_candle_count` is timeframe independent and defines the maximum *period* used in `feature_engineering_*()` for indicator creation. FreqAI uses this parameter together with the maximum timeframe in `include_time_frames` to calculate how many data points to download such that the first data point does not include a NaN. <br> **Datatype:** Positive integer.
|
||||||
@@ -55,7 +55,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
|
|||||||
| | **Data split parameters within the `freqai.data_split_parameters` sub dictionary**
|
| | **Data split parameters within the `freqai.data_split_parameters` sub dictionary**
|
||||||
| `data_split_parameters` | Include any additional parameters available from scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary.
|
| `data_split_parameters` | Include any additional parameters available from scikit-learn `test_train_split()`, which are shown [here](https://scikit-learn.org/stable/modules/generated/sklearn.model_selection.train_test_split.html) (external website). <br> **Datatype:** Dictionary.
|
||||||
| `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1.
|
| `test_size` | The fraction of data that should be used for testing instead of training. <br> **Datatype:** Positive float < 1.
|
||||||
| `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Defaut: `False`.
|
| `shuffle` | Shuffle the training data points during training. Typically, to not remove the chronological order of data in time-series forecasting, this is set to `False`. <br> **Datatype:** Boolean. <br> Default: `False`.
|
||||||
|
|
||||||
### Model training parameters
|
### Model training parameters
|
||||||
|
|
||||||
@@ -75,7 +75,7 @@ Mandatory parameters are marked as **Required** and have to be set in one of the
|
|||||||
| `rl_config` | A dictionary containing the control parameters for a Reinforcement Learning model. <br> **Datatype:** Dictionary.
|
| `rl_config` | A dictionary containing the control parameters for a Reinforcement Learning model. <br> **Datatype:** Dictionary.
|
||||||
| `train_cycles` | Training time steps will be set based on the `train_cycles * number of training data points. <br> **Datatype:** Integer.
|
| `train_cycles` | Training time steps will be set based on the `train_cycles * number of training data points. <br> **Datatype:** Integer.
|
||||||
| `max_trade_duration_candles`| Guides the agent training to keep trades below desired length. Example usage shown in `prediction_models/ReinforcementLearner.py` within the customizable `calculate_reward()` function. <br> **Datatype:** int.
|
| `max_trade_duration_candles`| Guides the agent training to keep trades below desired length. Example usage shown in `prediction_models/ReinforcementLearner.py` within the customizable `calculate_reward()` function. <br> **Datatype:** int.
|
||||||
| `model_type` | Model string from stable_baselines3 or SBcontrib. Available strings include: `'TRPO', 'ARS', 'RecurrentPPO', 'MaskablePPO', 'PPO', 'A2C', 'DQN'`. User should ensure that `model_training_parameters` match those available to the corresponding stable_baselines3 model by visiting their documentaiton. [PPO doc](https://stable-baselines3.readthedocs.io/en/master/modules/ppo.html) (external website) <br> **Datatype:** string.
|
| `model_type` | Model string from stable_baselines3 or SBcontrib. Available strings include: `'TRPO', 'ARS', 'RecurrentPPO', 'MaskablePPO', 'PPO', 'A2C', 'DQN'`. User should ensure that `model_training_parameters` match those available to the corresponding stable_baselines3 model by visiting their documentation. [PPO doc](https://stable-baselines3.readthedocs.io/en/master/modules/ppo.html) (external website) <br> **Datatype:** string.
|
||||||
| `policy_type` | One of the available policy types from stable_baselines3 <br> **Datatype:** string.
|
| `policy_type` | One of the available policy types from stable_baselines3 <br> **Datatype:** string.
|
||||||
| `max_training_drawdown_pct` | The maximum drawdown that the agent is allowed to experience during training. <br> **Datatype:** float. <br> Default: 0.8
|
| `max_training_drawdown_pct` | The maximum drawdown that the agent is allowed to experience during training. <br> **Datatype:** float. <br> Default: 0.8
|
||||||
| `cpu_count` | Number of threads/cpus to dedicate to the Reinforcement Learning training process (depending on if `ReinforcementLearning_multiproc` is selected or not). Recommended to leave this untouched, by default, this value is set to the total number of physical cores minus 1. <br> **Datatype:** int.
|
| `cpu_count` | Number of threads/cpus to dedicate to the Reinforcement Learning training process (depending on if `ReinforcementLearning_multiproc` is selected or not). Recommended to leave this untouched, by default, this value is set to the total number of physical cores minus 1. <br> **Datatype:** int.
|
||||||
|
|||||||
@@ -142,7 +142,7 @@ Parameter details can be found [here](freqai-parameter-table.md), but in general
|
|||||||
As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but this is *not* designed for production. Users *must* create their own custom reinforcement learning model class or use a pre-built one from outside the Freqtrade source code and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated:
|
As you begin to modify the strategy and the prediction model, you will quickly realize some important differences between the Reinforcement Learner and the Regressors/Classifiers. Firstly, the strategy does not set a target value (no labels!). Instead, you set the `calculate_reward()` function inside the `MyRLEnv` class (see below). A default `calculate_reward()` is provided inside `prediction_models/ReinforcementLearner.py` to demonstrate the necessary building blocks for creating rewards, but this is *not* designed for production. Users *must* create their own custom reinforcement learning model class or use a pre-built one from outside the Freqtrade source code and save it to `user_data/freqaimodels`. It is inside the `calculate_reward()` where creative theories about the market can be expressed. For example, you can reward your agent when it makes a winning trade, and penalize the agent when it makes a losing trade. Or perhaps, you wish to reward the agent for entering trades, and penalize the agent for sitting in trades too long. Below we show examples of how these rewards are all calculated:
|
||||||
|
|
||||||
!!! note "Hint"
|
!!! note "Hint"
|
||||||
The best reward functions are ones that are continuously differentiable, and well scaled. In other words, adding a single large negative penalty to a rare event is not a good idea, and the neural net will not be able to learn that function. Instead, it is better to add a small negative penalty to a common event. This will help the agent learn faster. Not only this, but you can help improve the continuity of your rewards/penalties by having them scale with severity according to some linear/exponential functions. In other words, you'd slowly scale the penalty as the duration of the trade increases. This is better than a single large penalty occuring at a single point in time.
|
The best reward functions are ones that are continuously differentiable, and well scaled. In other words, adding a single large negative penalty to a rare event is not a good idea, and the neural net will not be able to learn that function. Instead, it is better to add a small negative penalty to a common event. This will help the agent learn faster. Not only this, but you can help improve the continuity of your rewards/penalties by having them scale with severity according to some linear/exponential functions. In other words, you'd slowly scale the penalty as the duration of the trade increases. This is better than a single large penalty occurring at a single point in time.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner
|
from freqtrade.freqai.prediction_models.ReinforcementLearner import ReinforcementLearner
|
||||||
|
|||||||
@@ -41,11 +41,11 @@ FreqAI stores new model files after each successful training. These files become
|
|||||||
|
|
||||||
```json
|
```json
|
||||||
"freqai": {
|
"freqai": {
|
||||||
"purge_old_models": true,
|
"purge_old_models": 4,
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
This will automatically purge all models older than the two most recently trained ones to save disk space.
|
This will automatically purge all models older than the four most recently trained ones to save disk space. Inputing "0" will never purge any models.
|
||||||
|
|
||||||
## Backtesting
|
## Backtesting
|
||||||
|
|
||||||
@@ -68,7 +68,7 @@ Backtesting mode requires [downloading the necessary data](#downloading-data-to-
|
|||||||
This way, you can return to using any model you wish by simply specifying the `identifier`.
|
This way, you can return to using any model you wish by simply specifying the `identifier`.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Backtesting calls `set_freqai_targets()` one time for each backtest window (where the number of windows is the full backtest timerange divided by the `backtest_period_days` parameter). Doing this means that the targets simulate dry/live behavior without look ahead bias. However, the definition of the features in `feature_engineering_*()` is performed once on the entire backtest timerange. This means that you should be sure that features do look-ahead into the future.
|
Backtesting calls `set_freqai_targets()` one time for each backtest window (where the number of windows is the full backtest timerange divided by the `backtest_period_days` parameter). Doing this means that the targets simulate dry/live behavior without look ahead bias. However, the definition of the features in `feature_engineering_*()` is performed once on the entire training timerange. This means that you should be sure that features do not look-ahead into the future.
|
||||||
More details about look-ahead bias can be found in [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies).
|
More details about look-ahead bias can be found in [Common Mistakes](strategy-customization.md#common-mistakes-when-developing-strategies).
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|||||||
@@ -114,6 +114,11 @@ Here we compile some external materials that provide deeper looks into various c
|
|||||||
- [Real-time head-to-head: Adaptive modeling of financial market data using XGBoost and CatBoost](https://emergentmethods.medium.com/real-time-head-to-head-adaptive-modeling-of-financial-market-data-using-xgboost-and-catboost-995a115a7495)
|
- [Real-time head-to-head: Adaptive modeling of financial market data using XGBoost and CatBoost](https://emergentmethods.medium.com/real-time-head-to-head-adaptive-modeling-of-financial-market-data-using-xgboost-and-catboost-995a115a7495)
|
||||||
- [FreqAI - from price to prediction](https://emergentmethods.medium.com/freqai-from-price-to-prediction-6fadac18b665)
|
- [FreqAI - from price to prediction](https://emergentmethods.medium.com/freqai-from-price-to-prediction-6fadac18b665)
|
||||||
|
|
||||||
|
|
||||||
|
## Support
|
||||||
|
|
||||||
|
You can find support for FreqAI in a variety of places, including the [Freqtrade discord](https://discord.gg/Jd8JYeWHc4), the dedicated [FreqAI discord](https://discord.gg/7AMWACmbjT), and in [github issues](https://github.com/freqtrade/freqtrade/issues).
|
||||||
|
|
||||||
## Credits
|
## Credits
|
||||||
|
|
||||||
FreqAI is developed by a group of individuals who all contribute specific skillsets to the project.
|
FreqAI is developed by a group of individuals who all contribute specific skillsets to the project.
|
||||||
|
|||||||
+13
-8
@@ -14,8 +14,7 @@ To learn how to get data for the pairs and exchange you're interested in, head o
|
|||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Since 2021.4 release you no longer have to write a separate hyperopt class, but can configure the parameters directly in the strategy.
|
Since 2021.4 release you no longer have to write a separate hyperopt class, but can configure the parameters directly in the strategy.
|
||||||
The legacy method is still supported, but it is no longer the recommended way of setting up hyperopt.
|
The legacy method was supported up to 2021.8 and has been removed in 2021.9.
|
||||||
The legacy documentation is available at [Legacy Hyperopt](advanced-hyperopt.md#legacy-hyperopt).
|
|
||||||
|
|
||||||
## Install hyperopt dependencies
|
## Install hyperopt dependencies
|
||||||
|
|
||||||
@@ -439,7 +438,7 @@ While this strategy is most likely too simple to provide consistent profit, it s
|
|||||||
??? Hint "Performance tip"
|
??? Hint "Performance tip"
|
||||||
During normal hyperopting, indicators are calculated once and supplied to each epoch, linearly increasing RAM usage as a factor of increasing cores. As this also has performance implications, there are two alternatives to reduce RAM usage
|
During normal hyperopting, indicators are calculated once and supplied to each epoch, linearly increasing RAM usage as a factor of increasing cores. As this also has performance implications, there are two alternatives to reduce RAM usage
|
||||||
|
|
||||||
* Move `ema_short` and `ema_long` calculations from `populate_indicators()` to `populate_entry_trend()`. Since `populate_entry_trend()` gonna be calculated every epochs, you don't need to use `.range` functionality.
|
* Move `ema_short` and `ema_long` calculations from `populate_indicators()` to `populate_entry_trend()`. Since `populate_entry_trend()` will be calculated every epoch, you don't need to use `.range` functionality.
|
||||||
* hyperopt provides `--analyze-per-epoch` which will move the execution of `populate_indicators()` to the epoch process, calculating a single value per parameter per epoch instead of using the `.range` functionality. In this case, `.range` functionality will only return the actually used value.
|
* hyperopt provides `--analyze-per-epoch` which will move the execution of `populate_indicators()` to the epoch process, calculating a single value per parameter per epoch instead of using the `.range` functionality. In this case, `.range` functionality will only return the actually used value.
|
||||||
|
|
||||||
These alternatives will reduce RAM usage, but increase CPU usage. However, your hyperopting run will be less likely to fail due to Out Of Memory (OOM) issues.
|
These alternatives will reduce RAM usage, but increase CPU usage. However, your hyperopting run will be less likely to fail due to Out Of Memory (OOM) issues.
|
||||||
@@ -765,7 +764,7 @@ Override the `roi_space()` method if you need components of the ROI tables to va
|
|||||||
A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
A sample for these methods can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
||||||
|
|
||||||
!!! Note "Reduced search space"
|
!!! Note "Reduced search space"
|
||||||
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs.
|
||||||
|
|
||||||
### Understand Hyperopt Stoploss results
|
### Understand Hyperopt Stoploss results
|
||||||
|
|
||||||
@@ -807,7 +806,7 @@ If you have the `stoploss_space()` method in your custom hyperopt file, remove i
|
|||||||
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
Override the `stoploss_space()` method and define the desired range in it if you need stoploss values to vary in other range during hyperoptimization. A sample for this method can be found in the [overriding pre-defined spaces section](advanced-hyperopt.md#overriding-pre-defined-spaces).
|
||||||
|
|
||||||
!!! Note "Reduced search space"
|
!!! Note "Reduced search space"
|
||||||
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#pverriding-pre-defined-spaces) to change this to your needs.
|
To limit the search space further, Decimals are limited to 3 decimal places (a precision of 0.001). This is usually sufficient, every value more precise than this will usually result in overfitted results. You can however [overriding pre-defined spaces](advanced-hyperopt.md#overriding-pre-defined-spaces) to change this to your needs.
|
||||||
|
|
||||||
### Understand Hyperopt Trailing Stop results
|
### Understand Hyperopt Trailing Stop results
|
||||||
|
|
||||||
@@ -926,6 +925,12 @@ Once the optimized strategy has been implemented into your strategy, you should
|
|||||||
|
|
||||||
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
To achieve same the results (number of trades, their durations, profit, etc.) as during Hyperopt, please use the same configuration and parameters (timerange, timeframe, ...) used for hyperopt `--dmmp`/`--disable-max-market-positions` and `--eps`/`--enable-position-stacking` for Backtesting.
|
||||||
|
|
||||||
Should results not match, please double-check to make sure you transferred all conditions correctly.
|
### Why do my backtest results not match my hyperopt results?
|
||||||
Pay special care to the stoploss, max_open_trades and trailing stoploss parameters, as these are often set in configuration files, which override changes to the strategy.
|
Should results not match, check the following factors:
|
||||||
You should also carefully review the log of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss`, `max_open_trades` or `trailing_stop`).
|
|
||||||
|
* You may have added parameters to hyperopt in `populate_indicators()` where they will be calculated only once **for all epochs**. If you are, for example, trying to optimise multiple SMA timeperiod values, the hyperoptable timeperiod parameter should be placed in `populate_entry_trend()` which is calculated every epoch. See [Optimizing an indicator parameter](https://www.freqtrade.io/en/stable/hyperopt/#optimizing-an-indicator-parameter).
|
||||||
|
* If you have disabled the auto-export of hyperopt parameters into the JSON parameters file, double-check to make sure you transferred all hyperopted values into your strategy correctly.
|
||||||
|
* Check the logs to verify what parameters are being set and what values are being used.
|
||||||
|
* Pay special care to the stoploss, max_open_trades and trailing stoploss parameters, as these are often set in configuration files, which override changes to the strategy. Check the logs of your backtest to ensure that there were no parameters inadvertently set by the configuration (like `stoploss`, `max_open_trades` or `trailing_stop`).
|
||||||
|
* Verify that you do not have an unexpected parameters JSON file overriding the parameters or the default hyperopt settings in your strategy.
|
||||||
|
* Verify that any protections that are enabled in backtesting are also enabled when hyperopting, and vice versa. When using `--space protection`, protections are auto-enabled for hyperopting.
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ In your configuration, you can use Static Pairlist (defined by the [`StaticPairL
|
|||||||
|
|
||||||
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
|
Additionally, [`AgeFilter`](#agefilter), [`PrecisionFilter`](#precisionfilter), [`PriceFilter`](#pricefilter), [`ShuffleFilter`](#shufflefilter), [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) act as Pairlist Filters, removing certain pairs and/or moving their positions in the pairlist.
|
||||||
|
|
||||||
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You should always configure either `StaticPairList` or `VolumePairList` as the starting Pairlist Handler.
|
If multiple Pairlist Handlers are used, they are chained and a combination of all Pairlist Handlers forms the resulting pairlist the bot uses for trading and backtesting. Pairlist Handlers are executed in the sequence they are configured. You can define either `StaticPairList`, `VolumePairList`, `ProducerPairList`, `RemotePairList` or `MarketCapPairList` as the starting Pairlist Handler.
|
||||||
|
|
||||||
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
|
Inactive markets are always removed from the resulting pairlist. Explicitly blacklisted pairs (those in the `pair_blacklist` configuration setting) are also always removed from the resulting pairlist.
|
||||||
|
|
||||||
@@ -24,6 +24,7 @@ You may also use something like `.*DOWN/BTC` or `.*UP/BTC` to exclude leveraged
|
|||||||
* [`VolumePairList`](#volume-pair-list)
|
* [`VolumePairList`](#volume-pair-list)
|
||||||
* [`ProducerPairList`](#producerpairlist)
|
* [`ProducerPairList`](#producerpairlist)
|
||||||
* [`RemotePairList`](#remotepairlist)
|
* [`RemotePairList`](#remotepairlist)
|
||||||
|
* [`MarketCapPairList`](#marketcappairlist)
|
||||||
* [`AgeFilter`](#agefilter)
|
* [`AgeFilter`](#agefilter)
|
||||||
* [`FullTradesFilter`](#fulltradesfilter)
|
* [`FullTradesFilter`](#fulltradesfilter)
|
||||||
* [`OffsetFilter`](#offsetfilter)
|
* [`OffsetFilter`](#offsetfilter)
|
||||||
@@ -67,7 +68,7 @@ When used in the leading position of the chain of Pairlist Handlers, the `pair_w
|
|||||||
|
|
||||||
The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes).
|
The `refresh_period` setting allows to define the period (in seconds), at which the pairlist will be refreshed. Defaults to 1800s (30 minutes).
|
||||||
The pairlist cache (`refresh_period`) on `VolumePairList` is only applicable to generating pairlists.
|
The pairlist cache (`refresh_period`) on `VolumePairList` is only applicable to generating pairlists.
|
||||||
Filtering instances (not the first position in the list) will not apply any cache and will always use up-to-date data.
|
Filtering instances (not the first position in the list) will not apply any cache (beyond caching candles for the duration of the candle in advanced mode) and will always use up-to-date data.
|
||||||
|
|
||||||
`VolumePairList` is per default based on the ticker data from exchange, as reported by the ccxt library:
|
`VolumePairList` is per default based on the ticker data from exchange, as reported by the ccxt library:
|
||||||
|
|
||||||
@@ -80,12 +81,14 @@ Filtering instances (not the first position in the list) will not apply any cach
|
|||||||
"number_assets": 20,
|
"number_assets": 20,
|
||||||
"sort_key": "quoteVolume",
|
"sort_key": "quoteVolume",
|
||||||
"min_value": 0,
|
"min_value": 0,
|
||||||
|
"max_value": 8000000,
|
||||||
"refresh_period": 1800
|
"refresh_period": 1800
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
```
|
```
|
||||||
|
|
||||||
You can define a minimum volume with `min_value` - which will filter out pairs with a volume lower than the specified value in the specified timerange.
|
You can define a minimum volume with `min_value` - which will filter out pairs with a volume lower than the specified value in the specified timerange.
|
||||||
|
In addition to that, you can also define a maximum volume with `max_value` - which will filter out pairs with a volume higher than the specified value in the specified timerange.
|
||||||
|
|
||||||
##### VolumePairList Advanced mode
|
##### VolumePairList Advanced mode
|
||||||
|
|
||||||
@@ -192,14 +195,15 @@ The RemotePairList is defined in the pairlists section of the configuration sett
|
|||||||
"refresh_period": 1800,
|
"refresh_period": 1800,
|
||||||
"keep_pairlist_on_failure": true,
|
"keep_pairlist_on_failure": true,
|
||||||
"read_timeout": 60,
|
"read_timeout": 60,
|
||||||
"bearer_token": "my-bearer-token"
|
"bearer_token": "my-bearer-token",
|
||||||
|
"save_to_file": "user_data/filename.json"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
The optional `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist".
|
The optional `mode` option specifies if the pairlist should be used as a `blacklist` or as a `whitelist`. The default value is "whitelist".
|
||||||
|
|
||||||
The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append".
|
The optional `processing_mode` option in the RemotePairList configuration determines how the retrieved pairlist is processed. It can have two values: "filter" or "append". The default value is "filter".
|
||||||
|
|
||||||
In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs present in both the original pairlist and the retrieved pairlist are included in the final pairlist. Other pairs are filtered out.
|
In "filter" mode, the retrieved pairlist is used as a filter. Only the pairs present in both the original pairlist and the retrieved pairlist are included in the final pairlist. Other pairs are filtered out.
|
||||||
|
|
||||||
@@ -207,6 +211,42 @@ In "append" mode, the retrieved pairlist is added to the original pairlist. All
|
|||||||
|
|
||||||
The `pairlist_url` option specifies the URL of the remote server where the pairlist is located, or the path to a local file (if file:/// is prepended). This allows the user to use either a remote server or a local file as the source for the pairlist.
|
The `pairlist_url` option specifies the URL of the remote server where the pairlist is located, or the path to a local file (if file:/// is prepended). This allows the user to use either a remote server or a local file as the source for the pairlist.
|
||||||
|
|
||||||
|
The `save_to_file` option, when provided with a valid filename, saves the processed pairlist to that file in JSON format. This option is optional, and by default, the pairlist is not saved to a file.
|
||||||
|
|
||||||
|
??? Example "Multi bot with shared pairlist example"
|
||||||
|
|
||||||
|
`save_to_file` can be used to save the pairlist to a file with Bot1:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "RemotePairList",
|
||||||
|
"mode": "whitelist",
|
||||||
|
"pairlist_url": "https://example.com/pairlist",
|
||||||
|
"number_assets": 10,
|
||||||
|
"refresh_period": 1800,
|
||||||
|
"keep_pairlist_on_failure": true,
|
||||||
|
"read_timeout": 60,
|
||||||
|
"save_to_file": "user_data/filename.json"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
This saved pairlist file can be loaded by Bot2, or any additional bot with this configuration:
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "RemotePairList",
|
||||||
|
"mode": "whitelist",
|
||||||
|
"pairlist_url": "file:///user_data/filename.json",
|
||||||
|
"number_assets": 10,
|
||||||
|
"refresh_period": 10,
|
||||||
|
"keep_pairlist_on_failure": true,
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
The user is responsible for providing a server or local file that returns a JSON object with the following structure:
|
The user is responsible for providing a server or local file that returns a JSON object with the following structure:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
@@ -227,6 +267,25 @@ The optional `bearer_token` will be included in the requests Authorization Heade
|
|||||||
!!! Note
|
!!! Note
|
||||||
In case of a server error the last received pairlist will be kept if `keep_pairlist_on_failure` is set to true, when set to false a empty pairlist is returned.
|
In case of a server error the last received pairlist will be kept if `keep_pairlist_on_failure` is set to true, when set to false a empty pairlist is returned.
|
||||||
|
|
||||||
|
#### MarketCapPairList
|
||||||
|
|
||||||
|
`MarketCapPairList` employs sorting/filtering of pairs by their marketcap rank based of CoinGecko. It will only recognize coins up to the coin placed at rank 250. The returned pairlist will be sorted based of their marketcap ranks.
|
||||||
|
|
||||||
|
```json
|
||||||
|
"pairlists": [
|
||||||
|
{
|
||||||
|
"method": "MarketCapPairList",
|
||||||
|
"number_assets": 20,
|
||||||
|
"max_rank": 50,
|
||||||
|
"refresh_period": 86400
|
||||||
|
}
|
||||||
|
]
|
||||||
|
```
|
||||||
|
|
||||||
|
`number_assets` defines the maximum number of pairs returned by the pairlist. `max_rank` will determine the maximum rank used in creating/filtering the pairlist. It's expected that some coins within the top `max_rank` marketcap will not be included in the resulting pairlist since not all pairs will have active trading pairs in your preferred market/stake/exchange combination.
|
||||||
|
|
||||||
|
`refresh_period` setting defines the period (in seconds) at which the marketcap rank data will be refreshed. Defaults to 86,400s (1 day). The pairlist cache (`refresh_period`) is applicable on both generating pairlists (first position in the list) and filtering instances (not the first position in the list).
|
||||||
|
|
||||||
#### AgeFilter
|
#### AgeFilter
|
||||||
|
|
||||||
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity).
|
Removes pairs that have been listed on the exchange for less than `min_days_listed` days (defaults to `10`) or more than `max_days_listed` days (defaults `None` mean infinity).
|
||||||
@@ -312,6 +371,11 @@ As this Filter uses past performance of the bot, it'll have some startup-period
|
|||||||
|
|
||||||
Filters low-value coins which would not allow setting stoplosses.
|
Filters low-value coins which would not allow setting stoplosses.
|
||||||
|
|
||||||
|
Namely, pairs are blacklisted if a variance of one percent or more in the stop price would be caused by precision rounding on the exchange, i.e. `rounded(stop_price) <= rounded(stop_price * 0.99)`. The idea is to avoid coins with a value VERY close to their lower trading boundary, not allowing setting of proper stoploss.
|
||||||
|
|
||||||
|
!!! Tip "PerformanceFilter is pointless for futures trading"
|
||||||
|
The above does not apply to shorts. And for longs, in theory the trade will be liquidated first.
|
||||||
|
|
||||||
!!! Warning "Backtesting"
|
!!! Warning "Backtesting"
|
||||||
`PrecisionFilter` does not support backtesting mode using multiple strategies.
|
`PrecisionFilter` does not support backtesting mode using multiple strategies.
|
||||||
|
|
||||||
@@ -393,6 +457,8 @@ If the trading range over the last 10 days is <1% or >99%, remove the pair from
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting for this pairlist.
|
||||||
|
|
||||||
!!! Tip
|
!!! Tip
|
||||||
This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit.
|
This Filter can be used to automatically remove stable coin pairs, which have a very low trading range, and are therefore extremely difficult to trade with profit.
|
||||||
Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time.
|
Additionally, it can also be used to automatically remove pairs with extreme high/low variance over a given amount of time.
|
||||||
@@ -403,7 +469,7 @@ Volatility is the degree of historical variation of a pairs over time, it is mea
|
|||||||
|
|
||||||
This filter removes pairs if the average volatility over a `lookback_days` days is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
|
This filter removes pairs if the average volatility over a `lookback_days` days is below `min_volatility` or above `max_volatility`. Since this is a filter that requires additional data, the results are cached for `refresh_period`.
|
||||||
|
|
||||||
This filter can be used to narrow down your pairs to a certain volatility or avoid very volatile pairs.
|
This filter can be used to narrow down your pairs to a certain volatility or avoid very volatile pairs.
|
||||||
|
|
||||||
In the below example:
|
In the below example:
|
||||||
If the volatility over the last 10 days is not in the range of 0.05-0.50, remove the pair from the whitelist. The filter is applied every 24h.
|
If the volatility over the last 10 days is not in the range of 0.05-0.50, remove the pair from the whitelist. The filter is applied every 24h.
|
||||||
@@ -420,6 +486,8 @@ If the volatility over the last 10 days is not in the range of 0.05-0.50, remove
|
|||||||
]
|
]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
Adding `"sort_direction": "asc"` or `"sort_direction": "desc"` enables sorting mode for this pairlist.
|
||||||
|
|
||||||
### Full example of Pairlist Handlers
|
### Full example of Pairlist Handlers
|
||||||
|
|
||||||
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#pricefilter), filtering all assets where 1 price unit is > 1%. Then the [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) is applied and pairs are finally shuffled with the random seed set to some predefined value.
|
The below example blacklists `BNB/BTC`, uses `VolumePairList` with `20` assets, sorting pairs by `quoteVolume` and applies [`PrecisionFilter`](#precisionfilter) and [`PriceFilter`](#pricefilter), filtering all assets where 1 price unit is > 1%. Then the [`SpreadFilter`](#spreadfilter) and [`VolatilityFilter`](#volatilityfilter) is applied and pairs are finally shuffled with the random seed set to some predefined value.
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ This section will highlight a few projects from members of the community.
|
|||||||
- [Example freqtrade strategies](https://github.com/freqtrade/freqtrade-strategies/)
|
- [Example freqtrade strategies](https://github.com/freqtrade/freqtrade-strategies/)
|
||||||
- [FrequentHippo - Grafana dashboard with dry/live runs and backtests](http://frequenthippo.ddns.net:3000/) (by hippocritical).
|
- [FrequentHippo - Grafana dashboard with dry/live runs and backtests](http://frequenthippo.ddns.net:3000/) (by hippocritical).
|
||||||
- [Online pairlist generator](https://remotepairlist.com/) (by Blood4rc).
|
- [Online pairlist generator](https://remotepairlist.com/) (by Blood4rc).
|
||||||
- [Freqtrade Backtesting Project](https://bt.robot.co.network/) (by Blood4rc).
|
- [Freqtrade Backtesting Project](https://strat.ninja/) (by Blood4rc).
|
||||||
- [Freqtrade analysis notebook](https://github.com/froggleston/freqtrade_analysis_notebook) (by Froggleston).
|
- [Freqtrade analysis notebook](https://github.com/froggleston/freqtrade_analysis_notebook) (by Froggleston).
|
||||||
- [TUI for freqtrade](https://github.com/froggleston/freqtrade-frogtrade9000) (by Froggleston).
|
- [TUI for freqtrade](https://github.com/froggleston/freqtrade-frogtrade9000) (by Froggleston).
|
||||||
- [Bot Academy](https://botacademy.ddns.net/) (by stash86) - Blog about crypto bot projects.
|
- [Bot Academy](https://botacademy.ddns.net/) (by stash86) - Blog about crypto bot projects.
|
||||||
|
|||||||
+1
-1
@@ -42,7 +42,7 @@ Please read the [exchange specific notes](exchanges.md) to learn about eventual,
|
|||||||
- [X] [Binance](https://www.binance.com/)
|
- [X] [Binance](https://www.binance.com/)
|
||||||
- [X] [Bitmart](https://bitmart.com/)
|
- [X] [Bitmart](https://bitmart.com/)
|
||||||
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
- [X] [Gate.io](https://www.gate.io/ref/6266643)
|
||||||
- [X] [Huobi](http://huobi.com/)
|
- [X] [HTX](https://www.htx.com/) (Former Huobi)
|
||||||
- [X] [Kraken](https://kraken.com/)
|
- [X] [Kraken](https://kraken.com/)
|
||||||
- [X] [OKX](https://okx.com/) (Former OKEX)
|
- [X] [OKX](https://okx.com/) (Former OKEX)
|
||||||
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
- [ ] [potentially many others through <img alt="ccxt" width="30px" src="assets/ccxt-logo.svg" />](https://github.com/ccxt/ccxt/). _(We cannot guarantee they will work)_
|
||||||
|
|||||||
@@ -51,7 +51,7 @@ These requirements apply to both [Script Installation](#script-installation) and
|
|||||||
### Install code
|
### Install code
|
||||||
|
|
||||||
We've included/collected install instructions for Ubuntu, MacOS, and Windows. These are guidelines and your success may vary with other distros.
|
We've included/collected install instructions for Ubuntu, MacOS, and Windows. These are guidelines and your success may vary with other distros.
|
||||||
OS Specific steps are listed first, the [Common](#common) section below is necessary for all systems.
|
OS Specific steps are listed first, the common section below is necessary for all systems.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Python3.9 or higher and the corresponding pip are assumed to be available.
|
Python3.9 or higher and the corresponding pip are assumed to be available.
|
||||||
|
|||||||
+1
-1
@@ -17,7 +17,7 @@ If you already have an existing strategy, please read the [strategy migration gu
|
|||||||
|
|
||||||
## Shorting
|
## Shorting
|
||||||
|
|
||||||
Shorting is not possible when trading with [`trading_mode`](#understand-tradingmode) set to `spot`. To short trade, `trading_mode` must be set to `margin`(currently unavailable) or [`futures`](#futures), with [`margin_mode`](#margin-mode) set to `cross`(currently unavailable) or [`isolated`](#isolated-margin-mode)
|
Shorting is not possible when trading with [`trading_mode`](#leverage-trading-modes) set to `spot`. To short trade, `trading_mode` must be set to `margin`(currently unavailable) or [`futures`](#futures), with [`margin_mode`](#margin-mode) set to `cross`(currently unavailable) or [`isolated`](#isolated-margin-mode)
|
||||||
|
|
||||||
For a strategy to short, the strategy class must set the class variable `can_short = True`
|
For a strategy to short, the strategy class must set the class variable `can_short = True`
|
||||||
|
|
||||||
|
|||||||
@@ -23,6 +23,7 @@ It also supports the lookahead-analysis of freqai strategies.
|
|||||||
- `--max-open-trades` is forced to be at least equal to the number of pairs.
|
- `--max-open-trades` is forced to be at least equal to the number of pairs.
|
||||||
- `--dry-run-wallet` is forced to be basically infinite (1 billion).
|
- `--dry-run-wallet` is forced to be basically infinite (1 billion).
|
||||||
- `--stake-amount` is forced to be a static 10000 (10k).
|
- `--stake-amount` is forced to be a static 10000 (10k).
|
||||||
|
- `--enable-protections` is forced to be off.
|
||||||
|
|
||||||
Those are set to avoid users accidentally generating false positives.
|
Those are set to avoid users accidentally generating false positives.
|
||||||
|
|
||||||
@@ -40,7 +41,6 @@ usage: freqtrade lookahead-analysis [-h] [-v] [--logfile FILE] [-V] [-c PATH]
|
|||||||
[--max-open-trades INT]
|
[--max-open-trades INT]
|
||||||
[--stake-amount STAKE_AMOUNT]
|
[--stake-amount STAKE_AMOUNT]
|
||||||
[--fee FLOAT] [-p PAIRS [PAIRS ...]]
|
[--fee FLOAT] [-p PAIRS [PAIRS ...]]
|
||||||
[--enable-protections]
|
|
||||||
[--dry-run-wallet DRY_RUN_WALLET]
|
[--dry-run-wallet DRY_RUN_WALLET]
|
||||||
[--timeframe-detail TIMEFRAME_DETAIL]
|
[--timeframe-detail TIMEFRAME_DETAIL]
|
||||||
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
[--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]]
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
markdown==3.5.1
|
markdown==3.6
|
||||||
mkdocs==1.5.3
|
mkdocs==1.6.0
|
||||||
mkdocs-material==9.5.3
|
mkdocs-material==9.5.19
|
||||||
mdx_truly_sane_lists==1.3
|
mdx_truly_sane_lists==1.3
|
||||||
pymdown-extensions==10.5
|
pymdown-extensions==10.8.1
|
||||||
jinja2==3.1.2
|
jinja2==3.1.3
|
||||||
|
|||||||
+29
-7
@@ -89,17 +89,20 @@ Make sure that the following 2 lines are available in your docker-compose file:
|
|||||||
```
|
```
|
||||||
|
|
||||||
!!! Danger "Security warning"
|
!!! Danger "Security warning"
|
||||||
By using `8080:8080` in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot.
|
By using `"8080:8080"` (or `"0.0.0.0:8080:8080"`) in the docker port mapping, the API will be available to everyone connecting to the server under the correct port, so others may be able to control your bot.
|
||||||
|
This **may** be safe if you're running the bot in a secure environment (like your home network), but it's not recommended to expose the API to the internet.
|
||||||
|
|
||||||
## Rest API
|
## Rest API
|
||||||
|
|
||||||
### Consuming the API
|
### Consuming the API
|
||||||
|
|
||||||
You can consume the API by using the script `scripts/rest_client.py`.
|
You can consume the API by using `freqtrade-client` (also available as `scripts/rest_client.py`).
|
||||||
The client script only requires the `requests` module, so Freqtrade does not need to be installed on the system.
|
This command can be installed independent of the bot by using `pip install freqtrade-client`.
|
||||||
|
|
||||||
|
This module is designed to be lightweight, and only depends on the `requests` and `python-rapidjson` modules, skipping all heavy dependencies freqtrade otherwise needs.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
python3 scripts/rest_client.py <command> [optional parameters]
|
freqtrade-client <command> [optional parameters]
|
||||||
```
|
```
|
||||||
|
|
||||||
By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be used, however you can specify a configuration file to override this behaviour.
|
By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be used, however you can specify a configuration file to override this behaviour.
|
||||||
@@ -120,9 +123,27 @@ By default, the script assumes `127.0.0.1` (localhost) and port `8080` to be use
|
|||||||
```
|
```
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
python3 scripts/rest_client.py --config rest_config.json <command> [optional parameters]
|
freqtrade-client --config rest_config.json <command> [optional parameters]
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? Note "Programmatic use"
|
||||||
|
The `freqtrade-client` package (installable independent of freqtrade) can be used in your own scripts to interact with the freqtrade API.
|
||||||
|
to do so, please use the following:
|
||||||
|
|
||||||
|
``` python
|
||||||
|
from freqtrade_client import FtRestClient
|
||||||
|
|
||||||
|
|
||||||
|
client = FtRestClient(server_url, username, password)
|
||||||
|
|
||||||
|
# Get the status of the bot
|
||||||
|
ping = client.ping()
|
||||||
|
print(ping)
|
||||||
|
# ...
|
||||||
|
```
|
||||||
|
|
||||||
|
For a full list of available commands, please refer to the list below.
|
||||||
|
|
||||||
### Available endpoints
|
### Available endpoints
|
||||||
|
|
||||||
| Command | Description |
|
| Command | Description |
|
||||||
@@ -146,6 +167,7 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
|
|||||||
| `mix_tags [pair]` | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
|
| `mix_tags [pair]` | Shows profit statistics for each combinations of enter tag + exit reasons for given pair (or all pairs if pair isn't given). Pair is optional.
|
||||||
| `locks` | Displays currently locked pairs.
|
| `locks` | Displays currently locked pairs.
|
||||||
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
|
| `delete_lock <lock_id>` | Deletes (disables) the lock by id.
|
||||||
|
| `locks add <pair>, <until>, [side], [reason]` | Locks a pair until "until". (Until will be rounded up to the nearest timeframe).
|
||||||
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
|
| `profit` | Display a summary of your profit/loss from close trades and some stats about your performance.
|
||||||
| `forceexit <trade_id>` | Instantly exits the given trade (Ignoring `minimum_roi`).
|
| `forceexit <trade_id>` | Instantly exits the given trade (Ignoring `minimum_roi`).
|
||||||
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
|
| `forceexit all` | Instantly exits all open trades (Ignoring `minimum_roi`).
|
||||||
@@ -176,7 +198,7 @@ python3 scripts/rest_client.py --config rest_config.json <command> [optional par
|
|||||||
Possible commands can be listed from the rest-client script using the `help` command.
|
Possible commands can be listed from the rest-client script using the `help` command.
|
||||||
|
|
||||||
``` bash
|
``` bash
|
||||||
python3 scripts/rest_client.py help
|
freqtrade-client help
|
||||||
```
|
```
|
||||||
|
|
||||||
``` output
|
``` output
|
||||||
@@ -433,7 +455,7 @@ To properly configure your reverse proxy (securely), please consult it's documen
|
|||||||
- **Caddy**: Caddy v2 supports websockets out of the box, see the [documentation](https://caddyserver.com/docs/v2-upgrade#proxy)
|
- **Caddy**: Caddy v2 supports websockets out of the box, see the [documentation](https://caddyserver.com/docs/v2-upgrade#proxy)
|
||||||
|
|
||||||
!!! Tip "SSL certificates"
|
!!! Tip "SSL certificates"
|
||||||
You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any fo the above reverse proxies.
|
You can use tools like certbot to setup ssl certificates to access your bot's UI through encrypted connection by using any of the above reverse proxies.
|
||||||
While this will protect your data in transit, we do not recommend to run the freqtrade API outside of your private network (VPN, SSH tunnel).
|
While this will protect your data in transit, we do not recommend to run the freqtrade API outside of your private network (VPN, SSH tunnel).
|
||||||
|
|
||||||
### OpenAPI interface
|
### OpenAPI interface
|
||||||
|
|||||||
@@ -109,7 +109,7 @@ Freqtrade does not depend or install any additional database driver. Please refe
|
|||||||
The following systems have been tested and are known to work with freqtrade:
|
The following systems have been tested and are known to work with freqtrade:
|
||||||
|
|
||||||
* sqlite (default)
|
* sqlite (default)
|
||||||
* PostgreSQL)
|
* PostgreSQL
|
||||||
* MariaDB
|
* MariaDB
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning
|
||||||
|
|||||||
+3
-3
@@ -30,7 +30,7 @@ The Order-type will be ignored if only one mode is available.
|
|||||||
|----------|-------------|
|
|----------|-------------|
|
||||||
| Binance | limit |
|
| Binance | limit |
|
||||||
| Binance Futures | market, limit |
|
| Binance Futures | market, limit |
|
||||||
| Huobi | limit |
|
| HTX (former Huobi) | limit |
|
||||||
| kraken | market, limit |
|
| kraken | market, limit |
|
||||||
| Gate | limit |
|
| Gate | limit |
|
||||||
| Okx | limit |
|
| Okx | limit |
|
||||||
@@ -158,7 +158,7 @@ You could also have a default stop loss when you are in the red with your buy (b
|
|||||||
For example, your default stop loss is -10%, but once you have more than 0% profit (example 0.1%) a different trailing stoploss will be used.
|
For example, your default stop loss is -10%, but once you have more than 0% profit (example 0.1%) a different trailing stoploss will be used.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
If you want the stoploss to only be changed when you break even of making a profit (what most users want) please refer to next section with [offset enabled](#Trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset).
|
If you want the stoploss to only be changed when you break even of making a profit (what most users want) please refer to next section with [offset enabled](#trailing-stop-loss-only-once-the-trade-has-reached-a-certain-offset).
|
||||||
|
|
||||||
Both values require `trailing_stop` to be set to true and `trailing_stop_positive` with a value.
|
Both values require `trailing_stop` to be set to true and `trailing_stop_positive` with a value.
|
||||||
|
|
||||||
@@ -240,7 +240,7 @@ When using leverage, the same principle is applied - with stoploss defining the
|
|||||||
|
|
||||||
Therefore, a stoploss of 10% on a 10x trade would trigger on a 1% price move.
|
Therefore, a stoploss of 10% on a 10x trade would trigger on a 1% price move.
|
||||||
If your stake amount (own capital) was 100$ - this trade would be 1000$ at 10x (after leverage).
|
If your stake amount (own capital) was 100$ - this trade would be 1000$ at 10x (after leverage).
|
||||||
If price moves 1% - you've lost 10$ of your own capital - therfore stoploss will trigger in this case.
|
If price moves 1% - you've lost 10$ of your own capital - therefore stoploss will trigger in this case.
|
||||||
|
|
||||||
Make sure to be aware of this, and avoid using too tight stoploss (at 10x leverage, 10% risk may be too little to allow the trade to "breath" a little).
|
Make sure to be aware of this, and avoid using too tight stoploss (at 10x leverage, 10% risk may be too little to allow the trade to "breath" a little).
|
||||||
|
|
||||||
|
|||||||
+114
-19
@@ -11,34 +11,129 @@ The call sequence of the methods described here is covered under [bot execution
|
|||||||
!!! Tip
|
!!! Tip
|
||||||
Start off with a strategy template containing all available callback methods by running `freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced`
|
Start off with a strategy template containing all available callback methods by running `freqtrade new-strategy --strategy MyAwesomeStrategy --template advanced`
|
||||||
|
|
||||||
## Storing information
|
## Storing information (Persistent)
|
||||||
|
|
||||||
Storing information can be accomplished by creating a new dictionary within the strategy class.
|
Freqtrade allows storing/retrieving user custom information associated with a specific trade in the database.
|
||||||
|
|
||||||
The name of the variable can be chosen at will, but should be prefixed with `custom_` to avoid naming collisions with predefined strategy variables.
|
Using a trade object, information can be stored using `trade.set_custom_data(key='my_key', value=my_value)` and retrieved using `trade.get_custom_data(key='my_key')`. Each data entry is associated with a trade and a user supplied key (of type `string`). This means that this can only be used in callbacks that also provide a trade object.
|
||||||
|
|
||||||
|
For the data to be able to be stored within the database, freqtrade must serialized the data. This is done by converting the data to a JSON formatted string.
|
||||||
|
Freqtrade will attempt to reverse this action on retrieval, so from a strategy perspective, this should not be relevant.
|
||||||
|
|
||||||
```python
|
```python
|
||||||
|
from freqtrade.persistence import Trade
|
||||||
|
from datetime import timedelta
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
# Create custom dictionary
|
|
||||||
custom_info = {}
|
|
||||||
|
|
||||||
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def bot_loop_start(self, **kwargs) -> None:
|
||||||
# Check if the entry already exists
|
for trade in Trade.get_open_order_trades():
|
||||||
if not metadata["pair"] in self.custom_info:
|
fills = trade.select_filled_orders(trade.entry_side)
|
||||||
# Create empty entry for this pair
|
if trade.pair == 'ETH/USDT':
|
||||||
self.custom_info[metadata["pair"]] = {}
|
trade_entry_type = trade.get_custom_data(key='entry_type')
|
||||||
|
if trade_entry_type is None:
|
||||||
|
trade_entry_type = 'breakout' if 'entry_1' in trade.enter_tag else 'dip'
|
||||||
|
elif fills > 1:
|
||||||
|
trade_entry_type = 'buy_up'
|
||||||
|
trade.set_custom_data(key='entry_type', value=trade_entry_type)
|
||||||
|
return super().bot_loop_start(**kwargs)
|
||||||
|
|
||||||
if "crosstime" in self.custom_info[metadata["pair"]]:
|
def adjust_entry_price(self, trade: Trade, order: Optional[Order], pair: str,
|
||||||
self.custom_info[metadata["pair"]]["crosstime"] += 1
|
current_time: datetime, proposed_rate: float, current_order_rate: float,
|
||||||
else:
|
entry_tag: Optional[str], side: str, **kwargs) -> float:
|
||||||
self.custom_info[metadata["pair"]]["crosstime"] = 1
|
# Limit orders to use and follow SMA200 as price target for the first 10 minutes since entry trigger for BTC/USDT pair.
|
||||||
|
if (
|
||||||
|
pair == 'BTC/USDT'
|
||||||
|
and entry_tag == 'long_sma200'
|
||||||
|
and side == 'long'
|
||||||
|
and (current_time - timedelta(minutes=10)) > trade.open_date_utc
|
||||||
|
and order.filled == 0.0
|
||||||
|
):
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(pair=pair, timeframe=self.timeframe)
|
||||||
|
current_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
# store information about entry adjustment
|
||||||
|
existing_count = trade.get_custom_data('num_entry_adjustments', default=0)
|
||||||
|
if not existing_count:
|
||||||
|
existing_count = 1
|
||||||
|
else:
|
||||||
|
existing_count += 1
|
||||||
|
trade.set_custom_data(key='num_entry_adjustments', value=existing_count)
|
||||||
|
|
||||||
|
# adjust order price
|
||||||
|
return current_candle['sma_200']
|
||||||
|
|
||||||
|
# default: maintain existing order
|
||||||
|
return current_order_rate
|
||||||
|
|
||||||
|
def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_rate: float, current_profit: float, **kwargs):
|
||||||
|
|
||||||
|
entry_adjustment_count = trade.get_custom_data(key='num_entry_adjustments')
|
||||||
|
trade_entry_type = trade.get_custom_data(key='entry_type')
|
||||||
|
if entry_adjustment_count is None:
|
||||||
|
if current_profit > 0.01 and (current_time - timedelta(minutes=100) > trade.open_date_utc):
|
||||||
|
return True, 'exit_1'
|
||||||
|
else
|
||||||
|
if entry_adjustment_count > 0 and if current_profit > 0.05:
|
||||||
|
return True, 'exit_2'
|
||||||
|
if trade_entry_type == 'breakout' and current_profit > 0.1:
|
||||||
|
return True, 'exit_3
|
||||||
|
|
||||||
|
return False, None
|
||||||
```
|
```
|
||||||
|
|
||||||
!!! Warning
|
The above is a simple example - there are simpler ways to retrieve trade data like entry-adjustments.
|
||||||
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
|
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
|
It is recommended that simple data types are used `[bool, int, float, str]` to ensure no issues when serializing the data that needs to be stored.
|
||||||
|
Storing big junks of data may lead to unintended side-effects, like a database becoming big (and as a consequence, also slow).
|
||||||
|
|
||||||
|
!!! Warning "Non-serializable data"
|
||||||
|
If supplied data cannot be serialized a warning is logged and the entry for the specified `key` will contain `None` as data.
|
||||||
|
|
||||||
|
??? Note "All attributes"
|
||||||
|
custom-data has the following accessors through the Trade object (assumed as `trade` below):
|
||||||
|
|
||||||
|
* `trade.get_custom_data(key='something', default=0)` - Returns the actual value given in the type provided.
|
||||||
|
* `trade.get_custom_data_entry(key='something')` - Returns the entry - including metadata. The value is accessible via `.value` property.
|
||||||
|
* `trade.set_custom_data(key='something', value={'some': 'value'})` - set or update the corresponding key for this trade. Value must be serializable - and we recommend to keep the stored data relatively small.
|
||||||
|
|
||||||
|
"value" can be any type (both in setting and receiving) - but must be json serializable.
|
||||||
|
|
||||||
|
## Storing information (Non-Persistent)
|
||||||
|
|
||||||
|
!!! Warning "Deprecated"
|
||||||
|
This method of storing information is deprecated and we do advise against using non-persistent storage.
|
||||||
|
Please use [Persistent Storage](#storing-information-persistent) instead.
|
||||||
|
|
||||||
|
It's content has therefore been collapsed.
|
||||||
|
|
||||||
|
??? Abstract "Storing information"
|
||||||
|
Storing information can be accomplished by creating a new dictionary within the strategy class.
|
||||||
|
|
||||||
|
The name of the variable can be chosen at will, but should be prefixed with `custom_` to avoid naming collisions with predefined strategy variables.
|
||||||
|
|
||||||
|
```python
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
# Create custom dictionary
|
||||||
|
custom_info = {}
|
||||||
|
|
||||||
|
def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
|
# Check if the entry already exists
|
||||||
|
if not metadata["pair"] in self.custom_info:
|
||||||
|
# Create empty entry for this pair
|
||||||
|
self.custom_info[metadata["pair"]] = {}
|
||||||
|
|
||||||
|
if "crosstime" in self.custom_info[metadata["pair"]]:
|
||||||
|
self.custom_info[metadata["pair"]]["crosstime"] += 1
|
||||||
|
else:
|
||||||
|
self.custom_info[metadata["pair"]]["crosstime"] = 1
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning
|
||||||
|
The data is not persisted after a bot-restart (or config-reload). Also, the amount of data should be kept smallish (no DataFrames and such), otherwise the bot will start to consume a lot of memory and eventually run out of memory and crash.
|
||||||
|
|
||||||
|
!!! Note
|
||||||
|
If the data is pair-specific, make sure to use pair as one of the keys in the dictionary.
|
||||||
|
|
||||||
## Dataframe access
|
## Dataframe access
|
||||||
|
|
||||||
@@ -114,7 +209,7 @@ def custom_exit(self, pair: str, trade: Trade, current_time: datetime, current_r
|
|||||||
|
|
||||||
## Exit tag
|
## Exit tag
|
||||||
|
|
||||||
Similar to [Buy Tagging](#buy-tag), you can also specify a sell tag.
|
Similar to [Entry Tagging](#enter-tag), you can also specify an exit tag.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_exit_trend(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
@@ -231,4 +326,4 @@ for val in self.buy_ema_short.range:
|
|||||||
dataframe = pd.concat(frames, axis=1)
|
dataframe = pd.concat(frames, axis=1)
|
||||||
```
|
```
|
||||||
|
|
||||||
Freqtrade does however also counter this by running `dataframe.copy()` on the dataframe right after the `populate_indicators()` method - so performance implications of this should be low to non-existant.
|
Freqtrade does however also counter this by running `dataframe.copy()` on the dataframe right after the `populate_indicators()` method - so performance implications of this should be low to non-existent.
|
||||||
|
|||||||
+63
-14
@@ -19,6 +19,7 @@ Currently available callbacks:
|
|||||||
* [`adjust_trade_position()`](#adjust-trade-position)
|
* [`adjust_trade_position()`](#adjust-trade-position)
|
||||||
* [`adjust_entry_price()`](#adjust-entry-price)
|
* [`adjust_entry_price()`](#adjust-entry-price)
|
||||||
* [`leverage()`](#leverage-callback)
|
* [`leverage()`](#leverage-callback)
|
||||||
|
* [`order_filled()`](#order-filled-callback)
|
||||||
|
|
||||||
!!! Tip "Callback calling sequence"
|
!!! Tip "Callback calling sequence"
|
||||||
You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic)
|
You can find the callback calling sequence in [bot-basics](bot-basics.md#bot-execution-logic)
|
||||||
@@ -166,7 +167,7 @@ During backtesting, `current_rate` (and `current_profit`) are provided against t
|
|||||||
The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price.
|
The absolute value of the return value is used (the sign is ignored), so returning `0.05` or `-0.05` have the same result, a stoploss 5% below the current price.
|
||||||
Returning None will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss.
|
Returning None will be interpreted as "no desire to change", and is the only safe way to return when you'd like to not modify the stoploss.
|
||||||
|
|
||||||
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchange-freqtrade)).
|
Stoploss on exchange works similar to `trailing_stop`, and the stoploss on exchange is updated as configured in `stoploss_on_exchange_interval` ([More details about stoploss on exchange](stoploss.md#stop-loss-on-exchangefreqtrade)).
|
||||||
|
|
||||||
!!! Note "Use of dates"
|
!!! Note "Use of dates"
|
||||||
All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support.
|
All time-based calculations should be done based on `current_time` - using `datetime.now()` or `datetime.utcnow()` is discouraged, as this will break backtesting support.
|
||||||
@@ -331,7 +332,7 @@ class AwesomeStrategy(IStrategy):
|
|||||||
**kwargs) -> Optional[float]:
|
**kwargs) -> Optional[float]:
|
||||||
|
|
||||||
if current_profit < 0.04:
|
if current_profit < 0.04:
|
||||||
return -1 # return a value bigger than the initial stoploss to keep using the initial stoploss
|
return None # return None to keep using the initial stoploss
|
||||||
|
|
||||||
# After reaching the desired offset, allow the stoploss to trail by half the profit
|
# After reaching the desired offset, allow the stoploss to trail by half the profit
|
||||||
desired_stoploss = current_profit / 2
|
desired_stoploss = current_profit / 2
|
||||||
@@ -449,7 +450,7 @@ Stoploss values returned from `custom_stoploss()` must specify a percentage rela
|
|||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
Full examples can be found in the [Custom stoploss](strategy-advanced.md#custom-stoploss) section of the Documentation.
|
Full examples can be found in the [Custom stoploss](strategy-callbacks.md#custom-stoploss) section of the Documentation.
|
||||||
|
|
||||||
!!! Note
|
!!! Note
|
||||||
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
|
Providing invalid input to `stoploss_from_open()` may produce "CustomStoploss function did not return valid stoploss" warnings.
|
||||||
@@ -760,28 +761,38 @@ The `position_adjustment_enable` strategy property enables the usage of `adjust_
|
|||||||
For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled.
|
For performance reasons, it's disabled by default and freqtrade will show a warning message on startup if enabled.
|
||||||
`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease positions.
|
`adjust_trade_position()` can be used to perform additional orders, for example to manage risk with DCA (Dollar Cost Averaging) or to increase or decrease positions.
|
||||||
|
|
||||||
`max_entry_position_adjustment` property is used to limit the number of additional entries per trade (on top of the first entry order) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment entries.
|
|
||||||
|
|
||||||
The strategy is expected to return a stake_amount (in stake currency) between `min_stake` and `max_stake` if and when an additional entry order should be made (position is increased -> buy order for long trades, sell order for short trades).
|
|
||||||
If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored.
|
|
||||||
Additional orders also result in additional fees and those orders don't count towards `max_open_trades`.
|
Additional orders also result in additional fees and those orders don't count towards `max_open_trades`.
|
||||||
|
|
||||||
This callback is **not** called when there is an open order (either buy or sell) waiting for execution.
|
This callback is **not** called when there is an open order (either buy or sell) waiting for execution.
|
||||||
|
|
||||||
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
|
`adjust_trade_position()` is called very frequently for the duration of a trade, so you must keep your implementation as performant as possible.
|
||||||
|
|
||||||
Additional entries are ignored once you have reached the maximum amount of extra entries that you have set on `max_entry_position_adjustment`, but the callback is called anyway looking for partial exits.
|
|
||||||
|
|
||||||
Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade.
|
Position adjustments will always be applied in the direction of the trade, so a positive value will always increase your position (negative values will decrease your position), no matter if it's a long or short trade.
|
||||||
|
Adjustment orders can be assigned with a tag by returning a 2 element Tuple, with the first element being the adjustment amount, and the 2nd element the tag (e.g. `return 250, 'increase_favorable_conditions'`).
|
||||||
|
|
||||||
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
|
Modifications to leverage are not possible, and the stake-amount returned is assumed to be before applying leverage.
|
||||||
|
|
||||||
|
### Increase position
|
||||||
|
|
||||||
|
The strategy is expected to return a positive **stake_amount** (in stake currency) between `min_stake` and `max_stake` if and when an additional entry order should be made (position is increased -> buy order for long trades, sell order for short trades).
|
||||||
|
|
||||||
|
If there are not enough funds in the wallet (the return value is above `max_stake`) then the signal will be ignored.
|
||||||
|
`max_entry_position_adjustment` property is used to limit the number of additional entries per trade (on top of the first entry order) that the bot can execute. By default, the value is -1 which means the bot have no limit on number of adjustment entries.
|
||||||
|
|
||||||
|
Additional entries are ignored once you have reached the maximum amount of extra entries that you have set on `max_entry_position_adjustment`, but the callback is called anyway looking for partial exits.
|
||||||
|
|
||||||
|
### Decrease position
|
||||||
|
|
||||||
|
The strategy is expected to return a negative stake_amount (in stake currency) for a partial exit.
|
||||||
|
Returning the full owned stake at that point (`-trade.stake_amount`) results in a full exit.
|
||||||
|
Returning a value more than the above (so remaining stake_amount would become negative) will result in the bot ignoring the signal.
|
||||||
|
|
||||||
!!! Note "About stake size"
|
!!! Note "About stake size"
|
||||||
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment.
|
Using fixed stake size means it will be the amount used for the first order, just like without position adjustment.
|
||||||
If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that.
|
If you wish to buy additional orders with DCA, then make sure to leave enough funds in the wallet for that.
|
||||||
Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
|
Using 'unlimited' stake amount with DCA orders requires you to also implement the `custom_stake_amount()` callback to avoid allocating all funds to the initial order.
|
||||||
|
|
||||||
!!! Warning
|
!!! Warning "Stoploss calculation"
|
||||||
Stoploss is still calculated from the initial opening price, not averaged price.
|
Stoploss is still calculated from the initial opening price, not averaged price.
|
||||||
Regular stoploss rules still apply (cannot move down).
|
Regular stoploss rules still apply (cannot move down).
|
||||||
|
|
||||||
@@ -791,8 +802,14 @@ Modifications to leverage are not possible, and the stake-amount returned is ass
|
|||||||
During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected.
|
During backtesting this callback is called for each candle in `timeframe` or `timeframe_detail`, so run-time performance will be affected.
|
||||||
This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle.
|
This can also cause deviating results between live and backtesting, since backtesting can adjust the trade only once per candle, whereas live could adjust the trade multiple times per candle.
|
||||||
|
|
||||||
|
!!! Warning "Performance with many position adjustments"
|
||||||
|
Position adjustments can be a good approach to increase a strategy's output - but it can also have drawbacks if using this feature extensively.
|
||||||
|
Each of the orders will be attached to the trade object for the duration of the trade - hence increasing memory usage.
|
||||||
|
Trades with long duration and 10s or even 100ds of position adjustments are therefore not recommended, and should be closed at regular intervals to not affect performance.
|
||||||
|
|
||||||
``` python
|
``` python
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
|
from typing import Optional, Tuple, Union
|
||||||
|
|
||||||
|
|
||||||
class DigDeeperStrategy(IStrategy):
|
class DigDeeperStrategy(IStrategy):
|
||||||
@@ -824,7 +841,8 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
min_stake: Optional[float], max_stake: float,
|
min_stake: Optional[float], max_stake: float,
|
||||||
current_entry_rate: float, current_exit_rate: float,
|
current_entry_rate: float, current_exit_rate: float,
|
||||||
current_entry_profit: float, current_exit_profit: float,
|
current_entry_profit: float, current_exit_profit: float,
|
||||||
**kwargs) -> Optional[float]:
|
**kwargs
|
||||||
|
) -> Union[Optional[float], Tuple[Optional[float], Optional[str]]]:
|
||||||
"""
|
"""
|
||||||
Custom trade adjustment logic, returning the stake amount that a trade should be
|
Custom trade adjustment logic, returning the stake amount that a trade should be
|
||||||
increased or decreased.
|
increased or decreased.
|
||||||
@@ -850,11 +868,12 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
:return float: Stake amount to adjust your trade,
|
:return float: Stake amount to adjust your trade,
|
||||||
Positive values to increase position, Negative values to decrease position.
|
Positive values to increase position, Negative values to decrease position.
|
||||||
Return None for no action.
|
Return None for no action.
|
||||||
|
Optionally, return a tuple with a 2nd element with an order reason
|
||||||
"""
|
"""
|
||||||
|
|
||||||
if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
|
if current_profit > 0.05 and trade.nr_of_successful_exits == 0:
|
||||||
# Take half of the profit at +5%
|
# Take half of the profit at +5%
|
||||||
return -(trade.stake_amount / 2)
|
return -(trade.stake_amount / 2), 'half_profit_5%'
|
||||||
|
|
||||||
if current_profit > -0.05:
|
if current_profit > -0.05:
|
||||||
return None
|
return None
|
||||||
@@ -882,7 +901,7 @@ class DigDeeperStrategy(IStrategy):
|
|||||||
stake_amount = filled_entries[0].stake_amount
|
stake_amount = filled_entries[0].stake_amount
|
||||||
# This then calculates current safety order size
|
# This then calculates current safety order size
|
||||||
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
|
stake_amount = stake_amount * (1 + (count_of_entries * 0.25))
|
||||||
return stake_amount
|
return stake_amount, '1/3rd_increase'
|
||||||
except Exception as exception:
|
except Exception as exception:
|
||||||
return None
|
return None
|
||||||
|
|
||||||
@@ -930,7 +949,7 @@ If the cancellation of the original order fails, then the order will not be repl
|
|||||||
|
|
||||||
```python
|
```python
|
||||||
from freqtrade.persistence import Trade
|
from freqtrade.persistence import Trade
|
||||||
from datetime import timedelta
|
from datetime import timedelta, datetime
|
||||||
|
|
||||||
class AwesomeStrategy(IStrategy):
|
class AwesomeStrategy(IStrategy):
|
||||||
|
|
||||||
@@ -1005,3 +1024,33 @@ class AwesomeStrategy(IStrategy):
|
|||||||
|
|
||||||
All profit calculations include leverage. Stoploss / ROI also include leverage in their calculation.
|
All profit calculations include leverage. Stoploss / ROI also include leverage in their calculation.
|
||||||
Defining a stoploss of 10% at 10x leverage would trigger the stoploss with a 1% move to the downside.
|
Defining a stoploss of 10% at 10x leverage would trigger the stoploss with a 1% move to the downside.
|
||||||
|
|
||||||
|
## Order filled Callback
|
||||||
|
|
||||||
|
The `order_filled()` callback may be used to perform specific actions based on the current trade state after an order is filled.
|
||||||
|
It will be called independent of the order type (entry, exit, stoploss or position adjustment).
|
||||||
|
|
||||||
|
Assuming that your strategy needs to store the high value of the candle at trade entry, this is possible with this callback as the following example show.
|
||||||
|
|
||||||
|
``` python
|
||||||
|
class AwesomeStrategy(IStrategy):
|
||||||
|
def order_filled(self, pair: str, trade: Trade, order: Order, current_time: datetime, **kwargs) -> None:
|
||||||
|
"""
|
||||||
|
Called right after an order fills.
|
||||||
|
Will be called for all order types (entry, exit, stoploss, position adjustment).
|
||||||
|
:param pair: Pair for trade
|
||||||
|
:param trade: trade object.
|
||||||
|
:param order: Order object.
|
||||||
|
:param current_time: datetime object, containing the current datetime
|
||||||
|
:param **kwargs: Ensure to keep this here so updates to this won't break your strategy.
|
||||||
|
"""
|
||||||
|
# Obtain pair dataframe (just to show how to access it)
|
||||||
|
dataframe, _ = self.dp.get_analyzed_dataframe(trade.pair, self.timeframe)
|
||||||
|
last_candle = dataframe.iloc[-1].squeeze()
|
||||||
|
|
||||||
|
if (trade.nr_of_successful_entries == 1) and (order.ft_order_side == trade.entry_side):
|
||||||
|
trade.set_custom_data(key='entry_candle_high', value=last_candle['high'])
|
||||||
|
|
||||||
|
return None
|
||||||
|
|
||||||
|
```
|
||||||
|
|||||||
@@ -156,9 +156,9 @@ def populate_indicators(self, dataframe: DataFrame, metadata: dict) -> DataFrame
|
|||||||
|
|
||||||
Out of the box, freqtrade installs the following technical libraries:
|
Out of the box, freqtrade installs the following technical libraries:
|
||||||
|
|
||||||
* [ta-lib](http://mrjbq7.github.io/ta-lib/)
|
- [ta-lib](https://ta-lib.github.io/ta-lib-python/)
|
||||||
* [pandas-ta](https://twopirllc.github.io/pandas-ta/)
|
- [pandas-ta](https://twopirllc.github.io/pandas-ta/)
|
||||||
* [technical](https://github.com/freqtrade/technical/)
|
- [technical](https://github.com/freqtrade/technical/)
|
||||||
|
|
||||||
Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author.
|
Additional technical libraries can be installed as necessary, or custom indicators may be written / invented by the strategy author.
|
||||||
|
|
||||||
@@ -367,6 +367,11 @@ class AwesomeStrategy(IStrategy):
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
??? info "Orders that don't fill immediately"
|
||||||
|
`minimal_roi` will take the `trade.open_date` as reference, which is the time the trade was initialized / the first order for this trade was placed.
|
||||||
|
This will also hold true for limit orders that don't fill immediately (usually in combination with "off-spot" prices through `custom_entry_price()`), as well as for cases where the initial order is replaced through `adjust_entry_price()`.
|
||||||
|
The time used will still be from the initial `trade.open_date` (when the initial order was first placed), not from the newly placed order date.
|
||||||
|
|
||||||
### Stoploss
|
### Stoploss
|
||||||
|
|
||||||
Setting a stoploss is highly recommended to protect your capital from strong moves against you.
|
Setting a stoploss is highly recommended to protect your capital from strong moves against you.
|
||||||
@@ -400,7 +405,7 @@ The metadata-dict (available for `populate_entry_trend`, `populate_exit_trend`,
|
|||||||
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
|
Currently this is `pair`, which can be accessed using `metadata['pair']` - and will return a pair in the format `XRP/BTC`.
|
||||||
|
|
||||||
The Metadata-dict should not be modified and does not persist information across multiple calls.
|
The Metadata-dict should not be modified and does not persist information across multiple calls.
|
||||||
Instead, have a look at the [Storing information](strategy-advanced.md#Storing-information) section.
|
Instead, have a look at the [Storing information](strategy-advanced.md#storing-information-persistent) section.
|
||||||
|
|
||||||
## Strategy file loading
|
## Strategy file loading
|
||||||
|
|
||||||
@@ -546,8 +551,8 @@ for more information.
|
|||||||
|
|
||||||
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
|
# Define BTC/STAKE informative pair. A custom formatter may be specified for formatting
|
||||||
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
|
# column names. A callable `fmt(**kwargs) -> str` may be specified, to implement custom
|
||||||
# formatting. Available in populate_indicators and other methods as 'rsi_upper'.
|
# formatting. Available in populate_indicators and other methods as 'rsi_upper_1h'.
|
||||||
@informative('1h', 'BTC/{stake}', '{column}')
|
@informative('1h', 'BTC/{stake}', '{column}_{timeframe}')
|
||||||
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
def populate_indicators_btc_1h_2(self, dataframe: DataFrame, metadata: dict) -> DataFrame:
|
||||||
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
|
dataframe['rsi_upper'] = ta.RSI(dataframe, timeperiod=14)
|
||||||
return dataframe
|
return dataframe
|
||||||
@@ -771,7 +776,7 @@ The orderbook structure is aligned with the order structure from [ccxt](https://
|
|||||||
Therefore, using `ob['bids'][0][0]` as demonstrated above will result in using the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position.
|
Therefore, using `ob['bids'][0][0]` as demonstrated above will result in using the best bid price. `ob['bids'][0][1]` would look at the amount at this orderbook position.
|
||||||
|
|
||||||
!!! Warning "Warning about backtesting"
|
!!! Warning "Warning about backtesting"
|
||||||
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return uptodate values.
|
The order book is not part of the historic data which means backtesting and hyperopt will not work correctly if this method is used, as the method will return up-to-date values.
|
||||||
|
|
||||||
### *ticker(pair)*
|
### *ticker(pair)*
|
||||||
|
|
||||||
@@ -1004,8 +1009,8 @@ This is a common pain-point, which can cause huge differences between backtestin
|
|||||||
|
|
||||||
The following lists some common patterns which should be avoided to prevent frustration:
|
The following lists some common patterns which should be avoided to prevent frustration:
|
||||||
|
|
||||||
- don't use `shift(-1)`. This uses data from the future, which is not available.
|
- don't use `shift(-1)` or other negative values. This uses data from the future in backtesting, which is not available in dry or live modes.
|
||||||
- don't use `.iloc[-1]` or any other absolute position in the dataframe, this will be different between dry-run and backtesting.
|
- don't use `.iloc[-1]` or any other absolute position in the dataframe within `populate_` functions, as this will be different between dry-run and backtesting. Absolute `iloc` indexing is safe to use in callbacks however - see [Strategy Callbacks](strategy-callbacks.md).
|
||||||
- don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling(<window>).mean()` instead
|
- don't use `dataframe['volume'].mean()`. This uses the full DataFrame for backtesting, including data from the future. Use `dataframe['volume'].rolling(<window>).mean()` instead
|
||||||
- don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead.
|
- don't use `.resample('1h')`. This uses the left border of the interval, so moves data from an hour to the start of the hour. Use `.resample('1h', label='right')` instead.
|
||||||
|
|
||||||
|
|||||||
@@ -19,7 +19,7 @@ from pathlib import Path
|
|||||||
project_root = "somedir/freqtrade"
|
project_root = "somedir/freqtrade"
|
||||||
i=0
|
i=0
|
||||||
try:
|
try:
|
||||||
os.chdirdir(project_root)
|
os.chdir(project_root)
|
||||||
assert Path('LICENSE').is_file()
|
assert Path('LICENSE').is_file()
|
||||||
except:
|
except:
|
||||||
while i<4 and (not Path('LICENSE').is_file()):
|
while i<4 and (not Path('LICENSE').is_file()):
|
||||||
|
|||||||
@@ -53,13 +53,13 @@ You can use bots in telegram groups by just adding them to the group. You can fi
|
|||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
For the Freqtrade configuration, you can then use the the full value (including `-` if it's there) as string:
|
For the Freqtrade configuration, you can then use the full value (including `-` if it's there) as string:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"chat_id": "-1001332619709"
|
"chat_id": "-1001332619709"
|
||||||
```
|
```
|
||||||
!!! Warning "Using telegram groups"
|
!!! Warning "Using telegram groups"
|
||||||
When using telegram groups, you're giving every member of the telegram group access to your freqtrade bot and to all commands possible via telegram. Please make sure that you can trust everyone in the telegram group to avoid unpleasent surprises.
|
When using telegram groups, you're giving every member of the telegram group access to your freqtrade bot and to all commands possible via telegram. Please make sure that you can trust everyone in the telegram group to avoid unpleasant surprises.
|
||||||
|
|
||||||
## Control telegram noise
|
## Control telegram noise
|
||||||
|
|
||||||
@@ -181,6 +181,7 @@ official commands. You can ask at any moment for help with `/help`.
|
|||||||
| `/locks` | Show currently locked pairs.
|
| `/locks` | Show currently locked pairs.
|
||||||
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
|
| `/unlock <pair or lock_id>` | Remove the lock for this pair (or for this lock id).
|
||||||
| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. If no direction is provided, the currently set direction will be displayed.
|
| `/marketdir [long | short | even | none]` | Updates the user managed variable that represents the current market direction. If no direction is provided, the currently set direction will be displayed.
|
||||||
|
| `/list_custom_data <trade_id> [key]` | List custom_data for Trade ID & Key combination. If no Key is supplied it will list all key-value pairs found for that Trade ID.
|
||||||
| **Modify Trade states** |
|
| **Modify Trade states** |
|
||||||
| `/forceexit <trade_id> | /fx <tradeid>` | Instantly exits the given trade (Ignoring `minimum_roi`).
|
| `/forceexit <trade_id> | /fx <tradeid>` | Instantly exits the given trade (Ignoring `minimum_roi`).
|
||||||
| `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`).
|
| `/forceexit all | /fx all` | Instantly exits all open trades (Ignoring `minimum_roi`).
|
||||||
|
|||||||
@@ -126,7 +126,7 @@ An `Order` object will always be tied to it's corresponding [`Trade`](#trade-obj
|
|||||||
### Order - Available attributes
|
### Order - Available attributes
|
||||||
|
|
||||||
an Order object is typically attached to a trade.
|
an Order object is typically attached to a trade.
|
||||||
Most properties here can be None as they are dependant on the exchange response.
|
Most properties here can be None as they are dependent on the exchange response.
|
||||||
|
|
||||||
| Attribute | DataType | Description |
|
| Attribute | DataType | Description |
|
||||||
|------------|-------------|-------------|
|
|------------|-------------|-------------|
|
||||||
@@ -141,7 +141,7 @@ Most properties here can be None as they are dependant on the exchange response.
|
|||||||
`amount` | float | Amount in base currency
|
`amount` | float | Amount in base currency
|
||||||
`filled` | float | Filled amount (in base currency)
|
`filled` | float | Filled amount (in base currency)
|
||||||
`remaining` | float | Remaining amount
|
`remaining` | float | Remaining amount
|
||||||
`cost` | float | Cost of the order - usually average * filled (*Exchange dependant on futures, may contain the cost with or without leverage and may be in contracts.*)
|
`cost` | float | Cost of the order - usually average * filled (*Exchange dependent on futures, may contain the cost with or without leverage and may be in contracts.*)
|
||||||
`stake_amount` | float | Stake amount used for this order. *Added in 2023.7.*
|
`stake_amount` | float | Stake amount used for this order. *Added in 2023.7.*
|
||||||
`order_date` | datetime | Order creation date **use `order_date_utc` instead**
|
`order_date` | datetime | Order creation date **use `order_date_utc` instead**
|
||||||
`order_date_utc` | datetime | Order creation date (in UTC)
|
`order_date_utc` | datetime | Order creation date (in UTC)
|
||||||
|
|||||||
+1
-1
@@ -6,7 +6,7 @@ To update your freqtrade installation, please use one of the below methods, corr
|
|||||||
Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release.
|
Breaking changes / changed behavior will be documented in the changelog that is posted alongside every release.
|
||||||
For the develop branch, please follow PR's to avoid being surprised by changes.
|
For the develop branch, please follow PR's to avoid being surprised by changes.
|
||||||
|
|
||||||
## docker
|
## Docker
|
||||||
|
|
||||||
!!! Note "Legacy installations using the `master` image"
|
!!! Note "Legacy installations using the `master` image"
|
||||||
We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable`
|
We're switching from master to stable for the release Images - please adjust your docker-file and replace `freqtradeorg/freqtrade:master` with `freqtradeorg/freqtrade:stable`
|
||||||
|
|||||||
+79
-194
@@ -54,7 +54,7 @@ optional arguments:
|
|||||||
### Create config examples
|
### Create config examples
|
||||||
|
|
||||||
```
|
```
|
||||||
$ freqtrade new-config --config config_binance.json
|
$ freqtrade new-config --config user_data/config_binance.json
|
||||||
|
|
||||||
? Do you want to enable Dry-run (simulated trades)? Yes
|
? Do you want to enable Dry-run (simulated trades)? Yes
|
||||||
? Please insert your stake currency: BTC
|
? Please insert your stake currency: BTC
|
||||||
@@ -66,6 +66,53 @@ $ freqtrade new-config --config config_binance.json
|
|||||||
? Do you want to enable Telegram? No
|
? Do you want to enable Telegram? No
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Show config
|
||||||
|
|
||||||
|
Show configuration file (with sensitive values redacted by default).
|
||||||
|
Especially useful with [split configuration files](configuration.md#multiple-configuration-files) or [environment variables](configuration.md#environment-variables), where this command will show the merged configuration.
|
||||||
|
|
||||||
|

|
||||||
|
|
||||||
|
```
|
||||||
|
usage: freqtrade show-config [-h] [--userdir PATH] [-c PATH]
|
||||||
|
[--show-sensitive]
|
||||||
|
|
||||||
|
options:
|
||||||
|
-h, --help show this help message and exit
|
||||||
|
--userdir PATH, --user-data-dir PATH
|
||||||
|
Path to userdata directory.
|
||||||
|
-c PATH, --config PATH
|
||||||
|
Specify configuration file (default:
|
||||||
|
`userdir/config.json` or `config.json` whichever
|
||||||
|
exists). Multiple --config options may be used. Can be
|
||||||
|
set to `-` to read config from stdin.
|
||||||
|
--show-sensitive Show secrets in the output.
|
||||||
|
```
|
||||||
|
|
||||||
|
``` output
|
||||||
|
Your combined configuration is:
|
||||||
|
{
|
||||||
|
"exit_pricing": {
|
||||||
|
"price_side": "other",
|
||||||
|
"use_order_book": true,
|
||||||
|
"order_book_top": 1
|
||||||
|
},
|
||||||
|
"stake_currency": "USDT",
|
||||||
|
"exchange": {
|
||||||
|
"name": "binance",
|
||||||
|
"key": "REDACTED",
|
||||||
|
"secret": "REDACTED",
|
||||||
|
"ccxt_config": {},
|
||||||
|
"ccxt_async_config": {},
|
||||||
|
}
|
||||||
|
// ...
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
!!! Warning "Sharing information provided by this command"
|
||||||
|
We try to remove all known sensitive information from the default output (without `--show-sensitive`).
|
||||||
|
Yet, please do double-check for sensitive values in your output to make sure you're not accidentally exposing some private info.
|
||||||
|
|
||||||
## Create new strategy
|
## Create new strategy
|
||||||
|
|
||||||
Creates a new strategy from a template similar to SampleStrategy.
|
Creates a new strategy from a template similar to SampleStrategy.
|
||||||
@@ -219,207 +266,49 @@ optional arguments:
|
|||||||
-a, --all Print all exchanges known to the ccxt library.
|
-a, --all Print all exchanges known to the ccxt library.
|
||||||
```
|
```
|
||||||
|
|
||||||
* Example: see exchanges available for the bot:
|
Example: see exchanges available for the bot:
|
||||||
|
|
||||||
```
|
```
|
||||||
$ freqtrade list-exchanges
|
$ freqtrade list-exchanges
|
||||||
Exchanges available for Freqtrade:
|
Exchanges available for Freqtrade:
|
||||||
Exchange name Valid reason
|
Exchange name Supported Markets Reason
|
||||||
--------------- ------- --------------------------------------------
|
------------------ ----------- ---------------------- ------------------------------------------------------------------------
|
||||||
aax True
|
binance Official spot, isolated futures
|
||||||
ascendex True missing opt: fetchMyTrades
|
bitmart Official spot
|
||||||
bequant True
|
bybit spot, isolated futures
|
||||||
bibox True
|
gate Official spot, isolated futures
|
||||||
bigone True
|
htx Official spot
|
||||||
binance True
|
huobi spot
|
||||||
binanceus True
|
kraken Official spot
|
||||||
bitbank True missing opt: fetchTickers
|
okx Official spot, isolated futures
|
||||||
bitcoincom True
|
|
||||||
bitfinex True
|
|
||||||
bitforex True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
bitget True
|
|
||||||
bithumb True missing opt: fetchMyTrades
|
|
||||||
bitkk True missing opt: fetchMyTrades
|
|
||||||
bitmart True
|
|
||||||
bitmax True missing opt: fetchMyTrades
|
|
||||||
bitpanda True
|
|
||||||
bitvavo True
|
|
||||||
bitz True missing opt: fetchMyTrades
|
|
||||||
btcalpha True missing opt: fetchTicker, fetchTickers
|
|
||||||
btcmarkets True missing opt: fetchTickers
|
|
||||||
buda True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
bw True missing opt: fetchMyTrades, fetchL2OrderBook
|
|
||||||
bybit True
|
|
||||||
bytetrade True
|
|
||||||
cdax True
|
|
||||||
cex True missing opt: fetchMyTrades
|
|
||||||
coinbaseprime True missing opt: fetchTickers
|
|
||||||
coinbasepro True missing opt: fetchTickers
|
|
||||||
coinex True
|
|
||||||
crex24 True
|
|
||||||
deribit True
|
|
||||||
digifinex True
|
|
||||||
equos True missing opt: fetchTicker, fetchTickers
|
|
||||||
eterbase True
|
|
||||||
fcoin True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
gateio True
|
|
||||||
gemini True
|
|
||||||
gopax True
|
|
||||||
hbtc True
|
|
||||||
hitbtc True
|
|
||||||
huobijp True
|
|
||||||
huobipro True
|
|
||||||
idex True
|
|
||||||
kraken True
|
|
||||||
kucoin True
|
|
||||||
lbank True missing opt: fetchMyTrades
|
|
||||||
mercado True missing opt: fetchTickers
|
|
||||||
ndax True missing opt: fetchTickers
|
|
||||||
novadax True
|
|
||||||
okcoin True
|
|
||||||
okex True
|
|
||||||
probit True
|
|
||||||
qtrade True
|
|
||||||
stex True
|
|
||||||
timex True
|
|
||||||
upbit True missing opt: fetchMyTrades
|
|
||||||
vcc True
|
|
||||||
zb True missing opt: fetchMyTrades
|
|
||||||
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
Output reduced for clarity - supported and available exchanges may change over time.
|
||||||
|
|
||||||
!!! Note "missing opt exchanges"
|
!!! Note "missing opt exchanges"
|
||||||
Values with "missing opt:" might need special configuration (e.g. using orderbook if `fetchTickers` is missing) - but should in theory work (although we cannot guarantee they will).
|
Values with "missing opt:" might need special configuration (e.g. using orderbook if `fetchTickers` is missing) - but should in theory work (although we cannot guarantee they will).
|
||||||
|
|
||||||
* Example: see all exchanges supported by the ccxt library (including 'bad' ones, i.e. those that are known to not work with Freqtrade):
|
Example: see all exchanges supported by the ccxt library (including 'bad' ones, i.e. those that are known to not work with Freqtrade)
|
||||||
|
|
||||||
```
|
```
|
||||||
$ freqtrade list-exchanges -a
|
$ freqtrade list-exchanges -a
|
||||||
All exchanges supported by the ccxt library:
|
All exchanges supported by the ccxt library:
|
||||||
Exchange name Valid reason
|
Exchange name Valid Supported Markets Reason
|
||||||
------------------ ------- ---------------------------------------------------------------------------------------
|
------------------ ------- ----------- ---------------------- ---------------------------------------------------------------------------------
|
||||||
aax True
|
binance True Official spot, isolated futures
|
||||||
aofex False missing: fetchOrder
|
bitflyer False spot missing: fetchOrder. missing opt: fetchTickers.
|
||||||
ascendex True missing opt: fetchMyTrades
|
bitmart True Official spot
|
||||||
bequant True
|
bybit True spot, isolated futures
|
||||||
bibox True
|
gate True Official spot, isolated futures
|
||||||
bigone True
|
htx True Official spot
|
||||||
binance True
|
kraken True Official spot
|
||||||
binanceus True
|
okx True Official spot, isolated futures
|
||||||
bit2c False missing: fetchOrder, fetchOHLCV
|
|
||||||
bitbank True missing opt: fetchTickers
|
|
||||||
bitbay False missing: fetchOrder
|
|
||||||
bitcoincom True
|
|
||||||
bitfinex True
|
|
||||||
bitfinex2 False missing: fetchOrder
|
|
||||||
bitflyer False missing: fetchOrder, fetchOHLCV
|
|
||||||
bitforex True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
bitget True
|
|
||||||
bithumb True missing opt: fetchMyTrades
|
|
||||||
bitkk True missing opt: fetchMyTrades
|
|
||||||
bitmart True
|
|
||||||
bitmax True missing opt: fetchMyTrades
|
|
||||||
bitmex False Various reasons.
|
|
||||||
bitpanda True
|
|
||||||
bitso False missing: fetchOHLCV
|
|
||||||
bitstamp True missing opt: fetchTickers
|
|
||||||
bitstamp1 False missing: fetchOrder, fetchOHLCV
|
|
||||||
bitvavo True
|
|
||||||
bitz True missing opt: fetchMyTrades
|
|
||||||
bl3p False missing: fetchOrder, fetchOHLCV
|
|
||||||
bleutrade False missing: fetchOrder
|
|
||||||
braziliex False missing: fetchOHLCV
|
|
||||||
btcalpha True missing opt: fetchTicker, fetchTickers
|
|
||||||
btcbox False missing: fetchOHLCV
|
|
||||||
btcmarkets True missing opt: fetchTickers
|
|
||||||
btctradeua False missing: fetchOrder, fetchOHLCV
|
|
||||||
btcturk False missing: fetchOrder
|
|
||||||
buda True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
bw True missing opt: fetchMyTrades, fetchL2OrderBook
|
|
||||||
bybit True
|
|
||||||
bytetrade True
|
|
||||||
cdax True
|
|
||||||
cex True missing opt: fetchMyTrades
|
|
||||||
chilebit False missing: fetchOrder, fetchOHLCV
|
|
||||||
coinbase False missing: fetchOrder, cancelOrder, createOrder, fetchOHLCV
|
|
||||||
coinbaseprime True missing opt: fetchTickers
|
|
||||||
coinbasepro True missing opt: fetchTickers
|
|
||||||
coincheck False missing: fetchOrder, fetchOHLCV
|
|
||||||
coinegg False missing: fetchOHLCV
|
|
||||||
coinex True
|
|
||||||
coinfalcon False missing: fetchOHLCV
|
|
||||||
coinfloor False missing: fetchOrder, fetchOHLCV
|
|
||||||
coingi False missing: fetchOrder, fetchOHLCV
|
|
||||||
coinmarketcap False missing: fetchOrder, cancelOrder, createOrder, fetchBalance, fetchOHLCV
|
|
||||||
coinmate False missing: fetchOHLCV
|
|
||||||
coinone False missing: fetchOHLCV
|
|
||||||
coinspot False missing: fetchOrder, cancelOrder, fetchOHLCV
|
|
||||||
crex24 True
|
|
||||||
currencycom False missing: fetchOrder
|
|
||||||
delta False missing: fetchOrder
|
|
||||||
deribit True
|
|
||||||
digifinex True
|
|
||||||
equos True missing opt: fetchTicker, fetchTickers
|
|
||||||
eterbase True
|
|
||||||
exmo False missing: fetchOrder
|
|
||||||
exx False missing: fetchOHLCV
|
|
||||||
fcoin True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
fcoinjp True missing opt: fetchMyTrades, fetchTickers
|
|
||||||
flowbtc False missing: fetchOrder, fetchOHLCV
|
|
||||||
foxbit False missing: fetchOrder, fetchOHLCV
|
|
||||||
gateio True
|
|
||||||
gemini True
|
|
||||||
gopax True
|
|
||||||
hbtc True
|
|
||||||
hitbtc True
|
|
||||||
hollaex False missing: fetchOrder
|
|
||||||
huobijp True
|
|
||||||
huobipro True
|
|
||||||
idex True
|
|
||||||
independentreserve False missing: fetchOHLCV
|
|
||||||
indodax False missing: fetchOHLCV
|
|
||||||
itbit False missing: fetchOHLCV
|
|
||||||
kraken True
|
|
||||||
kucoin True
|
|
||||||
kuna False missing: fetchOHLCV
|
|
||||||
lakebtc False missing: fetchOrder, fetchOHLCV
|
|
||||||
latoken False missing: fetchOrder, fetchOHLCV
|
|
||||||
lbank True missing opt: fetchMyTrades
|
|
||||||
liquid False missing: fetchOHLCV
|
|
||||||
luno False missing: fetchOHLCV
|
|
||||||
lykke False missing: fetchOHLCV
|
|
||||||
mercado True missing opt: fetchTickers
|
|
||||||
mixcoins False missing: fetchOrder, fetchOHLCV
|
|
||||||
ndax True missing opt: fetchTickers
|
|
||||||
novadax True
|
|
||||||
oceanex False missing: fetchOHLCV
|
|
||||||
okcoin True
|
|
||||||
okex True
|
|
||||||
paymium False missing: fetchOrder, fetchOHLCV
|
|
||||||
phemex False Does not provide history.
|
|
||||||
poloniex False missing: fetchOrder
|
|
||||||
probit True
|
|
||||||
qtrade True
|
|
||||||
rightbtc False missing: fetchOrder
|
|
||||||
ripio False missing: fetchOHLCV
|
|
||||||
southxchange False missing: fetchOrder, fetchOHLCV
|
|
||||||
stex True
|
|
||||||
surbitcoin False missing: fetchOrder, fetchOHLCV
|
|
||||||
therock False missing: fetchOHLCV
|
|
||||||
tidebit False missing: fetchOrder
|
|
||||||
tidex False missing: fetchOHLCV
|
|
||||||
timex True
|
|
||||||
upbit True missing opt: fetchMyTrades
|
|
||||||
vbtc False missing: fetchOrder, fetchOHLCV
|
|
||||||
vcc True
|
|
||||||
wavesexchange False missing: fetchOrder
|
|
||||||
whitebit False missing: fetchOrder, cancelOrder, createOrder, fetchBalance
|
|
||||||
xbtce False missing: fetchOrder, fetchOHLCV
|
|
||||||
xena False missing: fetchOrder
|
|
||||||
yobit False missing: fetchOHLCV
|
|
||||||
zaif False missing: fetchOrder, fetchOHLCV
|
|
||||||
zb True missing opt: fetchMyTrades
|
|
||||||
```
|
```
|
||||||
|
|
||||||
|
!!! info ""
|
||||||
|
Reduced output - supported and available exchanges may change over time.
|
||||||
|
|
||||||
## List Timeframes
|
## List Timeframes
|
||||||
|
|
||||||
Use the `list-timeframes` subcommand to see the list of timeframes available for the exchange.
|
Use the `list-timeframes` subcommand to see the list of timeframes available for the exchange.
|
||||||
@@ -990,11 +879,7 @@ options:
|
|||||||
-h, --help show this help message and exit
|
-h, --help show this help message and exit
|
||||||
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
--strategy-list STRATEGY_LIST [STRATEGY_LIST ...]
|
||||||
Provide a space-separated list of strategies to
|
Provide a space-separated list of strategies to
|
||||||
backtest. Please note that timeframe needs to be set
|
be converted.
|
||||||
either in config or via command line. When using this
|
|
||||||
together with `--export trades`, the strategy-name is
|
|
||||||
injected into the filename (so `backtest-data.json`
|
|
||||||
becomes `backtest-data-SampleStrategy.json`
|
|
||||||
|
|
||||||
Common arguments:
|
Common arguments:
|
||||||
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
-v, --verbose Verbose mode (-vv for more, -vvv to get all messages).
|
||||||
|
|||||||
@@ -65,7 +65,7 @@ You can set the POST body format to Form-Encoded (default), JSON-Encoded, or raw
|
|||||||
|
|
||||||
The result would be a POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel.
|
The result would be a POST request with e.g. `{"text":"Status: running"}` body and `Content-Type: application/json` header which results `Status: running` message in the Mattermost channel.
|
||||||
|
|
||||||
When using the Form-Encoded or JSON-Encoded configuration you can configure any number of payload values, and both the key and value will be ouput in the POST request. However, when using the raw data format you can only configure one value and it **must** be named `"data"`. In this instance the data key will not be output in the POST request, only the value. For example:
|
When using the Form-Encoded or JSON-Encoded configuration you can configure any number of payload values, and both the key and value will be output in the POST request. However, when using the raw data format you can only configure one value and it **must** be named `"data"`. In this instance the data key will not be output in the POST request, only the value. For example:
|
||||||
|
|
||||||
```json
|
```json
|
||||||
"webhook": {
|
"webhook": {
|
||||||
@@ -134,6 +134,7 @@ Possible parameters are:
|
|||||||
* `stake_amount`
|
* `stake_amount`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `current_rate`
|
* `current_rate`
|
||||||
@@ -155,6 +156,7 @@ Possible parameters are:
|
|||||||
* `stake_amount`
|
* `stake_amount`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `current_rate`
|
* `current_rate`
|
||||||
@@ -176,6 +178,7 @@ Possible parameters are:
|
|||||||
* `stake_amount`
|
* `stake_amount`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
* `current_rate`
|
* `current_rate`
|
||||||
@@ -199,6 +202,7 @@ Possible parameters are:
|
|||||||
* `profit_ratio`
|
* `profit_ratio`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `exit_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
@@ -224,6 +228,7 @@ Possible parameters are:
|
|||||||
* `profit_ratio`
|
* `profit_ratio`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `exit_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
@@ -249,6 +254,7 @@ Possible parameters are:
|
|||||||
* `profit_ratio`
|
* `profit_ratio`
|
||||||
* `stake_currency`
|
* `stake_currency`
|
||||||
* `base_currency`
|
* `base_currency`
|
||||||
|
* `quote_currency`
|
||||||
* `fiat_currency`
|
* `fiat_currency`
|
||||||
* `exit_reason`
|
* `exit_reason`
|
||||||
* `order_type`
|
* `order_type`
|
||||||
|
|||||||
@@ -22,7 +22,7 @@ git clone https://github.com/freqtrade/freqtrade.git
|
|||||||
|
|
||||||
### 2. Install ta-lib
|
### 2. Install ta-lib
|
||||||
|
|
||||||
Install ta-lib according to the [ta-lib documentation](https://github.com/mrjbq7/ta-lib#windows).
|
Install ta-lib according to the [ta-lib documentation](https://github.com/TA-Lib/ta-lib-python#windows).
|
||||||
|
|
||||||
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10 and 3.11) and for 64bit Windows.
|
As compiling from source on windows has heavy dependencies (requires a partial visual studio installation), Freqtrade provides these dependencies (in the binary wheel format) for the latest 3 Python versions (3.9, 3.10 and 3.11) and for 64bit Windows.
|
||||||
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
|
These Wheels are also used by CI running on windows, and are therefore tested together with freqtrade.
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
""" Freqtrade bot """
|
""" Freqtrade bot """
|
||||||
__version__ = '2023.12'
|
__version__ = '2024.4'
|
||||||
|
|
||||||
if 'dev' in __version__:
|
if 'dev' in __version__:
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
|
|||||||
@@ -8,7 +8,7 @@ Note: Be careful with file-scoped imports in these subfiles.
|
|||||||
"""
|
"""
|
||||||
from freqtrade.commands.analyze_commands import start_analysis_entries_exits
|
from freqtrade.commands.analyze_commands import start_analysis_entries_exits
|
||||||
from freqtrade.commands.arguments import Arguments
|
from freqtrade.commands.arguments import Arguments
|
||||||
from freqtrade.commands.build_config_commands import start_new_config
|
from freqtrade.commands.build_config_commands import start_new_config, start_show_config
|
||||||
from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades,
|
from freqtrade.commands.data_commands import (start_convert_data, start_convert_trades,
|
||||||
start_download_data, start_list_data)
|
start_download_data, start_list_data)
|
||||||
from freqtrade.commands.db_commands import start_convert_db
|
from freqtrade.commands.db_commands import start_convert_db
|
||||||
|
|||||||
@@ -4,7 +4,7 @@ from typing import Any, Dict
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -34,9 +34,9 @@ def setup_analyze_configuration(args: Dict[str, Any], method: RunMode) -> Dict[s
|
|||||||
btfile = Path(config['exportfilename'])
|
btfile = Path(config['exportfilename'])
|
||||||
signals_file = f"{btfile.parent}/{btfile.stem}_signals.pkl"
|
signals_file = f"{btfile.parent}/{btfile.stem}_signals.pkl"
|
||||||
else:
|
else:
|
||||||
raise OperationalException(f"{config['exportfilename']} does not exist.")
|
raise ConfigurationError(f"{config['exportfilename']} does not exist.")
|
||||||
else:
|
else:
|
||||||
raise OperationalException('exportfilename not in config.')
|
raise ConfigurationError('exportfilename not in config.')
|
||||||
|
|
||||||
if (not Path(signals_file).exists()):
|
if (not Path(signals_file).exists()):
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
|
|||||||
@@ -62,6 +62,7 @@ ARGS_TEST_PAIRLIST = ["user_data_dir", "verbosity", "config", "quote_currencies"
|
|||||||
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
|
ARGS_CREATE_USERDIR = ["user_data_dir", "reset"]
|
||||||
|
|
||||||
ARGS_BUILD_CONFIG = ["config"]
|
ARGS_BUILD_CONFIG = ["config"]
|
||||||
|
ARGS_SHOW_CONFIG = ["user_data_dir", "config", "show_sensitive"]
|
||||||
|
|
||||||
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
ARGS_BUILD_STRATEGY = ["user_data_dir", "strategy", "template"]
|
||||||
|
|
||||||
@@ -69,7 +70,8 @@ ARGS_CONVERT_DATA_TRADES = ["pairs", "format_from_trades", "format_to", "erase",
|
|||||||
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
|
ARGS_CONVERT_DATA = ["pairs", "format_from", "format_to", "erase", "exchange"]
|
||||||
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode", "candle_types"]
|
ARGS_CONVERT_DATA_OHLCV = ARGS_CONVERT_DATA + ["timeframes", "trading_mode", "candle_types"]
|
||||||
|
|
||||||
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades"]
|
ARGS_CONVERT_TRADES = ["pairs", "timeframes", "exchange", "dataformat_ohlcv", "dataformat_trades",
|
||||||
|
"trading_mode"]
|
||||||
|
|
||||||
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
|
ARGS_LIST_DATA = ["exchange", "dataformat_ohlcv", "pairs", "trading_mode", "show_timerange"]
|
||||||
|
|
||||||
@@ -195,7 +197,10 @@ class Arguments:
|
|||||||
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
|
self._build_args(optionlist=ARGS_STRATEGY, parser=strategy_group)
|
||||||
|
|
||||||
# Build main command
|
# Build main command
|
||||||
self.parser = argparse.ArgumentParser(description='Free, open source crypto trading bot')
|
self.parser = argparse.ArgumentParser(
|
||||||
|
prog="freqtrade",
|
||||||
|
description='Free, open source crypto trading bot'
|
||||||
|
)
|
||||||
self._build_args(optionlist=['version'], parser=self.parser)
|
self._build_args(optionlist=['version'], parser=self.parser)
|
||||||
|
|
||||||
from freqtrade.commands import (start_analysis_entries_exits, start_backtesting,
|
from freqtrade.commands import (start_analysis_entries_exits, start_backtesting,
|
||||||
@@ -208,9 +213,9 @@ class Arguments:
|
|||||||
start_list_strategies, start_list_timeframes,
|
start_list_strategies, start_list_timeframes,
|
||||||
start_lookahead_analysis, start_new_config,
|
start_lookahead_analysis, start_new_config,
|
||||||
start_new_strategy, start_plot_dataframe, start_plot_profit,
|
start_new_strategy, start_plot_dataframe, start_plot_profit,
|
||||||
start_recursive_analysis, start_show_trades,
|
start_recursive_analysis, start_show_config,
|
||||||
start_strategy_update, start_test_pairlist, start_trading,
|
start_show_trades, start_strategy_update,
|
||||||
start_webserver)
|
start_test_pairlist, start_trading, start_webserver)
|
||||||
|
|
||||||
subparsers = self.parser.add_subparsers(dest='command',
|
subparsers = self.parser.add_subparsers(dest='command',
|
||||||
# Use custom message when no subhandler is added
|
# Use custom message when no subhandler is added
|
||||||
@@ -219,27 +224,43 @@ class Arguments:
|
|||||||
)
|
)
|
||||||
|
|
||||||
# Add trade subcommand
|
# Add trade subcommand
|
||||||
trade_cmd = subparsers.add_parser('trade', help='Trade module.',
|
trade_cmd = subparsers.add_parser(
|
||||||
parents=[_common_parser, _strategy_parser])
|
'trade',
|
||||||
|
help='Trade module.',
|
||||||
|
parents=[_common_parser, _strategy_parser]
|
||||||
|
)
|
||||||
trade_cmd.set_defaults(func=start_trading)
|
trade_cmd.set_defaults(func=start_trading)
|
||||||
self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd)
|
self._build_args(optionlist=ARGS_TRADE, parser=trade_cmd)
|
||||||
|
|
||||||
# add create-userdir subcommand
|
# add create-userdir subcommand
|
||||||
create_userdir_cmd = subparsers.add_parser('create-userdir',
|
create_userdir_cmd = subparsers.add_parser(
|
||||||
help="Create user-data directory.",
|
'create-userdir',
|
||||||
)
|
help="Create user-data directory.",
|
||||||
|
)
|
||||||
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
create_userdir_cmd.set_defaults(func=start_create_userdir)
|
||||||
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
self._build_args(optionlist=ARGS_CREATE_USERDIR, parser=create_userdir_cmd)
|
||||||
|
|
||||||
# add new-config subcommand
|
# add new-config subcommand
|
||||||
build_config_cmd = subparsers.add_parser('new-config',
|
build_config_cmd = subparsers.add_parser(
|
||||||
help="Create new config")
|
'new-config',
|
||||||
|
help="Create new config",
|
||||||
|
)
|
||||||
build_config_cmd.set_defaults(func=start_new_config)
|
build_config_cmd.set_defaults(func=start_new_config)
|
||||||
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
|
self._build_args(optionlist=ARGS_BUILD_CONFIG, parser=build_config_cmd)
|
||||||
|
|
||||||
|
# add show-config subcommand
|
||||||
|
show_config_cmd = subparsers.add_parser(
|
||||||
|
'show-config',
|
||||||
|
help="Show resolved config",
|
||||||
|
)
|
||||||
|
show_config_cmd.set_defaults(func=start_show_config)
|
||||||
|
self._build_args(optionlist=ARGS_SHOW_CONFIG, parser=show_config_cmd)
|
||||||
|
|
||||||
# add new-strategy subcommand
|
# add new-strategy subcommand
|
||||||
build_strategy_cmd = subparsers.add_parser('new-strategy',
|
build_strategy_cmd = subparsers.add_parser(
|
||||||
help="Create new strategy")
|
'new-strategy',
|
||||||
|
help="Create new strategy",
|
||||||
|
)
|
||||||
build_strategy_cmd.set_defaults(func=start_new_strategy)
|
build_strategy_cmd.set_defaults(func=start_new_strategy)
|
||||||
self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd)
|
self._build_args(optionlist=ARGS_BUILD_STRATEGY, parser=build_strategy_cmd)
|
||||||
|
|
||||||
@@ -289,8 +310,11 @@ class Arguments:
|
|||||||
self._build_args(optionlist=ARGS_LIST_DATA, parser=list_data_cmd)
|
self._build_args(optionlist=ARGS_LIST_DATA, parser=list_data_cmd)
|
||||||
|
|
||||||
# Add backtesting subcommand
|
# Add backtesting subcommand
|
||||||
backtesting_cmd = subparsers.add_parser('backtesting', help='Backtesting module.',
|
backtesting_cmd = subparsers.add_parser(
|
||||||
parents=[_common_parser, _strategy_parser])
|
'backtesting',
|
||||||
|
help='Backtesting module.',
|
||||||
|
parents=[_common_parser, _strategy_parser]
|
||||||
|
)
|
||||||
backtesting_cmd.set_defaults(func=start_backtesting)
|
backtesting_cmd.set_defaults(func=start_backtesting)
|
||||||
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
|
self._build_args(optionlist=ARGS_BACKTEST, parser=backtesting_cmd)
|
||||||
|
|
||||||
@@ -304,22 +328,29 @@ class Arguments:
|
|||||||
self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtesting_show_cmd)
|
self._build_args(optionlist=ARGS_BACKTEST_SHOW, parser=backtesting_show_cmd)
|
||||||
|
|
||||||
# Add backtesting analysis subcommand
|
# Add backtesting analysis subcommand
|
||||||
analysis_cmd = subparsers.add_parser('backtesting-analysis',
|
analysis_cmd = subparsers.add_parser(
|
||||||
help='Backtest Analysis module.',
|
'backtesting-analysis',
|
||||||
parents=[_common_parser])
|
help='Backtest Analysis module.',
|
||||||
|
parents=[_common_parser]
|
||||||
|
)
|
||||||
analysis_cmd.set_defaults(func=start_analysis_entries_exits)
|
analysis_cmd.set_defaults(func=start_analysis_entries_exits)
|
||||||
self._build_args(optionlist=ARGS_ANALYZE_ENTRIES_EXITS, parser=analysis_cmd)
|
self._build_args(optionlist=ARGS_ANALYZE_ENTRIES_EXITS, parser=analysis_cmd)
|
||||||
|
|
||||||
# Add edge subcommand
|
# Add edge subcommand
|
||||||
edge_cmd = subparsers.add_parser('edge', help='Edge module.',
|
edge_cmd = subparsers.add_parser(
|
||||||
parents=[_common_parser, _strategy_parser])
|
'edge',
|
||||||
|
help='Edge module.',
|
||||||
|
parents=[_common_parser, _strategy_parser]
|
||||||
|
)
|
||||||
edge_cmd.set_defaults(func=start_edge)
|
edge_cmd.set_defaults(func=start_edge)
|
||||||
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
|
self._build_args(optionlist=ARGS_EDGE, parser=edge_cmd)
|
||||||
|
|
||||||
# Add hyperopt subcommand
|
# Add hyperopt subcommand
|
||||||
hyperopt_cmd = subparsers.add_parser('hyperopt', help='Hyperopt module.',
|
hyperopt_cmd = subparsers.add_parser(
|
||||||
parents=[_common_parser, _strategy_parser],
|
'hyperopt',
|
||||||
)
|
help='Hyperopt module.',
|
||||||
|
parents=[_common_parser, _strategy_parser],
|
||||||
|
)
|
||||||
hyperopt_cmd.set_defaults(func=start_hyperopt)
|
hyperopt_cmd.set_defaults(func=start_hyperopt)
|
||||||
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
|
self._build_args(optionlist=ARGS_HYPEROPT, parser=hyperopt_cmd)
|
||||||
|
|
||||||
@@ -447,16 +478,20 @@ class Arguments:
|
|||||||
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
self._build_args(optionlist=ARGS_PLOT_PROFIT, parser=plot_profit_cmd)
|
||||||
|
|
||||||
# Add webserver subcommand
|
# Add webserver subcommand
|
||||||
webserver_cmd = subparsers.add_parser('webserver', help='Webserver module.',
|
webserver_cmd = subparsers.add_parser(
|
||||||
parents=[_common_parser])
|
'webserver',
|
||||||
|
help='Webserver module.',
|
||||||
|
parents=[_common_parser]
|
||||||
|
)
|
||||||
webserver_cmd.set_defaults(func=start_webserver)
|
webserver_cmd.set_defaults(func=start_webserver)
|
||||||
self._build_args(optionlist=ARGS_WEBSERVER, parser=webserver_cmd)
|
self._build_args(optionlist=ARGS_WEBSERVER, parser=webserver_cmd)
|
||||||
|
|
||||||
# Add strategy_updater subcommand
|
# Add strategy_updater subcommand
|
||||||
strategy_updater_cmd = subparsers.add_parser('strategy-updater',
|
strategy_updater_cmd = subparsers.add_parser(
|
||||||
help='updates outdated strategy'
|
'strategy-updater',
|
||||||
'files to the current version',
|
help='updates outdated strategy files to the current version',
|
||||||
parents=[_common_parser])
|
parents=[_common_parser]
|
||||||
|
)
|
||||||
strategy_updater_cmd.set_defaults(func=start_strategy_update)
|
strategy_updater_cmd.set_defaults(func=start_strategy_update)
|
||||||
self._build_args(optionlist=ARGS_STRATEGY_UPDATER, parser=strategy_updater_cmd)
|
self._build_args(optionlist=ARGS_STRATEGY_UPDATER, parser=strategy_updater_cmd)
|
||||||
|
|
||||||
@@ -464,8 +499,8 @@ class Arguments:
|
|||||||
lookahead_analayis_cmd = subparsers.add_parser(
|
lookahead_analayis_cmd = subparsers.add_parser(
|
||||||
'lookahead-analysis',
|
'lookahead-analysis',
|
||||||
help="Check for potential look ahead bias.",
|
help="Check for potential look ahead bias.",
|
||||||
parents=[_common_parser, _strategy_parser])
|
parents=[_common_parser, _strategy_parser]
|
||||||
|
)
|
||||||
lookahead_analayis_cmd.set_defaults(func=start_lookahead_analysis)
|
lookahead_analayis_cmd.set_defaults(func=start_lookahead_analysis)
|
||||||
|
|
||||||
self._build_args(optionlist=ARGS_LOOKAHEAD_ANALYSIS,
|
self._build_args(optionlist=ARGS_LOOKAHEAD_ANALYSIS,
|
||||||
@@ -475,8 +510,8 @@ class Arguments:
|
|||||||
recursive_analayis_cmd = subparsers.add_parser(
|
recursive_analayis_cmd = subparsers.add_parser(
|
||||||
'recursive-analysis',
|
'recursive-analysis',
|
||||||
help="Check for potential recursive formula issue.",
|
help="Check for potential recursive formula issue.",
|
||||||
parents=[_common_parser, _strategy_parser])
|
parents=[_common_parser, _strategy_parser]
|
||||||
|
)
|
||||||
recursive_analayis_cmd.set_defaults(func=start_recursive_analysis)
|
recursive_analayis_cmd.set_defaults(func=start_recursive_analysis)
|
||||||
|
|
||||||
self._build_args(optionlist=ARGS_RECURSIVE_ANALYSIS,
|
self._build_args(optionlist=ARGS_RECURSIVE_ANALYSIS,
|
||||||
|
|||||||
@@ -5,9 +5,12 @@ from typing import Any, Dict, List
|
|||||||
|
|
||||||
from questionary import Separator, prompt
|
from questionary import Separator, prompt
|
||||||
|
|
||||||
|
from freqtrade.configuration import sanitize_config
|
||||||
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.configuration.detect_environment import running_in_docker
|
from freqtrade.configuration.detect_environment import running_in_docker
|
||||||
from freqtrade.configuration.directory_operations import chown_user_directory
|
from freqtrade.configuration.directory_operations import chown_user_directory
|
||||||
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
from freqtrade.constants import UNLIMITED_STAKE_AMOUNT
|
||||||
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
|
from freqtrade.exchange import MAP_EXCHANGE_CHILDCLASS, available_exchanges
|
||||||
from freqtrade.util import render_template
|
from freqtrade.util import render_template
|
||||||
@@ -109,7 +112,7 @@ def ask_user_config() -> Dict[str, Any]:
|
|||||||
"binance",
|
"binance",
|
||||||
"binanceus",
|
"binanceus",
|
||||||
"gate",
|
"gate",
|
||||||
"huobi",
|
"htx",
|
||||||
"kraken",
|
"kraken",
|
||||||
"kucoin",
|
"kucoin",
|
||||||
"okx",
|
"okx",
|
||||||
@@ -264,3 +267,19 @@ def start_new_config(args: Dict[str, Any]) -> None:
|
|||||||
"Please delete it or use a different configuration file name.")
|
"Please delete it or use a different configuration file name.")
|
||||||
selections = ask_user_config()
|
selections = ask_user_config()
|
||||||
deploy_new_config(config_path, selections)
|
deploy_new_config(config_path, selections)
|
||||||
|
|
||||||
|
|
||||||
|
def start_show_config(args: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE, set_dry=False)
|
||||||
|
|
||||||
|
# TODO: Sanitize from sensitive info before printing
|
||||||
|
|
||||||
|
print("Your combined configuration is:")
|
||||||
|
config_sanitized = sanitize_config(
|
||||||
|
config['original_config'],
|
||||||
|
show_sensitive=args.get('show_sensitive', False)
|
||||||
|
)
|
||||||
|
|
||||||
|
from rich import print_json
|
||||||
|
print_json(data=config_sanitized)
|
||||||
|
|||||||
@@ -716,4 +716,10 @@ AVAILABLE_CLI_OPTIONS = {
|
|||||||
help='Specify startup candles to be checked (`199`, `499`, `999`, `1999`).',
|
help='Specify startup candles to be checked (`199`, `499`, `999`, `1999`).',
|
||||||
nargs='+',
|
nargs='+',
|
||||||
),
|
),
|
||||||
|
"show_sensitive": Arg(
|
||||||
|
'--show-sensitive',
|
||||||
|
help='Show secrets in the output.',
|
||||||
|
action='store_true',
|
||||||
|
default=False,
|
||||||
|
),
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,12 @@ from freqtrade.constants import DATETIME_PRINT_FORMAT, DL_DATA_TIMEFRAMES, Confi
|
|||||||
from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format,
|
from freqtrade.data.converter import (convert_ohlcv_format, convert_trades_format,
|
||||||
convert_trades_to_ohlcv)
|
convert_trades_to_ohlcv)
|
||||||
from freqtrade.data.history import download_data_main
|
from freqtrade.data.history import download_data_main
|
||||||
from freqtrade.enums import RunMode, TradingMode
|
from freqtrade.enums import CandleType, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_minutes
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
from freqtrade.util.binance_mig import migrate_binance_futures_data
|
from freqtrade.util.migrations import migrate_data
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -20,11 +21,11 @@ logger = logging.getLogger(__name__)
|
|||||||
|
|
||||||
def _check_data_config_download_sanity(config: Config) -> None:
|
def _check_data_config_download_sanity(config: Config) -> None:
|
||||||
if 'days' in config and 'timerange' in config:
|
if 'days' in config and 'timerange' in config:
|
||||||
raise OperationalException("--days and --timerange are mutually exclusive. "
|
raise ConfigurationError("--days and --timerange are mutually exclusive. "
|
||||||
"You can only specify one or the other.")
|
"You can only specify one or the other.")
|
||||||
|
|
||||||
if 'pairs' not in config:
|
if 'pairs' not in config:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Downloading data requires a list of pairs. "
|
"Downloading data requires a list of pairs. "
|
||||||
"Please check the documentation on how to configure this.")
|
"Please check the documentation on how to configure this.")
|
||||||
|
|
||||||
@@ -62,13 +63,21 @@ def start_convert_trades(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
for timeframe in config['timeframes']:
|
for timeframe in config['timeframes']:
|
||||||
exchange.validate_timeframes(timeframe)
|
exchange.validate_timeframes(timeframe)
|
||||||
|
available_pairs = [
|
||||||
|
p for p in exchange.get_markets(
|
||||||
|
tradable_only=True, active_only=not config.get('include_inactive')
|
||||||
|
).keys()
|
||||||
|
]
|
||||||
|
|
||||||
|
expanded_pairs = dynamic_expand_pairlist(config, available_pairs)
|
||||||
|
|
||||||
# Convert downloaded trade data to different timeframes
|
# Convert downloaded trade data to different timeframes
|
||||||
convert_trades_to_ohlcv(
|
convert_trades_to_ohlcv(
|
||||||
pairs=config.get('pairs', []), timeframes=config['timeframes'],
|
pairs=expanded_pairs, timeframes=config['timeframes'],
|
||||||
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
||||||
data_format_ohlcv=config['dataformat_ohlcv'],
|
data_format_ohlcv=config['dataformat_ohlcv'],
|
||||||
data_format_trades=config['dataformat_trades'],
|
data_format_trades=config['dataformat_trades'],
|
||||||
|
candle_type=config.get('candle_type_def', CandleType.SPOT)
|
||||||
)
|
)
|
||||||
|
|
||||||
|
|
||||||
@@ -78,7 +87,7 @@ def start_convert_data(args: Dict[str, Any], ohlcv: bool = True) -> None:
|
|||||||
"""
|
"""
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
if ohlcv:
|
if ohlcv:
|
||||||
migrate_binance_futures_data(config)
|
migrate_data(config)
|
||||||
convert_ohlcv_format(config,
|
convert_ohlcv_format(config,
|
||||||
convert_from=args['format_from'],
|
convert_from=args['format_from'],
|
||||||
convert_to=args['format_to'],
|
convert_to=args['format_to'],
|
||||||
@@ -98,7 +107,7 @@ def start_list_data(args: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
from tabulate import tabulate
|
from tabulate import tabulate
|
||||||
|
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history import get_datahandler
|
||||||
dhc = get_datahandler(config['datadir'], config['dataformat_ohlcv'])
|
dhc = get_datahandler(config['datadir'], config['dataformat_ohlcv'])
|
||||||
|
|
||||||
paircombs = dhc.ohlcv_get_available_data(
|
paircombs = dhc.ohlcv_get_available_data(
|
||||||
@@ -134,10 +143,10 @@ def start_list_data(args: Dict[str, Any]) -> None:
|
|||||||
print(tabulate([
|
print(tabulate([
|
||||||
(pair, timeframe, candle_type,
|
(pair, timeframe, candle_type,
|
||||||
start.strftime(DATETIME_PRINT_FORMAT),
|
start.strftime(DATETIME_PRINT_FORMAT),
|
||||||
end.strftime(DATETIME_PRINT_FORMAT))
|
end.strftime(DATETIME_PRINT_FORMAT), length)
|
||||||
for pair, timeframe, candle_type, start, end in sorted(
|
for pair, timeframe, candle_type, start, end, length in sorted(
|
||||||
paircombs1,
|
paircombs1,
|
||||||
key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]))
|
key=lambda x: (x[0], timeframe_to_minutes(x[1]), x[2]))
|
||||||
],
|
],
|
||||||
headers=("Pair", "Timeframe", "Type", 'From', 'To'),
|
headers=("Pair", "Timeframe", "Type", 'From', 'To', 'Candles'),
|
||||||
tablefmt='psql', stralign='right'))
|
tablefmt='psql', stralign='right'))
|
||||||
|
|||||||
@@ -9,13 +9,17 @@ from freqtrade.configuration import setup_utils_configuration
|
|||||||
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
from freqtrade.configuration.directory_operations import copy_sample_files, create_userdata_dir
|
||||||
from freqtrade.constants import USERPATH_STRATEGIES
|
from freqtrade.constants import USERPATH_STRATEGIES
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.util import render_template, render_template_with_fallback
|
from freqtrade.util import render_template, render_template_with_fallback
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
|
# Timeout for requests
|
||||||
|
req_timeout = 30
|
||||||
|
|
||||||
|
|
||||||
def start_create_userdir(args: Dict[str, Any]) -> None:
|
def start_create_userdir(args: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
Create "user_data" directory to contain user data strategies, hyperopt, ...)
|
Create "user_data" directory to contain user data strategies, hyperopt, ...)
|
||||||
@@ -89,7 +93,7 @@ def start_new_strategy(args: Dict[str, Any]) -> None:
|
|||||||
deploy_new_strategy(args['strategy'], new_path, args['template'])
|
deploy_new_strategy(args['strategy'], new_path, args['template'])
|
||||||
|
|
||||||
else:
|
else:
|
||||||
raise OperationalException("`new-strategy` requires --strategy to be set.")
|
raise ConfigurationError("`new-strategy` requires --strategy to be set.")
|
||||||
|
|
||||||
|
|
||||||
def clean_ui_subdir(directory: Path):
|
def clean_ui_subdir(directory: Path):
|
||||||
@@ -119,7 +123,7 @@ def download_and_install_ui(dest_folder: Path, dl_url: str, version: str):
|
|||||||
from zipfile import ZipFile
|
from zipfile import ZipFile
|
||||||
|
|
||||||
logger.info(f"Downloading {dl_url}")
|
logger.info(f"Downloading {dl_url}")
|
||||||
resp = requests.get(dl_url).content
|
resp = requests.get(dl_url, timeout=req_timeout).content
|
||||||
dest_folder.mkdir(parents=True, exist_ok=True)
|
dest_folder.mkdir(parents=True, exist_ok=True)
|
||||||
with ZipFile(BytesIO(resp)) as zf:
|
with ZipFile(BytesIO(resp)) as zf:
|
||||||
for fn in zf.filelist:
|
for fn in zf.filelist:
|
||||||
@@ -137,7 +141,7 @@ def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
|
|||||||
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
|
base_url = 'https://api.github.com/repos/freqtrade/frequi/'
|
||||||
# Get base UI Repo path
|
# Get base UI Repo path
|
||||||
|
|
||||||
resp = requests.get(f"{base_url}releases")
|
resp = requests.get(f"{base_url}releases", timeout=req_timeout)
|
||||||
resp.raise_for_status()
|
resp.raise_for_status()
|
||||||
r = resp.json()
|
r = resp.json()
|
||||||
|
|
||||||
@@ -158,7 +162,7 @@ def get_ui_download_url(version: Optional[str] = None) -> Tuple[str, str]:
|
|||||||
# URL not found - try assets url
|
# URL not found - try assets url
|
||||||
if not dl_url:
|
if not dl_url:
|
||||||
assets = r[0]['assets_url']
|
assets = r[0]['assets_url']
|
||||||
resp = requests.get(assets)
|
resp = requests.get(assets, timeout=req_timeout)
|
||||||
r = resp.json()
|
r = resp.json()
|
||||||
dl_url = r[0]['browser_download_url']
|
dl_url = r[0]['browser_download_url']
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ from tabulate import tabulate
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.exchange import list_available_exchanges, market_is_active
|
from freqtrade.exchange import list_available_exchanges, market_is_active
|
||||||
from freqtrade.misc import parse_db_uri_for_logging, plural
|
from freqtrade.misc import parse_db_uri_for_logging, plural
|
||||||
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
from freqtrade.resolvers import ExchangeResolver, StrategyResolver
|
||||||
@@ -246,7 +246,7 @@ def start_show_trades(args: Dict[str, Any]) -> None:
|
|||||||
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_NO_EXCHANGE)
|
||||||
|
|
||||||
if 'db_url' not in config:
|
if 'db_url' not in config:
|
||||||
raise OperationalException("--db-url is required for this command.")
|
raise ConfigurationError("--db-url is required for this command.")
|
||||||
|
|
||||||
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
logger.info(f'Using DB: "{parse_db_uri_for_logging(config["db_url"])}"')
|
||||||
init_db(config['db_url'])
|
init_db(config['db_url'])
|
||||||
|
|||||||
@@ -4,8 +4,8 @@ from typing import Any, Dict
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.misc import round_coin_value
|
from freqtrade.util import fmt_coin
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -29,9 +29,9 @@ def setup_optimize_configuration(args: Dict[str, Any], method: RunMode) -> Dict[
|
|||||||
# tradable_balance_ratio
|
# tradable_balance_ratio
|
||||||
if (config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT
|
if (config['stake_amount'] != constants.UNLIMITED_STAKE_AMOUNT
|
||||||
and config['stake_amount'] > wallet_size):
|
and config['stake_amount'] > wallet_size):
|
||||||
wallet = round_coin_value(wallet_size, config['stake_currency'])
|
wallet = fmt_coin(wallet_size, config['stake_currency'])
|
||||||
stake = round_coin_value(config['stake_amount'], config['stake_currency'])
|
stake = fmt_coin(config['stake_amount'], config['stake_currency'])
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Starting balance ({wallet}) is smaller than stake_amount {stake}. "
|
f"Starting balance ({wallet}) is smaller than stake_amount {stake}. "
|
||||||
f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`."
|
f"Wallet is calculated as `dry_run_wallet * tradable_balance_ratio`."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -15,6 +15,7 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
|
|||||||
"""
|
"""
|
||||||
Test Pairlist configuration
|
Test Pairlist configuration
|
||||||
"""
|
"""
|
||||||
|
from freqtrade.persistence import FtNoDBContext
|
||||||
from freqtrade.plugins.pairlistmanager import PairListManager
|
from freqtrade.plugins.pairlistmanager import PairListManager
|
||||||
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
config = setup_utils_configuration(args, RunMode.UTIL_EXCHANGE)
|
||||||
|
|
||||||
@@ -24,11 +25,12 @@ def start_test_pairlist(args: Dict[str, Any]) -> None:
|
|||||||
if not quote_currencies:
|
if not quote_currencies:
|
||||||
quote_currencies = [config.get('stake_currency')]
|
quote_currencies = [config.get('stake_currency')]
|
||||||
results = {}
|
results = {}
|
||||||
for curr in quote_currencies:
|
with FtNoDBContext():
|
||||||
config['stake_currency'] = curr
|
for curr in quote_currencies:
|
||||||
pairlists = PairListManager(exchange, config)
|
config['stake_currency'] = curr
|
||||||
pairlists.refresh_pairlist()
|
pairlists = PairListManager(exchange, config)
|
||||||
results[curr] = pairlists.whitelist
|
pairlists.refresh_pairlist()
|
||||||
|
results[curr] = pairlists.whitelist
|
||||||
|
|
||||||
for curr, pairlist in results.items():
|
for curr, pairlist in results.items():
|
||||||
if not args.get('print_one_column', False) and not args.get('list_pairs_print_json', False):
|
if not args.get('print_one_column', False) and not args.get('list_pairs_print_json', False):
|
||||||
|
|||||||
@@ -2,12 +2,12 @@ from typing import Any, Dict
|
|||||||
|
|
||||||
from freqtrade.configuration import setup_utils_configuration
|
from freqtrade.configuration import setup_utils_configuration
|
||||||
from freqtrade.enums import RunMode
|
from freqtrade.enums import RunMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
def validate_plot_args(args: Dict[str, Any]) -> None:
|
def validate_plot_args(args: Dict[str, Any]) -> None:
|
||||||
if not args.get('datadir') and not args.get('config'):
|
if not args.get('datadir') and not args.get('config'):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"You need to specify either `--datadir` or `--config` "
|
"You need to specify either `--datadir` or `--config` "
|
||||||
"for plot-profit and plot-dataframe.")
|
"for plot-profit and plot-dataframe.")
|
||||||
|
|
||||||
|
|||||||
@@ -23,11 +23,6 @@ def start_trading(args: Dict[str, Any]) -> int:
|
|||||||
signal.signal(signal.SIGTERM, term_handler)
|
signal.signal(signal.SIGTERM, term_handler)
|
||||||
worker = Worker(args)
|
worker = Worker(args)
|
||||||
worker.run()
|
worker.run()
|
||||||
except Exception as e:
|
|
||||||
logger.error(str(e))
|
|
||||||
logger.exception("Fatal exception!")
|
|
||||||
except (KeyboardInterrupt):
|
|
||||||
logger.info('SIGINT received, aborting ...')
|
|
||||||
finally:
|
finally:
|
||||||
if worker:
|
if worker:
|
||||||
logger.info("worker found ... calling exit")
|
logger.info("worker found ... calling exit")
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
|
||||||
|
from freqtrade.configuration.config_secrets import sanitize_config
|
||||||
from freqtrade.configuration.config_setup import setup_utils_configuration
|
from freqtrade.configuration.config_setup import setup_utils_configuration
|
||||||
from freqtrade.configuration.config_validation import validate_config_consistency
|
from freqtrade.configuration.config_validation import validate_config_consistency
|
||||||
from freqtrade.configuration.configuration import Configuration
|
from freqtrade.configuration.configuration import Configuration
|
||||||
|
|||||||
@@ -0,0 +1,36 @@
|
|||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
from freqtrade.constants import Config
|
||||||
|
|
||||||
|
|
||||||
|
def sanitize_config(config: Config, *, show_sensitive: bool = False) -> Config:
|
||||||
|
"""
|
||||||
|
Remove sensitive information from the config.
|
||||||
|
:param config: Configuration
|
||||||
|
:param show_sensitive: Show sensitive information
|
||||||
|
:return: Configuration
|
||||||
|
"""
|
||||||
|
if show_sensitive:
|
||||||
|
return config
|
||||||
|
keys_to_remove = [
|
||||||
|
"exchange.key",
|
||||||
|
"exchange.secret",
|
||||||
|
"exchange.password",
|
||||||
|
"exchange.uid",
|
||||||
|
"telegram.token",
|
||||||
|
"telegram.chat_id",
|
||||||
|
"discord.webhook_url",
|
||||||
|
"api_server.password",
|
||||||
|
]
|
||||||
|
config = deepcopy(config)
|
||||||
|
for key in keys_to_remove:
|
||||||
|
if '.' in key:
|
||||||
|
nested_keys = key.split('.')
|
||||||
|
nested_config = config
|
||||||
|
for nested_key in nested_keys[:-1]:
|
||||||
|
nested_config = nested_config.get(nested_key, {})
|
||||||
|
nested_config[nested_keys[-1]] = 'REDACTED'
|
||||||
|
else:
|
||||||
|
config[key] = 'REDACTED'
|
||||||
|
|
||||||
|
return config
|
||||||
@@ -10,7 +10,8 @@ from .configuration import Configuration
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str, Any]:
|
def setup_utils_configuration(
|
||||||
|
args: Dict[str, Any], method: RunMode, *, set_dry: bool = True) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Prepare the configuration for utils subcommands
|
Prepare the configuration for utils subcommands
|
||||||
:param args: Cli args from Arguments()
|
:param args: Cli args from Arguments()
|
||||||
@@ -21,7 +22,8 @@ def setup_utils_configuration(args: Dict[str, Any], method: RunMode) -> Dict[str
|
|||||||
config = configuration.get_config()
|
config = configuration.get_config()
|
||||||
|
|
||||||
# Ensure these modes are using Dry-run
|
# Ensure these modes are using Dry-run
|
||||||
config['dry_run'] = True
|
if set_dry:
|
||||||
|
config['dry_run'] = True
|
||||||
validate_config_consistency(config, preliminary=True)
|
validate_config_consistency(config, preliminary=True)
|
||||||
|
|
||||||
return config
|
return config
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from jsonschema.exceptions import ValidationError, best_match
|
|||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
|
from freqtrade.configuration.deprecated_settings import process_deprecated_setting
|
||||||
from freqtrade.enums import RunMode, TradingMode
|
from freqtrade.enums import RunMode, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -73,7 +73,7 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
Should be ran after loading both configuration and strategy,
|
Should be ran after loading both configuration and strategy,
|
||||||
since strategies can set certain configuration settings too.
|
since strategies can set certain configuration settings too.
|
||||||
:param conf: Config in JSON format
|
:param conf: Config in JSON format
|
||||||
:return: Returns None if everything is ok, otherwise throw an OperationalException
|
:return: Returns None if everything is ok, otherwise throw an ConfigurationError
|
||||||
"""
|
"""
|
||||||
|
|
||||||
# validating trailing stoploss
|
# validating trailing stoploss
|
||||||
@@ -98,12 +98,12 @@ def validate_config_consistency(conf: Dict[str, Any], *, preliminary: bool = Fal
|
|||||||
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
def _validate_unlimited_amount(conf: Dict[str, Any]) -> None:
|
||||||
"""
|
"""
|
||||||
If edge is disabled, either max_open_trades or stake_amount need to be set.
|
If edge is disabled, either max_open_trades or stake_amount need to be set.
|
||||||
:raise: OperationalException if config validation failed
|
:raise: ConfigurationError if config validation failed
|
||||||
"""
|
"""
|
||||||
if (not conf.get('edge', {}).get('enabled')
|
if (not conf.get('edge', {}).get('enabled')
|
||||||
and conf.get('max_open_trades') == float('inf')
|
and conf.get('max_open_trades') == float('inf')
|
||||||
and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
|
and conf.get('stake_amount') == constants.UNLIMITED_STAKE_AMOUNT):
|
||||||
raise OperationalException("`max_open_trades` and `stake_amount` cannot both be unlimited.")
|
raise ConfigurationError("`max_open_trades` and `stake_amount` cannot both be unlimited.")
|
||||||
|
|
||||||
|
|
||||||
def _validate_price_config(conf: Dict[str, Any]) -> None:
|
def _validate_price_config(conf: Dict[str, Any]) -> None:
|
||||||
@@ -113,18 +113,18 @@ def _validate_price_config(conf: Dict[str, Any]) -> None:
|
|||||||
# TODO: The below could be an enforced setting when using market orders
|
# TODO: The below could be an enforced setting when using market orders
|
||||||
if (conf.get('order_types', {}).get('entry') == 'market'
|
if (conf.get('order_types', {}).get('entry') == 'market'
|
||||||
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
and conf.get('entry_pricing', {}).get('price_side') not in ('ask', 'other')):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Market entry orders require entry_pricing.price_side = "other".')
|
'Market entry orders require entry_pricing.price_side = "other".')
|
||||||
|
|
||||||
if (conf.get('order_types', {}).get('exit') == 'market'
|
if (conf.get('order_types', {}).get('exit') == 'market'
|
||||||
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
and conf.get('exit_pricing', {}).get('price_side') not in ('bid', 'other')):
|
||||||
raise OperationalException('Market exit orders require exit_pricing.price_side = "other".')
|
raise ConfigurationError('Market exit orders require exit_pricing.price_side = "other".')
|
||||||
|
|
||||||
|
|
||||||
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
||||||
|
|
||||||
if conf.get('stoploss') == 0.0:
|
if conf.get('stoploss') == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
'The config stoploss needs to be different from 0 to avoid problems with sell orders.'
|
||||||
)
|
)
|
||||||
# Skip if trailing stoploss is not activated
|
# Skip if trailing stoploss is not activated
|
||||||
@@ -137,17 +137,17 @@ def _validate_trailing_stoploss(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if tsl_only_offset:
|
if tsl_only_offset:
|
||||||
if tsl_positive == 0.0:
|
if tsl_positive == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_only_offset_is_reached needs '
|
'The config trailing_only_offset_is_reached needs '
|
||||||
'trailing_stop_positive_offset to be more than 0 in your config.')
|
'trailing_stop_positive_offset to be more than 0 in your config.')
|
||||||
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
|
if tsl_positive > 0 and 0 < tsl_offset <= tsl_positive:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_stop_positive_offset needs '
|
'The config trailing_stop_positive_offset needs '
|
||||||
'to be greater than trailing_stop_positive in your config.')
|
'to be greater than trailing_stop_positive in your config.')
|
||||||
|
|
||||||
# Fetch again without default
|
# Fetch again without default
|
||||||
if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0:
|
if 'trailing_stop_positive' in conf and float(conf['trailing_stop_positive']) == 0.0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'The config trailing_stop_positive needs to be different from 0 '
|
'The config trailing_stop_positive needs to be different from 0 '
|
||||||
'to avoid problems with sell orders.'
|
'to avoid problems with sell orders.'
|
||||||
)
|
)
|
||||||
@@ -162,7 +162,7 @@ def _validate_edge(conf: Dict[str, Any]) -> None:
|
|||||||
return
|
return
|
||||||
|
|
||||||
if not conf.get('use_exit_signal', True):
|
if not conf.get('use_exit_signal', True):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
|
"Edge requires `use_exit_signal` to be True, otherwise no sells will happen."
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -178,7 +178,7 @@ def _validate_whitelist(conf: Dict[str, Any]) -> None:
|
|||||||
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
|
for pl in conf.get('pairlists', [{'method': 'StaticPairList'}]):
|
||||||
if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
|
if (isinstance(pl, dict) and pl.get('method') == 'StaticPairList'
|
||||||
and not conf.get('exchange', {}).get('pair_whitelist')):
|
and not conf.get('exchange', {}).get('pair_whitelist')):
|
||||||
raise OperationalException("StaticPairList requires pair_whitelist to be set.")
|
raise ConfigurationError("StaticPairList requires pair_whitelist to be set.")
|
||||||
|
|
||||||
|
|
||||||
def _validate_protections(conf: Dict[str, Any]) -> None:
|
def _validate_protections(conf: Dict[str, Any]) -> None:
|
||||||
@@ -188,13 +188,13 @@ def _validate_protections(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
for prot in conf.get('protections', []):
|
for prot in conf.get('protections', []):
|
||||||
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
|
if ('stop_duration' in prot and 'stop_duration_candles' in prot):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
"Protections must specify either `stop_duration` or `stop_duration_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
|
|
||||||
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
if ('lookback_period' in prot and 'lookback_period_candles' in prot):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
"Protections must specify either `lookback_period` or `lookback_period_candles`.\n"
|
||||||
f"Please fix the protection {prot.get('method')}"
|
f"Please fix the protection {prot.get('method')}"
|
||||||
)
|
)
|
||||||
@@ -206,7 +206,7 @@ def _validate_ask_orderbook(conf: Dict[str, Any]) -> None:
|
|||||||
ob_max = ask_strategy.get('order_book_max')
|
ob_max = ask_strategy.get('order_book_max')
|
||||||
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
if ob_min is not None and ob_max is not None and ask_strategy.get('use_order_book'):
|
||||||
if ob_min != ob_max:
|
if ob_min != ob_max:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
|
"Using order_book_max != order_book_min in exit_pricing is no longer supported."
|
||||||
"Please pick one value and use `order_book_top` in the future."
|
"Please pick one value and use `order_book_top` in the future."
|
||||||
)
|
)
|
||||||
@@ -234,7 +234,7 @@ def _validate_time_in_force(conf: Dict[str, Any]) -> None:
|
|||||||
time_in_force = conf.get('order_time_in_force', {})
|
time_in_force = conf.get('order_time_in_force', {})
|
||||||
if 'buy' in time_in_force or 'sell' in time_in_force:
|
if 'buy' in time_in_force or 'sell' in time_in_force:
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your time_in_force settings to use 'entry' and 'exit'.")
|
"Please migrate your time_in_force settings to use 'entry' and 'exit'.")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -255,7 +255,7 @@ def _validate_order_types(conf: Dict[str, Any]) -> None:
|
|||||||
'forcesell', 'emergencyexit', 'forceexit', 'forceentry']
|
'forcesell', 'emergencyexit', 'forceexit', 'forceentry']
|
||||||
if any(x in order_types for x in old_order_types):
|
if any(x in order_types for x in old_order_types):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your order_types settings to use the new wording.")
|
"Please migrate your order_types settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
logger.warning(
|
logger.warning(
|
||||||
@@ -280,7 +280,7 @@ def _validate_unfilledtimeout(conf: Dict[str, Any]) -> None:
|
|||||||
unfilledtimeout = conf.get('unfilledtimeout', {})
|
unfilledtimeout = conf.get('unfilledtimeout', {})
|
||||||
if any(x in unfilledtimeout for x in ['buy', 'sell']):
|
if any(x in unfilledtimeout for x in ['buy', 'sell']):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your unfilledtimeout settings to use the new wording.")
|
"Please migrate your unfilledtimeout settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -300,7 +300,7 @@ def _validate_pricing_rules(conf: Dict[str, Any]) -> None:
|
|||||||
|
|
||||||
if conf.get('ask_strategy') or conf.get('bid_strategy'):
|
if conf.get('ask_strategy') or conf.get('bid_strategy'):
|
||||||
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
if conf.get('trading_mode', TradingMode.SPOT) != TradingMode.SPOT:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"Please migrate your pricing settings to use the new wording.")
|
"Please migrate your pricing settings to use the new wording.")
|
||||||
else:
|
else:
|
||||||
|
|
||||||
@@ -331,7 +331,7 @@ def _validate_freqai_hyperopt(conf: Dict[str, Any]) -> None:
|
|||||||
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
|
freqai_enabled = conf.get('freqai', {}).get('enabled', False)
|
||||||
analyze_per_epoch = conf.get('analyze_per_epoch', False)
|
analyze_per_epoch = conf.get('analyze_per_epoch', False)
|
||||||
if analyze_per_epoch and freqai_enabled:
|
if analyze_per_epoch and freqai_enabled:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
|
'Using analyze-per-epoch parameter is not supported with a FreqAI strategy.')
|
||||||
|
|
||||||
|
|
||||||
@@ -350,7 +350,7 @@ def _validate_freqai_include_timeframes(conf: Dict[str, Any], preliminary: bool)
|
|||||||
if tf_s < main_tf_s:
|
if tf_s < main_tf_s:
|
||||||
offending_lines.append(tf)
|
offending_lines.append(tf)
|
||||||
if offending_lines:
|
if offending_lines:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
f"Main timeframe of {main_tf} must be smaller or equal to FreqAI "
|
||||||
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
f"`include_timeframes`.Offending include-timeframes: {', '.join(offending_lines)}")
|
||||||
|
|
||||||
@@ -368,17 +368,17 @@ def _validate_freqai_backtest(conf: Dict[str, Any]) -> None:
|
|||||||
timerange = conf.get('timerange')
|
timerange = conf.get('timerange')
|
||||||
freqai_backtest_live_models = conf.get('freqai_backtest_live_models', False)
|
freqai_backtest_live_models = conf.get('freqai_backtest_live_models', False)
|
||||||
if freqai_backtest_live_models and freqai_enabled and timerange:
|
if freqai_backtest_live_models and freqai_enabled and timerange:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using timerange parameter is not supported with '
|
'Using timerange parameter is not supported with '
|
||||||
'--freqai-backtest-live-models parameter.')
|
'--freqai-backtest-live-models parameter.')
|
||||||
|
|
||||||
if freqai_backtest_live_models and not freqai_enabled:
|
if freqai_backtest_live_models and not freqai_enabled:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Using --freqai-backtest-live-models parameter is only '
|
'Using --freqai-backtest-live-models parameter is only '
|
||||||
'supported with a FreqAI strategy.')
|
'supported with a FreqAI strategy.')
|
||||||
|
|
||||||
if freqai_enabled and not freqai_backtest_live_models and not timerange:
|
if freqai_enabled and not freqai_backtest_live_models and not timerange:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
'Please pass --timerange if you intend to use FreqAI for backtesting.')
|
'Please pass --timerange if you intend to use FreqAI for backtesting.')
|
||||||
|
|
||||||
|
|
||||||
@@ -386,12 +386,12 @@ def _validate_consumers(conf: Dict[str, Any]) -> None:
|
|||||||
emc_conf = conf.get('external_message_consumer', {})
|
emc_conf = conf.get('external_message_consumer', {})
|
||||||
if emc_conf.get('enabled', False):
|
if emc_conf.get('enabled', False):
|
||||||
if len(emc_conf.get('producers', [])) < 1:
|
if len(emc_conf.get('producers', [])) < 1:
|
||||||
raise OperationalException("You must specify at least 1 Producer to connect to.")
|
raise ConfigurationError("You must specify at least 1 Producer to connect to.")
|
||||||
|
|
||||||
producer_names = [p['name'] for p in emc_conf.get('producers', [])]
|
producer_names = [p['name'] for p in emc_conf.get('producers', [])]
|
||||||
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
|
duplicates = [item for item, count in Counter(producer_names).items() if count > 1]
|
||||||
if duplicates:
|
if duplicates:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
|
f"Producer names must be unique. Duplicate: {', '.join(duplicates)}")
|
||||||
if conf.get('process_only_new_candles', True):
|
if conf.get('process_only_new_candles', True):
|
||||||
# Warning here or require it?
|
# Warning here or require it?
|
||||||
|
|||||||
@@ -5,7 +5,7 @@ import logging
|
|||||||
import warnings
|
import warnings
|
||||||
from copy import deepcopy
|
from copy import deepcopy
|
||||||
from pathlib import Path
|
from pathlib import Path
|
||||||
from typing import Any, Callable, Dict, List, Optional
|
from typing import Any, Callable, Dict, List, Optional, Tuple
|
||||||
|
|
||||||
from freqtrade import constants
|
from freqtrade import constants
|
||||||
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
|
from freqtrade.configuration.deprecated_settings import process_temporary_deprecated_settings
|
||||||
@@ -13,7 +13,7 @@ from freqtrade.configuration.directory_operations import create_datadir, create_
|
|||||||
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
|
from freqtrade.configuration.environment_vars import enironment_vars_to_dict
|
||||||
from freqtrade.configuration.load_config import load_file, load_from_files
|
from freqtrade.configuration.load_config import load_file, load_from_files
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.enums import NON_UTIL_MODES, TRADING_MODES, CandleType, RunMode, TradingMode
|
from freqtrade.enums import NON_UTIL_MODES, TRADE_MODES, CandleType, RunMode, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.loggers import setup_logging
|
from freqtrade.loggers import setup_logging
|
||||||
from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging
|
from freqtrade.misc import deep_merge_dicts, parse_db_uri_for_logging
|
||||||
@@ -68,8 +68,10 @@ class Configuration:
|
|||||||
config: Config = load_from_files(self.args.get("config", []))
|
config: Config = load_from_files(self.args.get("config", []))
|
||||||
|
|
||||||
# Load environment variables
|
# Load environment variables
|
||||||
env_data = enironment_vars_to_dict()
|
from freqtrade.commands.arguments import NO_CONF_ALLOWED
|
||||||
config = deep_merge_dicts(env_data, config)
|
if self.args.get('command') not in NO_CONF_ALLOWED:
|
||||||
|
env_data = enironment_vars_to_dict()
|
||||||
|
config = deep_merge_dicts(env_data, config)
|
||||||
|
|
||||||
# Normalize config
|
# Normalize config
|
||||||
if 'internals' not in config:
|
if 'internals' not in config:
|
||||||
@@ -125,7 +127,7 @@ class Configuration:
|
|||||||
setup_logging(config)
|
setup_logging(config)
|
||||||
|
|
||||||
def _process_trading_options(self, config: Config) -> None:
|
def _process_trading_options(self, config: Config) -> None:
|
||||||
if config['runmode'] not in TRADING_MODES:
|
if config['runmode'] not in TRADE_MODES:
|
||||||
return
|
return
|
||||||
|
|
||||||
if config.get('dry_run', False):
|
if config.get('dry_run', False):
|
||||||
@@ -198,6 +200,12 @@ class Configuration:
|
|||||||
config['exportfilename'] = (config['user_data_dir']
|
config['exportfilename'] = (config['user_data_dir']
|
||||||
/ 'backtest_results')
|
/ 'backtest_results')
|
||||||
|
|
||||||
|
if self.args.get('show_sensitive'):
|
||||||
|
logger.warning(
|
||||||
|
"Sensitive information will be shown in the upcoming output. "
|
||||||
|
"Please make sure to never share this output without redacting "
|
||||||
|
"the information yourself.")
|
||||||
|
|
||||||
def _process_optimize_options(self, config: Config) -> None:
|
def _process_optimize_options(self, config: Config) -> None:
|
||||||
|
|
||||||
# This will override the strategy configuration
|
# This will override the strategy configuration
|
||||||
@@ -233,54 +241,37 @@ class Configuration:
|
|||||||
except ValueError:
|
except ValueError:
|
||||||
pass
|
pass
|
||||||
|
|
||||||
self._args_to_config(config, argname='timeframe_detail',
|
configurations = [
|
||||||
logstring='Parameter --timeframe-detail detected, '
|
('timeframe_detail',
|
||||||
'using {} for intra-candle backtesting ...')
|
'Parameter --timeframe-detail detected, using {} for intra-candle backtesting ...'),
|
||||||
|
('backtest_show_pair_list', 'Parameter --show-pair-list detected.'),
|
||||||
|
('stake_amount',
|
||||||
|
'Parameter --stake-amount detected, overriding stake_amount to: {} ...'),
|
||||||
|
('dry_run_wallet',
|
||||||
|
'Parameter --dry-run-wallet detected, overriding dry_run_wallet to: {} ...'),
|
||||||
|
('fee', 'Parameter --fee detected, setting fee to: {} ...'),
|
||||||
|
('timerange', 'Parameter --timerange detected: {} ...'),
|
||||||
|
]
|
||||||
|
|
||||||
self._args_to_config(config, argname='backtest_show_pair_list',
|
self._args_to_config_loop(config, configurations)
|
||||||
logstring='Parameter --show-pair-list detected.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='stake_amount',
|
|
||||||
logstring='Parameter --stake-amount detected, '
|
|
||||||
'overriding stake_amount to: {} ...')
|
|
||||||
self._args_to_config(config, argname='dry_run_wallet',
|
|
||||||
logstring='Parameter --dry-run-wallet detected, '
|
|
||||||
'overriding dry_run_wallet to: {} ...')
|
|
||||||
self._args_to_config(config, argname='fee',
|
|
||||||
logstring='Parameter --fee detected, '
|
|
||||||
'setting fee to: {} ...')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='timerange',
|
|
||||||
logstring='Parameter --timerange detected: {} ...')
|
|
||||||
|
|
||||||
self._process_datadir_options(config)
|
self._process_datadir_options(config)
|
||||||
|
|
||||||
self._args_to_config(config, argname='strategy_list',
|
self._args_to_config(config, argname='strategy_list',
|
||||||
logstring='Using strategy list of {} strategies', logfun=len)
|
logstring='Using strategy list of {} strategies', logfun=len)
|
||||||
|
|
||||||
self._args_to_config(
|
configurations = [
|
||||||
config,
|
('recursive_strategy_search',
|
||||||
argname='recursive_strategy_search',
|
'Recursively searching for a strategy in the strategies folder.'),
|
||||||
logstring='Recursively searching for a strategy in the strategies folder.',
|
('timeframe', 'Overriding timeframe with Command line argument'),
|
||||||
)
|
('export', 'Parameter --export detected: {} ...'),
|
||||||
|
('backtest_breakdown', 'Parameter --breakdown detected ...'),
|
||||||
self._args_to_config(config, argname='timeframe',
|
('backtest_cache', 'Parameter --cache={} detected ...'),
|
||||||
logstring='Overriding timeframe with Command line argument')
|
('disableparamexport', 'Parameter --disableparamexport detected: {} ...'),
|
||||||
|
('freqai_backtest_live_models',
|
||||||
self._args_to_config(config, argname='export',
|
'Parameter --freqai-backtest-live-models detected ...'),
|
||||||
logstring='Parameter --export detected: {} ...')
|
]
|
||||||
|
self._args_to_config_loop(config, configurations)
|
||||||
self._args_to_config(config, argname='backtest_breakdown',
|
|
||||||
logstring='Parameter --breakdown detected ...')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='backtest_cache',
|
|
||||||
logstring='Parameter --cache={} detected ...')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='disableparamexport',
|
|
||||||
logstring='Parameter --disableparamexport detected: {} ...')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='freqai_backtest_live_models',
|
|
||||||
logstring='Parameter --freqai-backtest-live-models detected ...')
|
|
||||||
|
|
||||||
# Edge section:
|
# Edge section:
|
||||||
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
if 'stoploss_range' in self.args and self.args["stoploss_range"]:
|
||||||
@@ -291,31 +282,18 @@ class Configuration:
|
|||||||
logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"])
|
logger.info('Parameter --stoplosses detected: %s ...', self.args["stoploss_range"])
|
||||||
|
|
||||||
# Hyperopt section
|
# Hyperopt section
|
||||||
self._args_to_config(config, argname='hyperopt',
|
|
||||||
logstring='Using Hyperopt class name: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_path',
|
configurations = [
|
||||||
logstring='Using additional Hyperopt lookup path: {}')
|
('hyperopt', 'Using Hyperopt class name: {}'),
|
||||||
|
('hyperopt_path', 'Using additional Hyperopt lookup path: {}'),
|
||||||
self._args_to_config(config, argname='hyperoptexportfilename',
|
('hyperoptexportfilename', 'Using hyperopt file: {}'),
|
||||||
logstring='Using hyperopt file: {}')
|
('lookahead_analysis_exportfilename', 'Saving lookahead analysis results into {} ...'),
|
||||||
|
('epochs', 'Parameter --epochs detected ... Will run Hyperopt with for {} epochs ...'),
|
||||||
self._args_to_config(config, argname='lookahead_analysis_exportfilename',
|
('spaces', 'Parameter -s/--spaces detected: {}'),
|
||||||
logstring='Saving lookahead analysis results into {} ...')
|
('analyze_per_epoch', 'Parameter --analyze-per-epoch detected.'),
|
||||||
|
('print_all', 'Parameter --print-all detected ...'),
|
||||||
self._args_to_config(config, argname='epochs',
|
]
|
||||||
logstring='Parameter --epochs detected ... '
|
self._args_to_config_loop(config, configurations)
|
||||||
'Will run Hyperopt with for {} epochs ...'
|
|
||||||
)
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='spaces',
|
|
||||||
logstring='Parameter -s/--spaces detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='analyze_per_epoch',
|
|
||||||
logstring='Parameter --analyze-per-epoch detected.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='print_all',
|
|
||||||
logstring='Parameter --print-all detected ...')
|
|
||||||
|
|
||||||
if 'print_colorized' in self.args and not self.args["print_colorized"]:
|
if 'print_colorized' in self.args and not self.args["print_colorized"]:
|
||||||
logger.info('Parameter --no-color detected ...')
|
logger.info('Parameter --no-color detected ...')
|
||||||
@@ -323,123 +301,55 @@ class Configuration:
|
|||||||
else:
|
else:
|
||||||
config.update({'print_colorized': True})
|
config.update({'print_colorized': True})
|
||||||
|
|
||||||
self._args_to_config(config, argname='print_json',
|
configurations = [
|
||||||
logstring='Parameter --print-json detected ...')
|
('print_json', 'Parameter --print-json detected ...'),
|
||||||
|
('export_csv', 'Parameter --export-csv detected: {}'),
|
||||||
|
('hyperopt_jobs', 'Parameter -j/--job-workers detected: {}'),
|
||||||
|
('hyperopt_random_state', 'Parameter --random-state detected: {}'),
|
||||||
|
('hyperopt_min_trades', 'Parameter --min-trades detected: {}'),
|
||||||
|
('hyperopt_loss', 'Using Hyperopt loss class name: {}'),
|
||||||
|
('hyperopt_show_index', 'Parameter -n/--index detected: {}'),
|
||||||
|
('hyperopt_list_best', 'Parameter --best detected: {}'),
|
||||||
|
('hyperopt_list_profitable', 'Parameter --profitable detected: {}'),
|
||||||
|
('hyperopt_list_min_trades', 'Parameter --min-trades detected: {}'),
|
||||||
|
('hyperopt_list_max_trades', 'Parameter --max-trades detected: {}'),
|
||||||
|
('hyperopt_list_min_avg_time', 'Parameter --min-avg-time detected: {}'),
|
||||||
|
('hyperopt_list_max_avg_time', 'Parameter --max-avg-time detected: {}'),
|
||||||
|
('hyperopt_list_min_avg_profit', 'Parameter --min-avg-profit detected: {}'),
|
||||||
|
('hyperopt_list_max_avg_profit', 'Parameter --max-avg-profit detected: {}'),
|
||||||
|
('hyperopt_list_min_total_profit', 'Parameter --min-total-profit detected: {}'),
|
||||||
|
('hyperopt_list_max_total_profit', 'Parameter --max-total-profit detected: {}'),
|
||||||
|
('hyperopt_list_min_objective', 'Parameter --min-objective detected: {}'),
|
||||||
|
('hyperopt_list_max_objective', 'Parameter --max-objective detected: {}'),
|
||||||
|
('hyperopt_list_no_details', 'Parameter --no-details detected: {}'),
|
||||||
|
('hyperopt_show_no_header', 'Parameter --no-header detected: {}'),
|
||||||
|
('hyperopt_ignore_missing_space', 'Paramter --ignore-missing-space detected: {}'),
|
||||||
|
]
|
||||||
|
|
||||||
self._args_to_config(config, argname='export_csv',
|
self._args_to_config_loop(config, configurations)
|
||||||
logstring='Parameter --export-csv detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_jobs',
|
|
||||||
logstring='Parameter -j/--job-workers detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_random_state',
|
|
||||||
logstring='Parameter --random-state detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_min_trades',
|
|
||||||
logstring='Parameter --min-trades detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_loss',
|
|
||||||
logstring='Using Hyperopt loss class name: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_show_index',
|
|
||||||
logstring='Parameter -n/--index detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_best',
|
|
||||||
logstring='Parameter --best detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_profitable',
|
|
||||||
logstring='Parameter --profitable detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_min_trades',
|
|
||||||
logstring='Parameter --min-trades detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_max_trades',
|
|
||||||
logstring='Parameter --max-trades detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_min_avg_time',
|
|
||||||
logstring='Parameter --min-avg-time detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_max_avg_time',
|
|
||||||
logstring='Parameter --max-avg-time detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_min_avg_profit',
|
|
||||||
logstring='Parameter --min-avg-profit detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_max_avg_profit',
|
|
||||||
logstring='Parameter --max-avg-profit detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_min_total_profit',
|
|
||||||
logstring='Parameter --min-total-profit detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_max_total_profit',
|
|
||||||
logstring='Parameter --max-total-profit detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_min_objective',
|
|
||||||
logstring='Parameter --min-objective detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_max_objective',
|
|
||||||
logstring='Parameter --max-objective detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_list_no_details',
|
|
||||||
logstring='Parameter --no-details detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='hyperopt_show_no_header',
|
|
||||||
logstring='Parameter --no-header detected: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname="hyperopt_ignore_missing_space",
|
|
||||||
logstring="Paramter --ignore-missing-space detected: {}")
|
|
||||||
|
|
||||||
def _process_plot_options(self, config: Config) -> None:
|
def _process_plot_options(self, config: Config) -> None:
|
||||||
|
|
||||||
self._args_to_config(config, argname='pairs',
|
configurations = [
|
||||||
logstring='Using pairs {}')
|
('pairs', 'Using pairs {}'),
|
||||||
|
('indicators1', 'Using indicators1: {}'),
|
||||||
self._args_to_config(config, argname='indicators1',
|
('indicators2', 'Using indicators2: {}'),
|
||||||
logstring='Using indicators1: {}')
|
('trade_ids', 'Filtering on trade_ids: {}'),
|
||||||
|
('plot_limit', 'Limiting plot to: {}'),
|
||||||
self._args_to_config(config, argname='indicators2',
|
('plot_auto_open', 'Parameter --auto-open detected.'),
|
||||||
logstring='Using indicators2: {}')
|
('trade_source', 'Using trades from: {}'),
|
||||||
|
('prepend_data', 'Prepend detected. Allowing data prepending.'),
|
||||||
self._args_to_config(config, argname='trade_ids',
|
('erase', 'Erase detected. Deleting existing data.'),
|
||||||
logstring='Filtering on trade_ids: {}')
|
('no_trades', 'Parameter --no-trades detected.'),
|
||||||
|
('timeframes', 'timeframes --timeframes: {}'),
|
||||||
self._args_to_config(config, argname='plot_limit',
|
('days', 'Detected --days: {}'),
|
||||||
logstring='Limiting plot to: {}')
|
('include_inactive', 'Detected --include-inactive-pairs: {}'),
|
||||||
|
('download_trades', 'Detected --dl-trades: {}'),
|
||||||
self._args_to_config(config, argname='plot_auto_open',
|
('dataformat_ohlcv', 'Using "{}" to store OHLCV data.'),
|
||||||
logstring='Parameter --auto-open detected.')
|
('dataformat_trades', 'Using "{}" to store trades data.'),
|
||||||
|
('show_timerange', 'Detected --show-timerange'),
|
||||||
self._args_to_config(config, argname='trade_source',
|
]
|
||||||
logstring='Using trades from: {}')
|
self._args_to_config_loop(config, configurations)
|
||||||
|
|
||||||
self._args_to_config(config, argname='prepend_data',
|
|
||||||
logstring='Prepend detected. Allowing data prepending.')
|
|
||||||
self._args_to_config(config, argname='erase',
|
|
||||||
logstring='Erase detected. Deleting existing data.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='no_trades',
|
|
||||||
logstring='Parameter --no-trades detected.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='timeframes',
|
|
||||||
logstring='timeframes --timeframes: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='days',
|
|
||||||
logstring='Detected --days: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='include_inactive',
|
|
||||||
logstring='Detected --include-inactive-pairs: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='download_trades',
|
|
||||||
logstring='Detected --dl-trades: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='dataformat_ohlcv',
|
|
||||||
logstring='Using "{}" to store OHLCV data.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='dataformat_trades',
|
|
||||||
logstring='Using "{}" to store trades data.')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='show_timerange',
|
|
||||||
logstring='Detected --show-timerange')
|
|
||||||
|
|
||||||
def _process_data_options(self, config: Config) -> None:
|
def _process_data_options(self, config: Config) -> None:
|
||||||
self._args_to_config(config, argname='new_pairs_days',
|
self._args_to_config(config, argname='new_pairs_days',
|
||||||
@@ -453,45 +363,27 @@ class Configuration:
|
|||||||
logstring='Detected --candle-types: {}')
|
logstring='Detected --candle-types: {}')
|
||||||
|
|
||||||
def _process_analyze_options(self, config: Config) -> None:
|
def _process_analyze_options(self, config: Config) -> None:
|
||||||
self._args_to_config(config, argname='analysis_groups',
|
configurations = [
|
||||||
logstring='Analysis reason groups: {}')
|
('analysis_groups', 'Analysis reason groups: {}'),
|
||||||
|
('enter_reason_list', 'Analysis enter tag list: {}'),
|
||||||
|
('exit_reason_list', 'Analysis exit tag list: {}'),
|
||||||
|
('indicator_list', 'Analysis indicator list: {}'),
|
||||||
|
('timerange', 'Filter trades by timerange: {}'),
|
||||||
|
('analysis_rejected', 'Analyse rejected signals: {}'),
|
||||||
|
('analysis_to_csv', 'Store analysis tables to CSV: {}'),
|
||||||
|
('analysis_csv_path', 'Path to store analysis CSVs: {}'),
|
||||||
|
# Lookahead analysis results
|
||||||
|
('targeted_trade_amount', 'Targeted Trade amount: {}'),
|
||||||
|
('minimum_trade_amount', 'Minimum Trade amount: {}'),
|
||||||
|
('lookahead_analysis_exportfilename', 'Path to store lookahead-analysis-results: {}'),
|
||||||
|
('startup_candle', 'Startup candle to be used on recursive analysis: {}'),
|
||||||
|
]
|
||||||
|
self._args_to_config_loop(config, configurations)
|
||||||
|
|
||||||
self._args_to_config(config, argname='enter_reason_list',
|
def _args_to_config_loop(self, config, configurations: List[Tuple[str, str]]) -> None:
|
||||||
logstring='Analysis enter tag list: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='exit_reason_list',
|
for argname, logstring in configurations:
|
||||||
logstring='Analysis exit tag list: {}')
|
self._args_to_config(config, argname=argname, logstring=logstring)
|
||||||
|
|
||||||
self._args_to_config(config, argname='indicator_list',
|
|
||||||
logstring='Analysis indicator list: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='timerange',
|
|
||||||
logstring='Filter trades by timerange: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='analysis_rejected',
|
|
||||||
logstring='Analyse rejected signals: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='analysis_to_csv',
|
|
||||||
logstring='Store analysis tables to CSV: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='analysis_csv_path',
|
|
||||||
logstring='Path to store analysis CSVs: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='analysis_csv_path',
|
|
||||||
logstring='Path to store analysis CSVs: {}')
|
|
||||||
|
|
||||||
# Lookahead analysis results
|
|
||||||
self._args_to_config(config, argname='targeted_trade_amount',
|
|
||||||
logstring='Targeted Trade amount: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='minimum_trade_amount',
|
|
||||||
logstring='Minimum Trade amount: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='lookahead_analysis_exportfilename',
|
|
||||||
logstring='Path to store lookahead-analysis-results: {}')
|
|
||||||
|
|
||||||
self._args_to_config(config, argname='startup_candle',
|
|
||||||
logstring='Startup candle to be used on recursive analysis: {}')
|
|
||||||
|
|
||||||
def _process_runmode(self, config: Config) -> None:
|
def _process_runmode(self, config: Config) -> None:
|
||||||
|
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ import logging
|
|||||||
from typing import Optional
|
from typing import Optional
|
||||||
|
|
||||||
from freqtrade.constants import Config
|
from freqtrade.constants import Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -41,7 +41,7 @@ def process_removed_setting(config: Config,
|
|||||||
section1_config = config.get(section1, {})
|
section1_config = config.get(section1, {})
|
||||||
if name1 in section1_config:
|
if name1 in section1_config:
|
||||||
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
|
section_2 = f"{section2}.{name2}" if section2 else f"{name2}"
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
|
f"Setting `{section1}.{name1}` has been moved to `{section_2}. "
|
||||||
f"Please delete it from your configuration and use the `{section_2}` "
|
f"Please delete it from your configuration and use the `{section_2}` "
|
||||||
"setting instead."
|
"setting instead."
|
||||||
@@ -122,7 +122,7 @@ def process_temporary_deprecated_settings(config: Config) -> None:
|
|||||||
None, 'ignore_roi_if_entry_signal')
|
None, 'ignore_roi_if_entry_signal')
|
||||||
if (config.get('edge', {}).get('enabled', False)
|
if (config.get('edge', {}).get('enabled', False)
|
||||||
and 'capital_available_percentage' in config.get('edge', {})):
|
and 'capital_available_percentage' in config.get('edge', {})):
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"DEPRECATED: "
|
"DEPRECATED: "
|
||||||
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
"Using 'edge.capital_available_percentage' has been deprecated in favor of "
|
||||||
"'tradable_balance_ratio'. Please migrate your configuration to "
|
"'tradable_balance_ratio'. Please migrate your configuration to "
|
||||||
@@ -131,7 +131,7 @@ def process_temporary_deprecated_settings(config: Config) -> None:
|
|||||||
)
|
)
|
||||||
if 'ticker_interval' in config:
|
if 'ticker_interval' in config:
|
||||||
|
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"DEPRECATED: 'ticker_interval' detected. "
|
"DEPRECATED: 'ticker_interval' detected. "
|
||||||
"Please use 'timeframe' instead of 'ticker_interval."
|
"Please use 'timeframe' instead of 'ticker_interval."
|
||||||
)
|
)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from freqtrade.misc import deep_merge_dicts
|
|||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
|
|
||||||
|
|
||||||
def get_var_typed(val):
|
def _get_var_typed(val):
|
||||||
try:
|
try:
|
||||||
return int(val)
|
return int(val)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
@@ -24,7 +24,7 @@ def get_var_typed(val):
|
|||||||
return val
|
return val
|
||||||
|
|
||||||
|
|
||||||
def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]:
|
def _flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
Environment variables must be prefixed with FREQTRADE.
|
Environment variables must be prefixed with FREQTRADE.
|
||||||
FREQTRADE__{section}__{key}
|
FREQTRADE__{section}__{key}
|
||||||
@@ -40,7 +40,7 @@ def flat_vars_to_nested_dict(env_dict: Dict[str, Any], prefix: str) -> Dict[str,
|
|||||||
logger.info(f"Loading variable '{env_var}'")
|
logger.info(f"Loading variable '{env_var}'")
|
||||||
key = env_var.replace(prefix, '')
|
key = env_var.replace(prefix, '')
|
||||||
for k in reversed(key.split('__')):
|
for k in reversed(key.split('__')):
|
||||||
val = {k.lower(): get_var_typed(val)
|
val = {k.lower(): _get_var_typed(val)
|
||||||
if not isinstance(val, dict) and k not in no_convert else val}
|
if not isinstance(val, dict) and k not in no_convert else val}
|
||||||
relevant_vars = deep_merge_dicts(val, relevant_vars)
|
relevant_vars = deep_merge_dicts(val, relevant_vars)
|
||||||
return relevant_vars
|
return relevant_vars
|
||||||
@@ -52,4 +52,4 @@ def enironment_vars_to_dict() -> Dict[str, Any]:
|
|||||||
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
Relevant variables must follow the FREQTRADE__{section}__{key} pattern
|
||||||
:return: Nested dict based on available and relevant variables.
|
:return: Nested dict based on available and relevant variables.
|
||||||
"""
|
"""
|
||||||
return flat_vars_to_nested_dict(os.environ.copy(), ENV_VAR_PREFIX)
|
return _flat_vars_to_nested_dict(os.environ.copy(), ENV_VAR_PREFIX)
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from typing import Any, Dict, List, Optional
|
|||||||
import rapidjson
|
import rapidjson
|
||||||
|
|
||||||
from freqtrade.constants import MINIMAL_CONFIG, Config
|
from freqtrade.constants import MINIMAL_CONFIG, Config
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.misc import deep_merge_dicts
|
from freqtrade.misc import deep_merge_dicts
|
||||||
|
|
||||||
|
|
||||||
@@ -46,7 +46,7 @@ def load_file(path: Path) -> Dict[str, Any]:
|
|||||||
with path.open('r') as file:
|
with path.open('r') as file:
|
||||||
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
|
config = rapidjson.load(file, parse_mode=CONFIG_PARSE_MODE)
|
||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise OperationalException(f'File "{path}" not found!')
|
raise OperationalException(f'File "{path}" not found!') from None
|
||||||
return config
|
return config
|
||||||
|
|
||||||
|
|
||||||
@@ -63,10 +63,10 @@ def load_config_file(path: str) -> Dict[str, Any]:
|
|||||||
except FileNotFoundError:
|
except FileNotFoundError:
|
||||||
raise OperationalException(
|
raise OperationalException(
|
||||||
f'Config file "{path}" not found!'
|
f'Config file "{path}" not found!'
|
||||||
' Please create a config file or check whether it exists.')
|
' Please create a config file or check whether it exists.') from None
|
||||||
except rapidjson.JSONDecodeError as e:
|
except rapidjson.JSONDecodeError as e:
|
||||||
err_range = log_config_error_range(path, str(e))
|
err_range = log_config_error_range(path, str(e))
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'{e}\n'
|
f'{e}\n'
|
||||||
f'Please verify the following segment of your configuration:\n{err_range}'
|
f'Please verify the following segment of your configuration:\n{err_range}'
|
||||||
if err_range else 'Please verify your configuration file for syntax errors.'
|
if err_range else 'Please verify your configuration file for syntax errors.'
|
||||||
@@ -83,7 +83,7 @@ def load_from_files(
|
|||||||
"""
|
"""
|
||||||
config: Config = {}
|
config: Config = {}
|
||||||
if level > 5:
|
if level > 5:
|
||||||
raise OperationalException("Config loop detected.")
|
raise ConfigurationError("Config loop detected.")
|
||||||
|
|
||||||
if not files:
|
if not files:
|
||||||
return deepcopy(MINIMAL_CONFIG)
|
return deepcopy(MINIMAL_CONFIG)
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from typing import Optional
|
|||||||
from typing_extensions import Self
|
from typing_extensions import Self
|
||||||
|
|
||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
from freqtrade.constants import DATETIME_PRINT_FORMAT
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -156,7 +156,7 @@ class TimeRange:
|
|||||||
else:
|
else:
|
||||||
stop = int(stops)
|
stop = int(stops)
|
||||||
if start > stop > 0:
|
if start > stop > 0:
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
f'Start date is after stop date for timerange "{text}"')
|
f'Start date is after stop date for timerange "{text}"')
|
||||||
return cls(stype[0], stype[1], start, stop)
|
return cls(stype[0], stype[1], start, stop)
|
||||||
raise OperationalException(f'Incorrect syntax for timerange "{text}"')
|
raise ConfigurationError(f'Incorrect syntax for timerange "{text}"')
|
||||||
|
|||||||
@@ -33,9 +33,10 @@ HYPEROPT_LOSS_BUILTIN = ['ShortTradeDurHyperOptLoss', 'OnlyProfitHyperOptLoss',
|
|||||||
'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss',
|
'MaxDrawDownHyperOptLoss', 'MaxDrawDownRelativeHyperOptLoss',
|
||||||
'ProfitDrawDownHyperOptLoss']
|
'ProfitDrawDownHyperOptLoss']
|
||||||
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', 'RemotePairList',
|
AVAILABLE_PAIRLISTS = ['StaticPairList', 'VolumePairList', 'ProducerPairList', 'RemotePairList',
|
||||||
'AgeFilter', "FullTradesFilter", 'OffsetFilter', 'PerformanceFilter',
|
'MarketCapPairList', 'AgeFilter', "FullTradesFilter", 'OffsetFilter',
|
||||||
'PrecisionFilter', 'PriceFilter', 'RangeStabilityFilter',
|
'PerformanceFilter', 'PrecisionFilter', 'PriceFilter',
|
||||||
'ShuffleFilter', 'SpreadFilter', 'VolatilityFilter']
|
'RangeStabilityFilter', 'ShuffleFilter', 'SpreadFilter',
|
||||||
|
'VolatilityFilter']
|
||||||
AVAILABLE_PROTECTIONS = ['CooldownPeriod',
|
AVAILABLE_PROTECTIONS = ['CooldownPeriod',
|
||||||
'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
'LowProfitPairs', 'MaxDrawdown', 'StoplossGuard']
|
||||||
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5', 'feather', 'parquet']
|
AVAILABLE_DATAHANDLERS = ['json', 'jsongz', 'hdf5', 'feather', 'parquet']
|
||||||
@@ -105,7 +106,7 @@ SUPPORTED_FIAT = [
|
|||||||
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
|
"EUR", "GBP", "HKD", "HUF", "IDR", "ILS", "INR", "JPY",
|
||||||
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
|
"KRW", "MXN", "MYR", "NOK", "NZD", "PHP", "PKR", "PLN",
|
||||||
"RUB", "UAH", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR",
|
"RUB", "UAH", "SEK", "SGD", "THB", "TRY", "TWD", "ZAR",
|
||||||
"USD", "BTC", "ETH", "XRP", "LTC", "BCH"
|
"USD", "BTC", "ETH", "XRP", "LTC", "BCH", "BNB"
|
||||||
]
|
]
|
||||||
|
|
||||||
MINIMAL_CONFIG = {
|
MINIMAL_CONFIG = {
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ import numpy as np
|
|||||||
import pandas as pd
|
import pandas as pd
|
||||||
|
|
||||||
from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf
|
from freqtrade.constants import LAST_BT_RESULT_FN, IntOrInf
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import ConfigurationError, OperationalException
|
||||||
from freqtrade.misc import file_dump_json, json_load
|
from freqtrade.misc import file_dump_json, json_load
|
||||||
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
|
from freqtrade.optimize.backtest_caching import get_backtest_metadata_filename
|
||||||
from freqtrade.persistence import LocalTrade, Trade, init_db
|
from freqtrade.persistence import LocalTrade, Trade, init_db
|
||||||
@@ -106,7 +106,7 @@ def get_latest_hyperopt_file(
|
|||||||
directory = Path(directory)
|
directory = Path(directory)
|
||||||
if predef_filename:
|
if predef_filename:
|
||||||
if Path(predef_filename).is_absolute():
|
if Path(predef_filename).is_absolute():
|
||||||
raise OperationalException(
|
raise ConfigurationError(
|
||||||
"--hyperopt-filename expects only the filename, not an absolute path.")
|
"--hyperopt-filename expects only the filename, not an absolute path.")
|
||||||
return directory / predef_filename
|
return directory / predef_filename
|
||||||
return directory / get_latest_hyperopt_filename(directory)
|
return directory / get_latest_hyperopt_filename(directory)
|
||||||
@@ -175,36 +175,40 @@ def _get_backtest_files(dirname: Path) -> List[Path]:
|
|||||||
return list(reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))))
|
return list(reversed(sorted(dirname.glob('backtest-result-*-[0-9][0-9].json'))))
|
||||||
|
|
||||||
|
|
||||||
def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
def _extract_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
||||||
"""
|
metadata = load_backtest_metadata(filename)
|
||||||
Get backtest result read from metadata file
|
|
||||||
"""
|
|
||||||
return [
|
return [
|
||||||
{
|
{
|
||||||
'filename': filename.stem,
|
'filename': filename.stem,
|
||||||
'strategy': s,
|
'strategy': s,
|
||||||
'notes': v.get('notes', ''),
|
|
||||||
'run_id': v['run_id'],
|
'run_id': v['run_id'],
|
||||||
|
'notes': v.get('notes', ''),
|
||||||
|
# Backtest "run" time
|
||||||
'backtest_start_time': v['backtest_start_time'],
|
'backtest_start_time': v['backtest_start_time'],
|
||||||
} for s, v in load_backtest_metadata(filename).items()
|
# Backtest timerange
|
||||||
|
'backtest_start_ts': v.get('backtest_start_ts', None),
|
||||||
|
'backtest_end_ts': v.get('backtest_end_ts', None),
|
||||||
|
'timeframe': v.get('timeframe', None),
|
||||||
|
'timeframe_detail': v.get('timeframe_detail', None),
|
||||||
|
} for s, v in metadata.items()
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
def get_backtest_result(filename: Path) -> List[BacktestHistoryEntryType]:
|
||||||
|
"""
|
||||||
|
Get backtest result read from metadata file
|
||||||
|
"""
|
||||||
|
return _extract_backtest_result(filename)
|
||||||
|
|
||||||
|
|
||||||
def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]:
|
def get_backtest_resultlist(dirname: Path) -> List[BacktestHistoryEntryType]:
|
||||||
"""
|
"""
|
||||||
Get list of backtest results read from metadata files
|
Get list of backtest results read from metadata files
|
||||||
"""
|
"""
|
||||||
return [
|
return [
|
||||||
{
|
result
|
||||||
'filename': filename.stem,
|
|
||||||
'strategy': s,
|
|
||||||
'run_id': v['run_id'],
|
|
||||||
'notes': v.get('notes', ''),
|
|
||||||
'backtest_start_time': v['backtest_start_time'],
|
|
||||||
}
|
|
||||||
for filename in _get_backtest_files(dirname)
|
for filename in _get_backtest_files(dirname)
|
||||||
for s, v in load_backtest_metadata(filename).items()
|
for result in _extract_backtest_result(filename)
|
||||||
if v
|
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
@@ -234,6 +238,16 @@ def update_backtest_metadata(filename: Path, strategy: str, content: Dict[str, A
|
|||||||
file_dump_json(get_backtest_metadata_filename(filename), metadata)
|
file_dump_json(get_backtest_metadata_filename(filename), metadata)
|
||||||
|
|
||||||
|
|
||||||
|
def get_backtest_market_change(filename: Path, include_ts: bool = True) -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Read backtest market change file.
|
||||||
|
"""
|
||||||
|
df = pd.read_feather(filename)
|
||||||
|
if include_ts:
|
||||||
|
df.loc[:, '__date_ts'] = df.loc[:, 'date'].astype(np.int64) // 1000 // 1000
|
||||||
|
return df
|
||||||
|
|
||||||
|
|
||||||
def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str],
|
def find_existing_backtest_stats(dirname: Union[Path, str], run_ids: Dict[str, str],
|
||||||
min_backtest_date: Optional[datetime] = None) -> Dict[str, Any]:
|
min_backtest_date: Optional[datetime] = None) -> Dict[str, Any]:
|
||||||
"""
|
"""
|
||||||
@@ -353,10 +367,10 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
|
|||||||
:param timeframe: Timeframe used for backtest
|
:param timeframe: Timeframe used for backtest
|
||||||
:return: dataframe with open-counts per time-period in timeframe
|
:return: dataframe with open-counts per time-period in timeframe
|
||||||
"""
|
"""
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
timeframe_min = timeframe_to_minutes(timeframe)
|
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
||||||
dates = [pd.Series(pd.date_range(row[1]['open_date'], row[1]['close_date'],
|
dates = [pd.Series(pd.date_range(row[1]['open_date'], row[1]['close_date'],
|
||||||
freq=f"{timeframe_min}min"))
|
freq=timeframe_freq))
|
||||||
for row in results[['open_date', 'close_date']].iterrows()]
|
for row in results[['open_date', 'close_date']].iterrows()]
|
||||||
deltas = [len(x) for x in dates]
|
deltas = [len(x) for x in dates]
|
||||||
dates = pd.Series(pd.concat(dates).values, name='date')
|
dates = pd.Series(pd.concat(dates).values, name='date')
|
||||||
@@ -364,7 +378,7 @@ def analyze_trade_parallelism(results: pd.DataFrame, timeframe: str) -> pd.DataF
|
|||||||
|
|
||||||
df2 = pd.concat([dates, df2], axis=1)
|
df2 = pd.concat([dates, df2], axis=1)
|
||||||
df2 = df2.set_index('date')
|
df2 = df2.set_index('date')
|
||||||
df_final = df2.resample(f"{timeframe_min}min")[['pair']].count()
|
df_final = df2.resample(timeframe_freq)[['pair']].count()
|
||||||
df_final = df_final.rename({'pair': 'open_trades'}, axis=1)
|
df_final = df_final.rename({'pair': 'open_trades'}, axis=1)
|
||||||
return df_final
|
return df_final
|
||||||
|
|
||||||
|
|||||||
@@ -84,7 +84,7 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
|
|||||||
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
using the previous close as price for "open", "high" "low" and "close", volume is set to 0
|
||||||
|
|
||||||
"""
|
"""
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
|
|
||||||
ohlcv_dict = {
|
ohlcv_dict = {
|
||||||
'open': 'first',
|
'open': 'first',
|
||||||
@@ -93,13 +93,7 @@ def ohlcv_fill_up_missing_data(dataframe: DataFrame, timeframe: str, pair: str)
|
|||||||
'close': 'last',
|
'close': 'last',
|
||||||
'volume': 'sum'
|
'volume': 'sum'
|
||||||
}
|
}
|
||||||
timeframe_minutes = timeframe_to_minutes(timeframe)
|
resample_interval = timeframe_to_resample_freq(timeframe)
|
||||||
resample_interval = f'{timeframe_minutes}min'
|
|
||||||
if timeframe_minutes >= 43200 and timeframe_minutes < 525600:
|
|
||||||
# Monthly candles need special treatment to stick to the 1st of the month
|
|
||||||
resample_interval = f'{timeframe}S'
|
|
||||||
elif timeframe_minutes > 43200:
|
|
||||||
resample_interval = timeframe
|
|
||||||
# Resample to create "NAN" values
|
# Resample to create "NAN" values
|
||||||
df = dataframe.resample(resample_interval, on='date').agg(ohlcv_dict)
|
df = dataframe.resample(resample_interval, on='date').agg(ohlcv_dict)
|
||||||
|
|
||||||
@@ -206,7 +200,7 @@ def convert_ohlcv_format(
|
|||||||
:param convert_to: Target format
|
:param convert_to: Target format
|
||||||
:param erase: Erase source data (does not apply if source and target format are identical)
|
:param erase: Erase source data (does not apply if source and target format are identical)
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
trg = get_datahandler(config['datadir'], convert_to)
|
trg = get_datahandler(config['datadir'], convert_to)
|
||||||
timeframes = config.get('timeframes', [config.get('timeframe')])
|
timeframes = config.get('timeframes', [config.get('timeframe')])
|
||||||
|
|||||||
@@ -11,7 +11,7 @@ from pandas import DataFrame, to_datetime
|
|||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TRADES_DTYPES,
|
from freqtrade.constants import (DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TRADES_DTYPES,
|
||||||
Config, TradeList)
|
Config, TradeList)
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
|
||||||
|
|
||||||
@@ -70,14 +70,13 @@ def trades_to_ohlcv(trades: DataFrame, timeframe: str) -> DataFrame:
|
|||||||
:return: OHLCV Dataframe.
|
:return: OHLCV Dataframe.
|
||||||
:raises: ValueError if no trades are provided
|
:raises: ValueError if no trades are provided
|
||||||
"""
|
"""
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
timeframe_minutes = timeframe_to_minutes(timeframe)
|
|
||||||
if trades.empty:
|
if trades.empty:
|
||||||
raise ValueError('Trade-list empty.')
|
raise ValueError('Trade-list empty.')
|
||||||
df = trades.set_index('date', drop=True)
|
df = trades.set_index('date', drop=True)
|
||||||
|
resample_interval = timeframe_to_resample_freq(timeframe)
|
||||||
df_new = df['price'].resample(f'{timeframe_minutes}min').ohlc()
|
df_new = df['price'].resample(resample_interval).ohlc()
|
||||||
df_new['volume'] = df['amount'].resample(f'{timeframe_minutes}min').sum()
|
df_new['volume'] = df['amount'].resample(resample_interval).sum()
|
||||||
df_new['date'] = df_new.index
|
df_new['date'] = df_new.index
|
||||||
# Drop 0 volume rows
|
# Drop 0 volume rows
|
||||||
df_new = df_new.dropna()
|
df_new = df_new.dropna()
|
||||||
@@ -89,25 +88,23 @@ def convert_trades_to_ohlcv(
|
|||||||
timeframes: List[str],
|
timeframes: List[str],
|
||||||
datadir: Path,
|
datadir: Path,
|
||||||
timerange: TimeRange,
|
timerange: TimeRange,
|
||||||
erase: bool = False,
|
erase: bool,
|
||||||
data_format_ohlcv: str = 'feather',
|
data_format_ohlcv: str,
|
||||||
data_format_trades: str = 'feather',
|
data_format_trades: str,
|
||||||
candle_type: CandleType = CandleType.SPOT
|
candle_type: CandleType,
|
||||||
) -> None:
|
) -> None:
|
||||||
"""
|
"""
|
||||||
Convert stored trades data to ohlcv data
|
Convert stored trades data to ohlcv data
|
||||||
"""
|
"""
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history import get_datahandler
|
||||||
data_handler_trades = get_datahandler(datadir, data_format=data_format_trades)
|
data_handler_trades = get_datahandler(datadir, data_format=data_format_trades)
|
||||||
data_handler_ohlcv = get_datahandler(datadir, data_format=data_format_ohlcv)
|
data_handler_ohlcv = get_datahandler(datadir, data_format=data_format_ohlcv)
|
||||||
if not pairs:
|
|
||||||
pairs = data_handler_trades.trades_get_pairs(datadir)
|
|
||||||
|
|
||||||
logger.info(f"About to convert pairs: '{', '.join(pairs)}', "
|
logger.info(f"About to convert pairs: '{', '.join(pairs)}', "
|
||||||
f"intervals: '{', '.join(timeframes)}' to {datadir}")
|
f"intervals: '{', '.join(timeframes)}' to {datadir}")
|
||||||
|
trading_mode = TradingMode.FUTURES if candle_type != CandleType.SPOT else TradingMode.SPOT
|
||||||
for pair in pairs:
|
for pair in pairs:
|
||||||
trades = data_handler_trades.trades_load(pair)
|
trades = data_handler_trades.trades_load(pair, trading_mode)
|
||||||
for timeframe in timeframes:
|
for timeframe in timeframes:
|
||||||
if erase:
|
if erase:
|
||||||
if data_handler_ohlcv.ohlcv_purge(pair, timeframe, candle_type=candle_type):
|
if data_handler_ohlcv.ohlcv_purge(pair, timeframe, candle_type=candle_type):
|
||||||
@@ -117,7 +114,7 @@ def convert_trades_to_ohlcv(
|
|||||||
# Store ohlcv
|
# Store ohlcv
|
||||||
data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv, candle_type=candle_type)
|
data_handler_ohlcv.ohlcv_store(pair, timeframe, data=ohlcv, candle_type=candle_type)
|
||||||
except ValueError:
|
except ValueError:
|
||||||
logger.exception(f'Could not convert {pair} to OHLCV.')
|
logger.warning(f'Could not convert {pair} to OHLCV.')
|
||||||
|
|
||||||
|
|
||||||
def convert_trades_format(config: Config, convert_from: str, convert_to: str, erase: bool):
|
def convert_trades_format(config: Config, convert_from: str, convert_to: str, erase: bool):
|
||||||
@@ -138,18 +135,19 @@ def convert_trades_format(config: Config, convert_from: str, convert_to: str, er
|
|||||||
import_kraken_trades_from_csv(config, convert_to)
|
import_kraken_trades_from_csv(config, convert_to)
|
||||||
return
|
return
|
||||||
|
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history import get_datahandler
|
||||||
src = get_datahandler(config['datadir'], convert_from)
|
src = get_datahandler(config['datadir'], convert_from)
|
||||||
trg = get_datahandler(config['datadir'], convert_to)
|
trg = get_datahandler(config['datadir'], convert_to)
|
||||||
|
|
||||||
if 'pairs' not in config:
|
if 'pairs' not in config:
|
||||||
config['pairs'] = src.trades_get_pairs(config['datadir'])
|
config['pairs'] = src.trades_get_pairs(config['datadir'])
|
||||||
logger.info(f"Converting trades for {config['pairs']}")
|
logger.info(f"Converting trades for {config['pairs']}")
|
||||||
|
trading_mode: TradingMode = config.get('trading_mode', TradingMode.SPOT)
|
||||||
for pair in config['pairs']:
|
for pair in config['pairs']:
|
||||||
data = src.trades_load(pair=pair)
|
data = src.trades_load(pair, trading_mode)
|
||||||
logger.info(f"Converting {len(data)} trades for {pair}")
|
logger.info(f"Converting {len(data)} trades for {pair}")
|
||||||
trg.trades_store(pair, data)
|
trg.trades_store(pair, data, trading_mode)
|
||||||
|
|
||||||
if erase and convert_from != convert_to:
|
if erase and convert_from != convert_to:
|
||||||
logger.info(f"Deleting source Trade data for {pair}.")
|
logger.info(f"Deleting source Trade data for {pair}.")
|
||||||
src.trades_purge(pair=pair)
|
src.trades_purge(pair, trading_mode)
|
||||||
|
|||||||
@@ -6,8 +6,10 @@ import pandas as pd
|
|||||||
from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_TRADES_COLUMNS, Config
|
from freqtrade.constants import DATETIME_PRINT_FORMAT, DEFAULT_TRADES_COLUMNS, Config
|
||||||
from freqtrade.data.converter.trade_converter import (trades_convert_types,
|
from freqtrade.data.converter.trade_converter import (trades_convert_types,
|
||||||
trades_df_remove_duplicates)
|
trades_df_remove_duplicates)
|
||||||
from freqtrade.data.history.idatahandler import get_datahandler
|
from freqtrade.data.history import get_datahandler
|
||||||
|
from freqtrade.enums import TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
|
from freqtrade.plugins.pairlist.pairlist_helpers import expand_pairlist
|
||||||
from freqtrade.resolvers import ExchangeResolver
|
from freqtrade.resolvers import ExchangeResolver
|
||||||
|
|
||||||
|
|
||||||
@@ -38,12 +40,22 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str):
|
|||||||
}
|
}
|
||||||
logger.info(f"Found csv files for {', '.join(data_symbols)}.")
|
logger.info(f"Found csv files for {', '.join(data_symbols)}.")
|
||||||
|
|
||||||
|
if pairs_raw := config.get('pairs'):
|
||||||
|
pairs = expand_pairlist(pairs_raw, [m[0] for m in markets])
|
||||||
|
markets = {m for m in markets if m[0] in pairs}
|
||||||
|
if not markets:
|
||||||
|
logger.info(f"No data found for pairs {', '.join(pairs_raw)}.")
|
||||||
|
return
|
||||||
|
logger.info(f"Converting pairs: {', '.join(m[0] for m in markets)}.")
|
||||||
|
|
||||||
for pair, name in markets:
|
for pair, name in markets:
|
||||||
|
logger.debug(f"Converting pair {pair}, files */{name}.csv")
|
||||||
dfs = []
|
dfs = []
|
||||||
# Load and combine all csv files for this pair
|
# Load and combine all csv files for this pair
|
||||||
for f in tradesdir.rglob(f"{name}.csv"):
|
for f in tradesdir.rglob(f"{name}.csv"):
|
||||||
df = pd.read_csv(f, names=KRAKEN_CSV_TRADE_COLUMNS)
|
df = pd.read_csv(f, names=KRAKEN_CSV_TRADE_COLUMNS)
|
||||||
dfs.append(df)
|
if not df.empty:
|
||||||
|
dfs.append(df)
|
||||||
|
|
||||||
# Load existing trades data
|
# Load existing trades data
|
||||||
if not dfs:
|
if not dfs:
|
||||||
@@ -52,19 +64,20 @@ def import_kraken_trades_from_csv(config: Config, convert_to: str):
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
trades = pd.concat(dfs, ignore_index=True)
|
trades = pd.concat(dfs, ignore_index=True)
|
||||||
|
del dfs
|
||||||
|
|
||||||
trades.loc[:, 'timestamp'] = trades['timestamp'] * 1e3
|
trades.loc[:, 'timestamp'] = trades['timestamp'] * 1e3
|
||||||
trades.loc[:, 'cost'] = trades['price'] * trades['amount']
|
trades.loc[:, 'cost'] = trades['price'] * trades['amount']
|
||||||
for col in DEFAULT_TRADES_COLUMNS:
|
for col in DEFAULT_TRADES_COLUMNS:
|
||||||
if col not in trades.columns:
|
if col not in trades.columns:
|
||||||
trades[col] = ''
|
trades.loc[:, col] = ''
|
||||||
|
|
||||||
trades = trades[DEFAULT_TRADES_COLUMNS]
|
trades = trades[DEFAULT_TRADES_COLUMNS]
|
||||||
trades = trades_convert_types(trades)
|
trades = trades_convert_types(trades)
|
||||||
|
|
||||||
trades_df = trades_df_remove_duplicates(trades)
|
trades_df = trades_df_remove_duplicates(trades)
|
||||||
|
del trades
|
||||||
logger.info(f"{pair}: {len(trades_df)} trades, from "
|
logger.info(f"{pair}: {len(trades_df)} trades, from "
|
||||||
f"{trades_df['date'].min():{DATETIME_PRINT_FORMAT}} to "
|
f"{trades_df['date'].min():{DATETIME_PRINT_FORMAT}} to "
|
||||||
f"{trades_df['date'].max():{DATETIME_PRINT_FORMAT}}")
|
f"{trades_df['date'].max():{DATETIME_PRINT_FORMAT}}")
|
||||||
|
|
||||||
data_handler.trades_store(pair, trades_df)
|
data_handler.trades_store(pair, trades_df, TradingMode.SPOT)
|
||||||
|
|||||||
@@ -311,11 +311,13 @@ class DataProvider:
|
|||||||
timerange = TimeRange.parse_timerange(None if self._config.get(
|
timerange = TimeRange.parse_timerange(None if self._config.get(
|
||||||
'timerange') is None else str(self._config.get('timerange')))
|
'timerange') is None else str(self._config.get('timerange')))
|
||||||
|
|
||||||
# It is not necessary to add the training candles, as they
|
startup_candles = self.get_required_startup(str(timeframe))
|
||||||
# were already added at the beginning of the backtest.
|
|
||||||
startup_candles = self.get_required_startup(str(timeframe), False)
|
|
||||||
tf_seconds = timeframe_to_seconds(str(timeframe))
|
tf_seconds = timeframe_to_seconds(str(timeframe))
|
||||||
timerange.subtract_start(tf_seconds * startup_candles)
|
timerange.subtract_start(tf_seconds * startup_candles)
|
||||||
|
|
||||||
|
logger.info(f"Loading data for {pair} {timeframe} "
|
||||||
|
f"from {timerange.start_fmt} to {timerange.stop_fmt}")
|
||||||
|
|
||||||
self.__cached_pairs_backtesting[saved_pair] = load_pair_history(
|
self.__cached_pairs_backtesting[saved_pair] = load_pair_history(
|
||||||
pair=pair,
|
pair=pair,
|
||||||
timeframe=timeframe,
|
timeframe=timeframe,
|
||||||
@@ -327,7 +329,7 @@ class DataProvider:
|
|||||||
)
|
)
|
||||||
return self.__cached_pairs_backtesting[saved_pair].copy()
|
return self.__cached_pairs_backtesting[saved_pair].copy()
|
||||||
|
|
||||||
def get_required_startup(self, timeframe: str, add_train_candles: bool = True) -> int:
|
def get_required_startup(self, timeframe: str) -> int:
|
||||||
freqai_config = self._config.get('freqai', {})
|
freqai_config = self._config.get('freqai', {})
|
||||||
if not freqai_config.get('enabled', False):
|
if not freqai_config.get('enabled', False):
|
||||||
return self._config.get('startup_candle_count', 0)
|
return self._config.get('startup_candle_count', 0)
|
||||||
@@ -337,12 +339,11 @@ class DataProvider:
|
|||||||
# make sure the startupcandles is at least the set maximum indicator periods
|
# make sure the startupcandles is at least the set maximum indicator periods
|
||||||
self._config['startup_candle_count'] = max(startup_candles, max(indicator_periods))
|
self._config['startup_candle_count'] = max(startup_candles, max(indicator_periods))
|
||||||
tf_seconds = timeframe_to_seconds(timeframe)
|
tf_seconds = timeframe_to_seconds(timeframe)
|
||||||
train_candles = 0
|
train_candles = freqai_config['train_period_days'] * 86400 / tf_seconds
|
||||||
if add_train_candles:
|
|
||||||
train_candles = freqai_config['train_period_days'] * 86400 / tf_seconds
|
|
||||||
total_candles = int(self._config['startup_candle_count'] + train_candles)
|
total_candles = int(self._config['startup_candle_count'] + train_candles)
|
||||||
logger.info(f'Increasing startup_candle_count for freqai to {total_candles}')
|
logger.info(
|
||||||
return total_candles
|
f'Increasing startup_candle_count for freqai on {timeframe} to {total_candles}')
|
||||||
|
return total_candles
|
||||||
|
|
||||||
def get_pair_dataframe(
|
def get_pair_dataframe(
|
||||||
self,
|
self,
|
||||||
@@ -522,7 +523,7 @@ class DataProvider:
|
|||||||
Send custom RPC Notifications from your bot.
|
Send custom RPC Notifications from your bot.
|
||||||
Will not send any bot in modes other than Dry-run or Live.
|
Will not send any bot in modes other than Dry-run or Live.
|
||||||
:param message: Message to be sent. Must be below 4096.
|
:param message: Message to be sent. Must be below 4096.
|
||||||
:param always_send: If False, will send the message only once per candle, and surpress
|
:param always_send: If False, will send the message only once per candle, and suppress
|
||||||
identical messages.
|
identical messages.
|
||||||
Careful as this can end up spaming your chat.
|
Careful as this can end up spaming your chat.
|
||||||
Defaults to False
|
Defaults to False
|
||||||
|
|||||||
@@ -6,7 +6,7 @@ Includes:
|
|||||||
* download data from exchange and store to disk
|
* download data from exchange and store to disk
|
||||||
"""
|
"""
|
||||||
# flake8: noqa: F401
|
# flake8: noqa: F401
|
||||||
|
from .datahandlers import get_datahandler
|
||||||
from .history_utils import (convert_trades_to_ohlcv, download_data_main, get_timerange, load_data,
|
from .history_utils import (convert_trades_to_ohlcv, download_data_main, get_timerange, load_data,
|
||||||
load_pair_history, refresh_backtest_ohlcv_data,
|
load_pair_history, refresh_backtest_ohlcv_data,
|
||||||
refresh_backtest_trades_data, refresh_data, validate_backtest_data)
|
refresh_backtest_trades_data, refresh_data, validate_backtest_data)
|
||||||
from .idatahandler import get_datahandler
|
|
||||||
|
|||||||
@@ -0,0 +1,2 @@
|
|||||||
|
# flake8: noqa: F401
|
||||||
|
from .idatahandler import IDataHandler, get_datahandler
|
||||||
+9
-5
@@ -5,7 +5,7 @@ from pandas import DataFrame, read_feather, to_datetime
|
|||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
|
|
||||||
from .idatahandler import IDataHandler
|
from .idatahandler import IDataHandler
|
||||||
|
|
||||||
@@ -82,14 +82,15 @@ class FeatherDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_store(self, pair: str, data: DataFrame) -> None:
|
def _trades_store(self, pair: str, data: DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
self.create_dir_if_needed(filename)
|
self.create_dir_if_needed(filename)
|
||||||
data.reset_index(drop=True).to_feather(filename, compression_level=9, compression='lz4')
|
data.reset_index(drop=True).to_feather(filename, compression_level=9, compression='lz4')
|
||||||
|
|
||||||
@@ -102,15 +103,18 @@ class FeatherDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> DataFrame:
|
def _trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
# TODO: respect timerange ...
|
# TODO: respect timerange ...
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: Dataframe containing trades
|
:return: Dataframe containing trades
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
if not filename.exists():
|
if not filename.exists():
|
||||||
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
||||||
|
|
||||||
+10
-6
@@ -6,7 +6,7 @@ import pandas as pd
|
|||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
|
|
||||||
from .idatahandler import IDataHandler
|
from .idatahandler import IDataHandler
|
||||||
|
|
||||||
@@ -35,7 +35,7 @@ class HDF5DataHandler(IDataHandler):
|
|||||||
self.create_dir_if_needed(filename)
|
self.create_dir_if_needed(filename)
|
||||||
|
|
||||||
_data.loc[:, self._columns].to_hdf(
|
_data.loc[:, self._columns].to_hdf(
|
||||||
filename, key, mode='a', complevel=9, complib='blosc',
|
filename, key=key, mode='a', complevel=9, complib='blosc',
|
||||||
format='table', data_columns=['date']
|
format='table', data_columns=['date']
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -100,17 +100,18 @@ class HDF5DataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_store(self, pair: str, data: pd.DataFrame) -> None:
|
def _trades_store(self, pair: str, data: pd.DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
key = self._pair_trades_key(pair)
|
key = self._pair_trades_key(pair)
|
||||||
|
|
||||||
data.to_hdf(
|
data.to_hdf(
|
||||||
self._pair_trades_filename(self._datadir, pair), key,
|
self._pair_trades_filename(self._datadir, pair, trading_mode), key=key,
|
||||||
mode='a', complevel=9, complib='blosc',
|
mode='a', complevel=9, complib='blosc',
|
||||||
format='table', data_columns=['timestamp']
|
format='table', data_columns=['timestamp']
|
||||||
)
|
)
|
||||||
@@ -124,15 +125,18 @@ class HDF5DataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> pd.DataFrame:
|
def _trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from h5 file.
|
Load a pair from h5 file.
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: Dataframe containing trades
|
:return: Dataframe containing trades
|
||||||
"""
|
"""
|
||||||
key = self._pair_trades_key(pair)
|
key = self._pair_trades_key(pair)
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
|
|
||||||
if not filename.exists():
|
if not filename.exists():
|
||||||
return pd.DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
return pd.DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
||||||
+61
-17
@@ -94,21 +94,22 @@ class IDataHandler(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
def ohlcv_data_min_max(self, pair: str, timeframe: str,
|
def ohlcv_data_min_max(self, pair: str, timeframe: str,
|
||||||
candle_type: CandleType) -> Tuple[datetime, datetime]:
|
candle_type: CandleType) -> Tuple[datetime, datetime, int]:
|
||||||
"""
|
"""
|
||||||
Returns the min and max timestamp for the given pair and timeframe.
|
Returns the min and max timestamp for the given pair and timeframe.
|
||||||
:param pair: Pair to get min/max for
|
:param pair: Pair to get min/max for
|
||||||
:param timeframe: Timeframe to get min/max for
|
:param timeframe: Timeframe to get min/max for
|
||||||
:param candle_type: Any of the enum CandleType (must match trading mode!)
|
:param candle_type: Any of the enum CandleType (must match trading mode!)
|
||||||
:return: (min, max)
|
:return: (min, max, len)
|
||||||
"""
|
"""
|
||||||
data = self._ohlcv_load(pair, timeframe, None, candle_type)
|
df = self._ohlcv_load(pair, timeframe, None, candle_type)
|
||||||
if data.empty:
|
if df.empty:
|
||||||
return (
|
return (
|
||||||
datetime.fromtimestamp(0, tz=timezone.utc),
|
datetime.fromtimestamp(0, tz=timezone.utc),
|
||||||
datetime.fromtimestamp(0, tz=timezone.utc)
|
datetime.fromtimestamp(0, tz=timezone.utc),
|
||||||
|
0,
|
||||||
)
|
)
|
||||||
return data.iloc[0]['date'].to_pydatetime(), data.iloc[-1]['date'].to_pydatetime()
|
return df.iloc[0]['date'].to_pydatetime(), df.iloc[-1]['date'].to_pydatetime(), len(df)
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange],
|
def _ohlcv_load(self, pair: str, timeframe: str, timerange: Optional[TimeRange],
|
||||||
@@ -171,12 +172,13 @@ class IDataHandler(ABC):
|
|||||||
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
|
return [cls.rebuild_pair_from_filename(match[0]) for match in _tmp if match]
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _trades_store(self, pair: str, data: DataFrame) -> None:
|
def _trades_store(self, pair: str, data: DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
@@ -189,45 +191,55 @@ class IDataHandler(ABC):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
@abstractmethod
|
@abstractmethod
|
||||||
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> DataFrame:
|
def _trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: Dataframe containing trades
|
:return: Dataframe containing trades
|
||||||
"""
|
"""
|
||||||
|
|
||||||
def trades_store(self, pair: str, data: DataFrame) -> None:
|
def trades_store(self, pair: str, data: DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
# Filter on expected columns (will remove the actual date column).
|
# Filter on expected columns (will remove the actual date column).
|
||||||
self._trades_store(pair, data[DEFAULT_TRADES_COLUMNS])
|
self._trades_store(pair, data[DEFAULT_TRADES_COLUMNS], trading_mode)
|
||||||
|
|
||||||
def trades_purge(self, pair: str) -> bool:
|
def trades_purge(self, pair: str, trading_mode: TradingMode) -> bool:
|
||||||
"""
|
"""
|
||||||
Remove data for this pair
|
Remove data for this pair
|
||||||
:param pair: Delete data for this pair.
|
:param pair: Delete data for this pair.
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:return: True when deleted, false if file did not exist.
|
:return: True when deleted, false if file did not exist.
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
if filename.exists():
|
if filename.exists():
|
||||||
filename.unlink()
|
filename.unlink()
|
||||||
return True
|
return True
|
||||||
return False
|
return False
|
||||||
|
|
||||||
def trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> DataFrame:
|
def trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
Removes duplicates in the process.
|
Removes duplicates in the process.
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: List of trades
|
:return: List of trades
|
||||||
"""
|
"""
|
||||||
trades = trades_df_remove_duplicates(self._trades_load(pair, timerange=timerange))
|
trades = trades_df_remove_duplicates(
|
||||||
|
self._trades_load(pair, trading_mode, timerange=timerange)
|
||||||
|
)
|
||||||
|
|
||||||
trades = trades_convert_types(trades)
|
trades = trades_convert_types(trades)
|
||||||
return trades
|
return trades
|
||||||
@@ -263,8 +275,12 @@ class IDataHandler(ABC):
|
|||||||
return filename
|
return filename
|
||||||
|
|
||||||
@classmethod
|
@classmethod
|
||||||
def _pair_trades_filename(cls, datadir: Path, pair: str) -> Path:
|
def _pair_trades_filename(cls, datadir: Path, pair: str, trading_mode: TradingMode) -> Path:
|
||||||
pair_s = misc.pair_to_filename(pair)
|
pair_s = misc.pair_to_filename(pair)
|
||||||
|
if trading_mode == TradingMode.FUTURES:
|
||||||
|
# Futures pair ...
|
||||||
|
datadir = datadir.joinpath('futures')
|
||||||
|
|
||||||
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
|
filename = datadir.joinpath(f'{pair_s}-trades.{cls._get_file_extension()}')
|
||||||
return filename
|
return filename
|
||||||
|
|
||||||
@@ -286,8 +302,8 @@ class IDataHandler(ABC):
|
|||||||
Rebuild pair name from filename
|
Rebuild pair name from filename
|
||||||
Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names.
|
Assumes a asset name of max. 7 length to also support BTC-PERP and BTC-PERP:USD names.
|
||||||
"""
|
"""
|
||||||
res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, 1)
|
res = re.sub(r'^(([A-Za-z\d]{1,10})|^([A-Za-z\-]{1,6}))(_)', r'\g<1>/', pair, count=1)
|
||||||
res = re.sub('_', ':', res, 1)
|
res = re.sub('_', ':', res, count=1)
|
||||||
return res
|
return res
|
||||||
|
|
||||||
def ohlcv_load(self, pair, timeframe: str,
|
def ohlcv_load(self, pair, timeframe: str,
|
||||||
@@ -403,6 +419,34 @@ class IDataHandler(ABC):
|
|||||||
return
|
return
|
||||||
file_old.rename(file_new)
|
file_old.rename(file_new)
|
||||||
|
|
||||||
|
def fix_funding_fee_timeframe(self, ff_timeframe: str):
|
||||||
|
"""
|
||||||
|
Temporary method to migrate data from old funding fee timeframe to the correct timeframe
|
||||||
|
Applies to bybit and okx, where funding-fee and mark candles have different timeframes.
|
||||||
|
"""
|
||||||
|
paircombs = self.ohlcv_get_available_data(self._datadir, TradingMode.FUTURES)
|
||||||
|
funding_rate_combs = [
|
||||||
|
f for f in paircombs if f[2] == CandleType.FUNDING_RATE and f[1] != ff_timeframe
|
||||||
|
]
|
||||||
|
|
||||||
|
if funding_rate_combs:
|
||||||
|
logger.warning(
|
||||||
|
f'Migrating {len(funding_rate_combs)} funding fees to correct timeframe.')
|
||||||
|
|
||||||
|
for pair, timeframe, candletype in funding_rate_combs:
|
||||||
|
old_name = self._pair_data_filename(self._datadir, pair, timeframe, candletype)
|
||||||
|
new_name = self._pair_data_filename(self._datadir, pair, ff_timeframe, candletype)
|
||||||
|
|
||||||
|
if not Path(old_name).exists():
|
||||||
|
logger.warning(f'{old_name} does not exist, skipping.')
|
||||||
|
continue
|
||||||
|
|
||||||
|
if Path(new_name).exists():
|
||||||
|
logger.warning(f'{new_name} already exists, Removing.')
|
||||||
|
Path(new_name).unlink()
|
||||||
|
|
||||||
|
Path(old_name).rename(new_name)
|
||||||
|
|
||||||
|
|
||||||
def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
|
def get_datahandlerclass(datatype: str) -> Type[IDataHandler]:
|
||||||
"""
|
"""
|
||||||
+10
-6
@@ -8,7 +8,7 @@ from freqtrade import misc
|
|||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
||||||
from freqtrade.data.converter import trades_dict_to_list, trades_list_to_df
|
from freqtrade.data.converter import trades_dict_to_list, trades_list_to_df
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
|
|
||||||
from .idatahandler import IDataHandler
|
from .idatahandler import IDataHandler
|
||||||
|
|
||||||
@@ -37,7 +37,7 @@ class JsonDataHandler(IDataHandler):
|
|||||||
self.create_dir_if_needed(filename)
|
self.create_dir_if_needed(filename)
|
||||||
_data = data.copy()
|
_data = data.copy()
|
||||||
# Convert date to int
|
# Convert date to int
|
||||||
_data['date'] = _data['date'].view(np.int64) // 1000 // 1000
|
_data['date'] = _data['date'].astype(np.int64) // 1000 // 1000
|
||||||
|
|
||||||
# Reset index, select only appropriate columns and save as json
|
# Reset index, select only appropriate columns and save as json
|
||||||
_data.reset_index(drop=True).loc[:, self._columns].to_json(
|
_data.reset_index(drop=True).loc[:, self._columns].to_json(
|
||||||
@@ -94,14 +94,15 @@ class JsonDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_store(self, pair: str, data: DataFrame) -> None:
|
def _trades_store(self, pair: str, data: DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
trades = data.values.tolist()
|
trades = data.values.tolist()
|
||||||
misc.file_dump_json(filename, trades, is_zip=self._use_zip)
|
misc.file_dump_json(filename, trades, is_zip=self._use_zip)
|
||||||
|
|
||||||
@@ -114,15 +115,18 @@ class JsonDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> DataFrame:
|
def _trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
# TODO: respect timerange ...
|
# TODO: respect timerange ...
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: Dataframe containing trades
|
:return: Dataframe containing trades
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
tradesdata = misc.file_load_json(filename)
|
tradesdata = misc.file_load_json(filename)
|
||||||
|
|
||||||
if not tradesdata:
|
if not tradesdata:
|
||||||
+10
-6
@@ -4,8 +4,8 @@ from typing import Optional
|
|||||||
from pandas import DataFrame, read_parquet, to_datetime
|
from pandas import DataFrame, read_parquet, to_datetime
|
||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS, TradeList
|
from freqtrade.constants import DEFAULT_DATAFRAME_COLUMNS, DEFAULT_TRADES_COLUMNS
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
|
|
||||||
from .idatahandler import IDataHandler
|
from .idatahandler import IDataHandler
|
||||||
|
|
||||||
@@ -81,14 +81,15 @@ class ParquetDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_store(self, pair: str, data: DataFrame) -> None:
|
def _trades_store(self, pair: str, data: DataFrame, trading_mode: TradingMode) -> None:
|
||||||
"""
|
"""
|
||||||
Store trades data (list of Dicts) to file
|
Store trades data (list of Dicts) to file
|
||||||
:param pair: Pair - used for filename
|
:param pair: Pair - used for filename
|
||||||
:param data: Dataframe containing trades
|
:param data: Dataframe containing trades
|
||||||
column sequence as in DEFAULT_TRADES_COLUMNS
|
column sequence as in DEFAULT_TRADES_COLUMNS
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
self.create_dir_if_needed(filename)
|
self.create_dir_if_needed(filename)
|
||||||
data.reset_index(drop=True).to_parquet(filename)
|
data.reset_index(drop=True).to_parquet(filename)
|
||||||
|
|
||||||
@@ -101,15 +102,18 @@ class ParquetDataHandler(IDataHandler):
|
|||||||
"""
|
"""
|
||||||
raise NotImplementedError()
|
raise NotImplementedError()
|
||||||
|
|
||||||
def _trades_load(self, pair: str, timerange: Optional[TimeRange] = None) -> TradeList:
|
def _trades_load(
|
||||||
|
self, pair: str, trading_mode: TradingMode, timerange: Optional[TimeRange] = None
|
||||||
|
) -> DataFrame:
|
||||||
"""
|
"""
|
||||||
Load a pair from file, either .json.gz or .json
|
Load a pair from file, either .json.gz or .json
|
||||||
# TODO: respect timerange ...
|
# TODO: respect timerange ...
|
||||||
:param pair: Load trades for this pair
|
:param pair: Load trades for this pair
|
||||||
|
:param trading_mode: Trading mode to use (used to determine the filename)
|
||||||
:param timerange: Timerange to load trades for - currently not implemented
|
:param timerange: Timerange to load trades for - currently not implemented
|
||||||
:return: List of trades
|
:return: List of trades
|
||||||
"""
|
"""
|
||||||
filename = self._pair_trades_filename(self._datadir, pair)
|
filename = self._pair_trades_filename(self._datadir, pair, trading_mode)
|
||||||
if not filename.exists():
|
if not filename.exists():
|
||||||
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
return DataFrame(columns=DEFAULT_TRADES_COLUMNS)
|
||||||
|
|
||||||
@@ -8,18 +8,18 @@ from pandas import DataFrame, concat
|
|||||||
|
|
||||||
from freqtrade.configuration import TimeRange
|
from freqtrade.configuration import TimeRange
|
||||||
from freqtrade.constants import (DATETIME_PRINT_FORMAT, DEFAULT_DATAFRAME_COLUMNS,
|
from freqtrade.constants import (DATETIME_PRINT_FORMAT, DEFAULT_DATAFRAME_COLUMNS,
|
||||||
DL_DATA_TIMEFRAMES, Config)
|
DL_DATA_TIMEFRAMES, DOCS_LINK, Config)
|
||||||
from freqtrade.data.converter import (clean_ohlcv_dataframe, convert_trades_to_ohlcv,
|
from freqtrade.data.converter import (clean_ohlcv_dataframe, convert_trades_to_ohlcv,
|
||||||
ohlcv_to_dataframe, trades_df_remove_duplicates,
|
ohlcv_to_dataframe, trades_df_remove_duplicates,
|
||||||
trades_list_to_df)
|
trades_list_to_df)
|
||||||
from freqtrade.data.history.idatahandler import IDataHandler, get_datahandler
|
from freqtrade.data.history.datahandlers import IDataHandler, get_datahandler
|
||||||
from freqtrade.enums import CandleType
|
from freqtrade.enums import CandleType, TradingMode
|
||||||
from freqtrade.exceptions import OperationalException
|
from freqtrade.exceptions import OperationalException
|
||||||
from freqtrade.exchange import Exchange
|
from freqtrade.exchange import Exchange
|
||||||
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
from freqtrade.plugins.pairlist.pairlist_helpers import dynamic_expand_pairlist
|
||||||
from freqtrade.util import dt_ts, format_ms_time
|
from freqtrade.util import dt_ts, format_ms_time
|
||||||
from freqtrade.util.binance_mig import migrate_binance_futures_data
|
|
||||||
from freqtrade.util.datetime_helpers import dt_now
|
from freqtrade.util.datetime_helpers import dt_now
|
||||||
|
from freqtrade.util.migrations import migrate_data
|
||||||
|
|
||||||
|
|
||||||
logger = logging.getLogger(__name__)
|
logger = logging.getLogger(__name__)
|
||||||
@@ -311,15 +311,19 @@ def refresh_backtest_ohlcv_data(exchange: Exchange, pairs: List[str], timeframes
|
|||||||
# Predefined candletype (and timeframe) depending on exchange
|
# Predefined candletype (and timeframe) depending on exchange
|
||||||
# Downloads what is necessary to backtest based on futures data.
|
# Downloads what is necessary to backtest based on futures data.
|
||||||
tf_mark = exchange.get_option('mark_ohlcv_timeframe')
|
tf_mark = exchange.get_option('mark_ohlcv_timeframe')
|
||||||
|
tf_funding_rate = exchange.get_option('funding_fee_timeframe')
|
||||||
|
|
||||||
fr_candle_type = CandleType.from_string(exchange.get_option('mark_ohlcv_price'))
|
fr_candle_type = CandleType.from_string(exchange.get_option('mark_ohlcv_price'))
|
||||||
# All exchanges need FundingRate for futures trading.
|
# All exchanges need FundingRate for futures trading.
|
||||||
# The timeframe is aligned to the mark-price timeframe.
|
# The timeframe is aligned to the mark-price timeframe.
|
||||||
for funding_candle_type in (CandleType.FUNDING_RATE, fr_candle_type):
|
combs = ((CandleType.FUNDING_RATE, tf_funding_rate), (fr_candle_type, tf_mark))
|
||||||
|
for candle_type_f, tf in combs:
|
||||||
|
logger.debug(f'Downloading pair {pair}, {candle_type_f}, interval {tf}.')
|
||||||
_download_pair_history(pair=pair, process=process,
|
_download_pair_history(pair=pair, process=process,
|
||||||
datadir=datadir, exchange=exchange,
|
datadir=datadir, exchange=exchange,
|
||||||
timerange=timerange, data_handler=data_handler,
|
timerange=timerange, data_handler=data_handler,
|
||||||
timeframe=str(tf_mark), new_pairs_days=new_pairs_days,
|
timeframe=str(tf), new_pairs_days=new_pairs_days,
|
||||||
candle_type=funding_candle_type,
|
candle_type=candle_type_f,
|
||||||
erase=erase, prepend=prepend)
|
erase=erase, prepend=prepend)
|
||||||
|
|
||||||
return pairs_not_available
|
return pairs_not_available
|
||||||
@@ -329,7 +333,8 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
pair: str, *,
|
pair: str, *,
|
||||||
new_pairs_days: int = 30,
|
new_pairs_days: int = 30,
|
||||||
timerange: Optional[TimeRange] = None,
|
timerange: Optional[TimeRange] = None,
|
||||||
data_handler: IDataHandler
|
data_handler: IDataHandler,
|
||||||
|
trading_mode: TradingMode,
|
||||||
) -> bool:
|
) -> bool:
|
||||||
"""
|
"""
|
||||||
Download trade history from the exchange.
|
Download trade history from the exchange.
|
||||||
@@ -345,7 +350,7 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
if timerange.stoptype == 'date':
|
if timerange.stoptype == 'date':
|
||||||
until = timerange.stopts * 1000
|
until = timerange.stopts * 1000
|
||||||
|
|
||||||
trades = data_handler.trades_load(pair)
|
trades = data_handler.trades_load(pair, trading_mode)
|
||||||
|
|
||||||
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
|
# TradesList columns are defined in constants.DEFAULT_TRADES_COLUMNS
|
||||||
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
|
# DEFAULT_TRADES_COLUMNS: 0 -> timestamp
|
||||||
@@ -384,7 +389,7 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
trades = concat([trades, new_trades_df], axis=0)
|
trades = concat([trades, new_trades_df], axis=0)
|
||||||
# Remove duplicates to make sure we're not storing data we don't need
|
# Remove duplicates to make sure we're not storing data we don't need
|
||||||
trades = trades_df_remove_duplicates(trades)
|
trades = trades_df_remove_duplicates(trades)
|
||||||
data_handler.trades_store(pair, data=trades)
|
data_handler.trades_store(pair, trades, trading_mode)
|
||||||
|
|
||||||
logger.debug("New Start: %s", 'None' if trades.empty else
|
logger.debug("New Start: %s", 'None' if trades.empty else
|
||||||
f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}")
|
f"{trades.iloc[0]['date']:{DATETIME_PRINT_FORMAT}}")
|
||||||
@@ -401,8 +406,10 @@ def _download_trades_history(exchange: Exchange,
|
|||||||
|
|
||||||
|
|
||||||
def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: Path,
|
def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir: Path,
|
||||||
timerange: TimeRange, new_pairs_days: int = 30,
|
timerange: TimeRange, trading_mode: TradingMode,
|
||||||
erase: bool = False, data_format: str = 'feather') -> List[str]:
|
new_pairs_days: int = 30,
|
||||||
|
erase: bool = False, data_format: str = 'feather',
|
||||||
|
) -> List[str]:
|
||||||
"""
|
"""
|
||||||
Refresh stored trades data for backtesting and hyperopt operations.
|
Refresh stored trades data for backtesting and hyperopt operations.
|
||||||
Used by freqtrade download-data subcommand.
|
Used by freqtrade download-data subcommand.
|
||||||
@@ -417,7 +424,7 @@ def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir:
|
|||||||
continue
|
continue
|
||||||
|
|
||||||
if erase:
|
if erase:
|
||||||
if data_handler.trades_purge(pair):
|
if data_handler.trades_purge(pair, trading_mode):
|
||||||
logger.info(f'Deleting existing data for pair {pair}.')
|
logger.info(f'Deleting existing data for pair {pair}.')
|
||||||
|
|
||||||
logger.info(f'Downloading trades for pair {pair}.')
|
logger.info(f'Downloading trades for pair {pair}.')
|
||||||
@@ -425,7 +432,8 @@ def refresh_backtest_trades_data(exchange: Exchange, pairs: List[str], datadir:
|
|||||||
pair=pair,
|
pair=pair,
|
||||||
new_pairs_days=new_pairs_days,
|
new_pairs_days=new_pairs_days,
|
||||||
timerange=timerange,
|
timerange=timerange,
|
||||||
data_handler=data_handler)
|
data_handler=data_handler,
|
||||||
|
trading_mode=trading_mode)
|
||||||
return pairs_not_available
|
return pairs_not_available
|
||||||
|
|
||||||
|
|
||||||
@@ -500,18 +508,24 @@ def download_data_main(config: Config) -> None:
|
|||||||
logger.info(f"About to download pairs: {expanded_pairs}, "
|
logger.info(f"About to download pairs: {expanded_pairs}, "
|
||||||
f"intervals: {config['timeframes']} to {config['datadir']}")
|
f"intervals: {config['timeframes']} to {config['datadir']}")
|
||||||
|
|
||||||
|
if len(expanded_pairs) == 0:
|
||||||
|
logger.warning(
|
||||||
|
"No pairs available for download. "
|
||||||
|
"Please make sure you're using the correct Pair naming for your selected trade mode. \n"
|
||||||
|
f"More info: {DOCS_LINK}/bot-basics/#pair-naming")
|
||||||
|
|
||||||
for timeframe in config['timeframes']:
|
for timeframe in config['timeframes']:
|
||||||
exchange.validate_timeframes(timeframe)
|
exchange.validate_timeframes(timeframe)
|
||||||
|
|
||||||
# Start downloading
|
# Start downloading
|
||||||
try:
|
try:
|
||||||
if config.get('download_trades'):
|
if config.get('download_trades'):
|
||||||
if config.get('trading_mode') == 'futures':
|
|
||||||
raise OperationalException("Trade download not supported for futures.")
|
|
||||||
pairs_not_available = refresh_backtest_trades_data(
|
pairs_not_available = refresh_backtest_trades_data(
|
||||||
exchange, pairs=expanded_pairs, datadir=config['datadir'],
|
exchange, pairs=expanded_pairs, datadir=config['datadir'],
|
||||||
timerange=timerange, new_pairs_days=config['new_pairs_days'],
|
timerange=timerange, new_pairs_days=config['new_pairs_days'],
|
||||||
erase=bool(config.get('erase')), data_format=config['dataformat_trades'])
|
erase=bool(config.get('erase')), data_format=config['dataformat_trades'],
|
||||||
|
trading_mode=config.get('trading_mode', TradingMode.SPOT),
|
||||||
|
)
|
||||||
|
|
||||||
# Convert downloaded trade data to different timeframes
|
# Convert downloaded trade data to different timeframes
|
||||||
convert_trades_to_ohlcv(
|
convert_trades_to_ohlcv(
|
||||||
@@ -519,6 +533,7 @@ def download_data_main(config: Config) -> None:
|
|||||||
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
datadir=config['datadir'], timerange=timerange, erase=bool(config.get('erase')),
|
||||||
data_format_ohlcv=config['dataformat_ohlcv'],
|
data_format_ohlcv=config['dataformat_ohlcv'],
|
||||||
data_format_trades=config['dataformat_trades'],
|
data_format_trades=config['dataformat_trades'],
|
||||||
|
candle_type=config.get('candle_type_def', CandleType.SPOT),
|
||||||
)
|
)
|
||||||
else:
|
else:
|
||||||
if not exchange.get_option('ohlcv_has_history', True):
|
if not exchange.get_option('ohlcv_has_history', True):
|
||||||
@@ -527,7 +542,7 @@ def download_data_main(config: Config) -> None:
|
|||||||
"Please use `--dl-trades` instead for this exchange "
|
"Please use `--dl-trades` instead for this exchange "
|
||||||
"(will unfortunately take a long time)."
|
"(will unfortunately take a long time)."
|
||||||
)
|
)
|
||||||
migrate_binance_futures_data(config)
|
migrate_data(config, exchange)
|
||||||
pairs_not_available = refresh_backtest_ohlcv_data(
|
pairs_not_available = refresh_backtest_ohlcv_data(
|
||||||
exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
|
exchange, pairs=expanded_pairs, timeframes=config['timeframes'],
|
||||||
datadir=config['datadir'], timerange=timerange,
|
datadir=config['datadir'], timerange=timerange,
|
||||||
|
|||||||
@@ -30,8 +30,25 @@ def calculate_market_change(data: Dict[str, pd.DataFrame], column: str = "close"
|
|||||||
return float(np.mean(tmp_means))
|
return float(np.mean(tmp_means))
|
||||||
|
|
||||||
|
|
||||||
def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
|
def combine_dataframes_by_column(
|
||||||
column: str = "close") -> pd.DataFrame:
|
data: Dict[str, pd.DataFrame], column: str = "close") -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Combine multiple dataframes "column"
|
||||||
|
:param data: Dict of Dataframes, dict key should be pair.
|
||||||
|
:param column: Column in the original dataframes to use
|
||||||
|
:return: DataFrame with the column renamed to the dict key.
|
||||||
|
:raise: ValueError if no data is provided.
|
||||||
|
"""
|
||||||
|
if not data:
|
||||||
|
raise ValueError("No data provided.")
|
||||||
|
df_comb = pd.concat([data[pair].set_index('date').rename(
|
||||||
|
{column: pair}, axis=1)[pair] for pair in data], axis=1)
|
||||||
|
return df_comb
|
||||||
|
|
||||||
|
|
||||||
|
def combined_dataframes_with_rel_mean(
|
||||||
|
data: Dict[str, pd.DataFrame], fromdt: datetime, todt: datetime,
|
||||||
|
column: str = "close") -> pd.DataFrame:
|
||||||
"""
|
"""
|
||||||
Combine multiple dataframes "column"
|
Combine multiple dataframes "column"
|
||||||
:param data: Dict of Dataframes, dict key should be pair.
|
:param data: Dict of Dataframes, dict key should be pair.
|
||||||
@@ -40,8 +57,26 @@ def combine_dataframes_with_mean(data: Dict[str, pd.DataFrame],
|
|||||||
named mean, containing the mean of all pairs.
|
named mean, containing the mean of all pairs.
|
||||||
:raise: ValueError if no data is provided.
|
:raise: ValueError if no data is provided.
|
||||||
"""
|
"""
|
||||||
df_comb = pd.concat([data[pair].set_index('date').rename(
|
df_comb = combine_dataframes_by_column(data, column)
|
||||||
{column: pair}, axis=1)[pair] for pair in data], axis=1)
|
# Trim dataframes to the given timeframe
|
||||||
|
df_comb = df_comb.iloc[(df_comb.index >= fromdt) & (df_comb.index < todt)]
|
||||||
|
df_comb['count'] = df_comb.count(axis=1)
|
||||||
|
df_comb['mean'] = df_comb.mean(axis=1)
|
||||||
|
df_comb['rel_mean'] = df_comb['mean'].pct_change().fillna(0).cumsum()
|
||||||
|
return df_comb[['mean', 'rel_mean', 'count']]
|
||||||
|
|
||||||
|
|
||||||
|
def combine_dataframes_with_mean(
|
||||||
|
data: Dict[str, pd.DataFrame], column: str = "close") -> pd.DataFrame:
|
||||||
|
"""
|
||||||
|
Combine multiple dataframes "column"
|
||||||
|
:param data: Dict of Dataframes, dict key should be pair.
|
||||||
|
:param column: Column in the original dataframes to use
|
||||||
|
:return: DataFrame with the column renamed to the dict key, and a column
|
||||||
|
named mean, containing the mean of all pairs.
|
||||||
|
:raise: ValueError if no data is provided.
|
||||||
|
"""
|
||||||
|
df_comb = combine_dataframes_by_column(data, column)
|
||||||
|
|
||||||
df_comb['mean'] = df_comb.mean(axis=1)
|
df_comb['mean'] = df_comb.mean(axis=1)
|
||||||
|
|
||||||
@@ -61,10 +96,10 @@ def create_cum_profit(df: pd.DataFrame, trades: pd.DataFrame, col_name: str,
|
|||||||
"""
|
"""
|
||||||
if len(trades) == 0:
|
if len(trades) == 0:
|
||||||
raise ValueError("Trade dataframe empty.")
|
raise ValueError("Trade dataframe empty.")
|
||||||
from freqtrade.exchange import timeframe_to_minutes
|
from freqtrade.exchange import timeframe_to_resample_freq
|
||||||
timeframe_minutes = timeframe_to_minutes(timeframe)
|
timeframe_freq = timeframe_to_resample_freq(timeframe)
|
||||||
# Resample to timeframe to make sure trades match candles
|
# Resample to timeframe to make sure trades match candles
|
||||||
_trades_sum = trades.resample(f'{timeframe_minutes}min', on='close_date'
|
_trades_sum = trades.resample(timeframe_freq, on='close_date'
|
||||||
)[['profit_abs']].sum()
|
)[['profit_abs']].sum()
|
||||||
df.loc[:, col_name] = _trades_sum['profit_abs'].cumsum()
|
df.loc[:, col_name] = _trades_sum['profit_abs'].cumsum()
|
||||||
# Set first value to 0
|
# Set first value to 0
|
||||||
@@ -143,8 +178,10 @@ def calculate_max_drawdown(trades: pd.DataFrame, *, date_col: str = 'close_date'
|
|||||||
starting_balance=starting_balance
|
starting_balance=starting_balance
|
||||||
)
|
)
|
||||||
|
|
||||||
idxmin = max_drawdown_df['drawdown_relative'].idxmax() if relative \
|
idxmin = (
|
||||||
else max_drawdown_df['drawdown'].idxmin()
|
max_drawdown_df['drawdown_relative'].idxmax()
|
||||||
|
if relative else max_drawdown_df['drawdown'].idxmin()
|
||||||
|
)
|
||||||
if idxmin == 0:
|
if idxmin == 0:
|
||||||
raise ValueError("No losing trade, therefore no drawdown.")
|
raise ValueError("No losing trade, therefore no drawdown.")
|
||||||
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
high_date = profit_results.loc[max_drawdown_df.iloc[:idxmin]['high_value'].idxmax(), date_col]
|
||||||
@@ -191,6 +228,9 @@ def calculate_cagr(days_passed: int, starting_balance: float, final_balance: flo
|
|||||||
:param final_balance: Final balance to calculate CAGR against
|
:param final_balance: Final balance to calculate CAGR against
|
||||||
:return: CAGR
|
:return: CAGR
|
||||||
"""
|
"""
|
||||||
|
if final_balance < 0:
|
||||||
|
# With leveraged trades, final_balance can become negative.
|
||||||
|
return 0
|
||||||
return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1
|
return (final_balance / starting_balance) ** (1 / (days_passed / 365)) - 1
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ from freqtrade.enums.marketstatetype import MarketDirection
|
|||||||
from freqtrade.enums.ordertypevalue import OrderTypeValues
|
from freqtrade.enums.ordertypevalue import OrderTypeValues
|
||||||
from freqtrade.enums.pricetype import PriceType
|
from freqtrade.enums.pricetype import PriceType
|
||||||
from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType
|
from freqtrade.enums.rpcmessagetype import NO_ECHO_MESSAGES, RPCMessageType, RPCRequestType
|
||||||
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADING_MODES, RunMode
|
from freqtrade.enums.runmode import NON_UTIL_MODES, OPTIMIZE_MODES, TRADE_MODES, RunMode
|
||||||
from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType
|
from freqtrade.enums.signaltype import SignalDirection, SignalTagType, SignalType
|
||||||
from freqtrade.enums.state import State
|
from freqtrade.enums.state import State
|
||||||
from freqtrade.enums.tradingmode import TradingMode
|
from freqtrade.enums.tradingmode import TradingMode
|
||||||
|
|||||||
@@ -18,6 +18,6 @@ class RunMode(Enum):
|
|||||||
OTHER = "other"
|
OTHER = "other"
|
||||||
|
|
||||||
|
|
||||||
TRADING_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
|
TRADE_MODES = [RunMode.LIVE, RunMode.DRY_RUN]
|
||||||
OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT]
|
OPTIMIZE_MODES = [RunMode.BACKTEST, RunMode.EDGE, RunMode.HYPEROPT]
|
||||||
NON_UTIL_MODES = TRADING_MODES + OPTIMIZE_MODES
|
NON_UTIL_MODES = TRADE_MODES + OPTIMIZE_MODES
|
||||||
|
|||||||
@@ -12,6 +12,12 @@ class OperationalException(FreqtradeException):
|
|||||||
"""
|
"""
|
||||||
|
|
||||||
|
|
||||||
|
class ConfigurationError(OperationalException):
|
||||||
|
"""
|
||||||
|
Configuration error. Usually caused by invalid configuration.
|
||||||
|
"""
|
||||||
|
|
||||||
|
|
||||||
class DependencyException(FreqtradeException):
|
class DependencyException(FreqtradeException):
|
||||||
"""
|
"""
|
||||||
Indicates that an assumed dependency is not met.
|
Indicates that an assumed dependency is not met.
|
||||||
|
|||||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user